From 3e0cd1a6b1c7d0c92c340cdad3944f66cc57483c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:54:37 +0200 Subject: [PATCH 01/87] Add `CMT_HOME` (or remove it?) (backport #983) (#999) * Add `CMT_HOME` (or remove it?) (#983) Closes #982 Added `CMT_HOME` everywhere `CMTHOME` is used. ### Notes to reviewers This could be fixed the opposite way, by removing the only reference to `CMT_HOME` in the code, and also the reference in `UPGRADING.md` (two lines of code). However, the reference in `UPGRADING.md`, which is part of our documentation, is already present in `v0.34.x` (not in `v0.37.x` though!). That's why this PR introduces `CMT_HOME` to work in equal conditions as `CMTHOME`. If reviewers lean toward removing `CMT_HOME` from the doc in `v0.34.x` (and unreleased `v0.38.x` and `main`), I can do it easily. --- #### PR checklist - [x] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit b7be568f4a59edf41942ec7e0e25edd72df604d8) # Conflicts: # UPGRADING.md # cmd/cometbft/commands/root_test.go * Revert "Add `CMT_HOME` (or remove it?) (#983)" * Add `CMT_HOME` (or remove it?) (#983) Closes #982 Added `CMT_HOME` everywhere `CMTHOME` is used. This could be fixed the opposite way, by removing the only reference to `CMT_HOME` in the code, and also the reference in `UPGRADING.md` (two lines of code). However, the reference in `UPGRADING.md`, which is part of our documentation, is already present in `v0.34.x` (not in `v0.37.x` though!). That's why this PR introduces `CMT_HOME` to work in equal conditions as `CMTHOME`. If reviewers lean toward removing `CMT_HOME` from the doc in `v0.34.x` (and unreleased `v0.38.x` and `main`), I can do it easily. --- - [x] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: Sergio Mena --- cmd/cometbft/commands/root_test.go | 62 +++++++++++++----------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/cmd/cometbft/commands/root_test.go b/cmd/cometbft/commands/root_test.go index 40909d86eb4..31c90ac868f 100644 --- a/cmd/cometbft/commands/root_test.go +++ b/cmd/cometbft/commands/root_test.go @@ -17,30 +17,12 @@ import ( cmtos "github.com/cometbft/cometbft/libs/os" ) -var ( - defaultRoot = os.ExpandEnv("$HOME/.some/test/dir") -) - // clearConfig clears env vars, the given root dir, and resets viper. -func clearConfig(dir string) { - if err := os.Unsetenv("CMTHOME"); err != nil { - panic(err) - } - if err := os.Unsetenv("CMT_HOME"); err != nil { - panic(err) - } - if err := os.Unsetenv("TMHOME"); err != nil { - //XXX: Deprecated. - panic(err) - } - if err := os.Unsetenv("TM_HOME"); err != nil { - //XXX: Deprecated. - panic(err) - } +func clearConfig(t *testing.T, dir string) { + os.Clearenv() + err := os.RemoveAll(dir) + require.NoError(t, err) - if err := os.RemoveAll(dir); err != nil { - panic(err) - } viper.Reset() config = cfg.DefaultConfig() } @@ -58,11 +40,11 @@ func testRootCmd() *cobra.Command { return rootCmd } -func testSetup(rootDir string, args []string, env map[string]string) error { - clearConfig(defaultRoot) +func testSetup(t *testing.T, root string, args []string, env map[string]string) error { + clearConfig(t, root) rootCmd := testRootCmd() - cmd := cli.PrepareBaseCmd(rootCmd, "CMT", defaultRoot) + cmd := cli.PrepareBaseCmd(rootCmd, "CMT", root) // run with the args and env args = append([]string{rootCmd.Use}, args...) @@ -70,22 +52,27 @@ func testSetup(rootDir string, args []string, env map[string]string) error { } func TestRootHome(t *testing.T) { - newRoot := filepath.Join(defaultRoot, "something-else") + tmpDir := os.TempDir() + root := filepath.Join(tmpDir, "adir") + newRoot := filepath.Join(tmpDir, "something-else") + defer clearConfig(t, root) + defer clearConfig(t, newRoot) + cases := []struct { args []string env map[string]string root string }{ - {nil, nil, defaultRoot}, + {nil, nil, root}, {[]string{"--home", newRoot}, nil, newRoot}, {nil, map[string]string{"TMHOME": newRoot}, newRoot}, //XXX: Deprecated. {nil, map[string]string{"CMTHOME": newRoot}, newRoot}, } for i, tc := range cases { - idxString := strconv.Itoa(i) + idxString := "idx: " + strconv.Itoa(i) - err := testSetup(defaultRoot, tc.args, tc.env) + err := testSetup(t, root, tc.args, tc.env) require.Nil(t, err, idxString) assert.Equal(t, tc.root, config.RootDir, idxString) @@ -118,8 +105,10 @@ func TestRootFlagsEnv(t *testing.T) { for i, tc := range cases { idxString := strconv.Itoa(i) - - err := testSetup(defaultRoot, tc.args, tc.env) + root := filepath.Join(os.TempDir(), "adir2_"+idxString) + idxString = "idx: " + idxString + defer clearConfig(t, root) + err := testSetup(t, root, tc.args, tc.env) require.Nil(t, err, idxString) assert.Equal(t, tc.logLevel, config.LogLevel, idxString) @@ -148,11 +137,12 @@ func TestRootConfig(t *testing.T) { for i, tc := range cases { idxString := strconv.Itoa(i) - clearConfig(defaultRoot) - + root := filepath.Join(os.TempDir(), "adir3_"+idxString) + idxString = "idx: " + idxString + defer clearConfig(t, root) // XXX: path must match cfg.defaultConfigPath - configFilePath := filepath.Join(defaultRoot, "config") - err := cmtos.EnsureDir(configFilePath, 0700) + configFilePath := filepath.Join(root, "config") + err := cmtos.EnsureDir(configFilePath, 0o700) require.Nil(t, err) // write the non-defaults to a different path @@ -161,7 +151,7 @@ func TestRootConfig(t *testing.T) { require.Nil(t, err) rootCmd := testRootCmd() - cmd := cli.PrepareBaseCmd(rootCmd, "CMT", defaultRoot) + cmd := cli.PrepareBaseCmd(rootCmd, "CMT", root) // run with the args and env tc.args = append([]string{rootCmd.Use}, tc.args...) From 36ceb601c2eb6ab28f9ec61803c24ac978aab800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:50:48 +0200 Subject: [PATCH 02/87] build(deps): Bump bufbuild/buf-setup-action from 1.21.0 to 1.22.0 (#1029) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.21.0 to 1.22.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.21.0...v1.22.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index a3404dd73e0..52771a442e5 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.21.0 + - uses: bufbuild/buf-setup-action@v1.22.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From b60b805432625ac92a2ad383e5b6e85362c120d9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:56:13 -0400 Subject: [PATCH 03/87] docs: Added double quotes to /abci_query path param (#1015) (#1046) Closes #666 This PR adds double quotes to `path` param of `/abci_query` endpoint. --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit f6f13b1f67a54549d9f212a859ca4924d6ad9127) Co-authored-by: Steven Ferrer --- rpc/openapi/openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index c267a09591c..89ef15eb016 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -886,7 +886,7 @@ paths: required: true schema: type: string - example: "tx.height=1000" + example: '"tx.height=1000"' - in: query name: prove description: Include proofs of the transactions inclusion in the block @@ -949,7 +949,7 @@ paths: required: true schema: type: string - example: "block.height > 1000 AND valset.changed > 0" + example: '"block.height > 1000 AND valset.changed > 0"' - in: query name: page description: "Page number (1-based)" @@ -1065,7 +1065,7 @@ paths: required: true schema: type: string - example: "/a/b/c" + example: '"/a/b/c"' - in: query name: data description: Data From 5806f5a468c3a7cdb66a2933ff22f31793676a32 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 09:28:37 -0300 Subject: [PATCH 04/87] Clarifies that processProposal may be called for set of transactions different from the one returned in the preceding prepareProposal (backport #1033) (#1053) * Clarifies that processProposal may be called for set of transactions different from the one returned in the preceding prepareProposal (#1033) If a proposer fails after calling prepareProposal and before calling processProposal, then the following may happen upon restarting: - if failed before signing another message, then will invoke prepareProposal again, sign a new block, probably empty, and propose it; - if failed after signing a proposal but before writing the proposal message into the WAL, then will invoke prepareProposal and produce a new, probably empty block, [fail to sign it](https://github.com/cometbft/cometbft/blob/2789a59a9cc61c6ea56a6b266eeadf0f26ca2456/consensus/state.go#L1221), and not invoke processProposal; prevote timeouts will ensure the CometBFT is not stuck; - if failed after writing the proposal message to the WAL, then will invoke prepareProposal, produce a new, probably empty block, fail to sign it, and invoke processProposal with the block signed before crashing. --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [x] Updated relevant documentation (`docs/` or `spec/`) and code comments (cherry picked from commit b23ef56f8e6d8a7015a7f816a61f2e53b0b07b0d) # Conflicts: # spec/abci/abci++_basic_concepts.md # spec/abci/abci++_comet_expected_behavior.md * solving cherry pick conflicts * solving cherry pick conflicts --------- Co-authored-by: Lasaro --- spec/abci/abci++_basic_concepts.md | 50 ++++++++++++--------- spec/abci/abci++_comet_expected_behavior.md | 24 +++++++--- spec/abci/abci++_example_scenarios.md | 48 ++++++++++---------- spec/abci/abci++_methods.md | 15 ++++--- 4 files changed, 79 insertions(+), 58 deletions(-) diff --git a/spec/abci/abci++_basic_concepts.md b/spec/abci/abci++_basic_concepts.md index 9643761e71d..9e141b64000 100644 --- a/spec/abci/abci++_basic_concepts.md +++ b/spec/abci/abci++_basic_concepts.md @@ -24,7 +24,7 @@ title: Overview and basic concepts # Overview and basic concepts -## ABCI++ vs. ABCI +## ABCI 2.0 vs. ABCI [↑ Back to Outline](#outline) @@ -40,7 +40,7 @@ as the Application cannot require validators to do more than executing the trans finalized blocks. This includes features such as threshold cryptography, and guaranteed IBC connection attempts. -ABCI++ addresses these limitations by allowing the application to intervene at two key places of +ABCI 2.0 addresses these limitations by allowing the application to intervene at two key places of consensus execution: (a) at the moment a new proposal is to be created and (b) at the moment a proposal is to be validated. The new interface allows block proposers to perform application-dependent work in a block through the `PrepareProposal` method (a); and validators to perform application-dependent work @@ -53,7 +53,7 @@ We plan to extend this to allow applications to intervene at the moment a (preco The applications could then require their validators to do more than just validating blocks through the `ExtendVote` and `VerifyVoteExtension` methods. -## Method overview +## Methods overview [↑ Back to Outline](#outline) @@ -85,19 +85,21 @@ call sequences of these methods. can make changes to the raw proposal, such as modifying the set of transactions or the order in which they appear, and returns the (potentially) modified proposal, called *prepared proposal* in the `ResponsePrepareProposal` - call. The logic modifying the raw proposal can be non-deterministic. + call. + The logic modifying the raw proposal MAY be non-deterministic. - [**ProcessProposal:**](./abci++_methods.md#processproposal) It allows a validator to perform application-dependent work in a proposed block. This enables features such as immediate block execution, and allows the Application to reject invalid blocks. CometBFT calls it when it receives a proposal and _validValue_ is `nil`. - The Application cannot modify the proposal at this point but can reject it if it is + The Application cannot modify the proposal at this point but can reject it if invalid. If that is the case, the consensus algorithm will prevote `nil` on the proposal, which has strong liveness implications for CometBFT. As a general rule, the Application SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of the proposal is invalid (e.g., an invalid transaction); the Application can ignore the invalid part of the prepared proposal at block execution time. + The logic in `ProcessProposal` MUST be deterministic. - [**BeginBlock:**](./abci++_methods.md#beginblock) Is called exactly once after a block has been decided and executes once before all `DeliverTx` method calls. @@ -125,6 +127,7 @@ call sequences of these methods. CometBFT calls `ExtendVote` when the consensus algorithm is about to send a non-`nil` precommit message. If the Application does not have vote extension information to provide at that time, it returns a 0-length byte array as its vote extension. + The logic in `ExtendVote` MAY be non-deterministic. - [**VerifyVoteExtension:**](./abci++_methods.md#verifyvoteextension) It allows validators to validate the vote extension data attached to a precommit message. If the validation @@ -224,23 +227,26 @@ More details on managing state across connections can be found in the section on ## Proposal timeout -Immediate execution requires the Application to fully execute the prepared block -before returning from `PrepareProposal`, this means that CometBFT cannot make progress -during the block execution. -This stands on the consensus algorithm critical path: if the Application takes a long time -executing the block, the default value of *TimeoutPropose* might not be sufficient -to accommodate the long block execution time and non-proposer nodes might time -out and prevote `nil`. The proposal, in this case, will probably be rejected and a new round will be necessary. +`PrepareProposal` stands on the consensus algorithm critical path, +i.e., CometBFT cannot make progress while this method is being executed. +Hence, if the Application takes a long time preparing a proposal, +the default value of *TimeoutPropose* might not be sufficient +to accommodate the method's execution and validator nodes might time out and prevote `nil`. +The proposal, in this case, will probably be rejected and a new round will be necessary. - -Operators will need to adjust the default value of *TimeoutPropose* in CometBFT's configuration file, +Timeouts are automatically increased for each new round of a height and, if the execution of `PrepareProposal` is bound, eventually *TimeoutPropose* will be long enough to accommodate the execution of `PrepareProposal`. +However, relying on this self adaptation could lead to performance degradation and, therefore, +operators are suggested to adjust the initial value of *TimeoutPropose* in CometBFT's configuration file, in order to suit the needs of the particular application being deployed. +This is particularly important if applications implement *immediate execution*. +To implement this technique, proposers need to execute the block being proposed within `PrepareProposal`, which could take longer than *TimeoutPropose*. + ## Deterministic State-Machine Replication [↑ Back to Outline](#outline) -ABCI++ applications must implement deterministic finite-state machines to be +ABCI applications must implement deterministic finite-state machines to be securely replicated by the CometBFT consensus engine. This means block execution must be strictly deterministic: given the same ordered set of transactions, all nodes will compute identical responses, for all @@ -255,11 +261,13 @@ from block execution (`BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` calls) any other kind of request. This is the only way to ensure all nodes see the same transactions and compute the same results. -Some Applications may choose to implement immediate execution, which entails executing the blocks -that are about to be proposed (via `PrepareProposal`), and those that the Application is asked to -validate (via `ProcessProposal`). However, the state changes caused by processing those -proposed blocks must never replace the previous state until the block execution calls confirm -the block decided. +Applications that implement immediate execution (execute the blocks +that are about to be proposed, in `PrepareProposal`, or that require validation, in `ProcessProposal`) produce a new candidate state before a block is decided. +The state changes caused by processing those +proposed blocks must never replace the previous state until `FinalizeBlock` confirms +that the proposed block was decided and `Commit` is invoked for it. + +The same is true to Applications that quickly accept blocks and execute the blocks optimistically in parallel with the remaining consensus steps to save time during block execution (`BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` calls); they must only apply state changes in `Commit`. >```abnf ->proposer = [prepare-proposal process-proposal] +>proposer = [prepare-proposal [process-proposal]] >``` * Also for every round, if the local process is _not_ the proposer of the current round, CometBFT diff --git a/spec/abci/abci++_example_scenarios.md b/spec/abci/abci++_example_scenarios.md index 93fb6a7a6f7..1a7de60b91d 100644 --- a/spec/abci/abci++_example_scenarios.md +++ b/spec/abci/abci++_example_scenarios.md @@ -4,13 +4,13 @@ title: ABCI++ extra --- # Introduction -In the section [CometBFT's expected behaviour](./abci++_comet_expected_behavior.md#valid-method-call-sequences), -we presented the most common behaviour, usually referred to as the good case. -However, the grammar specified in the same section is more general and covers more scenarios -that an Application designer needs to account for. +In the section [CometBFT's expected behaviour](./abci++_comet_expected_behavior.md#valid-method-call-sequences), +we presented the most common behaviour, usually referred to as the good case. +However, the grammar specified in the same section is more general and covers more scenarios +that an Application designer needs to account for. -In this section, we give more information about these possible scenarios. We focus on methods -introduced by ABCI++: `PrepareProposal` and `ProcessProposal`. Specifically, we concentrate +In this section, we give more information about these possible scenarios. We focus on methods +introduced by ABCI++: `PrepareProposal` and `ProcessProposal`. Specifically, we concentrate on the part of the grammar presented below. ```abnf @@ -21,25 +21,27 @@ proposer = [prepare-proposal process-proposal] non-proposer = [process-proposal] ``` -We can see from the grammar that we can have several rounds before deciding a block. The reasons +We can see from the grammar that we can have several rounds before deciding a block. The reasons why one round may not be enough are: + * network asynchrony, and -* a Byzantine process being the proposer. +* a Byzantine process being the proposer. -If we assume that the consensus algorithm decides on block $X$ in round $r$, in the rounds +If we assume that the consensus algorithm decides on block $X$ in round $r$, in the rounds $r' <= r$, CometBFT can exhibit any of the following behaviours: -1. Call `PrepareProposal` and/or `ProcessProposal` for block $X$. +1. Call `PrepareProposal` and/or `ProcessProposal` for block $X$. 1. Call `PrepareProposal` and/or `ProcessProposal` for block $Y \neq X$. 1. Does not call `PrepareProposal` and/or `ProcessProposal`. -In the rounds when it is the proposer, CometBFT's `PrepareProposal` call is always followed by the -`ProcessProposal` call. The reason is that the process always delivers the proposal to itself, which -triggers the `ProcessProposal` call. +In the rounds in which the process is the proposer, CometBFT's `PrepareProposal` call is always followed by the +`ProcessProposal` call. The reason is that the process also broadcasts the proposal to itself, which is locally delivered and triggers the `ProcessProposal` call. +The proposal processed by `ProcessProposal` is the same as what was returned by any of the preceding `PrepareProposal` invoked for the same height and round. +While in the absence of restarts there is only one such preceding invocations, if the proposer restarts there could have been one extra invocation to `PrepareProposal` for each restart. -As the number of rounds the consensus algorithm needs to decide in a given run is a priori unknown, the -application needs to account for any number of rounds, where each round can exhibit any of these three -behaviours. Recall that the application is unaware of the internals of consensus and thus of the rounds. +As the number of rounds the consensus algorithm needs to decide in a given run is a priori unknown, the +application needs to account for any number of rounds, where each round can exhibit any of these three +behaviours. Recall that the application is unaware of the internals of consensus and thus of the rounds. # Possible scenarios The unknown number of rounds we can have when following the consensus algorithm yields a vast number of @@ -125,19 +127,20 @@ some process will propose block $X$ and if $p$ receives $2f+1$ $Precommit$ messa value. -## Scenario 3 +## Scenario 3 -$p$ calls `PrepareProposal` and `ProcessProposal` for many values, but decides on a value for which it did +$p$ calls `PrepareProposal` and `ProcessProposal` for many values, but decides on a value for which it did not call `PrepareProposal` or `ProcessProposal`. -In this scenario, in all rounds before $r$ we can have any round presented in [Scenario 1](#scenario-1) or +In this scenario, in all rounds before $r$ we can have any round presented in [Scenario 1](#scenario-1) or [Scenario 2](#scenario-2). What is important is that: -- no proposer proposed block $X$ or if it did, process $p$, due to asynchrony, did not receive it in time, + +* no proposer proposed block $X$ or if it did, process $p$, due to asynchrony, did not receive it in time, so it did not call `ProcessProposal`, and -- if $p$ was the proposer it proposed some other value $\neq X$. +* if $p$ was the proposer it proposed some other value $\neq X$. -### Round $r$: +### Round $r$: 1. **Propose:** A correct process is the proposer in this round, and it proposes block $X$. Due to asynchrony, the proposal message arrives to process $p$ after its $timeoutPropose$ @@ -158,4 +161,3 @@ rounds $0 <= r' <= r$ and that due to network asynchrony or Byzantine proposer, proposal before $timeoutPropose$ expires. As a result, it will enter round $r$ without calling `PrepareProposal` and `ProcessProposal` before it, and as shown in Round $r$ of [Scenario 3](#scenario-3) it will decide in this round. Again without calling any of these two calls. - diff --git a/spec/abci/abci++_methods.md b/spec/abci/abci++_methods.md index 2edd07080b9..0b83236889a 100644 --- a/spec/abci/abci++_methods.md +++ b/spec/abci/abci++_methods.md @@ -390,7 +390,7 @@ title: Methods peers are available), it will reject the snapshot and try a different one via `OfferSnapshot`. The application should be prepared to reset and accept it or abort as appropriate. -## New methods introduced in ABCI++ +## New methods introduced in ABCI 2.0 ### PrepareProposal @@ -479,8 +479,7 @@ and _p_'s _validValue_ is `nil`: returns from the call. 3. The Application uses the information received (transactions, commit info, misbehavior, time) to (potentially) modify the proposal. - * the Application MAY fully execute the block and produce a candidate state — immediate - execution + * the Application MAY fully execute the block and produce a candidate state (immediate execution) * the Application can manipulate transactions: * leave transactions untouched * add new transactions (not present initially) to the proposal @@ -524,10 +523,12 @@ the consensus algorithm will use it as proposal and will not call `RequestPrepar * The Application may fully execute the block as though it was handling the calls to `BeginBlock-DeliverTx-EndBlock`. * However, any resulting state changes must be kept as _candidate state_, and the Application should be ready to discard it in case another block is decided. - * `RequestProcessProposal` is also called at the proposer of a round. The reason for this is to - inform the Application of the block header's hash, which cannot be done at `PrepareProposal` - time. In this case, the call to `RequestProcessProposal` occurs right after the call to - `RequestPrepareProposal`. + * `RequestProcessProposal` is also called at the proposer of a round. + Normally the call to `RequestProcessProposal` occurs right after the call to `RequestPrepareProposal` and + `RequestProcessProposal` matches the block produced based on `ResponsePrepareProposal` (i.e., + `RequestPrepareProposal.txs` equals `RequestProcessProposal.txs`). + However, no such guarantee is made since, in the presence of failures, `RequestProcessProposal` may match + `ResponsePrepareProposal` from an earlier invocation or `ProcessProposal` may not be invoked at all. * The height and time values match the values from the header of the proposed block. * If `ResponseProcessProposal.status` is `REJECT`, consensus assumes the proposal received is not valid. From 75f090def3894fc1441faeb8777bb86d5c7f144e Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 29 Jun 2023 16:25:26 +0200 Subject: [PATCH 05/87] v0.37.x: backport of new content on spec/p2p (#1004) This is backport of three PRs originally merged against main: * spec/p2p: Specify the operation of a Reactor (#714) * spec/p2p: document the p2p API used by Reactors (#851) * spec/p2p: new structure for the p2p specification (#966) Co-authored-by: Josef Widder <44643235+josef-widder@users.noreply.github.com> Co-authored-by: Lasaro Co-authored-by: Sergio Mena --- p2p/README.md | 8 +- spec/README.md | 12 +- spec/abci/abci++_basic_concepts.md | 2 +- spec/abci/abci++_methods.md | 4 +- spec/p2p/README.md | 46 +++ spec/p2p/images/p2p-reactors.png | Bin 0 -> 32410 bytes spec/p2p/images/p2p_state.png | Bin 0 -> 132059 bytes spec/p2p/implementation/README.md | 38 ++ spec/p2p/implementation/addressbook.md | 368 ++++++++++++++++++ spec/p2p/implementation/configuration.md | 51 +++ spec/p2p/implementation/peer_manager.md | 147 +++++++ spec/p2p/implementation/pex-protocol.md | 240 ++++++++++++ spec/p2p/implementation/pex.md | 111 ++++++ spec/p2p/implementation/switch.md | 238 +++++++++++ spec/p2p/implementation/transport.md | 222 +++++++++++ spec/p2p/implementation/types.md | 239 ++++++++++++ spec/p2p/{ => legacy-docs}/config.md | 0 spec/p2p/{ => legacy-docs}/connection.md | 0 spec/p2p/{ => legacy-docs}/messages/README.md | 0 .../{ => legacy-docs}/messages/block-sync.md | 2 +- .../{ => legacy-docs}/messages/consensus.md | 18 +- .../{ => legacy-docs}/messages/evidence.md | 4 +- .../p2p/{ => legacy-docs}/messages/mempool.md | 0 spec/p2p/{ => legacy-docs}/messages/pex.md | 0 .../{ => legacy-docs}/messages/state-sync.md | 6 +- spec/p2p/{ => legacy-docs}/node.md | 0 spec/p2p/{ => legacy-docs}/peer.md | 0 spec/p2p/reactor-api/README.md | 43 ++ spec/p2p/reactor-api/p2p-api.md | 311 +++++++++++++++ spec/p2p/reactor-api/reactor.md | 230 +++++++++++ spec/p2p/reactor-api/reactor.qnt | 276 +++++++++++++ spec/p2p/readme.md | 7 - 32 files changed, 2588 insertions(+), 35 deletions(-) create mode 100644 spec/p2p/README.md create mode 100644 spec/p2p/images/p2p-reactors.png create mode 100644 spec/p2p/images/p2p_state.png create mode 100644 spec/p2p/implementation/README.md create mode 100644 spec/p2p/implementation/addressbook.md create mode 100644 spec/p2p/implementation/configuration.md create mode 100644 spec/p2p/implementation/peer_manager.md create mode 100644 spec/p2p/implementation/pex-protocol.md create mode 100644 spec/p2p/implementation/pex.md create mode 100644 spec/p2p/implementation/switch.md create mode 100644 spec/p2p/implementation/transport.md create mode 100644 spec/p2p/implementation/types.md rename spec/p2p/{ => legacy-docs}/config.md (100%) rename spec/p2p/{ => legacy-docs}/connection.md (100%) rename spec/p2p/{ => legacy-docs}/messages/README.md (100%) rename spec/p2p/{ => legacy-docs}/messages/block-sync.md (96%) rename spec/p2p/{ => legacy-docs}/messages/consensus.md (88%) rename spec/p2p/{ => legacy-docs}/messages/evidence.md (78%) rename spec/p2p/{ => legacy-docs}/messages/mempool.md (100%) rename spec/p2p/{ => legacy-docs}/messages/pex.md (100%) rename spec/p2p/{ => legacy-docs}/messages/state-sync.md (94%) rename spec/p2p/{ => legacy-docs}/node.md (100%) rename spec/p2p/{ => legacy-docs}/peer.md (100%) create mode 100644 spec/p2p/reactor-api/README.md create mode 100644 spec/p2p/reactor-api/p2p-api.md create mode 100644 spec/p2p/reactor-api/reactor.md create mode 100644 spec/p2p/reactor-api/reactor.qnt delete mode 100644 spec/p2p/readme.md diff --git a/p2p/README.md b/p2p/README.md index 99903f405dd..bdde71d201d 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -4,7 +4,7 @@ The p2p package provides an abstraction around peer-to-peer communication. Docs: -- [Connection](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/p2p/connection.md) for details on how connections and multiplexing work -- [Peer](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/p2p/node.md) for details on peer ID, handshakes, and peer exchange -- [Node](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/p2p/node.md) for details about different types of nodes and how they should work -- [Config](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/p2p/config.md) for details on some config option +- [Connection](../spec/p2p/legacy-docs/connection.md) for details on how connections and multiplexing work +- [Peer](../spec/p2p/legacy-docs/node.md) for details on peer ID, handshakes, and peer exchange +- [Node](../spec/p2p/legacy-docs/node.md) for details about different types of nodes and how they should work +- [Config](../spec/p2p/legacy-docs/config.md) for details on some config option diff --git a/spec/README.md b/spec/README.md index 921c68b7cb0..61c4d3fc927 100644 --- a/spec/README.md +++ b/spec/README.md @@ -35,12 +35,12 @@ please submit them to our [bug bounty](https://cometbft.com/security)! ### P2P and Network Protocols -- [The Base P2P Layer](./p2p/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections -- [Peer Exchange (PEX)](./p2p/messages/pex.md): gossip known peer addresses so peers can find each other -- [Block Sync](./p2p/messages/block-sync.md): gossip blocks so peers can catch up quickly -- [Consensus](./p2p/messages/consensus.md): gossip votes and block parts so new blocks can be committed -- [Mempool](./p2p/messages/mempool.md): gossip transactions so they get included in blocks -- [Evidence](./p2p/messages/evidence.md): sending invalid evidence will stop the peer +- [The Base P2P Layer](./p2p/legacy-docs/node.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections +- [Peer Exchange (PEX)](./p2p/legacy-docs/messages/pex.md): gossip known peer addresses so peers can find each other +- [Block Sync](./p2p/legacy-docs/messages/block-sync.md): gossip blocks so peers can catch up quickly +- [Consensus](./p2p/legacy-docs/messages/consensus.md): gossip votes and block parts so new blocks can be committed +- [Mempool](./p2p/legacy-docs/messages/mempool.md): gossip transactions so they get included in blocks +- [Evidence](./p2p/legacy-docs/messages/evidence.md): sending invalid evidence will stop the peer ### RPC diff --git a/spec/abci/abci++_basic_concepts.md b/spec/abci/abci++_basic_concepts.md index 9e141b64000..c0e60077835 100644 --- a/spec/abci/abci++_basic_concepts.md +++ b/spec/abci/abci++_basic_concepts.md @@ -177,7 +177,7 @@ call sequences of these methods. State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying state machine (application) snapshots instead of replaying historical blocks. For more details, see the -[state sync documentation](../p2p/messages/state-sync.md). +[state sync documentation](../p2p/legacy-docs/messages/state-sync.md). New nodes discover and request snapshots from other nodes in the P2P network. A CometBFT node that receives a request for snapshots from a peer will call diff --git a/spec/abci/abci++_methods.md b/spec/abci/abci++_methods.md index 0b83236889a..983f9022465 100644 --- a/spec/abci/abci++_methods.md +++ b/spec/abci/abci++_methods.md @@ -346,7 +346,7 @@ title: Methods can be spoofed by adversaries, so applications should employ additional verification schemes to avoid denial-of-service attacks. The verified `AppHash` is automatically checked against the restored application at the end of snapshot restoration. - * For more information, see the `Snapshot` data type or the [state sync section](../p2p/messages/state-sync.md). + * For more information, see the `Snapshot` data type or the [state sync section](../p2p/legacy-docs/messages/state-sync.md). ### ApplySnapshotChunk @@ -864,7 +864,7 @@ Most of the data structures used in ABCI are shared [common data structures](../ | metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 5 | * **Usage**: - * Used for state sync snapshots, see the [state sync section](../p2p/messages/state-sync.md) for details. + * Used for state sync snapshots, see the [state sync section](../p2p/legacy-docs/messages/state-sync.md) for details. * A snapshot is considered identical across nodes only if _all_ fields are equal (including `Metadata`). Chunks may be retrieved from all nodes that have the same snapshot. * When sent across the network, a snapshot message can be at most 4 MB. diff --git a/spec/p2p/README.md b/spec/p2p/README.md new file mode 100644 index 00000000000..29efd8ecadf --- /dev/null +++ b/spec/p2p/README.md @@ -0,0 +1,46 @@ +--- +order: 1 +parent: + title: P2P + order: 6 +--- + +# Peer-to-Peer Communication + +A CometBFT network is composed of multiple CometBFT instances, hereafter called +`nodes`, that interact by exchanging messages. + +The CometBFT protocols are designed under the assumption of a partially-connected network model. +This means that a node is not assumed to be directly connected to every other +node in the network. +Instead, each node is directly connected to only a subset of other nodes, +hereafter called its `peers`. + +The peer-to-peer (p2p) communication layer is then the component of CometBFT that: + +1. establishes connections between nodes in a CometBFT network +2. manages the communication between a node and the connected peers +3. intermediates the exchange of messages between peers in CometBFT protocols + +The specification the p2p layer is a work in progress, +tracked by [issue #19](https://github.com/cometbft/cometbft/issues/19). +The current content is organized as follows: + +- [`implementation`](./implementation/README.md): documents the current state + of the implementation of the p2p layer, covering the main components of the + `p2p` package. The documentation covers, in a fairly comprehensive way, + the items 1. and 2. from the list above. +- [`reactor-api`](./reactor-api/README.md): specifies the API offered by the + p2p layer to the protocol layer, through the `Reactor` abstraction. + This is a high-level specification (i.e., it should not be implementation-specific) + of the p2p layer API, covering item 3. from the list above. +- [`legacy-docs`](./legacy-docs/): We keep older documentation in + the `legacy-docs` directory, as overall, it contains useful information. + However, part of this content is redundant, + being more comprehensively covered in more recent documents, + and some implementation details might be outdated + (see [issue #981](https://github.com/cometbft/cometbft/issues/981)). + +In addition to this content, some unfinished, work in progress, and auxiliary +material can be found in the +[knowledge-base](https://github.com/cometbft/knowledge-base/tree/main/p2p) repository. diff --git a/spec/p2p/images/p2p-reactors.png b/spec/p2p/images/p2p-reactors.png new file mode 100644 index 0000000000000000000000000000000000000000..5515976c10cc6389589e0531edaba65abfee56d2 GIT binary patch literal 32410 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 LT|l3^dH??ai5!|y literal 0 HcmV?d00001 diff --git a/spec/p2p/images/p2p_state.png b/spec/p2p/images/p2p_state.png new file mode 100644 index 0000000000000000000000000000000000000000..c86d01682624fd92ead0739519d906bf65a82ec3 GIT binary patch literal 132059 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 Note that new addresses does not mean bad addresses. +> The addresses of peers marked as [bad peers](#bad-peers) are removed from the +> buckets where they are stored, and temporarily kept in a table of banned peers. + +The number of buckets is fixed and there are more buckets for new addresses +(`256`) than buckets for old addresses (`64`), a ratio of 4:1. +Each bucket can store up to `64` addresses. +When a bucket becomes full, the peer address with the lowest ranking is removed +from the bucket. +The first choice is to remove bad addresses, with multiple failed attempts +associated. +In the absence of those, the *oldest* address in the bucket is removed, i.e., +the address with the oldest last attempt to dial. + +When a bucket for old addresses becomes full, the lowest-ranked peer address in +the bucket is moved to a bucket of new addresses. +When a bucket for new addresses becomes full, the lowest-ranked peer address in +the bucket is removed from the address book. +In other words, exceeding old or good addresses are downgraded to new +addresses, while exceeding new addresses are dropped. + +The bucket that stores an `address` is defined by the following two methods, +for new and old addresses: + +- `calcNewBucket(address, source) = hash(key + groupKey(source) + hash(key + groupKey(address) + groupKey(source)) % newBucketsPerGroup) % newBucketCount` +- `calcOldBucket(address) = hash(key + groupKey(address) + hash(key + address) % oldBucketsPerGroup) % oldBucketCount` + +The `key` is a fixed random 96-bit (8-byte) string. +The `groupKey` for an address is a string representing its network group. +The `source` of an address is the address of the peer from which we learn the +address.. +The first (internal) hash is reduced to an integer up to `newBucketsPerGroup = +32`, for new addresses, and `oldBucketsPerGroup = 4`, for old addresses. +The second (external) hash is reduced to bucket indexes, in the interval from 0 +to the number of new (`newBucketCount = 256`) or old (`oldBucketCount = 64`) buckets. + +Notice that new addresses with sources from the same network group are more +likely to end up in the same bucket, therefore to competing for it. +For old address, instead, two addresses are more likely to end up in the same +bucket when they belong to the same network group. + +## Adding addresses + +The `AddAddress` method adds the address of a peer to the address book. + +The added address is associated to a *source* address, which identifies the +node from which the peer address was learned. + +Addresses are added to the address book in the following situations: + +1. When a peer address is learned via PEX protocol, having the sender + of the PEX message as its source +2. When an inbound peer is added, in this case the peer itself is set as the + source of its own address +3. When the switch is instructed to dial addresses via the `DialPeersAsync` + method, in this case the node itself is set as the source + +If the added address contains a node ID that is not registered in the address +book, the address is added to a [bucket](#buckets) of new addresses. +Otherwise, the additional address for an existing node ID is **not added** to +the address book when: + +- The last address added with the same node ID is stored in an old bucket, so + it is considered a "good" address +- There are addresses associated to the same node ID stored in + `maxNewBucketsPerAddress = 4` distinct buckets +- Randomly, with a probability that increases exponentially with the number of + buckets in which there is an address with the same node ID. + So, a new address for a node ID which is already present in one bucket is + added with 50% of probability; if the node ID is present in two buckets, the + probability decreases to 25%; and if it is present in three buckets, the + probability is 12.5%. + +The new address is also added to the `addrLookup` table, which stores +`knownAddress` entries indexed by their node IDs. +If the new address is from an unknown peer, a new entry is added to the +`addrLookup` table; otherwise, the existing entry is updated with the new +address. +Entries of this table contain, among other fields, the list of buckets where +addresses of a peer are stored. +The `addrLookup` table is used by most of the address book methods (e.g., +`HasAddress`, `IsGood`, `MarkGood`, `MarkAttempt`), as it provides fast access +to addresses. + +### Errors + +- if the added address or the associated source address are nil +- if the added address is invalid +- if the added address is the local node's address +- if the added address ID is of a [banned](#bad-peers) peer +- if either the added address or the associated source address IDs are configured as private IDs +- if `routabilityStrict` is set and the address is not routable +- in case of failures computing the bucket for the new address (`calcNewBucket` method) +- if the added address instance, which is a new address, is configured as an + old address (sanity check of `addToNewBucket` method) + +## Need for Addresses + +The `NeedMoreAddrs` method verifies whether the address book needs more addresses. + +It is invoked by the PEX reactor to define whether to request peer addresses +to a new outbound peer or to a randomly selected connected peer. + +The address book needs more addresses when it has less than `1000` addresses +registered, counting all buckets for new and old addresses. + +## Pick address + +The `PickAddress` method returns an address stored in the address book, chosen +at random with a configurable bias towards new addresses. + +It is invoked by the Peer Manager to obtain a peer address to dial, as part of +its `ensurePeers` routine. +The bias starts from 10%, when the peer has no outbound peers, increasing by +10% for each outbound peer the node has, up to 90%, when the node has at least +8 outbound peers. + +The configured bias is a parameter that influences the probability of choosing +an address from a bucket of new addresses or from a bucket of old addresses. +A second parameter influencing this choice is the number of new and old +addresses stored in the address book. +In the absence of bias (i.e., if the configured bias is 50%), the probability +of picking a new address is given by the square root of the number of new +addresses divided by the sum of the square roots of the numbers of new and old +addresses. +By adding a bias toward new addresses (i.e., configured bias larger than 50%), +the portion on the sample occupied by the square root of the number of new +addresses increases, while the corresponding portion for old addresses decreases. +As a result, it becomes more likely to pick a new address at random from this sample. + +> The use of the square roots softens the impact of disproportional numbers of +> new and old addresses in the address book. This is actually the expected +> scenario, as there are 4 times more buckets for new addresses than buckets +> for old addresses. + +Once the type of address, new or old, is defined, a non-empty bucket of this +type is selected at random. +From the selected bucket, an address is chosen at random and returned. +If all buckets of the selected type are empty, no address is returned. + +## Random selection + +The `GetSelection` method returns a selection of addresses stored in the +address book, with no bias toward new or old addresses. + +It is invoked by the PEX protocol to obtain a list of peer addresses with two +purposes: + +- To send to a peer in a PEX response, in the case of outbound peers or of + nodes not operating in seed mode +- To crawl, in the case of nodes operating in seed mode, as part of every + interaction of the `crawlPeersRoutine` + +The selection is a random subset of the peer addresses stored in the +`addrLookup` table, which stores the last address added for each peer ID. +The target size of the selection is `23%` (`getSelectionPercent`) of the +number of addresses stored in the address book, but it should not be lower than +`32` (`minGetSelection`) --- if it is, all addresses in the book are returned +--- nor greater than `250` (`maxGetSelection`). + +> The random selection is produced by: +> +> - Retrieving all entries of the `addrLookup` map, which by definition are +> returned in random order. +> - Randomly shuffling the retrieved list, using the Fisher-Yates algorithm + +## Random selection with bias + +The `GetSelectionWithBias` method returns a selection of addresses stored in +the address book, with bias toward new addresses. + +It is invoked by the PEX protocol to obtain a list of peer addresses to be sent +to a peer in a PEX response. +This method is only invoked by seed nodes, when replying to a PEX request +received from an inbound peer (i.e., a peer that dialed the seed node). +The bias used in this scenario is hard-coded to 30%, meaning that 70% of +the returned addresses are expected to be old addresses. + +The number of addresses that compose the selection is computed in the same way +as for the non-biased random selection. +The bias toward new addresses is implemented by requiring that the configured +bias, interpreted as a percentage, of the select addresses come from buckets of +new addresses, while the remaining come from buckets of old addresses. +Since the number of old addresses is typically lower than the number of new +addresses, it is possible that the address book does not have enough old +addresses to include in the selection. +In this case, additional new addresses are included in the selection. +Thus, the configured bias, in practice, is towards old addresses, not towards +new addresses. + +To randomly select addresses of a type, the address book considers all +addresses present in every bucket of that type. +This list of all addresses of a type is randomly shuffled, and the requested +number of addresses are retrieved from the tail of this list. +The returned selection contains, at its beginning, a random selection of new +addresses in random order, followed by a random selection of old addresses, in +random order. + +## Dial Attempts + +The `MarkAttempt` method records a failed attempt to connect to an address. + +It is invoked by the Peer Manager when it fails dialing a peer, but the failure +is not in the authentication step (`ErrSwitchAuthenticationFailure` error). +In case of authentication errors, the peer is instead marked as a [bad peer](#bad-peers). + +The failed connection attempt is recorded in the address registered for the +peer's ID in the `addrLookup` table, which is the last address added with that ID. +The known address' counter of failed `Attempts` is increased and the failure +time is registered in `LastAttempt`. + +The possible effect of recording multiple failed connect attempts to a peer is +to turn its address into a *bad* address (do not confuse with banned addresses). +A known address becomes bad if it is stored in buckets of new addresses, and +when connection attempts: + +- Have not been made over a week, i.e., `LastAttempt` is older than a week +- Have failed 3 times and never succeeded, i.e., `LastSucess` field is unset +- Have failed 10 times in the last week, i.e., `LastSucess` is older than a week + +Addresses marked as *bad* are the first candidates to be removed from a bucket of +new addresses when the bucket becomes full. + +> Note that failed connection attempts are reported for a peer address, but in +> fact the address book records them for a peer. +> +> More precisely, failed connection attempts are recorded in the entry of the +> `addrLookup` table with reported peer ID, which contains the last address +> added for that node ID, which is not necessarily the reported peer address. + +## Good peers + +The `MarkGood` method marks a peer ID as good. + +It is invoked by the consensus reactor, via switch, when the number of useful +messages received from a peer is a multiple of `10000`. +Vote and block part messages are considered for this number, they must be valid +and not be duplicated messages to be considered useful. + +> The `SwitchReporter` type of `behaviour` package also invokes the `MarkGood` +> method when a "reason" associated with consensus votes and block parts is +> reported. +> No reactor, however, currently provides these "reasons" to the `SwitchReporter`. + +The effect of this action is that the address registered for the peer's ID in the +`addrLookup` table, which is the last address added with that ID, is marked as +good and moved to a bucket of old addresses. +An address marked as good has its failed to connect counter and timestamp reset. +If the destination bucket of old addresses is full, the oldest address in the +bucket is moved (downgraded) to a bucket of new addresses. + +Moving the peer address to a bucket of old addresses has the effect of +upgrading, or increasing the ranking of a peer in the address book. + +## Bad peers + +The `MarkBad` method marks a peer as bad and bans it for a period of time. + +This method is only invoked within the PEX reactor, with a banning time of 24 +hours, for the following reasons: + +- A peer misbehaves in the [PEX protocol](./pex-protocol.md#misbehavior) +- When the `maxAttemptsToDial` limit (`16`) is reached for a peer +- If an `ErrSwitchAuthenticationFailure` error is returned when dialing a peer + +The effect of this action is that the address registered for the peer's ID in the +`addrLookup` table, which is the last address added with that ID, is banned for +a period of time. +The banned peer is removed from the `addrLookup` table and from all buckets +where its addresses are stored. + +The information about banned peers, however, is not discarded. +It is maintained in the `badPeers` map, indexed by peer ID. +This allows, in particular, addresses of banned peers to be +[reinstated](#reinstating-addresses), i.e., to be added +back to the address book, when their ban period expires. + +## Reinstating addresses + +The `ReinstateBadPeers` method attempts to re-add banned addresses to the address book. + +It is invoked by the PEX reactor when dialing new peers. +This action is taken before requesting additional addresses to peers, +in the case that the node needs more peer addresses. + +The set of banned peer addresses is retrieved from the `badPeers` map. +Addresses that are not any longer banned, i.e., whose banned period has expired, +are added back to the address book as new addresses, while the corresponding +node IDs are removed from the `badPeers` map. + +## Removing addresses + +The `RemoveAddress` method removes an address from the address book. + +It is invoked by the switch when it dials a peer or accepts a connection from a +peer that ends up being the node itself (`IsSelf` error). +In both cases, the address dialed or accepted is also added to the address book +as a local address, via the `AddOurAddress` method. + +The same logic is also internally used by the address book for removing +addresses of a peer that is [marked as a bad peer](#bad-peers). + +The entry registered with the peer ID of the address in the `addrLookup` table, +which is the last address added with that ID, is removed from all buckets where +it is stored and from the `addrLookup` table. + +> FIXME: is it possible that addresses with the same ID as the removed address, +> but with distinct network addresses, are kept in buckets of the address book? +> While they will not be accessible anymore, as there is no reference to them +> in the `addrLookup`, they will still be there. + +## Persistence + +The `loadFromFile` method, called when the address book is started, reads +address book entries from a file, passed to the address book constructor. +The file, at this point, does not need to exist. + +The `saveRoutine` is started when the address book is started. +It saves the address book to the configured file every `dumpAddressInterval`, +hard-coded to 2 minutes. +It is also possible to save the content of the address book using the `Save` +method. +Saving the address book content to a file acquires the address book lock, also +employed by all other public methods. diff --git a/spec/p2p/implementation/configuration.md b/spec/p2p/implementation/configuration.md new file mode 100644 index 00000000000..977c8c3e049 --- /dev/null +++ b/spec/p2p/implementation/configuration.md @@ -0,0 +1,51 @@ +# CometBFT p2p configuration + +This document contains configurable parameters a node operator can use to tune the p2p behaviour. + +| Parameter| Default| Description | +| --- | --- | ---| +| ListenAddress | "tcp://0.0.0.0:26656" | Address to listen for incoming connections (0.0.0.0:0 means any interface, any port) | +| ExternalAddress | "" | Address to advertise to peers for them to dial | +| [Seeds](./pex-protocol.md#seed-nodes) | empty | Comma separated list of seed nodes to connect to (ID@host:port )| +| [Persistent peers](./peer_manager.md#persistent-peers) | empty | Comma separated list of nodes to keep persistent connections to (ID@host:port ) | +| UPNP | false | UPNP port forwarding enabled | +| [AddrBook](./addressbook.md) | defaultAddrBookPath | Path do address book | +| AddrBookStrict | true | Set true for strict address routability rules and false for private or local networks | +| [MaxNumInboundPeers](./switch.md#accepting-peers) | 40 | Maximum number of inbound peers | +| [MaxNumOutboundPeers](./peer_manager.md#ensure-peers) | 10 | Maximum number of outbound peers to connect to, excluding persistent peers | +| [UnconditionalPeers](./switch.md#accepting-peers) | empty | These are IDs of the peers which are allowed to be (re)connected as both inbound or outbound regardless of whether the node reached `max_num_inbound_peers` or `max_num_outbound_peers` or not. | +| PersistentPeersMaxDialPeriod| 0 * time.Second | Maximum pause when redialing a persistent peer (if zero, exponential backoff is used) | +| FlushThrottleTimeout |100 * time.Millisecond| Time to wait before flushing messages out on the connection | +| MaxPacketMsgPayloadSize | 1024 | Maximum size of a message packet payload, in bytes | +| SendRate | 5120000 (5 mB/s) | Rate at which packets can be sent, in bytes/second | +| RecvRate | 5120000 (5 mB/s) | Rate at which packets can be received, in bytes/second| +| [PexReactor](./pex.md) | true | Set true to enable the peer-exchange reactor | +| SeedMode | false | Seed mode, in which node constantly crawls the network and looks for. Does not work if the peer-exchange reactor is disabled. | +| PrivatePeerIDs | empty | Comma separated list of peer IDsthat we do not add to the address book or gossip to other peers. They stay private to us. | +| AllowDuplicateIP | false | Toggle to disable guard against peers connecting from the same ip.| +| [HandshakeTimeout](./transport.md#connection-upgrade) | 20 * time.Second | Timeout for handshake completion between peers | +| [DialTimeout](./switch.md#dialing-peers) | 3 * time.Second | Timeout for dialing a peer | + + +These parameters can be set using the `$CMTHOME/config/config.toml` file. A subset of them can also be changed via command line using the following command line flags: + +| Parameter | Flag| Example| +| --- | --- | ---| +| Listen address| `p2p.laddr` | "tcp://0.0.0.0:26656" | +| Seed nodes | `p2p.seeds` | `--p2p.seeds “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:4444”` | +| Persistent peers | `p2p.persistent_peers` | `--p2p.persistent_peers “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:26656”` | +| Unconditional peers | `p2p.unconditional_peer_ids` | `--p2p.unconditional_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | + | UPNP | `p2p.upnp` | `--p2p.upnp` | + | PexReactor | `p2p.pex` | `--p2p.pex` | + | Seed mode | `p2p.seed_mode` | `--p2p.seed_mode` | + | Private peer ids | `p2p.private_peer_ids` | `--p2p.private_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | + + **Note on persistent peers** + + If `persistent_peers_max_dial_period` is set greater than zero, the +pause between each dial to each persistent peer will not exceed `persistent_peers_max_dial_period` +during exponential backoff and we keep trying again without giving up. + +If `seeds` and `persistent_peers` intersect, +the user will be warned that seeds may auto-close connections +and that the node may not be able to keep the connection persistent. diff --git a/spec/p2p/implementation/peer_manager.md b/spec/p2p/implementation/peer_manager.md new file mode 100644 index 00000000000..4f83cc6dfdf --- /dev/null +++ b/spec/p2p/implementation/peer_manager.md @@ -0,0 +1,147 @@ +# Peer Manager + +The peer manager is responsible for establishing connections with peers. +It defines when a node should dial peers and which peers it should dial. +The peer manager is not an implementation abstraction of the p2p layer, +but a role that is played by the [PEX reactor](./pex.md). + +## Outbound peers + +The `ensurePeersRoutine` is a persistent routine intended to ensure that a node +is connected to `MaxNumOutboundPeers` outbound peers. +This routine is continuously executed by regular nodes, i.e. nodes not +operating in seed mode, as part of the PEX reactor implementation. + +The logic defining when the node should dial peers, for selecting peers to dial +and for actually dialing them is implemented in the `ensurePeers` method. +This method is periodically invoked -- every `ensurePeersPeriod`, with default +value to 30 seconds -- by the `ensurePeersRoutine`. + +A node is expected to dial peers whenever the number of outbound peers is lower +than the configured `MaxNumOutboundPeers` parameter. +The current number of outbound peers is retrieved from the switch, using the +`NumPeers` method, which also reports the number of nodes to which the switch +is currently dialing. +If the number of outbound peers plus the number of dialing routines equals to +`MaxNumOutboundPeers`, nothing is done. +Otherwise, the `ensurePeers` method will attempt to dial node addresses in +order to reach the target number of outbound peers. + +Once defined that the node needs additional outbound peers, the node queries +the address book for candidate addresses. +This is done using the [`PickAddress`](./addressbook.md#pick-address) method, +which returns an address selected at random on the address book, with some bias +towards new or old addresses. +When the node has up to 3 outbound peers, the adopted bias is towards old +addresses, i.e., addresses of peers that are believed to be "good". +When the node has from 5 outbound peers, the adopted bias is towards new +addresses, i.e., addresses of peers about which the node has not yet collected +much information. +So, the more outbound peers a node has, the less conservative it will be when +selecting new peers. + +The selected peer addresses are then dialed in parallel, by starting a dialing +routine per peer address. +Dialing a peer address can fail for multiple reasons. +The node might have attempted to dial the peer too many times. +In this case, the peer address is marked as bad and removed from the address book. +The node might have attempted and failed to dial the peer recently +and the exponential `backoffDuration` has not yet passed. +Or the current connection attempt might fail, which is registered in the address book. +None of these errors are explicitly handled by the `ensurePeers` method, which +also does not wait until the connections are established. + +The third step of the `ensurePeers` method is to ensure that the address book +has enough addresses. +This is done, first, by [reinstating banned peers](./addressbook.md#Reinstating-addresses) +whose ban period has expired. +Then, the node randomly selects a connected peer, which can be either an +inbound or outbound peer, to [requests addresses](./pex-protocol.md#Requesting-Addresses) +using the PEX protocol. +Last, and this action is only performed if the node could not retrieve any new +address to dial from the address book, the node dials the configured seed nodes +in order to establish a connection to at least one of them. + +### Fast dialing + +As above described, seed nodes are actually the last source of peer addresses +for regular nodes. +They are contacted by a node when, after an invocation of the `ensurePeers` +method, no suitable peer address to dial is retrieved from the address book +(e.g., because it is empty). + +Once a connection with a seed node is established, the node immediately +[sends a PEX request](./pex-protocol.md#Requesting-Addresses) to it, as it is +added as an outbound peer. +When the corresponding PEX response is received, the addresses provided by the +seed node are added to the address book. +As a result, in the next invocation of the `ensurePeers` method, the node +should be able to dial some of the peer addresses provided by the seed node. + +However, as observed in this [issue](https://github.com/tendermint/tendermint/issues/2093), +it can take some time, up to `ensurePeersPeriod` or 30 seconds, from when the +node receives new peer addresses and when it dials the received addresses. +To avoid this delay, which can be particularly relevant when the node has no +peers, a node immediately attempts to dial peer addresses when they are +received from a peer that is locally configured as a seed node. + +> FIXME: The current logic was introduced in [#3762](https://github.com/tendermint/tendermint/pull/3762). +> Although it fix the issue, the delay between receiving an address and dialing +> the peer, it does not impose and limit on how many addresses are dialed in this +> scenario. +> So, all addresses received from a seed node are dialed, regardless of the +> current number of outbound peers, the number of dialing routines, or the +> `MaxNumOutboundPeers` parameter. +> +> Issue [#9548](https://github.com/tendermint/tendermint/issues/9548) was +> created to handle this situation. + +### First round + +When the PEX reactor is started, the `ensurePeersRoutine` is created and it +runs thorough the operation of a node, periodically invoking the `ensurePeers` +method. +However, if when the persistent routine is started the node already has some +peers, either inbound or outbound peers, or is dialing some addresses, the +first invocation of `ensurePeers` is delayed by a random amount of time from 0 +to `ensurePeersPeriod`. + +### Persistent peers + +The node configuration can contain a list of *persistent peers*. +Those peers have preferential treatment compared to regular peers and the node +is always trying to connect to them. +Moreover, these peers are not removed from the address book in the case of +multiple failed dial attempts. + +On startup, the node immediately tries to dial the configured persistent peers +by calling the switch's [`DialPeersAsync`](./switch.md#manual-operation) method. +This is not done in the p2p package, but it is part of the procedure to set up a node. + +> TODO: the handling of persistent peers should be described in more detail. + +### Life cycle + +The picture below is a first attempt of illustrating the life cycle of an outbound peer: + + + +A peer can be in the following states: + +- Candidate peers: peer addresses stored in the address boook, that can be + retrieved via the [`PickAddress`](./addressbook.md#pick-address) method +- [Dialing](./switch.md#dialing-peers): peer addresses that are currently being + dialed. This state exists to ensure that a single dialing routine exist per peer. +- [Reconnecting](./switch.md#reconnect-to-peer): persistent peers to which a node + is currently reconnecting, as a previous connection attempt has failed. +- Connected peers: peers that a node has successfully dialed, added as outbound peers. +- [Bad peers](./addressbook.md#bad-peers): peers marked as bad in the address + book due to exhibited [misbehavior](./pex-protocol.md#misbehavior). + Peers can be reinstated after being marked as bad. + +## Pending of documentation + +The `dialSeeds` method of the PEX reactor. + +The `dialPeer` method of the PEX reactor. +This includes `dialAttemptsInfo`, `maxBackoffDurationForPeer` methods. diff --git a/spec/p2p/implementation/pex-protocol.md b/spec/p2p/implementation/pex-protocol.md new file mode 100644 index 00000000000..760a56bd9dc --- /dev/null +++ b/spec/p2p/implementation/pex-protocol.md @@ -0,0 +1,240 @@ +# Peer Exchange Protocol + +The Peer Exchange (PEX) protocol enables nodes to exchange peer addresses, thus +implementing a peer discovery mechanism. + +The PEX protocol uses two messages: + +- `PexRequest`: sent by a node to [request](#requesting-addresses) peer + addresses to a peer +- `PexAddrs`: a list of peer addresses [provided](#providing-addresses) to a + peer as response to a `PexRequest` message + +While all nodes, with few exceptions, participate on the PEX protocol, +a subset of nodes, configured as [seed nodes](#seed-nodes) have a particular +role in the protocol. +They crawl the network, connecting to random peers, in order to learn as many +peer addresses as possible to provide to other nodes. + +## Requesting Addresses + +A node requests peer addresses by sending a `PexRequest` message to a peer. + +For regular nodes, not operating in seed mode, a PEX request is sent when +the node *needs* peers addresses, a condition checked: + +1. When an *outbound* peer is added, causing the node to request addresses from + the new peer +2. Periodically, by the `ensurePeersRoutine`, causing the node to request peer + addresses to a randomly selected peer + +A node needs more peer addresses when its addresses book has +[less than 1000 records](./addressbook.md#need-for-addresses). +It is thus reasonable to assume that the common case is that a peer needs more +peer addresses, so that PEX requests are sent whenever the above two situations happen. + +A PEX request is sent when a new *outbound* peer is added. +The same does not happen with new inbound peers because the implementation +considers outbound peers, that the node has chosen for dialing, more +trustworthy than inbound peers, that the node has accepted. +Moreover, when a node is short of peer addresses, it dials the configured seed nodes; +since they are added as outbound peers, the node can immediately request peer addresses. + +The `ensurePeersRoutine` periodically checks, by default every 30 seconds (`ensurePeersPeriod`), +whether the node has enough outbound peers. +If it does not have, the node tries dialing some peer addresses stored in the address book. +As part of this procedure, the node selects a peer at random, +from the set of connected peers retrieved from the switch, +and sends a PEX request to the selected peer. + +Sending a PEX request to a peer is implemented by the `RequestAddrs` method of +the PEX reactor. + +### Responses + +After a PEX request is sent to a peer, the node expects to receive, +as a response, a `PexAddrs` message from the peer. +This message encodes a list of peer addresses that are +[added to address book](./addressbook.md#adding-addresses), +having the peer from which the PEX response was received as their source. + +Received PEX responses are handled by the `ReceiveAddrs` method of the PEX reactor. +In the case of a PEX response received from a peer which is configured as +a seed node, the PEX reactor attempts immediately to dial the provided peer +addresses, as detailed [here](./peer_manager.md#fast-dialing). + +### Misbehavior + +Sending multiple PEX requests to a peer, before receiving a reply from it, +is considered a misbehavior. +To prevent it, the node maintains a `requestsSent` set of outstanding +requests, indexed by destination peers. +While a peer ID is present in the `requestsSent` set, the node does not send +further PEX requests to that peer. +A peer ID is removed from the `requestsSent` set when a PEX response is +received from it. + +Sending a PEX response to a peer that has not requested peer addresses +is also considered a misbehavior. +So, if a PEX response is received from a peer that is not registered in +the `requestsSent` set, a `ErrUnsolicitedList` error is produced. +This leads the peer to be disconnected and [marked as a bad peer](./addressbook.md#bad-peers). + +## Providing Addresses + +When a node receives a `PexRequest` message from a peer, +it replies with a `PexAddrs` message. + +This message encodes a [random selection of peer addresses](./addressbook.md#random-selection) +retrieved from the address book. + +Sending a PEX response to a peer is implemented by the `SendAddrs` method of +the PEX reactor. + +### Misbehavior + +Requesting peer addresses too often is considered a misbehavior. +Since node are expected to send PEX requests every `ensurePeersPeriod`, +the minimum accepted interval between requests from the same peer is set +to `ensurePeersPeriod / 3`, 10 seconds by default. + +The `receiveRequest` method is responsible for verifying this condition. +The node keeps a `lastReceivedRequests` map with the time of the last PEX +request received from every peer. +If the interval between successive requests is less than the minimum accepted +one, the peer is disconnected and [marked as a bad peer](./addressbook.md#bad-peers). +An exception is made for the first two PEX requests received from a peer. + +> The probably reason is that, when a new peer is added, the two conditions for +> a node to request peer addresses can be triggered with an interval lower than +> the minimum accepted interval. +> Since this is a legit behavior, it should not be punished. + +## Seed nodes + +A seed node is a node configured to operate in `SeedMode`. + +### Crawling peers + +Seed nodes crawl the network, connecting to random peers and sending PEX +requests to them, in order to learn as many peer addresses as possible. +More specifically, a node operating in seed mode sends PEX requests in two cases: + +1. When an outbound peer is added, and the seed node needs more peer addresses, + it requests peer addresses to the new peer +2. Periodically, the `crawlPeersRoutine` sends PEX requests to a random set of + peers, whose addresses are registered in the Address Book + +The first case also applies for nodes not operating in seed mode. +The second case replaces the second for regular nodes, as seed nodes do not +run the `ensurePeersRoutine`, as regular nodes, +but run the `crawlPeersRoutine`, which is not run by regular nodes. + +The `crawlPeersRoutine` periodically, every 30 seconds (`crawlPeerPeriod`), +starts a new peer discovery round. +First, the seed node retrieves a random selection of peer addresses from its +Address Book. +This selection is produced in the same way as in the random selection of peer +addresses that are [provided](#providing-addresses) to a requesting peer. +Peers that the seed node has crawled recently, +less than 2 minutes ago (`minTimeBetweenCrawls`), are removed from this selection. +The remaining peer addresses are registered in the `crawlPeerInfos` table. + +The seed node is not necessarily connected to the peer whose address is +selected for each round of crawling. +So, the seed node dials the selected peer addresses. +This is performed in foreground, one peer at a time. +As a result, a round of crawling can take a substantial amount of time. +For each selected peer it succeeds dialing to, this include already connected +peers, the seed node sends a PEX request. + +Dialing a selected peer address can fail for multiple reasons. +The seed node might have attempted to dial the peer too many times. +In this case, the peer address is marked as [bad in the address book](./addressbook.md#bad-peers). +The seed node might have attempted to dial the peer recently, without success, +and the exponential `backoffDuration` has not yet passed. +Or the current connection attempt might fail, which is registered in the address book. + +Failures to dial to a peer address produce an information that is important for +a seed node. +They indicate that a peer is unreachable, or is not operating correctly, and +therefore its address should not be provided to other nodes. +This occurs when, due to multiple failed connection attempts or authentication +failures, the peer address ends up being removed from the address book. +As a result, the periodically crawling of selected peers not only enables the +discovery of new peers, but also allows the seed node to stop providing +addresses of bad peers. + +### Offering addresses + +Nodes operating in seed mode handle PEX requests differently than regular +nodes, whose operation is described [here](#providing-addresses). + +This distinction exists because nodes dial a seed node with the main, if not +exclusive goal of retrieving peer addresses. +In other words, nodes do not dial a seed node because they intend to have it as +a peer in the multiple CometBFT protocols, but because they believe that a +seed node is a good source of addresses of nodes to which they can establish +connections and interact in the multiple CometBFT protocols. + +So, when a seed node receives a `PexRequest` message from an inbound peer, +it sends a `PexAddrs` message, containing a selection of peer +addresses, back to the peer and *disconnects* from it. +Seed nodes therefore treat inbound connections from peers as a short-term +connections, exclusively intended to retrieve peer addresses. +Once the requested peer addresses are sent, the connection with the peer is closed. + +Moreover, the selection of peer addresses provided to inbound peers by a seed +node, although still essentially random, has a [bias toward old +addresses](./addressbook.md#random-selection-with-bias). +The selection bias is defined by `biasToSelectNewPeers`, hard-coded to `30%`, +meaning that `70%` of the peer addresses provided by a seed node are expected +to be old addresses. +Although this nomenclature is not clear, *old* addresses are the addresses that +survived the most in the address book, that is, are addresses that the seed +node believes being from *good* peers (more details [here](./addressbook.md#good-peers)). + +Another distinction is on the handling of potential [misbehavior](#misbehavior-1) +of peers requesting addresses. +A seed node does not enforce, a priori, a minimal interval between PEX requests +from inbound peers. +Instead, it does not reply to more than one PEX request per peer inbound +connection, and, as above mentioned, it disconnects from incoming peers after +responding to them. +If the same peer dials again to the seed node and requests peer addresses, the +seed node will reply to this peer like it was the first time it has requested +peer addresses. + +> This is more an implementation restriction than a desired behavior. +> The `lastReceivedRequests` map stores the last time a PEX request was +> received from a peer, and the entry relative to a peer is removed from this +> map when the peer is disconnected. +> +> It is debatable whether this approach indeed prevents abuse against seed nodes. + +### Disconnecting from peers + +Seed nodes treat connections with peers as short-term connections, which are +mainly, if not exclusively, intended to exchange peer addresses. + +In the case of inbound peers, that have dialed the seed node, the intent of the +connection is achieved once a PEX response is sent to the peer. +The seed node thus disconnects from an inbound peer after sending a `PexAddrs` +message to it. + +In the case of outbound peers, which the seed node has dialed for crawling peer +addresses, the intent of the connection is essentially achieved when a PEX +response is received from the peer. +The seed node, however, does not disconnect from a peer after receiving a +selection of peer addresses from it. +As a result, after some rounds of crawling, a seed node will have established +connections to a substantial amount of peers. + +To couple with the existence of multiple connections with peers that have no +longer purpose for the seed node, the `crawlPeersRoutine` also invokes, after +each round of crawling, the `attemptDisconnects` method. +This method retrieves the list of connected peers from the switch, and +disconnects from peers that are not persistent peers, and with which a +connection is established for more than `SeedDisconnectWaitPeriod`. +This period is a configuration parameter, set to 28 hours when the PEX reactor +is created by the default node constructor. diff --git a/spec/p2p/implementation/pex.md b/spec/p2p/implementation/pex.md new file mode 100644 index 00000000000..8243eaa559d --- /dev/null +++ b/spec/p2p/implementation/pex.md @@ -0,0 +1,111 @@ +# PEX Reactor + +The PEX reactor is one of the reactors running in a CometBFT node. + +Its implementation is located in the `p2p/pex` package, and it is considered +part of the implementation of the p2p layer. + +This document overviews the implementation of the PEX reactor, describing how +the methods from the `Reactor` interface are implemented. + +The actual operation of the PEX reactor is presented in documents describing +the roles played by the PEX reactor in the p2p layer: + +- [Address Book](./addressbook.md): stores known peer addresses and information + about peers to which the node is connected or has attempted to connect +- [Peer Manager](./peer_manager.md): manages connections established with peers, + defining when a node should dial peers and which peers it should dial +- [Peer Exchange protocol](./pex-protocol.md): enables nodes to exchange peer + addresses, thus implementing a peer discovery service + +## OnStart + +The `OnStart` method implements `BaseService` and starts the PEX reactor. + +The [address book](./addressbook.md), which is a `Service` is started. +This loads the address book content from disk, +and starts a routine that periodically persists the address book content to disk. + +The PEX reactor is configured with the addresses of a number of seed nodes, +the `Seeds` parameter of the `ReactorConfig`. +The addresses of seed nodes are parsed into `NetAddress` instances and resolved +into IP addresses, which is implemented by the `checkSeeds` method. +Valid seed node addresses are stored in the `seedAddrs` field, +and are used by the `dialSeeds` method to contact the configured seed nodes. + +The last action is to start one of the following persistent routines, based on +the `SeedMode` configuration parameter: + +- Regular nodes run the `ensurePeersRoutine` to check whether the node has + enough outbound peers, dialing peers when necessary +- Seed nodes run the `crawlPeersRoutine` to periodically start a new round + of [crawling](./pex-protocol.md#Crawling-peers) to discover as many peer + addresses as possible + +### Errors + +Errors encountered when loading the address book from disk are returned, +and prevent the reactor from being started. +An exception is made for the `service.ErrAlreadyStarted` error, which is ignored. + +Errors encountered when parsing the configured addresses of seed nodes +are returned and cause the reactor startup to fail. +An exception is made for DNS resolution `ErrNetAddressLookup` errors, +which are not deemed fatal and are only logged as invalid addresses. + +If none of the configured seed node addresses is valid, and the loaded address +book is empty, the reactor is not started and an error is returned. + +## OnStop + +The `OnStop` method implements `BaseService` and stops the PEX reactor. + +The address book routine that periodically saves its content to disk is stopped. + +## GetChannels + +The `GetChannels` method, from the `Reactor` interface, returns the descriptor +of the channel used by the PEX protocol. + +The channel ID is `PexChannel` (0), with priority `1`, send queue capacity of +`10`, and maximum message size of `64000` bytes. + +## AddPeer + +The `AddPeer` method, from the `Reactor` interface, +adds a new peer to the PEX protocol. + +If the new peer is an **inbound peer**, i.e., if the peer has dialed the node, +the peer's address is [added to the address book](./addressbook.md#adding-addresses). +Since the peer was authenticated when establishing a secret connection with it, +the source of the peer address is trusted, and its source is set by the peer itself. +In the case of an outbound peer, the node should already have its address in +the address book, as the switch has dialed the peer. + +If the peer is an **outbound peer**, i.e., if the node has dialed the peer, +and the PEX protocol needs more addresses, +the node [sends a PEX request](./pex-protocol.md#Requesting-Addresses) to the peer. +The same is not done when inbound peers are added because they are deemed least +trustworthy than outbound peers. + +## RemovePeer + +The `RemovePeer` method, from the `Reactor` interface, +removes a peer from the PEX protocol. + +The peer's ID is removed from the tables tracking PEX requests +[sent](./pex-protocol.md#misbehavior) but not yet replied +and PEX requests [received](./pex-protocol.md#misbehavior-1). + +## Receive + +The `Receive` method, from the `Reactor` interface, +handles a message received by the PEX protocol. + +A node receives two type of messages as part of the PEX protocol: + +- `PexRequest`: a request for addresses received from a peer, handled as + described [here](./pex-protocol.md#providing-addresses) +- `PexAddrs`: a list of addresses received from a peer, as a reponse to a PEX + request sent by the node, as described [here](./pex-protocol.md#responses) + diff --git a/spec/p2p/implementation/switch.md b/spec/p2p/implementation/switch.md new file mode 100644 index 00000000000..4497fef96e2 --- /dev/null +++ b/spec/p2p/implementation/switch.md @@ -0,0 +1,238 @@ +# Switch + +The switch is a core component of the p2p layer. +It manages the procedures for [dialing peers](#dialing-peers) and +[accepting](#accepting-peers) connections from peers, which are actually +implemented by the [transport](./transport.md). +It also manages the reactors, i.e., protocols implemented by the node that +interact with its peers. +Once a connection with a peer is established, the peer is [added](#add-peer) to +the switch and all registered reactors. +Reactors may also instruct the switch to [stop a peer](#stop-peer), namely +disconnect from it. +The switch, in this case, makes sure that the peer is removed from all +registered reactors. + +## Dialing peers + +Dialing a peer is implemented by the `DialPeerWithAddress` method. + +This method is invoked by the [peer manager](./peer_manager.md#ensure-peers) +to dial a peer address and establish a connection with an outbound peer. + +The switch keeps a single dialing routine per peer ID. +This is ensured by keeping a synchronized map `dialing` with the IDs of peers +to which the peer is dialing. +A peer ID is added to `dialing` when the `DialPeerWithAddress` method is called +for that peer, and it is removed when the method returns for whatever reason. +The method returns immediately when invoked for a peer which ID is already in +the `dialing` structure. + +The actual dialing is implemented by the [`Dial`](./transport.md#dial) method +of the transport configured for the switch, in the `addOutboundPeerWithConfig` +method. +If the transport succeeds establishing a connection, the returned `Peer` is +added to the switch using the [`addPeer`](#add-peer) method. +This operation can fail, returning an error. In this case, the switch invokes +the transport's [`Cleanup`](./transport.md#cleanup) method to clean any resources +associated with the peer. + +If the transport fails to establish a connection with the peer that is configured +as a persistent peer, the switch spawns a routine to [reconnect to the peer](#reconnect-to-peer). +If the peer is already in the `reconnecting` state, the spawned routine has no +effect and returns immediately. +This is in fact a likely scenario, as the `reconnectToPeer` routine relies on +this same `DialPeerWithAddress` method for dialing peers. + +### Manual operation + +The `DialPeersAsync` method receives a list of peer addresses (strings) +and dials all of them in parallel. +It is invoked in two situations: + +- In the [setup](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L987) +of a node, to establish connections with every configured persistent peer +- In the RPC package, to implement two unsafe RPC commands, not used in production: + [`DialSeeds`](https://github.com/cometbft/cometbft/blob/v0.34.x/rpc/core/net.go#L47) and + [`DialPeers`](https://github.com/cometbft/cometbft/blob/v0.34.x/rpc/core/net.go#L87) + +The received list of peer addresses to dial is parsed into `NetAddress` instances. +In case of parsing errors, the method returns. An exception is made for +DNS resolution `ErrNetAddressLookup` errors, which do not interrupt the procedure. + +As the peer addresses provided to this method are typically not known by the node, +contrarily to the addressed dialed using the `DialPeerWithAddress` method, +they are added to the node's address book, which is persisted to disk. + +The switch dials the provided peers in parallel. +The list of peer addresses is randomly shuffled, and for each peer a routine is +spawned. +Each routine sleeps for a random interval, up to 3 seconds, then invokes the +`DialPeerWithAddress` method that actually dials the peer. + +### Reconnect to peer + +The `reconnectToPeer` method is invoked when a connection attempt to a peer fails, +and the peer is configured as a persistent peer. + +The `reconnecting` synchronized map keeps the peer's in this state, identified +by their IDs (string). +This should ensure that a single instance of this method is running at any time. +The peer is kept in this map while this method is running for it: it is set on +the beginning, and removed when the method returns for whatever reason. +If the peer is already in the `reconnecting` state, nothing is done. + +The remaining of the method performs multiple connection attempts to the peer, +via `DialPeerWithAddress` method. +If a connection attempt succeeds, the methods returns and the routine finishes. +The same applies when an `ErrCurrentlyDialingOrExistingAddress` error is +returned by the dialing method, as it indicates that peer is already connected +or that another routine is attempting to (re)connect to it. + +A first set of connection attempts is done at (about) regular intervals. +More precisely, between two attempts, the switch waits for a interval of +`reconnectInterval`, hard-coded to 5 seconds, plus a random jitter up to +`dialRandomizerIntervalMilliseconds`, hard-coded to 3 seconds. +At most `reconnectAttempts`, hard-coded to 20, are made using this +regular-interval approach. + +A second set of connection attempts is done with exponentially increasing +intervals. +The base interval `reconnectBackOffBaseSeconds` is hard-coded to 3 seconds, +which is also the increasing factor. +The exponentially increasing dialing interval is adjusted as well by a random +jitter up to `dialRandomizerIntervalMilliseconds`. +At most `reconnectBackOffAttempts`, hard-coded to 10, are made using this approach. + +> Note: the first sleep interval, to which a random jitter is applied, is 1, +> not `reconnectBackOffBaseSeconds`, as the first exponent is `0`... + +## Accepting peers + +The `acceptRoutine` method is a persistent routine that handles connections +accepted by the transport configured for the switch. + +The [`Accept`](./transport.md#accept) method of the configured transport +returns a `Peer` with which an inbound connection was established. +The switch accepts a new peer if the maximum number of inbound peers was not +reached, or if the peer was configured as an _unconditional peer_. +The maximum number of inbound peers is determined by the `MaxNumInboundPeers` +configuration parameter, whose default value is `40`. + +If accepted, the peer is added to the switch using the [`addPeer`](#add-peer) method. +If the switch does not accept the established incoming connection, or if the +`addPeer` method returns an error, the switch invokes the transport's +[`Cleanup`](./transport.md#cleanup) method to clean any resources associated +with the peer. + +The transport's `Accept` method can also return a number of errors. +Errors of `ErrRejected` or `ErrFilterTimeout` types are ignored, +an `ErrTransportClosed` causes the accepted routine to be interrupted, +while other errors cause the routine to panic. + +> TODO: which errors can cause the routine to panic? + +## Add peer + +The `addPeer` method adds a peer to the switch, +either after dialing (by `addOutboundPeerWithConfig`, called by `DialPeerWithAddress`) +a peer and establishing an outbound connection, +or after accepting (`acceptRoutine`) a peer and establishing an inbound connection. + +The first step is to invoke the `filterPeer` method. +It checks whether the peer is already in the set of connected peers, +and whether any of the configured `peerFilter` methods reject the peer. +If the peer is already present or it is rejected by any filter, the `addPeer` +method fails and returns an error. + +Then, the new peer is started, added to the set of connected peers, and added +to all reactors. +More precisely, first the new peer's information is first provided to every +reactor (`InitPeer` method). +Next, the peer's sending and receiving routines are started, and the peer is +added to set of connected peers. +These two operations can fail, causing `addPeer` to return an error. +Then, in the absence of previous errors, the peer is added to every reactor (`AddPeer` method). + +> Adding the peer to the peer set returns a `ErrSwitchDuplicatePeerID` error +> when a peer with the same ID is already presented. +> +> TODO: Starting a peer could be reduced as starting the MConn with that peer? + +## Stop peer + +There are two methods for stopping a peer, namely disconnecting from it, and +removing it from the table of connected peers. + +The `StopPeerForError` method is invoked to stop a peer due to an external +error, which is provided to method as a generic "reason". + +The `StopPeerGracefully` method stops a peer in the absence of errors or, more +precisely, not providing to the switch any "reason" for that. + +In both cases the `Peer` instance is stopped, the peer is removed from all +registered reactors, and finally from the list of connected peers. + +> Issue is mentioned in +> the internal `stopAndRemovePeer` method explaining why removing the peer from +> the list of connected peers is the last action taken. + +When there is a "reason" for stopping the peer (`StopPeerForError` method) +and the peer is a persistent peer, the method creates a routine to attempt +reconnecting to the peer address, using the `reconnectToPeer` method. +If the peer is an outbound peer, the peer's address is know, since the switch +has dialed the peer. +Otherwise, the peer address is retrieved from the `NodeInfo` instance from the +connection handshake. + +## Add reactor + +The `AddReactor` method registers a `Reactor` to the switch. + +The reactor is associated to the set of channel ids it employs. +Two reactors (in the same node) cannot share the same channel id. + +There is a call back to the reactor, in which the switch passes itself to the +reactor. + +## Remove reactor + +The `RemoveReactor` method unregisters a `Reactor` from the switch. + +The reactor is disassociated from the set of channel ids it employs. + +There is a call back to the reactor, in which the switch passes `nil` to the +reactor. + +## OnStart + +This is a `BaseService` method. + +All registered reactors are started. + +The switch's `acceptRoutine` is started. + +## OnStop + +This is a `BaseService` method. + +All (connected) peers are stopped and removed from the peer's list using the +`stopAndRemovePeer` method. + +All registered reactors are stopped. + +## Broadcast + +This method broadcasts a message on a channel, by sending the message in +parallel to all connected peers. + +The method spawns a thread for each connected peer, invoking the `Send` method +provided by each `Peer` instance with the provided message and channel ID. +The return value (a boolean) of these calls are redirected to a channel that is +returned by the method. + +> TODO: detail where this method is invoked: +> +> - By the consensus protocol, in `broadcastNewRoundStepMessage`, +> `broadcastNewValidBlockMessage`, and `broadcastHasVoteMessage` +> - By the state sync protocol diff --git a/spec/p2p/implementation/transport.md b/spec/p2p/implementation/transport.md new file mode 100644 index 00000000000..20d4db87a43 --- /dev/null +++ b/spec/p2p/implementation/transport.md @@ -0,0 +1,222 @@ +# Transport + +The transport establishes secure and authenticated connections with peers. + +The transport [`Dial`](#dial)s peer addresses to establish outbound connections, +and [`Listen`](#listen)s in a configured network address +to [`Accept`](#accept) inbound connections from peers. + +The transport establishes raw TCP connections with peers +and [upgrade](#connection-upgrade) them into authenticated secret connections. +The established secret connection is then wrapped into `Peer` instance, which +is returned to the caller, typically the [switch](./switch.md). + +## Dial + +The `Dial` method is used by the switch to establish an outbound connection with a peer. +It is a synchronous method, which blocks until a connection is established or an error occurs. +The method returns an outbound `Peer` instance wrapping the established connection. + +The transport first dials the provided peer's address to establish a raw TCP connection. +The dialing maximum duration is determined by `dialTimeout`, hard-coded to 1 second. +The established raw connection is then submitted to a set of [filters](#connection-filtering), +which can reject it. +If the connection is not rejected, it is recorded in the table of established connections. + +The established raw TCP connection is then [upgraded](#connection-upgrade) into +an authenticated secret connection. +This procedure should ensure, in particular, that the public key of the remote peer +matches the ID of the dialed peer, which is part of peer address provided to this method. +In the absence of errors, +the established secret connection (`conn.SecretConnection` type) +and the information about the peer (`NodeInfo` record) retrieved and verified +during the version handshake, +are wrapped into an outbound `Peer` instance and returned to the switch. + +## Listen + +The `Listen` method produces a TCP listener instance for the provided network +address, and spawns an `acceptPeers` routine to handle the raw connections +accepted by the listener. +The `NetAddress` method exports the listen address configured for the transport. + +The maximum number of simultaneous incoming connections accepted by the listener +is bound to `MaxNumInboundPeer` plus the configured number of unconditional peers, +using the `MultiplexTransportMaxIncomingConnections` option, +in the node [initialization](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L563). + +This method is called when a node is [started](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L974). +In case of errors, the `acceptPeers` routine is not started and the error is returned. + +## Accept + +The `Accept` method returns to the switch inbound connections established with a peer. +It is a synchronous method, which blocks until a connection is accepted or an error occurs. +The method returns an inbound `Peer` instance wrapping the established connection. + +The transport handles incoming connections in the `acceptPeers` persistent routine. +This routine is started by the [`Listen`](#listen) method +and accepts raw connections from a TCP listener. +A new routine is spawned for each accepted connection. +The raw connection is submitted to a set of [filters](#connection-filtering), +which can reject it. +If the connection is not rejected, it is recorded in the table of established connections. + +The established raw TCP connection is then [upgraded](#connection-upgrade) into +an authenticated secret connection. +The established secret connection (`conn.SecretConnection` type), +the information about the peer (`NodeInfo` record) retrieved and verified +during the version handshake, +as well any error returned in this process are added to a queue of accepted connections. +This queue is consumed by the `Accept` method. + +> Handling accepted connection asynchronously was introduced due to this issue: +> + +## Connection Filtering + +The `filterConn` method is invoked for every new raw connection established by the transport. +Its main goal is avoid the transport to maintain duplicated connections with the same peer. +It also runs a set of configured connection filters. + +The transports keeps a table `conns` of established connections. +The table maps the remote address returned by a generic connection to a list of +IP addresses, to which the connection remote address is resolved. +If the remote address of the new connection is already present in the table, +the connection is rejected. +Otherwise, the connection's remote address is resolved into a list of IPs, +which are recorded in the established connections table. + +The connection and the resolved IPs are then passed through a set of connection filters, +configured via the `MultiplexTransportConnFilters` transport option. +The maximum duration for the filters execution, which is performed in parallel, +is determined by `filterTimeout`. +Its default value is 5 seconds, +which can be changed using the `MultiplexTransportFilterTimeout` transport option. + +If the connection and the resolved remote addresses are not filtered out, +the transport registers them into the `conns` table and returns. + +In case of errors, the connection is removed from the table of established +connections and closed. + +### Errors + +If the address of the new connection is already present in the `conns` table, +an `ErrRejected` error with the `isDuplicate` reason is returned. + +If the IP resolution of the connection's remote address fails, +an `AddrError` or `DNSError` error is returned. + +If any of the filters reject the connection, +an `ErrRejected` error with the `isRejected` reason is returned. + +If the filters execution times out, +an `ErrFilterTimeout` error is returned. + +## Connection Upgrade + +The `upgrade` method is invoked for every new raw connection established by the +transport that was not [filtered out](#connection-filtering). +It upgrades an established raw TCP connection into a secret authenticated +connection, and validates the information provided by the peer. + +This is a complex procedure, that can be summarized by the following three +message exchanges between the node and the new peer: + +1. Encryption: the nodes produce ephemeral key pairs and exchange ephemeral + public keys, from which are derived: (i) a pair of secret keys used to + encrypt the data exchanged between the nodes, and (ii) a challenge message. +1. Authentication: the nodes exchange their persistent public keys and a + signature of the challenge message produced with the their persistent + private keys. This allows validating the peer's persistent public key, + which plays the role of node ID. +1. Version handshake: nodes exchange and validate each other `NodeInfo` records. + This records contain, among other fields, their node IDs, the network/chain + ID they are part of, and the list of supported channel IDs. + +Steps (1) and (2) are implemented in the `conn` package. +In case of success, they produce the secret connection that is actually used by +the node to communicate with the peer. +An overview of this procedure, which implements the station-to-station (STS) +[protocol][sts-paper] ([PDF][sts-paper-pdf]), can be found [here][peer-sts]. +The maximum duration for establishing a secret connection with the peer is +defined by `handshakeTimeout`, hard-coded to 3 seconds. + +The established secret connection stores the persistent public key of the peer, +which has been validated via the challenge authentication of step (2). +If the connection being upgraded is an outbound connection, i.e., if the node has +dialed the peer, the dialed peer's ID is compared to the peer's persistent public key: +if they do not match, the connection is rejected. +This verification is not performed in the case of inbound (accepted) connections, +as the node does not know a priori the remote node's ID. + +Step (3), the version handshake, is performed by the transport. +Its maximum duration is also defined by `handshakeTimeout`, hard-coded to 3 seconds. +The version handshake retrieves the `NodeInfo` record of the new peer, +which can be rejected for multiple reasons, listed [here][peer-handshake]. + +If the connection upgrade succeeds, the method returns the established secret +connection, an instance of `conn.SecretConnection` type, +and the `NodeInfo` record of the peer. + +In case of errors, the connection is removed from the table of established +connections and closed. + +### Errors + +The timeouts for steps (1) and (2), and for step (3), are configured as the +deadline for operations on the TCP connection that is being upgraded. +If this deadline it is reached, the connection produces an +`os.ErrDeadlineExceeded` error, returned by the corresponding step. + +Any error produced when establishing a secret connection with the peer (steps 1 and 2) or +during the version handshake (step 3), including timeouts, +is encapsulated into an `ErrRejected` error with reason `isAuthFailure` and returned. + +If the upgraded connection is an outbound connection, and the peer ID learned in step (2) +does not match the dialed peer's ID, +an `ErrRejected` error with reason `isAuthFailure` is returned. + +If the peer's `NodeInfo` record, retrieved in step (3), is invalid, +or if reports a node ID that does not match peer ID learned in step (2), +an `ErrRejected` error with reason `isAuthFailure` is returned. +If it reports a node ID equals to the local node ID, +an `ErrRejected` error with reason `isSelf` is returned. +If it is not compatible with the local `NodeInfo`, +an `ErrRejected` error with reason `isIncompatible` is returned. + +## Close + +The `Close` method closes the TCP listener created by the `Listen` method, +and sends a signal for interrupting the `acceptPeers` routine. + +This method is called when a node is [stopped](https://github.com/cometbft/cometbft/blob/v0.34.x/node/node.go#L1023). + +## Cleanup + +The `Cleanup` method receives a `Peer` instance, +and removes the connection established with a peer from the table of established connections. +It also invokes the `Peer` interface method to close the connection associated with a peer. + +It is invoked when the connection with a peer is closed. + +## Supported channels + +The `AddChannel` method registers a channel in the transport. + +The channel ID is added to the list of supported channel IDs, +stored in the local `NodeInfo` record. + +The `NodeInfo` record is exchanged with peers in the version handshake. +For this reason, this method is not invoked with a started transport. + +> The only call to this method is performed in the `CustomReactors` constructor +> option of a node, i.e., before the node is started. +> Note that the default list of supported channel IDs, including the default reactors, +> is provided to the transport as its original `NodeInfo` record. + +[peer-sts]: ../legacy-docs/peer.md#authenticated-encryption-handshake +[peer-handshake]: ../legacy-docs/peer.md#cometbft-version-handshake +[sts-paper]: https://link.springer.com/article/10.1007/BF00124891 +[sts-paper-pdf]: https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf diff --git a/spec/p2p/implementation/types.md b/spec/p2p/implementation/types.md new file mode 100644 index 00000000000..6d71da03fb7 --- /dev/null +++ b/spec/p2p/implementation/types.md @@ -0,0 +1,239 @@ +# Types adopted in the p2p implementation + +This document lists the packages and source files, excluding test units, that +implement the p2p layer, and summarizes the main types they implement. +Types play the role of classes in Go. + +The reference version for this documentation is the branch +[`v0.34.x`](https://github.com/cometbft/cometbft/tree/v0.34.x/p2p). + +State of August 2022. + +## Package `p2p` + +Implementation of the p2p layer of CometBFT. + +### `base_reactor.go` + +`Reactor` interface. + +`BaseReactor` implements `Reactor`. + +**Not documented yet**. + +### `conn_set.go` + +`ConnSet` interface, a "lookup table for connections and their ips". + +Internal type `connSet` implements the `ConnSet` interface. + +Used by the [transport](#transportgo) to store connected peers. + +### `errors.go` + +Defines several error types. + +`ErrRejected` enumerates a number of reason for which a peer was rejected. +Mainly produced by the [transport](#transportgo), +but also by the [switch](#switchgo). + +`ErrSwitchDuplicatePeerID` is produced by the `PeerSet` used by the [switch](#switchgo). + +`ErrSwitchConnectToSelf` is handled by the [switch](#switchgo), +but currently is not produced outside tests. + +`ErrSwitchAuthenticationFailure` is handled by the [PEX reactor](#pex_reactorgo), +but currently is not produced outside tests. + +`ErrTransportClosed` is produced by the [transport](#transportgo) +and handled by the [switch](#switchgo). + +`ErrNetAddressNoID`, `ErrNetAddressInvalid`, and `ErrNetAddressLookup` +are parsing a string to create an instance of `NetAddress`. +It can be returned in the setup of the [switch](#switchgo) +and of the [PEX reactor](#pex_reactorgo), +as well when the [transport](#transportgo) validates a `NodeInfo`, as part of +the connection handshake. + +`ErrCurrentlyDialingOrExistingAddress` is produced by the [switch](#switchgo), +and handled by the switch and the [PEX reactor](#pex_reactorgo). + +### `fuzz.go` + +For testing purposes. + +`FuzzedConnection` wraps a `net.Conn` and injects random delays. + +### `key.go` + +`NodeKey` is the persistent key of a node, namely its private key. + +The `ID` of a node is a string representing the node's public key. + +### `metrics.go` + +Prometheus `Metrics` exposed by the p2p layer. + +### `netaddress.go` + +Type `NetAddress` contains the `ID` and the network address (IP and port) of a node. + +The API of the [address book](#addrbookgo) receives and returns `NetAddress` instances. + +This source file was adapted from [`btcd`](https://github.com/btcsuite/btcd), +a Go implementation of Bitcoin. + +### `node_info.go` + +Interface `NodeInfo` stores the basic information about a node exchanged with a +peer during the handshake. + +It is implemented by `DefaultNodeInfo` type. + +The [switch](#switchgo) stores the local `NodeInfo`. + +The `NodeInfo` of connected peers is produced by the +[transport](#transportgo) during the handshake, and stored in [`Peer`](#peergo) instances. + +### `peer.go` + +Interface `Peer` represents a connected peer. + +It is implemented by the internal `peer` type. + +The [transport](#transportgo) API methods return `Peer` instances, +wrapping established secure connection with peers. + +The [switch](#switchgo) API methods receive `Peer` instances. +The switch stores connected peers in a `PeerSet`. + +The [`Reactor`](#base_reactorgo) methods, invoked by the switch, receive `Peer` instances. + +### `peer_set.go` + +Interface `IPeerSet` offers methods to access a table of [`Peer`](#peergo) instances. + +Type `PeerSet` implements a thread-safe table of [`Peer`](#peergo) instances, +used by the [switch](#switchgo). + +The switch provides limited access to this table by returing a `IPeerSet` +instance, used by the [PEX reactor](#pex_reactorgo). + +### `switch.go` + +Documented in [switch](./switch.md). + +The `Switch` implements the [peer manager](./peer_manager.md) role for inbound peers. + +[`Reactor`](#base_reactorgo)s have access to the `Switch` and may invoke its methods. +This includes the [PEX reactor](#pex_reactorgo). + +### `transport.go` + +Documented in [transport](./transport.md). + +The `Transport` interface is implemented by `MultiplexTransport`. + +The [switch](#switchgo) contains a `Transport` and uses it to establish +connections with peers. + +### `types.go` + +Aliases for p2p's `conn` package types. + +## Package `p2p.conn` + +Implements the connection between CometBFT nodes, +which is encrypted, authenticated, and multiplexed. + +### `connection.go` + +Implements the `MConnection` type and the `Channel` abstraction. + +A `MConnection` multiplexes a generic network connection (`net.Conn`) into +multiple independent `Channel`s, used by different [`Reactor`](#base_reactorgo)s. + +A [`Peer`](#peergo) stores the `MConnection` instance used to interact with a +peer, which multiplex a [`SecretConnection`](#secret_connectiongo). + +### `conn_go110.go` + +Support for go 1.10. + +### `secret_connection.go` + +Implements the `SecretConnection` type, which is an encrypted authenticated +connection built atop a raw network (TCP) connection. + +A [`Peer`](#peergo) stores the `SecretConnection` established by the transport, +which is the underlying connection multiplexed by [`MConnection`](#connectiongo). + +As briefly documented in the [transport](./transport.md#Connection-Upgrade), +a `SecretConnection` implements the Station-To-Station (STS) protocol. + +The `SecretConnection` type implements the `net.Conn` interface, +which is a generic network connection. + +## Package `p2p.mock` + +Mock implementations of [`Peer`](#peergo) and [`Reactor`](#base_reactorgo) interfaces. + +## Package `p2p.mocks` + +Code generated by `mockery`. + +## Package `p2p.pex` + +Implementation of the [PEX reactor](./pex.md). + +### `addrbook.go` + +Documented in [address book](./addressbook.md). + +This source file was adapted from [`btcd`](https://github.com/btcsuite/btcd), +a Go implementation of Bitcoin. + +### `errors.go` + +A number of errors produced and handled by the [address book](#addrbookgo). + +`ErrAddrBookNilAddr` is produced by the address book, but handled (logged) by +the [PEX reactor](#pex_reactorgo). + +`ErrUnsolicitedList` is produced and handled by the [PEX protocol](#pex_reactorgo). + +### `file.go` + +Implements the [address book](#addrbookgo) persistence. + +### `known_address.go` + +Type `knownAddress` represents an address stored in the [address book](#addrbookgo). + +### `params.go` + +Constants used by the [address book](#addrbookgo). + +### `pex_reactor.go` + +Implementation of the [PEX reactor](./pex.md), which is a [`Reactor`](#base_reactorgo). + +This includes the implementation of the [PEX protocol](./pex-protocol.md) +and of the [peer manager](./peer_manager.md) role for outbound peers. + +The PEX reactor also manages an [address book](#addrbookgo) instance. + +## Package `p2p.trust` + +Go documentation of `Metric` type: + +> // Metric - keeps track of peer reliability +> // See cometbft/docs/architecture/adr-006-trust-metric.md for details + +Not imported by any other CometBFT source file. + +## Package `p2p.upnp` + +This package implementation was taken from "taipei-torrent". + +It is used by the `probe-upnp` command of the CometBFT binary. diff --git a/spec/p2p/config.md b/spec/p2p/legacy-docs/config.md similarity index 100% rename from spec/p2p/config.md rename to spec/p2p/legacy-docs/config.md diff --git a/spec/p2p/connection.md b/spec/p2p/legacy-docs/connection.md similarity index 100% rename from spec/p2p/connection.md rename to spec/p2p/legacy-docs/connection.md diff --git a/spec/p2p/messages/README.md b/spec/p2p/legacy-docs/messages/README.md similarity index 100% rename from spec/p2p/messages/README.md rename to spec/p2p/legacy-docs/messages/README.md diff --git a/spec/p2p/messages/block-sync.md b/spec/p2p/legacy-docs/messages/block-sync.md similarity index 96% rename from spec/p2p/messages/block-sync.md rename to spec/p2p/legacy-docs/messages/block-sync.md index 122702f4fc3..187cc8af2d3 100644 --- a/spec/p2p/messages/block-sync.md +++ b/spec/p2p/legacy-docs/messages/block-sync.md @@ -38,7 +38,7 @@ BlockResponse contains the block requested. | Name | Type | Description | Field Number | |-------|----------------------------------------------|-----------------|--------------| -| Block | [Block](../../core/data_structures.md#block) | Requested Block | 1 | +| Block | [Block](../../../core/data_structures.md#block) | Requested Block | 1 | ### StatusRequest diff --git a/spec/p2p/messages/consensus.md b/spec/p2p/legacy-docs/messages/consensus.md similarity index 88% rename from spec/p2p/messages/consensus.md rename to spec/p2p/legacy-docs/messages/consensus.md index 807eca64b15..c9b421f7e13 100644 --- a/spec/p2p/messages/consensus.md +++ b/spec/p2p/legacy-docs/messages/consensus.md @@ -24,13 +24,13 @@ next block in the blockchain should be. | Name | Type | Description | Field Number | |----------|----------------------------------------------------|----------------------------------------|--------------| -| proposal | [Proposal](../../core/data_structures.md#proposal) | Proposed Block to come to consensus on | 1 | +| proposal | [Proposal](../../../core/data_structures.md#proposal) | Proposed Block to come to consensus on | 1 | ### Vote Vote is sent to vote for some block (or to inform others that a process does not vote in the current round). Vote is defined in the -[Blockchain](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/core/data_structures.md#blockidd) +[Blockchain](../../../core/data_structures.md#blockidd) section and contains validator's information (validator address and index), height and round for which the vote is sent, vote type, blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The @@ -38,7 +38,7 @@ message is signed by the validator private key. | Name | Type | Description | Field Number | |------|--------------------------------------------|---------------------------|--------------| -| vote | [Vote](../../core/data_structures.md#vote) | Vote for a proposed Block | 1 | +| vote | [Vote](../../../core/data_structures.md#vote) | Vote for a proposed Block | 1 | ### BlockPart @@ -49,7 +49,7 @@ and the block part. |--------|--------------------------------------------|----------------------------------------|--------------| | height | int64 | Height of corresponding block. | 1 | | round | int32 | Round of voting to finalize the block. | 2 | -| part | [Part](../../core/data_structures.md#part) | A part of the block. | 3 | +| part | [Part](../../../core/data_structures.md#part) | A part of the block. | 3 | ### NewRoundStep @@ -79,7 +79,7 @@ In case the block is also committed, then IsCommit flag is set to true. |-----------------------|--------------------------------------------------------------|----------------------------------------|--------------| | height | int64 | Height of corresponding block | 1 | | round | int32 | Round of voting to finalize the block. | 2 | -| block_part_set_header | [PartSetHeader](../../core/data_structures.md#partsetheader) | | 3 | +| block_part_set_header | [PartSetHeader](../../../core/data_structures.md#partsetheader) | | 3 | | block_parts | int32 | | 4 | | is_commit | bool | | 5 | @@ -104,7 +104,7 @@ round, vote type and the index of the validator that is the originator of the co |--------|------------------------------------------------------------------|----------------------------------------|--------------| | height | int64 | Height of corresponding block | 1 | | round | int32 | Round of voting to finalize the block. | 2 | -| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | +| type | [SignedMessageType](../../../core/data_structures.md#signedmsgtype) | | 3 | | index | int32 | | 4 | ### VoteSetMaj23 @@ -116,7 +116,7 @@ It contains height, round, vote type and the BlockID. |--------|------------------------------------------------------------------|----------------------------------------|--------------| | height | int64 | Height of corresponding block | 1 | | round | int32 | Round of voting to finalize the block. | 2 | -| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | +| type | [SignedMessageType](../../../core/data_structures.md#signedmsgtype) | | 3 | ### VoteSetBits @@ -128,8 +128,8 @@ the votes a process has. |----------|------------------------------------------------------------------|----------------------------------------|--------------| | height | int64 | Height of corresponding block | 1 | | round | int32 | Round of voting to finalize the block. | 2 | -| type | [SignedMessageType](../../core/data_structures.md#signedmsgtype) | | 3 | -| block_id | [BlockID](../../core/data_structures.md#blockid) | | 4 | +| type | [SignedMessageType](../../../core/data_structures.md#signedmsgtype) | | 3 | +| block_id | [BlockID](../../../core/data_structures.md#blockid) | | 4 | | votes | BitArray | Round of voting to finalize the block. | 5 | ### Message diff --git a/spec/p2p/messages/evidence.md b/spec/p2p/legacy-docs/messages/evidence.md similarity index 78% rename from spec/p2p/messages/evidence.md rename to spec/p2p/legacy-docs/messages/evidence.md index 34fc40a9155..7db104b3477 100644 --- a/spec/p2p/messages/evidence.md +++ b/spec/p2p/legacy-docs/messages/evidence.md @@ -16,8 +16,8 @@ Evidence has one channel. The channel identifier is listed below. ### EvidenceList -EvidenceList consists of a list of verified evidence. This evidence will already have been propagated throughout the network. EvidenceList is used in two places, as a p2p message and within the block [block](../../core/data_structures.md#block) as well. +EvidenceList consists of a list of verified evidence. This evidence will already have been propagated throughout the network. EvidenceList is used in two places, as a p2p message and within the block [block](../../../core/data_structures.md#block) as well. | Name | Type | Description | Field Number | |----------|-------------------------------------------------------------|------------------------|--------------| -| evidence | repeated [Evidence](../../core/data_structures.md#evidence) | List of valid evidence | 1 | +| evidence | repeated [Evidence](../../../core/data_structures.md#evidence) | List of valid evidence | 1 | diff --git a/spec/p2p/messages/mempool.md b/spec/p2p/legacy-docs/messages/mempool.md similarity index 100% rename from spec/p2p/messages/mempool.md rename to spec/p2p/legacy-docs/messages/mempool.md diff --git a/spec/p2p/messages/pex.md b/spec/p2p/legacy-docs/messages/pex.md similarity index 100% rename from spec/p2p/messages/pex.md rename to spec/p2p/legacy-docs/messages/pex.md diff --git a/spec/p2p/messages/state-sync.md b/spec/p2p/legacy-docs/messages/state-sync.md similarity index 94% rename from spec/p2p/messages/state-sync.md rename to spec/p2p/legacy-docs/messages/state-sync.md index cfc958e08da..e7be056c442 100644 --- a/spec/p2p/messages/state-sync.md +++ b/spec/p2p/legacy-docs/messages/state-sync.md @@ -89,9 +89,9 @@ if necessary. The light block at the height of the snapshot will be used to veri | Name | Type | Description | Field Number | |---------------|---------------------------------------------------------|--------------------------------------|--------------| -| light_block | [LightBlock](../../core/data_structures.md#lightblock) | Light block at the height requested | 1 | +| light_block | [LightBlock](../../../core/data_structures.md#lightblock) | Light block at the height requested | 1 | -State sync will use [light client verification](../../../spec/light-client/verification/README.md) to verify +State sync will use [light client verification](../../../light-client/verification/README.md) to verify the light blocks. If no state sync is in progress (i.e. during normal operation), any unsolicited response messages @@ -113,7 +113,7 @@ A reciever to the request will use the state store to fetch the consensus params | Name | Type | Description | Field Number | |----------|--------|---------------------------------|--------------| | height | uint64 | Height of the consensus params | 1 | -| consensus_params | [ConsensusParams](../../core/data_structures.md#ConsensusParams) | Consensus params at the height requested | 2 | +| consensus_params | [ConsensusParams](../../../core/data_structures.md#ConsensusParams) | Consensus params at the height requested | 2 | ### Message diff --git a/spec/p2p/node.md b/spec/p2p/legacy-docs/node.md similarity index 100% rename from spec/p2p/node.md rename to spec/p2p/legacy-docs/node.md diff --git a/spec/p2p/peer.md b/spec/p2p/legacy-docs/peer.md similarity index 100% rename from spec/p2p/peer.md rename to spec/p2p/legacy-docs/peer.md diff --git a/spec/p2p/reactor-api/README.md b/spec/p2p/reactor-api/README.md new file mode 100644 index 00000000000..401805c4b90 --- /dev/null +++ b/spec/p2p/reactor-api/README.md @@ -0,0 +1,43 @@ +# Reactors + +Reactor is the generic name for a component that employs the p2p communication layer. + +This section documents the interaction of the p2p communication layer with the +reactors. +The diagram below summarizes this interaction, namely the **northbound interface** +of the p2p communication layer, representing some relevant event flows: + + + +Each of the protocols running a CometBFT node implements a reactor and registers +the implementation with the p2p layer. +The p2p layer provides network events to the registered reactors, the main +two being new connections with peers and received messages. +The reactors provide to the p2p layer messages to be sent to +peers and commands to control the operation of the p2p layer. + +It is worth noting that the components depicted in the diagram below run +multiple routines and that the illustrated actions happen in parallel. +For instance, the connection establishment routines run in parallel, invoking +the depicted `AddPeer` method concurrently. +Once a connection is fully established, each `Peer` instance runs a send and a +receive routines. +The send routine collects messages from multiple reactors to a peer, packaging +then into raw messages which are transmitted to the peer. +The receive routine processes incoming messages and forwards them to the +destination reactors, invoking the depicted `Receive` methods. +In addition, the reactors run multiple routines for interacting +with the peers (for example, to send messages to them) or with the `Switch`. + +The remaining of the documentation is organized as follows: + +- [Reactor API](./reactor.md): documents the [`p2p.Reactor`][reactor-interface] + interface and specifies the behaviour of the p2p layer when interacting with + a reactor. + In other words, the interaction of the p2p layer with the protocol layer (bottom-up). + +- [P2P API](./p2p-api.md): documents the interface provided by the p2p + layer to the reactors, through the `Switch` and `Peer` abstractions. + In other words, the interaction of the protocol layer with the p2p layer (top-down). + +[reactor-interface]: ../../../p2p/base_reactor.go diff --git a/spec/p2p/reactor-api/p2p-api.md b/spec/p2p/reactor-api/p2p-api.md new file mode 100644 index 00000000000..927e416c72b --- /dev/null +++ b/spec/p2p/reactor-api/p2p-api.md @@ -0,0 +1,311 @@ +# API for Reactors + +This document describes the API provided by the p2p layer to the protocol +layer, namely to the registered reactors. + +This API consists of two interfaces: the one provided by the `Switch` instance, +and the ones provided by multiple `Peer` instances, one per connected peer. +The `Switch` instance is provided to every reactor as part of the reactor's +[registration procedure][reactor-registration]. +The multiple `Peer` instances are provided to every registered reactor whenever +a [new connection with a peer][reactor-addpeer] is established. + +> **Note** +> +> The practical reasons that lead to the interface to be provided in two parts, +> `Switch` and `Peer` instances are discussed in more datail in the +> [knowledge-base repository](https://github.com/cometbft/knowledge-base/blob/main/p2p/reactors/switch-peer.md). + +## `Switch` API + +The [`Switch`][switch-type] is the central component of the p2p layer +implementation. It manages all the reactors running in a node and keeps track +of the connections with peers. +The table below summarizes the interaction of the standard reactors with the `Switch`: + +| `Switch` API method | consensus | block sync | state sync | mempool | evidence | PEX | +|--------------------------------------------|-----------|------------|------------|---------|-----------|-------| +| `Peers() IPeerSet` | x | x | | | | x | +| `NumPeers() (int, int, int)` | | x | | | | x | +| `Broadcast(Envelope) chan bool` | x | x | x | | | | +| `MarkPeerAsGood(Peer)` | x | | | | | | +| `StopPeerForError(Peer, interface{})` | x | x | x | x | x | x | +| `StopPeerGracefully(Peer)` | | | | | | x | +| `Reactor(string) Reactor` | | x | | | | | + +The above list is not exhaustive as it does not include all the `Switch` methods +invoked by the PEX reactor, a special component that should be considered part +of the p2p layer. This document does not cover the operation of the PEX reactor +as a connection manager. + +### Peers State + +The first two methods in the switch API allow reactors to query the state of +the p2p layer: the set of connected peers. + + func (sw *Switch) Peers() IPeerSet + +The `Peers()` method returns the current set of connected peers. +The returned `IPeerSet` is an immutable concurrency-safe copy of this set. +Observe that the `Peer` handlers returned by this method were previously +[added to the reactor][reactor-addpeer] via the `InitPeer(Peer)` method, +but not yet removed via the `RemovePeer(Peer)` method. +Thus, a priori, reactors should already have this information. + + func (sw *Switch) NumPeers() (outbound, inbound, dialing int) + +The `NumPeers()` method returns the current number of connected peers, +distinguished between `outbound` and `inbound` peers. +An `outbound` peer is a peer the node has dialed to, while an `inbound` peer is +a peer the node has accepted a connection from. +The third field `dialing` reports the number of peers to which the node is +currently attempting to connect, so not (yet) connected peers. + +> **Note** +> +> The third field returned by `NumPeers()`, the number of peers in `dialing` +> state, is not an information that should regard the protocol layer. +> In fact, with the exception of the PEX reactor, which can be considered part +> of the p2p layer implementation, no standard reactor actually uses this +> information, that could be removed when this interface is refactored. + +### Broadcast + +The switch provides, mostly for historical or retro-compatibility reasons, +a method for sending a message to all connected peers: + + func (sw *Switch) Broadcast(e Envelope) chan bool + +The `Broadcast()` method is not blocking and returns a channel of booleans. +For every connected `Peer`, it starts a background thread for sending the +message to that peer, using the `Peer.Send()` method +(which is blocking, as detailed in [Send Methods](#send-methods)). +The result of each unicast send operation (success or failure) is added to the +returned channel, which is closed when all operations are completed. + +> **Note** +> +> - The current _implementation_ of the `Switch.Broadcast(Envelope)` method is +> not efficient, as the marshalling of the provided message is performed as +> part of the `Peer.Send(Envelope)` helper method, that is, once per +> connected peer. +> - The return value of the broadcast method is not considered by any of the +> standard reactors that employ the method. One of the reasons is that is is +> not possible to associate each of the boolean outputs added to the +> returned channel to a peer. + +### Vetting Peers + +The p2p layer relies on the registered reactors to gauge the _quality_ of peers. +The following method can be invoked by a reactor to inform the p2p layer that a +peer has presented a "good" behaviour. +This information is registered in the node's address book and influences the +operation of the Peer Exchange (PEX) protocol, as node discovery adopts a bias +towards "good" peers: + + func (sw *Switch) MarkPeerAsGood(peer Peer) + +At the moment, it is up to the consensus reactor to vet a peer. +In the current logic, a peer is marked as good whenever the consensus protocol +collects a multiple of `votesToContributeToBecomeGoodPeer = 10000` useful votes +or `blocksToContributeToBecomeGoodPeer = 10000` useful block parts from that peer. +By "useful", the consensus implementation considers messages that are valid and +that are received by the node when the node is expected for such information, +which excludes duplicated or late received messages. + +> **Note** +> +> The switch doesn't currently provide a method to mark a peer as a bad peer. +> In fact, the peer quality management is really implemented in the current +> version of the p2p layer. +> This topic is being discussed in the [knowledge-base repository](https://github.com/cometbft/knowledge-base/blob/main/p2p/reactors/peer-quality.md). + +### Stopping Peers + +Reactors can instruct the p2p layer to disconnect from a peer. +Using the p2p layer's nomenclature, the reactor requests a peer to be stopped. +The peer's send and receive routines are in fact stopped, interrupting the +communication with the peer. +The `Peer` is then [removed from every registered reactor][reactor-removepeer], +using the `RemovePeer(Peer)` method, and from the set of connected peers. + + func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) + +All the standard reactors employ the above method for disconnecting from a peer +in case of errors. +These are errors that occur when processing a message received from a `Peer`. +The produced `error` is provided to the method as the `reason`. + +The `StopPeerForError()` method has an important *caveat*: if the peer to be +stopped is configured as a _persistent peer_, the switch will attempt +reconnecting to that same peer. +While this behaviour makes sense when the method is invoked by other components +of the p2p layer (e.g., in the case of communication errors), it does not make +sense when it is invoked by a reactor. + +> **Note** +> +> A more comprehensive discussion regarding this topic can be found on the +> [knowledge-base repository](https://github.com/cometbft/knowledge-base/blob/main/p2p/reactors/stop-peer.md). + + func (sw *Switch) StopPeerGracefully(peer Peer) + +The second method instructs the switch to disconnect from a peer for no +particular reason. +This method is only adopted by the PEX reactor of a node operating in _seed mode_, +as seed nodes disconnect from a peer after exchanging peer addresses with it. + +### Reactors Table + +The switch keeps track of all registered reactors, indexed by unique reactor names. +A reactor can therefore use the switch to access another `Reactor` from its `name`: + + func (sw *Switch) Reactor(name string) Reactor + +This method is currently only used by the Block Sync reactor to access the +Consensus reactor implementation, from which it uses the exported +`SwitchToConsensus()` method. +While available, this inter-reactor interaction approach is discouraged and +should be avoided, as it violates the assumption that reactors are independent. + + +## `Peer` API + +The [`Peer`][peer-interface] interface represents a connected peer. +A `Peer` instance encapsulates a multiplex connection that implements the +actual communication (sending and receiving messages) with a peer. +When a connection is established with a peer, the `Switch` provides the +corresponding `Peer` instance to all registered reactors. +From this point, reactors can use the methods of the new `Peer` instance. + +The table below summarizes the interaction of the standard reactors with +connected peers, with the `Peer` methods used by them: + +| `Peer` API method | consensus | block sync | state sync | mempool | evidence | PEX | +|--------------------------------------------|-----------|------------|------------|---------|-----------|-------| +| `ID() ID` | x | x | x | x | x | x | +| `IsRunning() bool` | x | | | x | x | | +| `Quit() <-chan struct{}` | | | | x | x | | +| `Get(string) interface{}` | x | | | x | x | | +| `Set(string, interface{})` | x | | | | | | +| `Send(Envelope) bool` | x | x | x | x | x | x | +| `TrySend(Envelope) bool` | x | x | | | | | + +The above list is not exhaustive as it does not include all the `Peer` methods +invoked by the PEX reactor, a special component that should be considered part +of the p2p layer. This document does not cover the operation of the PEX reactor +as a connection manager. + +### Identification + +Nodes in the p2p network are configured with a unique cryptographic key pair. +The public part of this key pair is verified when establishing a connection +with the peer, as part of the authentication handshake, and constitutes the +peer's `ID`: + + func (p Peer) ID() p2p.ID + +Observe that each time the node connects to a peer (e.g., after disconnecting +from it), a new (distinct) `Peer` handler is provided to the reactors via +`InitPeer(Peer)` method. +In fact, the `Peer` handler is associated to a _connection_ with a peer, not to +the actual _node_ in the network. +To keep track of actual peers, the unique peer `p2p.ID` provided by the above +method should be employed. + +### Peer state + +The switch starts the peer's send and receive routines before adding the peer +to every registered reactor using the `AddPeer(Peer)` method. +The reactors then usually start routines to interact with the new connected +peer using the received `Peer` handler. +For these routines it is useful to check whether the peer is still connected +and its send and receive routines are still running: + + func (p Peer) IsRunning() bool + func (p Peer) Quit() <-chan struct{} + +The above two methods provide the same information about the state of a `Peer` +instance in two different ways. +Both of them are defined in the [`Service`][service-interface] interface. +The `IsRunning()` method is synchronous and returns whether the peer has been +started and has not been stopped. +The `Quit()` method returns a channel that is closed when the peer is stopped; +it is an asynchronous state query. + +### Key-value store + +Each `Peer` instance provides a synchronized key-value store that allows +sharing peer-specific state between reactors: + + + func (p Peer) Get(key string) interface{} + func (p Peer) Set(key string, data interface{}) + +This key-value store can be seen as an asynchronous mechanism to exchange the +state of a peer between reactors. +In the current use-case of this mechanism, the Consensus reactor populates the +key-value store with a `PeerState` instance for each connected peer. +The Consensus reactor routines interacting with a peer read and update the +shared peer state. +The Evidence and Mempool reactors, in their turn, periodically query the +key-value store of each peer for retrieving, in particular, the last height +reported by the peer. +This information, produced by the Consensus reactor, influences the interaction +of these two reactors with their peers. + +> **Note** +> +> More details of how this key-value store is used to share state between reactors can be found on the +> [knowledge-base repository](https://github.com/cometbft/knowledge-base/blob/main/p2p/reactors/peer-kvstore.md). + +### Send methods + +Finally, a `Peer` instance allows a reactor to send messages to companion +reactors running at that peer. +This is ultimately the goal of the switch when it provides `Peer` instances to +the registered reactors. +There are two methods for sending messages: + + func (p Peer) Send(e Envelope) bool + func (p Peer) TrySend(e Envelope) bool + +The two message-sending methods receive an `Envelope`, whose content should be +set as follows: + +- `ChannelID`: the channel the message should be sent through, which defines + the reactor that will process the message; +- `Src`: this field represents the source of an incoming message, which is + irrelevant for outgoing messages; +- `Message`: the actual message's payload, which is marshalled using protocol buffers. + +The two message-sending methods attempt to add the message (`e.Payload`) to the +send queue of the peer's destination channel (`e.ChannelID`). +There is a send queue for each registered channel supported by the peer, and +each send queue has a capacity. +The capacity of the send queues for each channel are [configured][reactor-channels] +by reactors via the corresponding `ChannelDescriptor`. + +The two message-sending methods return whether it was possible to enqueue +the marshalled message to the channel's send queue. +The most common reason for these methods to return `false` is the channel's +send queue being full. +Further reasons for returning `false` are: the peer being stopped, providing a +non-registered channel ID, or errors when marshalling the message's payload. + +The difference between the two message-sending methods is _when_ they return `false`. +The `Send()` method is a _blocking_ method, it returns `false` if the message +could not be enqueued, because the channel's send queue is still full, after a +10-second _timeout_. +The `TrySend()` method is a _non-blocking_ method, it _immediately_ returns +`false` when the channel's send queue is full. + +[peer-interface]: ../../../p2p/peer.go +[service-interface]: ../../../libs/service/service.go +[switch-type]: ../../../p2p/switch.go + +[reactor-interface]: ../../../p2p/base_reactor.go +[reactor-registration]: ./reactor.md#registration +[reactor-channels]: ./reactor.md#registration +[reactor-addpeer]: ./reactor.md#peer-management +[reactor-removepeer]: ./reactor.md#stop-peer diff --git a/spec/p2p/reactor-api/reactor.md b/spec/p2p/reactor-api/reactor.md new file mode 100644 index 00000000000..9d85e7ccd0c --- /dev/null +++ b/spec/p2p/reactor-api/reactor.md @@ -0,0 +1,230 @@ +# Reactor API + +A component has to implement the [`p2p.Reactor` interface][reactor-interface] +in order to use communication services provided by the p2p layer. +This interface is currently the main source of documentation for a reactor. + +The goal of this document is to specify the behaviour of the p2p communication +layer when interacting with a reactor. +So while the [`Reactor interface`][reactor-interface] declares the methods +invoked and determines what the p2p layer expects from a reactor, +this documentation focuses on the **temporal behaviour** that a reactor implementation +should expect from the p2p layer. (That is, in which orders the functions may be called) + +This specification is accompanied by the [`reactor.qnt`](./reactor.qnt) file, +a more comprehensive model of the reactor's operation written in +[Quint][quint-repo], an executable specification language. +The methods declared in the [`Reactor`][reactor-interface] interface are +modeled in Quint, in the form of `pure def` methods, providing some examples of +how they should be implemented. +The behaviour of the p2p layer when interacting with a reactor, by invoking the +interface methods, is modeled in the form of state transitions, or `action`s in +the Quint nomenclature. + +## Overview + +The following _grammar_ is a simplified representation of the expected sequence of calls +from the p2p layer to a reactor. +Note that the grammar represents events referring to a _single reactor_, while +the p2p layer supports the execution of multiple reactors. +For a more detailed representation of the sequence of calls from the p2p layer +to reactors, please refer to the companion Quint model. + +While useful to provide an overview of the operation of a reactor, +grammars have some limitations in terms of the behaviour they can express. +For instance, the following grammar only represents the management of _a single peer_, +namely of a peer with a given ID which can connect, disconnect, and reconnect +multiple times to the node. +The p2p layer and every reactor should be able to handle multiple distinct peers in parallel. +This means that multiple occurrences of non-terminal `peer-management` of the +grammar below can "run" independently and in parallel, each one referring and +producing events associated to a different peer: + +```abnf +start = registration on-start *peer-management on-stop +registration = get-channels set-switch + +; Refers to a single peer, a reactor must support multiple concurrent peers +peer-management = init-peer start-peer stop-peer +start-peer = [*receive] (connected-peer / start-error) +connected-peer = add-peer *receive +stop-peer = [peer-error] remove-peer + +; Service interface +on-start = %s"OnStart()" +on-stop = %s"OnStop()" +; Reactor interface +get-channels = %s"GetChannels()" +set-switch = %s"SetSwitch(*Switch)" +init-peer = %s"InitPeer(Peer)" +add-peer = %s"AddPeer(Peer)" +remove-peer = %s"RemovePeer(Peer, reason)" +receive = %s"Receive(Envelope)" + +; Errors, for reference +start-error = %s"log(Error starting peer)" +peer-error = %s"log(Stopping peer for error)" +``` + +The grammar is written in case-sensitive Augmented Backus–Naur form (ABNF, +specified in [IETF RFC 7405](https://datatracker.ietf.org/doc/html/rfc7405)). +It is inspired on the grammar produced to specify the interaction of CometBFT +with an ABCI++ application, available [here](../../abci/abci%2B%2B_comet_expected_behavior.md). + +## Registration + +To become a reactor, a component has first to implement the +[`Reactor`][reactor-interface] interface, +then to register the implementation with the p2p layer, using the +`Switch.AddReactor(name string, reactor Reactor)` method, +with a global unique `name` for the reactor. + +The registration must happen before the node, in general, and the p2p layer, +in particular, are started. +In other words, there is no support for registering a reactor on a running node: +reactors must be registered as part of the setup of a node. + +```abnf +registration = get-channels set-switch +``` + +The p2p layer retrieves from the reactor a list of channels the reactor is +responsible for, using the `GetChannels()` method. +The reactor implementation should thereafter expect the delivery of every +message received by the p2p layer in the informed channels. + +The second method `SetSwitch(Switch)` concludes the handshake between the +reactor and the p2p layer. +The `Switch` is the main component of the p2p layer, being responsible for +establishing connections with peers and routing messages. +The `Switch` instance provides a number of methods for all registered reactors, +documented in the companion [API for Reactors](./p2p-api.md#switch-api) document. + +## Service interface + +A reactor must implement the [`Service`](../../../libs/service/service.go) interface, +in particular, a startup `OnStart()` and a shutdown `OnStop()` methods: + +```abnf +start = registration on-start *peer-management on-stop +``` + +As part of the startup of a node, all registered reactors are started by the p2p layer. +And when the node is shut down, all registered reactors are stopped by the p2p layer. +Observe that the `Service` interface specification establishes that a service +can be started and stopped only once. +So before being started or once stopped by the p2p layer, the reactor should +not expect any interaction. + +## Peer management + +The core of a reactor's operation is the interaction with peers or, more +precisely, with companion reactors operating on the same channels in peers connected to the node. +The grammar extract below represents the interaction of the reactor with a +single peer: + +```abnf +; Refers to a single peer, a reactor must support multiple concurrent peers +peer-management = init-peer start-peer stop-peer +``` + +The p2p layer informs all registered reactors when it establishes a connection +with a `Peer`, using the `InitPeer(Peer)` method. +When this method is invoked, the `Peer` has not yet been started, namely the +routines for sending messages to and receiving messages from the peer are not running. +This method should be used to initialize state or data related to the new +peer, but not to interact with it. + +The next step is to start the communication routines with the new `Peer`. +As detailed in the following, this procedure may or may not succeed. +In any case, the peer is eventually stopped, which concludes the management of +that `Peer` instance. + +## Start peer + +Once `InitPeer(Peer)` is invoked for every registered reactor, the p2p layer starts the peer's +communication routines and adds the `Peer` to the set of connected peers. +If both steps are concluded without errors, the reactor's `AddPeer(Peer)` is invoked: + +```abnf +start-peer = [*receive] (connected-peer / start-error) +connected-peer = add-peer *receive +``` + +In case of errors, a message is logged informing that the p2p layer failed to start the peer. +This is not a common scenario and it is only expected to happen when +interacting with a misbehaving or slow peer. A practical example is reported on this +[issue](https://github.com/tendermint/tendermint/pull/9500). + +It is up to the reactor to define how to process the `AddPeer(Peer)` event. +The typical behavior is to start routines that, given some conditions or events, +send messages to the added peer, using the provided `Peer` instance. +The companion [API for Reactors](./p2p-api.md#peer-api) documents the methods +provided by `Peer` instances, available from when they are added to the reactors. + +## Stop Peer + +The p2p layer informs all registered reactors when it disconnects from a `Peer`, +using the `RemovePeer(Peer, reason)` method: + +```abnf +stop-peer = [peer-error] remove-peer +``` + +This method is invoked after the p2p layer has stopped peer's send and receive routines. +Depending of the `reason` for which the peer was stopped, different log +messages can be produced. +After removing a peer from all reactors, the `Peer` instance is also removed from +the set of connected peers. +This enables the same peer to reconnect and `InitPeer(Peer)` to be invoked for +the new connection. + +From the removal of a `Peer` , the reactor should not receive any further message +from the peer and must not try sending messages to the removed peer. +This usually means stopping the routines that were started by the companion +`Add(Peer)` method. + +## Receive messages + +The main duty of a reactor is to handle incoming messages on the channels it +has registered with the p2p layer. + +The _pre-condition_ for receiving a message from a `Peer` is that the p2p layer +has previously invoked `InitPeer(Peer)`. +This means that the reactor must be able to receive a message from a `Peer` +_before_ `AddPeer(Peer)` is invoked. +This happens because the peer's send and receive routines are started before, +and should be already running when the p2p layer adds the peer to every +registered reactor. + +```abnf +start-peer = [*receive] (connected-peer / start-error) +connected-peer = add-peer *receive +``` + +The most common scenario, however, is to start receiving messages from a peer +after `AddPeer(Peer)` is invoked. +An arbitrary number of messages can be received, until the peer is stopped and +`RemovePeer(Peer)` is invoked. + +When a message is received from a connected peer on any of the channels +registered by the reactor, the p2p layer will deliver the message to the +reactor via the `Receive(Envelope)` method. +The message is packed into an `Envelope` that contains: + +- `ChannelID`: the channel the message belongs to +- `Src`: the source `Peer` handler, from which the message was received +- `Message`: the actual message's payload, unmarshalled using protocol buffers + +Two important observations regarding the implementation of the `Receive` method: + +1. Concurrency: the implementation should consider concurrent invocations of + the `Receive` method carrying messages from different peers, as the + interaction with different peers is independent and messages can be received in parallel. +1. Non-blocking: the implementation of the `Receive` method is expected not to block, + as it is invoked directly by the receive routines. + In other words, while `Receive` does not return, other messages from the + same sender are not delivered to any reactor. + +[reactor-interface]: ../../../p2p/base_reactor.go +[quint-repo]: https://github.com/informalsystems/quint diff --git a/spec/p2p/reactor-api/reactor.qnt b/spec/p2p/reactor-api/reactor.qnt new file mode 100644 index 00000000000..002c57023af --- /dev/null +++ b/spec/p2p/reactor-api/reactor.qnt @@ -0,0 +1,276 @@ +// -*- mode: Bluespec; -*- +/* + * Reactor is responsible for handling incoming messages on one or more + * Channel. Switch calls GetChannels when reactor is added to it. When a new + * peer joins our node, InitPeer and AddPeer are called. RemovePeer is called + * when the peer is stopped. Receive is called when a message is received on a + * channel associated with this reactor. + */ +// Code: https://github.com/cometbft/cometbft/blob/main/p2p/base_reactor.go +module reactor { + + // Unique ID of a node. + type NodeID = str + + /* + * Peer is an interface representing a peer connected on a reactor. + */ + type Peer = { + ID: NodeID, + + // Other fields can be added to represent the p2p operation. + } + + // Byte ID used by channels, must be globally unique. + type Byte = str + + // Channel configuration. + type ChannelDescriptor = { + ID: Byte, + Priority: int, + } + + /* + * Envelope contains a message with sender routing info. + */ + type Envelope = { + Src: Peer, // Sender + Message: str, // Payload + ChannelID: Byte, + } + + // A Routine is used to interact with an active Peer. + type Routine = { + name: str, + peer: Peer, + } + + type ReactorState = { + // Peers that have been initialized but not yet removed. + // The reactor should expect receiving messages from them. + peers: Set[NodeID], + + // The reactor runs multiple routines. + routines: Set[Routine], + + // Values: init -> registered -> running -> stopped + state: str, + + // Name with which the reactor was registered. + name: str, + + // Channels the reactor is responsible for. + channels: Set[ChannelDescriptor], + } + + // Produces a new, uninitialized reactor. + pure def NewReactor(): ReactorState = { + { + peers: Set(), + routines: Set(), + state: "init", + name: "", + channels: Set(), + } + } + + // Pure definitions below represent the `p2p.Reactor` interface methods: + + /* + * GetChannels returns the list of MConnection.ChannelDescriptor. Make sure + * that each ID is unique across all the reactors added to the switch. + */ + pure def GetChannels(s: ReactorState): Set[ChannelDescriptor] = { + s.channels // Static list, configured at initialization. + } + + /* + * SetSwitch allows setting a switch. + */ + pure def SetSwitch(s: ReactorState, switch: bool): ReactorState = { + s.with("state", "registered") + } + + /* + * Start the service. + * If it's already started or stopped, will return an error. + */ + pure def OnStart(s: ReactorState): ReactorState = { + // Startup procedures should come here. + s.with("state", "running") + } + + /* + * Stop the service. + * If it's already stopped, will return an error. + */ + pure def OnStop(s: ReactorState): ReactorState = { + // Shutdown procedures should come here. + s.with("state", "stopped") + } + + /* + * InitPeer is called by the switch before the peer is started. Use it to + * initialize data for the peer (e.g. peer state). + */ + pure def InitPeer(s: ReactorState, peer: Peer): (ReactorState, Peer) = { + // This method can update the received peer, which is returned. + val updatedPeer = peer + (s.with("peers", s.peers.union(Set(peer.ID))), updatedPeer) + } + + /* + * AddPeer is called by the switch after the peer is added and successfully + * started. Use it to start goroutines communicating with the peer. + */ + pure def AddPeer(s: ReactorState, peer: Peer): ReactorState = { + // This method can be used to start routines to handle the peer. + // Below an example of an arbitrary 'ioRoutine' routine. + val startedRoutines = Set( {name: "ioRoutine", peer: peer} ) + s.with("routines", s.routines.union(startedRoutines)) + } + + /* + * RemovePeer is called by the switch when the peer is stopped (due to error + * or other reason). + */ + pure def RemovePeer(s: ReactorState, peer: Peer, reason: str): ReactorState = { + // This method should stop routines created by `AddPeer(Peer)`. + val stoppedRoutines = s.routines.filter(r => r.peer.ID == peer.ID) + s.with("peers", s.peers.exclude(Set(peer.ID))) + .with("routines", s.routines.exclude(stoppedRoutines)) + } + + /* + * Receive is called by the switch when an envelope is received from any connected + * peer on any of the channels registered by the reactor. + */ + pure def Receive(s: ReactorState, e: Envelope): ReactorState = { + // This method should process the message payload: e.Message. + s + } + + // Global state + + // Reactors are uniquely identified by their names. + var reactors: str -> ReactorState + + // Reactor (name) assigned to each channel ID. + var reactorsByCh: Byte -> str + + // Helper action to (only) update the state of a given reactor. + action updateReactorTo(reactor: ReactorState): bool = all { + reactors' = reactors.set(reactor.name, reactor), + reactorsByCh' = reactorsByCh + } + + // State transitions performed by the p2p layer, invoking `p2p.Reactor` methods: + + // Code: Switch.AddReactor(name string, reactor Reactor) + action register(name: str, reactor: ReactorState): bool = all { + reactor.state == "init", + // Assign the reactor as responsible for its channel IDs, which + // should not be already assigned to another reactor. + val chIDs = reactor.GetChannels().map(c => c.ID) + all { + size(chIDs.intersect(reactorsByCh.keys())) == 0, + reactorsByCh' = reactorsByCh.keys().union(chIDs). + mapBy(id => if (id.in(chIDs)) name + else reactorsByCh.get(id)), + }, + // Register the reactor by its name, which must be unique. + not(name.in(reactors.keys())), + reactors' = reactors.put(name, + reactor.SetSwitch(true).with("name", name)) + } + + // Code: Switch.OnStart() + action start(reactor: ReactorState): bool = all { + reactor.state == "registered", + updateReactorTo(reactor.OnStart()) + } + + // Code: Switch.addPeer(p Peer): preamble + action initPeer(reactor: ReactorState, peer: Peer): bool = all { + reactor.state == "running", + not(peer.ID.in(reactor.peers)), + updateReactorTo(reactor.InitPeer(peer)._1) + } + + // Code: Switch.addPeer(p Peer): conclusion + action addPeer(reactor: ReactorState, peer: Peer): bool = all { + reactor.state == "running", + peer.ID.in(reactor.peers), // InitPeer(peer) and not RemovePeer(peer) + reactor.routines.filter(r => r.peer.ID == peer.ID).size() == 0, + updateReactorTo(reactor.AddPeer(peer)) + } + + // Code: Switch.stopAndRemovePeer(peer Peer, reason interface{}) + action removePeer(reactor: ReactorState, peer: Peer, reason: str): bool = all { + reactor.state == "running", + peer.ID.in(reactor.peers), // InitPeer(peer) and not RemovePeer(peer) + // Routines might not be started, namely: not AddPeer(peer) + // Routines could also be already stopped if Peer has erroed. + updateReactorTo(reactor.RemovePeer(peer, reason)) + } + + // Code: Peer type, onReceive := func(chID byte, msgBytes []byte) + action receive(reactor: ReactorState, e: Envelope): bool = all { + reactor.state == "running", + // The message's sender is an active peer + e.Src.ID.in(reactor.peers), + // Reactor is assigned to the message's channel ID + e.ChannelID.in(reactorsByCh.keys()), + reactorsByCh.get(e.ChannelID) == reactor.name, + reactor.GetChannels().exists(c => c.ID == e.ChannelID), + updateReactorTo(reactor.Receive(e)) + } + + // Code: Switch.OnStop() + action stop(reactor: ReactorState): bool = all { + reactor.state == "running", + // Either no peer was added or all peers were removed + reactor.peers.size() == 0, + updateReactorTo(reactor.OnStop()) + } + + // Simulation support + + action init = all { + reactors' = Map(), + reactorsByCh' = Map(), + } + + // Modelled reactor configuration + pure val reactorName = "myReactor" + pure val reactorChannels = Set({ID: "3", Priority: 1}, {ID: "7", Priority: 2}) + + // For retro-compatibility: the state of the modelled reactor + def state(): ReactorState = { + reactors.get(reactorName) + } + + pure val samplePeers = Set({ID: "p1"}, {ID: "p3"}) + pure val sampleChIDs = Set("1", "3", "7") // ChannelID 1 not registered + pure val sampleMsgs = Set("ping", "pong") + + action step = any { + register(reactorName, NewReactor.with("channels", reactorChannels)), + val reactor = reactors.get(reactorName) + any { + reactor.start(), + reactor.stop(), + nondet peer = oneOf(samplePeers) + any { + // Peer-specific actions + reactor.initPeer(peer), + reactor.addPeer(peer), + reactor.removePeer(peer, "no reason"), + reactor.receive({Src: peer, + ChannelID: oneOf(sampleChIDs), + Message: oneOf(sampleMsgs)}), + } + } + } + +} diff --git a/spec/p2p/readme.md b/spec/p2p/readme.md deleted file mode 100644 index 9b39f3fc12e..00000000000 --- a/spec/p2p/readme.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -order: 1 -parent: - title: P2P - order: 6 ---- - From c7eed7e8cf66b95ce27d0190e7115bdf780c84b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 13:49:12 +0300 Subject: [PATCH 06/87] build(deps): Bump bufbuild/buf-setup-action from 1.22.0 to 1.23.1 (#1063) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.22.0 to 1.23.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.22.0...v1.23.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 52771a442e5..c116d96ca6f 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.22.0 + - uses: bufbuild/buf-setup-action@v1.23.1 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From c6d6c7c2e584e58a684afa10210c8dc62c4138cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 18:28:26 +0200 Subject: [PATCH 07/87] build(deps): Bump docker/setup-buildx-action from 2.7.0 to 2.8.0 (#1062) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.7.0...v2.8.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index eb471088385..2f3b2bdd3c2 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.7.0 + uses: docker/setup-buildx-action@v2.8.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index d4b6126d74f..fb897fe34dc 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.7.0 + uses: docker/setup-buildx-action@v2.8.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From e94a9dd06c948be68ca693bc66c6b7c27e3d268f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 07:20:03 -0400 Subject: [PATCH 08/87] build(deps): Bump docker/setup-buildx-action from 2.8.0 to 2.9.0 (#1104) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.8.0 to 2.9.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.8.0...v2.9.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 2f3b2bdd3c2..21633bc72ea 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.8.0 + uses: docker/setup-buildx-action@v2.9.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index fb897fe34dc..a6f00948c44 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.8.0 + uses: docker/setup-buildx-action@v2.9.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From edffdc55853295a710ae25d743d4e1ee0a50d033 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 05:54:51 -0400 Subject: [PATCH 09/87] build(deps): Bump docker/setup-buildx-action from 2.9.0 to 2.9.1 (#1129) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.0...v2.9.1) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 21633bc72ea..6f8f3cba559 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.9.0 + uses: docker/setup-buildx-action@v2.9.1 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index a6f00948c44..e07d41400f1 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.9.0 + uses: docker/setup-buildx-action@v2.9.1 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From 75b81abe86d2b5eff7ca7347886fcd48b1953ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 06:00:48 -0400 Subject: [PATCH 10/87] build(deps): Bump bufbuild/buf-setup-action from 1.23.1 to 1.24.0 (#1130) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.23.1 to 1.24.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.23.1...v1.24.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index c116d96ca6f..aa30bcff8ef 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.23.1 + - uses: bufbuild/buf-setup-action@v1.24.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From f689a1c14f7e8052f8c5a434a3a3ccd82e50259d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 08:23:31 -0400 Subject: [PATCH 11/87] p2p: Remove UPnP functionality (backport #1114) (#1126) --- .../breaking-changes/1113-rm-upnp.md | 2 + cmd/cometbft/commands/probe_upnp.go | 33 -- cmd/cometbft/commands/run_node.go | 1 - cmd/cometbft/main.go | 1 - config/config.go | 12 +- config/toml.go | 11 +- docs/core/configuration.md | 11 +- docs/core/how-to-read-logs.md | 5 +- p2p/upnp/probe.go | 117 ----- p2p/upnp/upnp.go | 404 ------------------ spec/p2p/implementation/configuration.md | 14 +- spec/p2p/implementation/types.md | 6 - 12 files changed, 22 insertions(+), 595 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/1113-rm-upnp.md delete mode 100644 cmd/cometbft/commands/probe_upnp.go delete mode 100644 p2p/upnp/probe.go delete mode 100644 p2p/upnp/upnp.go diff --git a/.changelog/unreleased/breaking-changes/1113-rm-upnp.md b/.changelog/unreleased/breaking-changes/1113-rm-upnp.md new file mode 100644 index 00000000000..5526319ae5b --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1113-rm-upnp.md @@ -0,0 +1,2 @@ +- `[p2p]` Remove unused UPnP functionality + ([\#1113](https://github.com/cometbft/cometbft/issues/1113)) diff --git a/cmd/cometbft/commands/probe_upnp.go b/cmd/cometbft/commands/probe_upnp.go deleted file mode 100644 index c2c4609a135..00000000000 --- a/cmd/cometbft/commands/probe_upnp.go +++ /dev/null @@ -1,33 +0,0 @@ -package commands - -import ( - "fmt" - - "github.com/spf13/cobra" - - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/p2p/upnp" -) - -// ProbeUpnpCmd adds capabilities to test the UPnP functionality. -var ProbeUpnpCmd = &cobra.Command{ - Use: "probe-upnp", - Aliases: []string{"probe_upnp"}, - Short: "Test UPnP functionality", - RunE: probeUpnp, -} - -func probeUpnp(cmd *cobra.Command, args []string) error { - capabilities, err := upnp.Probe(logger) - if err != nil { - fmt.Println("Probe failed: ", err) - } else { - fmt.Println("Probe success!") - jsonBytes, err := cmtjson.Marshal(capabilities) - if err != nil { - return err - } - fmt.Println(string(jsonBytes)) - } - return nil -} diff --git a/cmd/cometbft/commands/run_node.go b/cmd/cometbft/commands/run_node.go index b8fa35f26d4..c25e9e448c5 100644 --- a/cmd/cometbft/commands/run_node.go +++ b/cmd/cometbft/commands/run_node.go @@ -68,7 +68,6 @@ func AddNodeFlags(cmd *cobra.Command) { cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "comma-delimited ID@host:port persistent peers") cmd.Flags().String("p2p.unconditional_peer_ids", config.P2P.UnconditionalPeerIDs, "comma-delimited IDs of unconditional peers") - cmd.Flags().Bool("p2p.upnp", config.P2P.UPNP, "enable/disable UPNP port forwarding") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "enable/disable Peer-Exchange") cmd.Flags().Bool("p2p.seed_mode", config.P2P.SeedMode, "enable/disable seed mode") cmd.Flags().String("p2p.private_peer_ids", config.P2P.PrivatePeerIDs, "comma-delimited private peer IDs") diff --git a/cmd/cometbft/main.go b/cmd/cometbft/main.go index b7af0ebbb84..e35c0788678 100644 --- a/cmd/cometbft/main.go +++ b/cmd/cometbft/main.go @@ -16,7 +16,6 @@ func main() { rootCmd.AddCommand( cmd.GenValidatorCmd, cmd.InitFilesCmd, - cmd.ProbeUpnpCmd, cmd.LightCmd, cmd.ReplayCmd, cmd.ReplayConsoleCmd, diff --git a/config/config.go b/config/config.go index 9f467520743..7af22c83a16 100644 --- a/config/config.go +++ b/config/config.go @@ -73,7 +73,7 @@ type Config struct { Mempool *MempoolConfig `mapstructure:"mempool"` StateSync *StateSyncConfig `mapstructure:"statesync"` BlockSync *BlockSyncConfig `mapstructure:"blocksync"` - //TODO(williambanfield): remove this field once v0.37 is released. + // TODO(williambanfield): remove this field once v0.37 is released. // https://github.com/tendermint/tendermint/issues/9279 DeprecatedFastSyncConfig map[interface{}]interface{} `mapstructure:"fastsync"` Consensus *ConsensusConfig `mapstructure:"consensus"` @@ -174,6 +174,9 @@ func (cfg *Config) CheckDeprecated() []string { if cfg.BaseConfig.DeprecatedFastSyncMode != nil { warnings = append(warnings, "fast_sync key detected. This key has been renamed to block_sync. The value of this deprecated key will be disregarded.") } + if cfg.P2P.UPNP != nil { + warnings = append(warnings, "unused and deprecated upnp field detected in P2P config.") + } return warnings } @@ -202,7 +205,7 @@ type BaseConfig struct { //nolint: maligned // Deprecated: BlockSync will be enabled unconditionally in the next major release. BlockSyncMode bool `mapstructure:"block_sync"` - //TODO(williambanfield): remove this field once v0.37 is released. + // TODO(williambanfield): remove this field once v0.37 is released. // https://github.com/tendermint/tendermint/issues/9279 DeprecatedFastSyncMode interface{} `mapstructure:"fast_sync"` @@ -561,8 +564,8 @@ type P2PConfig struct { //nolint: maligned // Comma separated list of nodes to keep persistent connections to PersistentPeers string `mapstructure:"persistent_peers"` - // UPNP port forwarding - UPNP bool `mapstructure:"upnp"` + // Deprecated and unused. + UPNP interface{} `mapstructure:"upnp"` // Path to address book AddrBook string `mapstructure:"addr_book_file"` @@ -628,7 +631,6 @@ func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:26656", ExternalAddress: "", - UPNP: false, AddrBook: defaultAddrBookPath, AddrBookStrict: true, MaxNumInboundPeers: 40, diff --git a/config/toml.go b/config/toml.go index ad560960af8..4ff8eecca2a 100644 --- a/config/toml.go +++ b/config/toml.go @@ -274,11 +274,9 @@ pprof_laddr = "{{ .RPC.PprofListenAddress }}" # Address to listen for incoming connections laddr = "{{ .P2P.ListenAddress }}" -# Address to advertise to peers for them to dial -# If empty, will use the same port as the laddr, -# and will introspect on the listener or use UPnP -# to figure out the address. ip and port are required -# example: 159.89.10.97:26656 +# Address to advertise to peers for them to dial. If empty, will use the same +# port as the laddr, and will introspect on the listener to figure out the +# address. IP and port are required. Example: 159.89.10.97:26656 external_address = "{{ .P2P.ExternalAddress }}" # Comma separated list of seed nodes to connect to @@ -287,9 +285,6 @@ seeds = "{{ .P2P.Seeds }}" # Comma separated list of nodes to keep persistent connections to persistent_peers = "{{ .P2P.PersistentPeers }}" -# UPNP port forwarding -upnp = {{ .P2P.UPNP }} - # Path to address book addr_book_file = "{{ js .P2P.AddrBook }}" diff --git a/docs/core/configuration.md b/docs/core/configuration.md index 0fc008e5c38..f38770c9907 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -223,11 +223,9 @@ pprof_laddr = "" # Address to listen for incoming connections laddr = "tcp://0.0.0.0:26656" -# Address to advertise to peers for them to dial -# If empty, will use the same port as the laddr, -# and will introspect on the listener or use UPnP -# to figure out the address. ip and port are required -# example: 159.89.10.97:26656 +# Address to advertise to peers for them to dial. If empty, will use the same +# port as the laddr, and will introspect on the listener to figure out the +# address. IP and port are required. Example: 159.89.10.97:26656 external_address = "" # Comma separated list of seed nodes to connect to @@ -236,9 +234,6 @@ seeds = "" # Comma separated list of nodes to keep persistent connections to persistent_peers = "" -# UPNP port forwarding -upnp = false - # Path to address book addr_book_file = "config/addrbook.json" diff --git a/docs/core/how-to-read-logs.md b/docs/core/how-to-read-logs.md index 509216fcba1..07024d4893b 100644 --- a/docs/core/how-to-read-logs.md +++ b/docs/core/how-to-read-logs.md @@ -24,16 +24,13 @@ I[10-04|13:54:27.368] ABCI Replay Blocks module=consen I[10-04|13:54:27.368] Completed ABCI Handshake - CometBFT and App are synced module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD ``` -After that, we start a few more things like the event switch, reactors, -and perform UPNP discover in order to detect the IP address. +After that, we start a few more things like the event switch and reactors. ```sh I[10-04|13:54:27.374] Starting EventSwitch module=types impl=EventSwitch I[10-04|13:54:27.375] This node is a validator module=consensus I[10-04|13:54:27.379] Starting Node module=main impl=Node I[10-04|13:54:27.381] Local listener module=p2p ip=:: port=26656 -I[10-04|13:54:27.382] Getting UPNP external address module=p2p -I[10-04|13:54:30.386] Could not perform UPNP discover module=p2p err="write udp4 0.0.0.0:38238->239.255.255.250:1900: i/o timeout" I[10-04|13:54:30.386] Starting DefaultListener module=p2p impl=Listener(@10.0.2.15:26656) I[10-04|13:54:30.387] Starting P2P Switch module=p2p impl="P2P Switch" I[10-04|13:54:30.387] Starting MempoolReactor module=mempool impl=MempoolReactor diff --git a/p2p/upnp/probe.go b/p2p/upnp/probe.go deleted file mode 100644 index b40d92e65ad..00000000000 --- a/p2p/upnp/probe.go +++ /dev/null @@ -1,117 +0,0 @@ -package upnp - -import ( - "fmt" - "net" - "time" - - "github.com/cometbft/cometbft/libs/log" -) - -type Capabilities struct { - PortMapping bool - Hairpin bool -} - -func makeUPNPListener(intPort int, extPort int, logger log.Logger) (NAT, net.Listener, net.IP, error) { - nat, err := Discover() - if err != nil { - return nil, nil, nil, fmt.Errorf("nat upnp could not be discovered: %v", err) - } - logger.Info("make upnp listener", "msg", log.NewLazySprintf("ourIP: %v", nat.(*upnpNAT).ourIP)) - - ext, err := nat.GetExternalAddress() - if err != nil { - return nat, nil, nil, fmt.Errorf("external address error: %v", err) - } - logger.Info("make upnp listener", "msg", log.NewLazySprintf("External address: %v", ext)) - - port, err := nat.AddPortMapping("tcp", extPort, intPort, "CometBFT UPnP Probe", 0) - if err != nil { - return nat, nil, ext, fmt.Errorf("port mapping error: %v", err) - } - logger.Info("make upnp listener", "msg", log.NewLazySprintf("Port mapping mapped: %v", port)) - - // also run the listener, open for all remote addresses. - listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort)) - if err != nil { - return nat, nil, ext, fmt.Errorf("error establishing listener: %v", err) - } - return nat, listener, ext, nil -} - -func testHairpin(listener net.Listener, extAddr string, logger log.Logger) (supportsHairpin bool) { - // Listener - go func() { - inConn, err := listener.Accept() - if err != nil { - logger.Info("test hair pin", "msg", log.NewLazySprintf("Listener.Accept() error: %v", err)) - return - } - logger.Info("test hair pin", - "msg", - log.NewLazySprintf("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr())) - buf := make([]byte, 1024) - n, err := inConn.Read(buf) - if err != nil { - logger.Info("test hair pin", - "msg", - log.NewLazySprintf("Incoming connection read error: %v", err)) - return - } - logger.Info("test hair pin", - "msg", - log.NewLazySprintf("Incoming connection read %v bytes: %X", n, buf)) - if string(buf) == "test data" { - supportsHairpin = true - return - } - }() - - // Establish outgoing - outConn, err := net.Dial("tcp", extAddr) - if err != nil { - logger.Info("test hair pin", "msg", log.NewLazySprintf("Outgoing connection dial error: %v", err)) - return - } - - n, err := outConn.Write([]byte("test data")) - if err != nil { - logger.Info("test hair pin", "msg", log.NewLazySprintf("Outgoing connection write error: %v", err)) - return - } - logger.Info("test hair pin", "msg", log.NewLazySprintf("Outgoing connection wrote %v bytes", n)) - - // Wait for data receipt - time.Sleep(1 * time.Second) - return supportsHairpin -} - -func Probe(logger log.Logger) (caps Capabilities, err error) { - logger.Info("Probing for UPnP!") - - intPort, extPort := 8001, 8001 - - nat, listener, ext, err := makeUPNPListener(intPort, extPort, logger) - if err != nil { - return - } - caps.PortMapping = true - - // Deferred cleanup - defer func() { - if err := nat.DeletePortMapping("tcp", intPort, extPort); err != nil { - logger.Error(fmt.Sprintf("Port mapping delete error: %v", err)) - } - if err := listener.Close(); err != nil { - logger.Error(fmt.Sprintf("Listener closing error: %v", err)) - } - }() - - supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort), logger) - if supportsHairpin { - caps.Hairpin = true - } - - return -} diff --git a/p2p/upnp/upnp.go b/p2p/upnp/upnp.go deleted file mode 100644 index 45da9d33cb8..00000000000 --- a/p2p/upnp/upnp.go +++ /dev/null @@ -1,404 +0,0 @@ -// Taken from taipei-torrent. -// Just enough UPnP to be able to forward ports -// For more information, see: http://www.upnp-hacks.org/upnp.html -package upnp - -// TODO: use syscalls to get actual ourIP, see issue #712 - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "time" -) - -type upnpNAT struct { - serviceURL string - ourIP string - urnDomain string -} - -// protocol is either "udp" or "tcp" -type NAT interface { - GetExternalAddress() (addr net.IP, err error) - AddPortMapping( - protocol string, - externalPort, - internalPort int, - description string, - timeout int) (mappedExternalPort int, err error) - DeletePortMapping(protocol string, externalPort, internalPort int) (err error) -} - -func Discover() (nat NAT, err error) { - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return - } - conn, err := net.ListenPacket("udp4", ":0") - if err != nil { - return - } - socket := conn.(*net.UDPConn) - defer socket.Close() - - if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil { - return nil, err - } - - st := "InternetGatewayDevice:1" - - buf := bytes.NewBufferString( - "M-SEARCH * HTTP/1.1\r\n" + - "HOST: 239.255.255.250:1900\r\n" + - "ST: ssdp:all\r\n" + - "MAN: \"ssdp:discover\"\r\n" + - "MX: 2\r\n\r\n") - message := buf.Bytes() - answerBytes := make([]byte, 1024) - for i := 0; i < 3; i++ { - _, err = socket.WriteToUDP(message, ssdp) - if err != nil { - return - } - var n int - _, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - return - } - for { - n, _, err = socket.ReadFromUDP(answerBytes) - if err != nil { - break - } - answer := string(answerBytes[0:n]) - if !strings.Contains(answer, st) { - continue - } - // HTTP header field names are case-insensitive. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - locString := "\r\nlocation:" - answer = strings.ToLower(answer) - locIndex := strings.Index(answer, locString) - if locIndex < 0 { - continue - } - loc := answer[locIndex+len(locString):] - endIndex := strings.Index(loc, "\r\n") - if endIndex < 0 { - continue - } - locURL := strings.TrimSpace(loc[0:endIndex]) - var serviceURL, urnDomain string - serviceURL, urnDomain, err = getServiceURL(locURL) - if err != nil { - return - } - var ourIP net.IP - ourIP, err = localIPv4() - if err != nil { - return - } - nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain} - return - } - } - err = errors.New("upnp port discovery failed") - return nat, err -} - -type Envelope struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` - Soap *SoapBody -} -type SoapBody struct { - XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` - ExternalIP *ExternalIPAddressResponse -} - -type ExternalIPAddressResponse struct { - XMLName xml.Name `xml:"GetExternalIPAddressResponse"` - IPAddress string `xml:"NewExternalIPAddress"` -} - -type ExternalIPAddress struct { - XMLName xml.Name `xml:"NewExternalIPAddress"` - IP string -} - -type Service struct { - ServiceType string `xml:"serviceType"` - ControlURL string `xml:"controlURL"` -} - -type DeviceList struct { - Device []Device `xml:"device"` -} - -type ServiceList struct { - Service []Service `xml:"service"` -} - -type Device struct { - XMLName xml.Name `xml:"device"` - DeviceType string `xml:"deviceType"` - DeviceList DeviceList `xml:"deviceList"` - ServiceList ServiceList `xml:"serviceList"` -} - -type Root struct { - Device Device -} - -func getChildDevice(d *Device, deviceType string) *Device { - dl := d.DeviceList.Device - for i := 0; i < len(dl); i++ { - if strings.Contains(dl[i].DeviceType, deviceType) { - return &dl[i] - } - } - return nil -} - -func getChildService(d *Device, serviceType string) *Service { - sl := d.ServiceList.Service - for i := 0; i < len(sl); i++ { - if strings.Contains(sl[i].ServiceType, serviceType) { - return &sl[i] - } - } - return nil -} - -func localIPv4() (net.IP, error) { - tt, err := net.Interfaces() - if err != nil { - return nil, err - } - for _, t := range tt { - aa, err := t.Addrs() - if err != nil { - return nil, err - } - for _, a := range aa { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - v4 := ipnet.IP.To4() - if v4 == nil || v4[0] == 127 { // loopback address - continue - } - return v4, nil - } - } - return nil, errors.New("cannot find local IP address") -} - -func getServiceURL(rootURL string) (url, urnDomain string, err error) { - r, err := http.Get(rootURL) //nolint: gosec - if err != nil { - return - } - defer r.Body.Close() - - if r.StatusCode >= 400 { - err = errors.New(string(rune(r.StatusCode))) - return - } - var root Root - err = xml.NewDecoder(r.Body).Decode(&root) - if err != nil { - return - } - a := &root.Device - if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") { - err = errors.New("no InternetGatewayDevice") - return - } - b := getChildDevice(a, "WANDevice:1") - if b == nil { - err = errors.New("no WANDevice") - return - } - c := getChildDevice(b, "WANConnectionDevice:1") - if c == nil { - err = errors.New("no WANConnectionDevice") - return - } - d := getChildService(c, "WANIPConnection:1") - if d == nil { - // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice, - // instead of under WanConnectionDevice - d = getChildService(b, "WANIPConnection:1") - - if d == nil { - err = errors.New("no WANIPConnection") - return - } - } - // Extract the domain name, which isn't always 'schemas-upnp-org' - urnDomain = strings.Split(d.ServiceType, ":")[1] - url = combineURL(rootURL, d.ControlURL) - return url, urnDomain, err -} - -func combineURL(rootURL, subURL string) string { - protocolEnd := "://" - protoEndIndex := strings.Index(rootURL, protocolEnd) - a := rootURL[protoEndIndex+len(protocolEnd):] - rootIndex := strings.Index(a, "/") - return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL -} - -func soapRequest(url, function, message, domain string) (r *http.Response, err error) { - fullMessage := "" + - "\r\n" + - "" + message + "" - - req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") - req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") - // req.Header.Set("Transfer-Encoding", "chunked") - req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"") - req.Header.Set("Connection", "Close") - req.Header.Set("Cache-Control", "no-cache") - req.Header.Set("Pragma", "no-cache") - - // log.Stderr("soapRequest ", req) - - r, err = http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - /*if r.Body != nil { - defer r.Body.Close() - }*/ - - if r.StatusCode >= 400 { - // log.Stderr(function, r.StatusCode) - err = errors.New("error " + strconv.Itoa(r.StatusCode) + " for " + function) - r = nil - return - } - return r, err -} - -type statusInfo struct { - externalIPAddress string -} - -func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) { - - message := "\r\n" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain) - if response != nil { - defer response.Body.Close() - } - if err != nil { - return - } - var envelope Envelope - data, err := io.ReadAll(response.Body) - if err != nil { - return - } - reader := bytes.NewReader(data) - err = xml.NewDecoder(reader).Decode(&envelope) - if err != nil { - return - } - - info = statusInfo{envelope.Soap.ExternalIP.IPAddress} - - if err != nil { - return - } - - return info, err -} - -// GetExternalAddress returns an external IP. If GetExternalIPAddress action -// fails or IP returned is invalid, GetExternalAddress returns an error. -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - info, err := n.getExternalIPAddress() - if err != nil { - return - } - addr = net.ParseIP(info.externalIPAddress) - if addr == nil { - err = fmt.Errorf("failed to parse IP: %v", info.externalIPAddress) - } - return -} - -func (n *upnpNAT) AddPortMapping( - protocol string, - externalPort, - internalPort int, - description string, - timeout int) (mappedExternalPort int, err error) { - // A single concatenation would break ARM compilation. - message := "\r\n" + - "" + strconv.Itoa(externalPort) - message += "" + protocol + "" - message += "" + strconv.Itoa(internalPort) + "" + - "" + n.ourIP + "" + - "1" - message += description + - "" + strconv.Itoa(timeout) + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() - } - if err != nil { - return - } - - // TODO: check response to see if the port was forwarded - // log.Println(message, response) - // JAE: - // body, err := io.ReadAll(response.Body) - // fmt.Println(string(body), err) - mappedExternalPort = externalPort - _ = response - return mappedExternalPort, err -} - -func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { - - message := "\r\n" + - "" + strconv.Itoa(externalPort) + - "" + protocol + "" + - "" - - var response *http.Response - response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain) - if response != nil { - defer response.Body.Close() - } - if err != nil { - return - } - - // TODO: check response to see if the port was deleted - // log.Println(message, response) - _ = response - return -} diff --git a/spec/p2p/implementation/configuration.md b/spec/p2p/implementation/configuration.md index 977c8c3e049..9f172c22c81 100644 --- a/spec/p2p/implementation/configuration.md +++ b/spec/p2p/implementation/configuration.md @@ -8,7 +8,6 @@ This document contains configurable parameters a node operator can use to tune t | ExternalAddress | "" | Address to advertise to peers for them to dial | | [Seeds](./pex-protocol.md#seed-nodes) | empty | Comma separated list of seed nodes to connect to (ID@host:port )| | [Persistent peers](./peer_manager.md#persistent-peers) | empty | Comma separated list of nodes to keep persistent connections to (ID@host:port ) | -| UPNP | false | UPNP port forwarding enabled | | [AddrBook](./addressbook.md) | defaultAddrBookPath | Path do address book | | AddrBookStrict | true | Set true for strict address routability rules and false for private or local networks | | [MaxNumInboundPeers](./switch.md#accepting-peers) | 40 | Maximum number of inbound peers | @@ -29,18 +28,17 @@ This document contains configurable parameters a node operator can use to tune t These parameters can be set using the `$CMTHOME/config/config.toml` file. A subset of them can also be changed via command line using the following command line flags: -| Parameter | Flag| Example| -| --- | --- | ---| +| Parameter | Flag | Example | +| --- | --- | --- | | Listen address| `p2p.laddr` | "tcp://0.0.0.0:26656" | | Seed nodes | `p2p.seeds` | `--p2p.seeds “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:4444”` | | Persistent peers | `p2p.persistent_peers` | `--p2p.persistent_peers “id100000000000000000000000000000000@1.2.3.4:26656,id200000000000000000000000000000000@2.3.4.5:26656”` | | Unconditional peers | `p2p.unconditional_peer_ids` | `--p2p.unconditional_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | - | UPNP | `p2p.upnp` | `--p2p.upnp` | - | PexReactor | `p2p.pex` | `--p2p.pex` | - | Seed mode | `p2p.seed_mode` | `--p2p.seed_mode` | - | Private peer ids | `p2p.private_peer_ids` | `--p2p.private_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | +| PexReactor | `p2p.pex` | `--p2p.pex` | +| Seed mode | `p2p.seed_mode` | `--p2p.seed_mode` | +| Private peer ids | `p2p.private_peer_ids` | `--p2p.private_peer_ids “id100000000000000000000000000000000,id200000000000000000000000000000000”` | - **Note on persistent peers** + **Note on persistent peers** If `persistent_peers_max_dial_period` is set greater than zero, the pause between each dial to each persistent peer will not exceed `persistent_peers_max_dial_period` diff --git a/spec/p2p/implementation/types.md b/spec/p2p/implementation/types.md index 6d71da03fb7..cef2632936b 100644 --- a/spec/p2p/implementation/types.md +++ b/spec/p2p/implementation/types.md @@ -231,9 +231,3 @@ Go documentation of `Metric` type: > // See cometbft/docs/architecture/adr-006-trust-metric.md for details Not imported by any other CometBFT source file. - -## Package `p2p.upnp` - -This package implementation was taken from "taipei-torrent". - -It is used by the `probe-upnp` command of the CometBFT binary. From 20f3c5761636e145c45d07c4bf0ae4bdf5dbbc21 Mon Sep 17 00:00:00 2001 From: Antonio Pitasi Date: Thu, 20 Jul 2023 13:21:17 +0200 Subject: [PATCH 12/87] crypto/sr25519: upgrade to go-schnorrkel@v1.0.0 (#1151) --- .../unreleased/improvements/475-upgrade-go-schnorrkel.md | 1 + crypto/sr25519/pubkey.go | 3 ++- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md diff --git a/.changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md b/.changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md new file mode 100644 index 00000000000..bdaf96c14cf --- /dev/null +++ b/.changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md @@ -0,0 +1 @@ +- `[crypto/sr25519]` Upgrade to go-schnorrkel@v1.0.0 ([\#475](https://github.com/cometbft/cometbft/issues/475)) diff --git a/crypto/sr25519/pubkey.go b/crypto/sr25519/pubkey.go index 52af0f4dad5..1e9b791811c 100644 --- a/crypto/sr25519/pubkey.go +++ b/crypto/sr25519/pubkey.go @@ -55,7 +55,8 @@ func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool { return false } - return publicKey.Verify(signature, signingContext) + ok, err := publicKey.Verify(signature, signingContext) + return ok && err == nil } func (pubKey PubKey) String() string { diff --git a/go.mod b/go.mod index bb73b433cc3..43e7f2a85b8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/BurntSushi/toml v1.2.1 - github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d + github.com/ChainSafe/go-schnorrkel v1.0.0 github.com/adlio/schema v1.3.3 github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.12.0 diff --git a/go.sum b/go.sum index 0a96d6de643..2c933987731 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= +github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= From 3f7932132fcdacce4136f7bffba21dc5bd2469aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:38:18 -0400 Subject: [PATCH 13/87] build(deps): Bump bufbuild/buf-setup-action from 1.24.0 to 1.25.0 (#1158) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.24.0 to 1.25.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.24.0...v1.25.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index aa30bcff8ef..ec49714cb9a 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.24.0 + - uses: bufbuild/buf-setup-action@v1.25.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 7c001da29c7bf148b09e35992b398b09506ea7c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 06:52:04 -0400 Subject: [PATCH 14/87] build(deps): Bump bufbuild/buf-setup-action from 1.25.0 to 1.25.1 (#1219) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.25.0 to 1.25.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.25.0...v1.25.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index ec49714cb9a..a8cc0e06a3f 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.25.0 + - uses: bufbuild/buf-setup-action@v1.25.1 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 83ff5be1cb15a3de41c6dd884c6f2b6c5a92eb2a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 16:00:56 +0200 Subject: [PATCH 15/87] Close evidence.db OnStop (#1210) (#1225) * CV OnStop close evidenceStore * CV OnStop print db close * CV add changelog * CV update changelog with attribution (cherry picked from commit 48335a06f01524b036fde4dc1bab569bfc4ab9c7) Co-authored-by: Chill Validation <92176880+chillyvee@users.noreply.github.com> Co-authored-by: Sergio Mena --- .../unreleased/improvements/1210-close-evidence-db.md | 1 + node/node.go | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 .changelog/unreleased/improvements/1210-close-evidence-db.md diff --git a/.changelog/unreleased/improvements/1210-close-evidence-db.md b/.changelog/unreleased/improvements/1210-close-evidence-db.md new file mode 100644 index 00000000000..e32bc87dbe1 --- /dev/null +++ b/.changelog/unreleased/improvements/1210-close-evidence-db.md @@ -0,0 +1 @@ +- `[node]` Close evidence.db OnStop ([cometbft/cometbft\#1210](https://github.com/cometbft/cometbft/pull/1210): @chillyvee) diff --git a/node/node.go b/node/node.go index 4e5238d1dad..1cb06d8bbe1 100644 --- a/node/node.go +++ b/node/node.go @@ -1048,15 +1048,23 @@ func (n *Node) OnStop() { } } if n.blockStore != nil { + n.Logger.Info("Closing blockstore") if err := n.blockStore.Close(); err != nil { n.Logger.Error("problem closing blockstore", "err", err) } } if n.stateStore != nil { + n.Logger.Info("Closing statestore") if err := n.stateStore.Close(); err != nil { n.Logger.Error("problem closing statestore", "err", err) } } + if n.evidencePool != nil { + n.Logger.Info("Closing evidencestore") + if err := n.EvidencePool().Close(); err != nil { + n.Logger.Error("problem closing evidencestore", "err", err) + } + } } // ConfigureRPC makes sure RPC has all the objects it needs to operate. From 88ce9ee2b30cf36e62a3d2e5dd42ea858132fc24 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:02:22 +0200 Subject: [PATCH 16/87] Log proposer's address when correctly accepting a proposal (backport #1079) (#1221) * Log proposer's address when correctly accepting a proposal (#1079) * Log proposer when logging received proposal * Addressed review comments * Promote updates to validator to Info level (cherry picked from commit cf230821c899f1f0cd3ef9cbbcc678ca01008d46) # Conflicts: # consensus/state.go * Revert "Log proposer's address when correctly accepting a proposal (#1079)" This reverts commit b2aaa8fdd95e3f8bf6876e874620ab3396694be2. * Log proposer's address when correctly accepting a proposal (#1079) * Log proposer when logging received proposal * Addressed review comments * Promote updates to validator to Info level --------- Co-authored-by: Sergio Mena --- consensus/state.go | 15 +++++++++++---- state/execution.go | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index f6715cd8c53..e785a2e8837 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -996,7 +996,7 @@ func (cs *State) enterNewRound(height int64, round int32) { logger.Debug("need to set a buffer and log message here for sanity", "start_time", cs.StartTime, "now", now) } - logger.Debug("entering new round", "current", log.NewLazySprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step)) + prevHeight, prevRound, prevStep := cs.Height, cs.Round, cs.Step // increment validators if necessary validators := cs.Validators @@ -1010,17 +1010,23 @@ func (cs *State) enterNewRound(height int64, round int32) { // but we fire an event, so update the round step first cs.updateRoundStep(round, cstypes.RoundStepNewRound) cs.Validators = validators + propAddress := validators.GetProposer().PubKey.Address() if round == 0 { // We've already reset these upon new height, // and meanwhile we might have received a proposal // for round 0. } else { - logger.Debug("resetting proposal info") + logger.Info("resetting proposal info", "proposer", propAddress) cs.Proposal = nil cs.ProposalBlock = nil cs.ProposalBlockParts = nil } + logger.Debug("entering new round", + "previous", log.NewLazySprintf("%v/%v/%v", prevHeight, prevRound, prevStep), + "proposer", propAddress, + ) + cs.Votes.SetRound(cmtmath.SafeAddInt32(round, 1)) // also track next round (round+1) to allow round-skipping cs.TriggeredTimeoutPrecommit = false @@ -1870,7 +1876,8 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error { p := proposal.ToProto() // Verify signature - if !cs.Validators.GetProposer().PubKey.VerifySignature( + pubKey := cs.Validators.GetProposer().PubKey + if !pubKey.VerifySignature( types.ProposalSignBytes(cs.state.ChainID, p), proposal.Signature, ) { return ErrInvalidProposalSignature @@ -1885,7 +1892,7 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error { cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartSetHeader) } - cs.Logger.Info("received proposal", "proposal", proposal) + cs.Logger.Info("received proposal", "proposal", proposal, "proposer", pubKey.Address()) return nil } diff --git a/state/execution.go b/state/execution.go index 91f1719ca53..4d312a99ea7 100644 --- a/state/execution.go +++ b/state/execution.go @@ -224,7 +224,7 @@ func (blockExec *BlockExecutor) ApplyBlock( return state, 0, err } if len(validatorUpdates) > 0 { - blockExec.logger.Debug("updates to validators", "updates", types.ValidatorListString(validatorUpdates)) + blockExec.logger.Info("updates to validators", "updates", types.ValidatorListString(validatorUpdates)) blockExec.metrics.ValidatorSetUpdates.Add(1) } if abciResponses.EndBlock.ConsensusParamUpdates != nil { From 4560c1142c050c979682b852a37d2e4169680bfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 07:33:24 -0400 Subject: [PATCH 17/87] build(deps): Bump bufbuild/buf-setup-action from 1.25.1 to 1.26.0 (#1237) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.25.1 to 1.26.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.25.1...v1.26.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index a8cc0e06a3f..9119043d2ac 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.25.1 + - uses: bufbuild/buf-setup-action@v1.26.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 80cfaedd0a83e35dbfdd5048ab64488a46089c33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 06:31:58 -0400 Subject: [PATCH 18/87] build(deps): Bump bufbuild/buf-setup-action from 1.26.0 to 1.26.1 (#1274) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.26.0 to 1.26.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.26.0...v1.26.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 9119043d2ac..7a7754f3536 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.26.0 + - uses: bufbuild/buf-setup-action@v1.26.1 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From cfc87ac87b4a21a1d03977756e403994316e506e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:35:48 +0200 Subject: [PATCH 19/87] build(deps): Bump docker/setup-buildx-action from 2.9.1 to 2.10.0 (#1301) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.1 to 2.10.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.1...v2.10.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 6f8f3cba559..4b66c1a3f61 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v2.10.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index e07d41400f1..3e89a2a3ef6 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.9.1 + uses: docker/setup-buildx-action@v2.10.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From 790d57e1748f854f36b5acf9f146f597a31714a5 Mon Sep 17 00:00:00 2001 From: yihuang Date: Tue, 5 Sep 2023 19:56:01 +0800 Subject: [PATCH 20/87] feat: make handshake cancelable (backport #857) (#1013) it'll make the handshake work with graceful shutdown(see: https://github.com/cosmos/cosmos-sdk/issues/16202) handshake could be a long running process if there are many local blocks to replay, for example we use it to do profiling. Hope we can backport this to 0.34.x. --- - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --- .../857-make-handshake-cancelable.md | 1 + consensus/replay.go | 30 +++++++++++++++++-- node/node.go | 22 ++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 .changelog/unreleased/improvements/857-make-handshake-cancelable.md diff --git a/.changelog/unreleased/improvements/857-make-handshake-cancelable.md b/.changelog/unreleased/improvements/857-make-handshake-cancelable.md new file mode 100644 index 00000000000..16b447f6d23 --- /dev/null +++ b/.changelog/unreleased/improvements/857-make-handshake-cancelable.md @@ -0,0 +1 @@ +- `[node]` Make handshake cancelable ([cometbft/cometbft\#857](https://github.com/cometbft/cometbft/pull/857)) diff --git a/consensus/replay.go b/consensus/replay.go index 43930f631ca..7a9d07cb78c 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -2,6 +2,7 @@ package consensus import ( "bytes" + "context" "fmt" "hash/crc32" "io" @@ -239,6 +240,11 @@ func (h *Handshaker) NBlocks() int { // TODO: retry the handshake/replay if it fails ? func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { + return h.HandshakeWithContext(context.TODO(), proxyApp) +} + +// HandshakeWithContext is cancellable version of Handshake +func (h *Handshaker) HandshakeWithContext(ctx context.Context, proxyApp proxy.AppConns) error { // Handshake is done via ABCI Info on the query conn. res, err := proxyApp.Query().InfoSync(proxy.RequestInfo) @@ -265,7 +271,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { } // Replay blocks up to the latest in the blockstore. - _, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp) + appHash, err = h.ReplayBlocksWithContext(ctx, h.initialState, appHash, blockHeight, proxyApp) if err != nil { return fmt.Errorf("error on replay: %v", err) } @@ -286,6 +292,17 @@ func (h *Handshaker) ReplayBlocks( appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns, +) ([]byte, error) { + return h.ReplayBlocksWithContext(context.TODO(), state, appHash, appBlockHeight, proxyApp) +} + +// ReplayBlocksWithContext is cancellable version of ReplayBlocks. +func (h *Handshaker) ReplayBlocksWithContext( + ctx context.Context, + state sm.State, + appHash []byte, + appBlockHeight int64, + proxyApp proxy.AppConns, ) ([]byte, error) { storeBlockBase := h.store.Base() storeBlockHeight := h.store.Height() @@ -390,7 +407,7 @@ func (h *Handshaker) ReplayBlocks( // Either the app is asking for replay, or we're all synced up. if appBlockHeight < storeBlockHeight { // the app is behind, so replay blocks, but no need to go through WAL (state is already synced to store) - return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, false) + return h.replayBlocks(ctx, state, proxyApp, appBlockHeight, storeBlockHeight, false) } else if appBlockHeight == storeBlockHeight { // We're good! @@ -405,7 +422,7 @@ func (h *Handshaker) ReplayBlocks( case appBlockHeight < stateBlockHeight: // the app is further behind than it should be, so replay blocks // but leave the last block to go through the WAL - return h.replayBlocks(state, proxyApp, appBlockHeight, storeBlockHeight, true) + return h.replayBlocks(ctx, state, proxyApp, appBlockHeight, storeBlockHeight, true) case appBlockHeight == stateBlockHeight: // We haven't run Commit (both the state and app are one block behind), @@ -435,6 +452,7 @@ func (h *Handshaker) ReplayBlocks( } func (h *Handshaker) replayBlocks( + ctx context.Context, state sm.State, proxyApp proxy.AppConns, appBlockHeight, @@ -461,6 +479,12 @@ func (h *Handshaker) replayBlocks( firstBlock = state.InitialHeight } for i := firstBlock; i <= finalBlock; i++ { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + h.logger.Info("Applying block", "height", i) block := h.store.LoadBlock(i) // Extra check to ensure the app was not changed in a way it shouldn't have. diff --git a/node/node.go b/node/node.go index 1cb06d8bbe1..183fe223958 100644 --- a/node/node.go +++ b/node/node.go @@ -314,6 +314,7 @@ func createAndStartIndexerService( } func doHandshake( + ctx context.Context, stateStore sm.Store, state sm.State, blockStore sm.BlockStore, @@ -325,7 +326,7 @@ func doHandshake( handshaker := cs.NewHandshaker(stateStore, state, blockStore, genDoc) handshaker.SetLogger(consensusLogger) handshaker.SetEventBus(eventBus) - if err := handshaker.Handshake(proxyApp); err != nil { + if err := handshaker.HandshakeWithContext(ctx, proxyApp); err != nil { return fmt.Errorf("error during handshake: %v", err) } return nil @@ -711,6 +712,23 @@ func NewNode(config *cfg.Config, metricsProvider MetricsProvider, logger log.Logger, options ...Option, +) (*Node, error) { + return NewNodeWithContext(context.TODO(), config, privValidator, + nodeKey, clientCreator, genesisDocProvider, dbProvider, + metricsProvider, logger, options...) +} + +// NewNodeWithContext is cancellable version of NewNode. +func NewNodeWithContext(ctx context.Context, + config *cfg.Config, + privValidator types.PrivValidator, + nodeKey *p2p.NodeKey, + clientCreator proxy.ClientCreator, + genesisDocProvider GenesisDocProvider, + dbProvider DBProvider, + metricsProvider MetricsProvider, + logger log.Logger, + options ...Option, ) (*Node, error) { blockStore, stateDB, err := initDBs(config, dbProvider) if err != nil { @@ -775,7 +793,7 @@ func NewNode(config *cfg.Config, // and replays any blocks as necessary to sync CometBFT with the app. consensusLogger := logger.With("module", "consensus") if !stateSync { - if err := doHandshake(stateStore, state, blockStore, genDoc, eventBus, proxyApp, consensusLogger); err != nil { + if err := doHandshake(ctx, stateStore, state, blockStore, genDoc, eventBus, proxyApp, consensusLogger); err != nil { return nil, err } From 11f2dcccce6ba9f2b1ecbbaf5d9638fcbf9bfbd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:07:14 +0200 Subject: [PATCH 21/87] build(deps): Bump docker/build-push-action from 4.1.1 to 4.2.1 (#1314) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.1 to 4.2.1. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4.1.1...v4.2.1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 4b66c1a3f61..e355b1a7517 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v4.1.1 + uses: docker/build-push-action@v4.2.1 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index 3e89a2a3ef6..586a0c68353 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v4.1.1 + uses: docker/build-push-action@v4.2.1 with: context: . file: ./test/e2e/docker/Dockerfile From 11f60903ca79520cd38ad25b021e6c241d54293e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:11:30 +0200 Subject: [PATCH 22/87] build(deps): Bump actions/checkout from 3 to 4 (#1315) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergio Mena --- .github/workflows/build.yml | 6 +++--- .github/workflows/check-generated.yml | 4 ++-- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/docs-toc.yml | 2 +- .github/workflows/e2e-long-37x.yml | 2 +- .github/workflows/e2e-manual-multiversion.yml | 2 +- .github/workflows/e2e-manual.yml | 2 +- .github/workflows/e2e-nightly-34x.yml | 2 +- .github/workflows/e2e-nightly-37x.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/linter.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/proto-lint.yml | 2 +- .github/workflows/release-version.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- .github/workflows/tests.yml | 2 +- 21 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c95d625629d..6688465a7a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | @@ -44,7 +44,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | @@ -66,7 +66,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 70943e9aef6..ce242887ea4 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -20,7 +20,7 @@ jobs: with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Check generated mocks" run: | @@ -44,7 +44,7 @@ jobs: with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 1 # we need a .git directory to run git diff diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index e355b1a7517..5a65bbcbb05 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -15,7 +15,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare id: prep run: | diff --git a/.github/workflows/docs-toc.yml b/.github/workflows/docs-toc.yml index e4ad7f03e06..225b33bbdb4 100644 --- a/.github/workflows/docs-toc.yml +++ b/.github/workflows/docs-toc.yml @@ -10,7 +10,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/e2e-long-37x.yml b/.github/workflows/e2e-long-37x.yml index 51c38f1cda8..9b55794681d 100644 --- a/.github/workflows/e2e-long-37x.yml +++ b/.github/workflows/e2e-long-37x.yml @@ -19,7 +19,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: 'v0.37.x' diff --git a/.github/workflows/e2e-manual-multiversion.yml b/.github/workflows/e2e-manual-multiversion.yml index 164ee3b9e21..e1ecd3dc37f 100644 --- a/.github/workflows/e2e-manual-multiversion.yml +++ b/.github/workflows/e2e-manual-multiversion.yml @@ -19,7 +19,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index d69cdf892c3..1eefdec5131 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -19,7 +19,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e-nightly-34x.yml b/.github/workflows/e2e-nightly-34x.yml index d232806afe2..66a30b537c5 100644 --- a/.github/workflows/e2e-nightly-34x.yml +++ b/.github/workflows/e2e-nightly-34x.yml @@ -23,7 +23,7 @@ jobs: with: go-version: '1.18' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: 'v0.34.x' diff --git a/.github/workflows/e2e-nightly-37x.yml b/.github/workflows/e2e-nightly-37x.yml index 0ac1f50d313..68f7062bb85 100644 --- a/.github/workflows/e2e-nightly-37x.yml +++ b/.github/workflows/e2e-nightly-37x.yml @@ -23,7 +23,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: 'v0.37.x' diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index 6fa05d90b9d..5e99df58223 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -24,7 +24,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 494b9513ff1..eef8a21060c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index 4af09787419..4416820bb69 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -17,7 +17,7 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install go-fuzz working-directory: test/fuzz diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 4e39bb302dc..7e95736e302 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -18,7 +18,7 @@ jobs: with: go-version: "1.20" check-latest: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7b932c3c8c5..ff1e624dd95 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 8 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: go-version: '1.20' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index c98da6e2f7b..bd22d4bb8cf 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Lint Code Base uses: docker://github/super-linter:v4 env: diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 4cf6a83cb87..6464aced09a 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 7a7754f3536..41074cca412 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: bufbuild/buf-setup-action@v1.26.1 - uses: bufbuild/buf-lint-action@v1 with: diff --git a/.github/workflows/release-version.yml b/.github/workflows/release-version.yml index 773e9339ab3..09b442ed7b0 100644 --- a/.github/workflows/release-version.yml +++ b/.github/workflows/release-version.yml @@ -11,7 +11,7 @@ jobs: check-version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 343818356e2..088fb6fd632 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index 586a0c68353..a9070da1077 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -15,7 +15,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare id: prep run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46a2358a77f..be011bb6807 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-go@v4 with: go-version: "1.20" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | From be0df2e241f0bd20d51ea2b63e25583ea73dbe21 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Date: Wed, 13 Sep 2023 01:30:10 +0200 Subject: [PATCH 23/87] Fix typo: exent -> event (#1329) --- state/txindex/indexer_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/txindex/indexer_service.go b/state/txindex/indexer_service.go index 4b0cfb90ce0..b5d1eb52ebb 100644 --- a/state/txindex/indexer_service.go +++ b/state/txindex/indexer_service.go @@ -94,7 +94,7 @@ func (is *IndexerService) OnStart() error { return } } else { - is.Logger.Info("indexed block exents", "height", height) + is.Logger.Info("indexed block events", "height", height) } if err = is.txIdxr.AddBatch(batch); err != nil { From 63ef8c81b67d87db066ed00351183c0e701ae084 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:48:26 +0200 Subject: [PATCH 24/87] build(deps): Bump docker/build-push-action from 4.2.1 to 5.0.0 (#1356) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.2.1 to 5.0.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4.2.1...v5.0.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 5a65bbcbb05..b4bc1d645f6 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v4.2.1 + uses: docker/build-push-action@v5.0.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index a9070da1077..dcd6d082ff1 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v4.2.1 + uses: docker/build-push-action@v5.0.0 with: context: . file: ./test/e2e/docker/Dockerfile From bf8fd8c4784b803c34d5bc7b42648cad5ec99359 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:02:15 +0200 Subject: [PATCH 25/87] build(deps): Bump docker/login-action from 2.2.0 to 3.0.0 (#1359) Bumps [docker/login-action](https://github.com/docker/login-action) from 2.2.0 to 3.0.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2.2.0...v3.0.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index b4bc1d645f6..ba62cc58f86 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -45,7 +45,7 @@ jobs: - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} - uses: docker/login-action@v2.2.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index dcd6d082ff1..498ed65948d 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -45,7 +45,7 @@ jobs: - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} - uses: docker/login-action@v2.2.0 + uses: docker/login-action@v3.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From 24d3670d8b50faff6ff73cdad21bf9f61a1a3c6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:15:51 +0200 Subject: [PATCH 26/87] build(deps): Bump docker/setup-buildx-action from 2.10.0 to 3.0.0 (#1358) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.10.0 to 3.0.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2.10.0...v3.0.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index ba62cc58f86..93628994c3e 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.10.0 + uses: docker/setup-buildx-action@v3.0.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index 498ed65948d..6199c6cecb9 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v2.10.0 + uses: docker/setup-buildx-action@v3.0.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From 46df7b597e3c2a55f1e766029afa89e4dcb8165e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:39:34 +0200 Subject: [PATCH 27/87] build(deps): Bump goreleaser/goreleaser-action from 4 to 5 (#1357) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v4...v5) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergio Mena --- .github/workflows/pre-release.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 6464aced09a..aa34290e7df 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -44,7 +44,7 @@ jobs: echo "See the [CHANGELOG](${CHANGELOG_URL}) for changes available in this pre-release, but not yet officially released." > ../release_notes.md - name: Release - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: version: latest args: release --clean --release-notes ../release_notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 088fb6fd632..8ce400b4f04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: echo "See the [CHANGELOG](${CHANGELOG_URL}) for this release." > ../release_notes.md - name: Release - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: version: latest args: release --clean --release-notes ../release_notes.md From 8d18d1da4e015f56ee4b1dab2ef9ed960bca4bf1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:23:54 +0200 Subject: [PATCH 28/87] Update to string (#1385) (#1390) (cherry picked from commit dad1253e89a4968b353625e7a4d54b53523ca2d7) Co-authored-by: Aliasgar Merchant <44069404+alijnmerchant21@users.noreply.github.com> --- rpc/openapi/openapi.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index 89ef15eb016..e4e55a91c7c 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -2812,10 +2812,10 @@ components: properties: key: type: string - example: "YWN0aW9u" + example: "action" value: type: string - example: "c2VuZA==" + example: "send" index: type: boolean example: false From cd9d6d7781182a1e88b4999e4c37a91508196731 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:35:25 +0200 Subject: [PATCH 29/87] doc: improve documentation of BlockParams.MaxBytes (backport #1405) (#1410) * doc: improve documentation of BlockParams.MaxBytes (#1405) * spec: comment on BlockParams.MaxBytes and timeouts * spec/abci: link to consensus timeouts doc fixed * spec/abci: comment on timeout parameters moved down * spec: advice to wind down default BlockParams.MaxBytes * spec: more on winding down default BlockParams.MaxBytes * spec: fixes on winding down default BlockParams.MaxBytes * spec: nit pic on winding down default BlockParams.MaxBytes * Applying @sergio-mena suggestion Co-authored-by: Sergio Mena * Applying @sergio-mena suggestion Co-authored-by: Sergio Mena * Update spec/abci/abci++_app_requirements.md Co-authored-by: glnro <8335464+glnro@users.noreply.github.com> --------- Co-authored-by: Sergio Mena Co-authored-by: glnro <8335464+glnro@users.noreply.github.com> (cherry picked from commit 80648c45a3acb27791087b427b73dcde19a47abd) # Conflicts: # spec/abci/abci++_app_requirements.md * spec/abci: fix conflicts when backporting * Fixing typo on backported file --------- Co-authored-by: Daniel --- spec/abci/abci++_app_requirements.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/spec/abci/abci++_app_requirements.md b/spec/abci/abci++_app_requirements.md index 52c325d6c3e..8d017553efe 100644 --- a/spec/abci/abci++_app_requirements.md +++ b/spec/abci/abci++_app_requirements.md @@ -582,10 +582,24 @@ These are the current consensus parameters (as of v0.37.x): The maximum size of a complete Protobuf encoded block. This is enforced by the consensus algorithm. -This implies a maximum transaction size that is this `MaxBytes`, less the expected size of +This implies a maximum transaction size that is `MaxBytes`, less the expected size of the header, the validator set, and any included evidence in the block. -Must have `0 < MaxBytes < 100 MB`. +The Application should be aware that honest validators _may_ produce and +broadcast blocks with up to the configured `MaxBytes` size. +As a result, the consensus +[timeout parameters](../../docs/core/configuration.md#consensus-timeouts-explained) +adopted by nodes should be configured so as to account for the worst-case +latency for the delivery of a full block with `MaxBytes` size to all validators. + +Must have `0 < MaxBytes <= 100 MB`. + +> Bear in mind that the default value for the `BlockParams.MaxBytes` consensus +> parameter accepts as valid blocks with size up to 21 MB. +> If the Application's use case does not need blocks of that size, +> or if the impact (specially on bandwidth consumption and block latency) +> of propagating blocks of that size was not evaluated, +> it is strongly recommended to wind down this default value. ##### BlockParams.MaxGas From fb5f1795d764f28b1be261ef7577ff3ab43ee5d3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:27:20 -0300 Subject: [PATCH 30/87] update language (backport #1263) (#1268) * update language (#1263) Using "a validator should" instead of 'we' (cherry picked from commit 3d1c36d1ecb27a8f9791fe661d4e2f15224a3796) # Conflicts: # docs/core/configuration.md * resolves the conflicts --------- Co-authored-by: Aliasgar Merchant <44069404+alijnmerchant21@users.noreply.github.com> Co-authored-by: lasaro --- docs/core/configuration.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/core/configuration.md b/docs/core/configuration.md index f38770c9907..cf88034d91c 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -521,15 +521,16 @@ timeout_commit = "1s" Note that in a successful round, the only timeout that we absolutely wait no matter what is `timeout_commit`. Here's a brief summary of the timeouts: -- `timeout_propose` = how long we wait for a proposal block before prevoting nil -- `timeout_propose_delta` = how much `timeout_propose` increases with each round -- `timeout_prevote` = how long we wait after receiving +2/3 prevotes for + +- `timeout_propose` = how long a validator should wait for a proposal block before prevoting nil +- `timeout_propose_delta` = how much `timeout_propose` increases with each round +- `timeout_prevote` = how long a validator should wait after receiving +2/3 prevotes for anything (ie. not a single block or nil) - `timeout_prevote_delta` = how much the `timeout_prevote` increases with each round -- `timeout_precommit` = how long we wait after receiving +2/3 precommits for +- `timeout_precommit` = how long a validator should wait after receiving +2/3 precommits for anything (ie. not a single block or nil) - `timeout_precommit_delta` = how much the `timeout_precommit` increases with each round -- `timeout_commit` = how long we wait after committing a block, before starting +- `timeout_commit` = how long a validator should wait after committing a block, before starting on the new height (this gives us a chance to receive some more precommits, even though we already have +2/3) From 8b3bad5b2c2ebbeff356927f2e37c3d55bc15a60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:34:37 +0200 Subject: [PATCH 31/87] build(deps): Bump styfle/cancel-workflow-action from 0.11.0 to 0.12.0 (#1445) Bumps [styfle/cancel-workflow-action](https://github.com/styfle/cancel-workflow-action) from 0.11.0 to 0.12.0. - [Release notes](https://github.com/styfle/cancel-workflow-action/releases) - [Commits](https://github.com/styfle/cancel-workflow-action/compare/0.11.0...0.12.0) --- updated-dependencies: - dependency-name: styfle/cancel-workflow-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/janitor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/janitor.yml b/.github/workflows/janitor.yml index 22cba4a93af..9c28eb4fd37 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.11.0 + - uses: styfle/cancel-workflow-action@0.12.0 with: workflow_id: 1041851,1401230,2837803 access_token: ${{ github.token }} From fe17b835e69dd3b93a4e670bf3f9382cd09cc5de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:42:47 +0200 Subject: [PATCH 32/87] build(deps): Bump bufbuild/buf-setup-action from 1.26.1 to 1.27.0 (#1446) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.26.1 to 1.27.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.26.1...v1.27.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergio Mena --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 41074cca412..21371419c02 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.26.1 + - uses: bufbuild/buf-setup-action@v1.27.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 05b2347c762ead9465e18444953f7ae09f13e803 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 05:42:21 -0400 Subject: [PATCH 33/87] build(deps): Bump pillow from 9.3.0 to 10.0.1 in /scripts/qa/reporting (backport #1493) (#1495) * build(deps): Bump pillow from 9.3.0 to 10.0.1 in /scripts/qa/reporting (#1493) Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.3.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.3.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit c1abe91f1ba28cdacdb902b2f500512fbfe67b7a) # Conflicts: # scripts/qa/reporting/requirements.txt * Resolve conflicts Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Thane Thomson --- scripts/qa/reporting/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/qa/reporting/requirements.txt b/scripts/qa/reporting/requirements.txt index be1613b382e..335c842fdf1 100644 --- a/scripts/qa/reporting/requirements.txt +++ b/scripts/qa/reporting/requirements.txt @@ -5,7 +5,7 @@ kiwisolver==1.4.4 matplotlib==3.6.3 numpy==1.24.2 packaging==21.3 -Pillow==9.2.0 +Pillow==10.0.1 pyparsing==3.0.9 python-dateutil==2.8.2 six==1.16.0 From 522f666ba0f237b39869a0cba2095731a6607d0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 19:37:15 +0200 Subject: [PATCH 34/87] build(deps): Bump bufbuild/buf-setup-action from 1.27.0 to 1.27.1 (#1520) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.27.0 to 1.27.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.27.0...v1.27.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 21371419c02..a1cf1665c55 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.27.0 + - uses: bufbuild/buf-setup-action@v1.27.1 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From ca5cbfbec5ef569ea02874d75e6aab55e4bc6722 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Thu, 26 Oct 2023 11:53:42 +0200 Subject: [PATCH 35/87] state: node bootstrap in v0.37.x (#1514) * node/state:bootstrap state api (#1057) * add bootstrap state cmd * add a missing line * Initial API impl * Added error message for missing appHash * Added changelog, removed cli * Added PR number * Unified hex encoding with rest of the code * Applied PR review comments * Proper blockstore initialization in case of offline statesync * Reverted forcing blocksync, not needed for correct operation * Added changelog and comments * Removed printfs, added check for empty state store * Fixed linter * Apply minor suggestions from code review Co-authored-by: Thane Thomson * Moved the appHash check up * Apply minor suggestions from code review Co-authored-by: Sergio Mena * Apply suggestions from code review Co-authored-by: Sergio Mena * Fixed linter * Do not look for VE when starting up after offline statesync * Extracted check for offline statesync outside load commit * Reconstruct seen commit after offline statesync * Call reconstructSeenCommit from reconstructLastCommit * Reading offline statesync height only once and passing it as a parameter * Moved up option initialization to make sure offline statesync is enabled * Added error to panic message * Update consensus/state.go Co-authored-by: Sergio Mena * Apply suggestions from code review Co-authored-by: Sergio Mena * Adjusted new lines * Added unit test to test int conversion and fixed linter * Apply suggestions from code review Co-authored-by: Thane Thomson * Replaced closing ifs with defer, added errors to error messages * linter fix * Adapted bootstrap code to use proper genesis file functions * Reverted genesis doc changes * Moved deferred closing before checking for whether the store is empty * Moved deferred close before error check --------- Co-authored-by: HuangYi Co-authored-by: yihuang Co-authored-by: Thane Thomson Co-authored-by: Sergio Mena * Fixed cherry pick conflicts * Removed go API changes in state store * Remove breaking go API changes in the blocksync reactor * Removed breaking changelog and fixed mocks * [pair programming] Found a more readable way to add the methods needed for the store * Removed duplicated code from blocksync reactor * Apply suggestions from code review Co-authored-by: Sergio Mena * Removed comments * Removed unused variable from state --------- Co-authored-by: HuangYi Co-authored-by: yihuang Co-authored-by: Thane Thomson Co-authored-by: Sergio Mena --- .../features/1057-bootstrap-state-api.md | 2 + blocksync/reactor.go | 34 +++-- consensus/state.go | 12 +- node/node.go | 143 +++++++++++++++++- state/export_test.go | 8 + state/store.go | 65 ++++++++ state/store_test.go | 5 + store/store.go | 6 + 8 files changed, 256 insertions(+), 19 deletions(-) create mode 100644 .changelog/unreleased/features/1057-bootstrap-state-api.md diff --git a/.changelog/unreleased/features/1057-bootstrap-state-api.md b/.changelog/unreleased/features/1057-bootstrap-state-api.md new file mode 100644 index 00000000000..ff3dcb6820a --- /dev/null +++ b/.changelog/unreleased/features/1057-bootstrap-state-api.md @@ -0,0 +1,2 @@ +- `[node/state]` Add Go API to bootstrap block store and state store to a height + ([\#1057](https://github.com/tendermint/tendermint/pull/#1057)) (@yihuang) \ No newline at end of file diff --git a/blocksync/reactor.go b/blocksync/reactor.go index cd5a811688e..91477ceeb67 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -60,21 +60,29 @@ type Reactor struct { errorsCh <-chan peerError } -// NewReactor returns new reactor instance. -func NewReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, - blockSync bool) *Reactor { - - if state.LastBlockHeight != store.Height() { - panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, - store.Height())) +func NewReactorWithOfflineStateSync(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, + blockSync bool, offlineStateSyncHeight int64) *Reactor { + + storeHeight := store.Height() + if storeHeight == 0 { + // If state sync was performed offline and the stores were bootstrapped to height H + // the state store's lastHeight will be H while blockstore's Height and Base are still 0 + // 1. This scenario should not lead to a panic in this case, which is indicated by + // having a OfflineStateSyncHeight > 0 + // 2. We need to instruct the blocksync reactor to start fetching blocks from H+1 + // instead of 0. + storeHeight = offlineStateSyncHeight + } + if state.LastBlockHeight != storeHeight { + panic(fmt.Sprintf("state (%v) and store (%v) height mismatch, stores were left in an inconsistent state", state.LastBlockHeight, + storeHeight)) } - requestsCh := make(chan BlockRequest, maxTotalRequesters) const capacity = 1000 // must be bigger than peers count errorsCh := make(chan peerError, capacity) // so we don't block in #Receive#pool.AddBlock - startHeight := store.Height() + 1 + startHeight := storeHeight + 1 if startHeight == 1 { startHeight = state.InitialHeight } @@ -93,6 +101,14 @@ func NewReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockS return bcR } +// NewReactor returns new reactor instance. +func NewReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, + blockSync bool) *Reactor { + + return NewReactorWithOfflineStateSync(state, blockExec, store, blockSync, 0) + +} + // SetLogger implements service.Service by setting the logger on reactor and pool. func (bcR *Reactor) SetLogger(l log.Logger) { bcR.BaseService.Logger = l diff --git a/consensus/state.go b/consensus/state.go index e785a2e8837..6fb202af23c 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -171,7 +171,9 @@ func NewState( evsw: cmtevents.NewEventSwitch(), metrics: NopMetrics(), } - + for _, option := range options { + option(cs) + } // set function defaults (may be overwritten before calling Start) cs.decideProposal = cs.defaultDecideProposal cs.doPrevote = cs.defaultDoPrevote @@ -179,6 +181,11 @@ func NewState( // We have no votes, so reconstruct LastCommit from SeenCommit. if state.LastBlockHeight > 0 { + // In case of out of band performed statesync, the state store + // will have a state but no extended commit (as no block has been downloaded). + // If the height at which the vote extensions are enabled is lower + // than the height at which we statesync, consensus will panic because + // it will try to reconstruct the extended commit here. cs.reconstructLastCommit(state) } @@ -187,9 +194,6 @@ func NewState( // NOTE: we do not call scheduleRound0 yet, we do that upon Start() cs.BaseService = *service.NewBaseService(nil, "State", cs) - for _, option := range options { - option(cs) - } return cs } diff --git a/node/node.go b/node/node.go index 183fe223958..868bd8ea008 100644 --- a/node/node.go +++ b/node/node.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + "os" "strings" "time" @@ -21,12 +22,13 @@ import ( cs "github.com/cometbft/cometbft/consensus" "github.com/cometbft/cometbft/crypto" "github.com/cometbft/cometbft/evidence" + "github.com/cometbft/cometbft/light" cmtjson "github.com/cometbft/cometbft/libs/json" "github.com/cometbft/cometbft/libs/log" cmtpubsub "github.com/cometbft/cometbft/libs/pubsub" "github.com/cometbft/cometbft/libs/service" - "github.com/cometbft/cometbft/light" + mempl "github.com/cometbft/cometbft/mempool" mempoolv0 "github.com/cometbft/cometbft/mempool/v0" mempoolv1 "github.com/cometbft/cometbft/mempool/v1" //nolint:staticcheck // SA1019 Priority mempool deprecated but still supported in this release. @@ -188,6 +190,124 @@ func StateProvider(stateProvider statesync.StateProvider) Option { } } +// BootstrapState synchronizes the stores with the application after state sync +// has been performed offline. It is expected that the block store and state +// store are empty at the time the function is called. +// +// If the block store is not empty, the function returns an error. +func BootstrapState(ctx context.Context, config *cfg.Config, dbProvider DBProvider, height uint64, appHash []byte) (err error) { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + if ctx == nil { + ctx = context.Background() + } + + if config == nil { + logger.Info("no config provided, using default configuration") + config = cfg.DefaultConfig() + } + + if dbProvider == nil { + dbProvider = DefaultDBProvider + } + blockStore, stateDB, err := initDBs(config, dbProvider) + + defer func() { + if derr := blockStore.Close(); derr != nil { + logger.Error("Failed to close blockstore", "err", derr) + // Set the return value + err = derr + } + }() + + if err != nil { + return err + } + + if !blockStore.IsEmpty() { + return fmt.Errorf("blockstore not empty, trying to initialize non empty state") + } + + stateStore := sm.NewBootstrapStore(stateDB, sm.StoreOptions{ + DiscardABCIResponses: config.Storage.DiscardABCIResponses, + }) + + defer func() { + if derr := stateStore.Close(); derr != nil { + logger.Error("Failed to close statestore", "err", derr) + // Set the return value + err = derr + } + }() + state, err := stateStore.Load() + if err != nil { + return err + } + + if !state.IsEmpty() { + return fmt.Errorf("state not empty, trying to initialize non empty state") + } + + genState, _, err := LoadStateFromDBOrGenesisDocProvider(stateDB, DefaultGenesisDocProviderFunc(config)) + if err != nil { + return err + } + + stateProvider, err := statesync.NewLightClientStateProvider( + ctx, + genState.ChainID, genState.Version, genState.InitialHeight, + config.StateSync.RPCServers, light.TrustOptions{ + Period: config.StateSync.TrustPeriod, + Height: config.StateSync.TrustHeight, + Hash: config.StateSync.TrustHashBytes(), + }, logger.With("module", "light")) + if err != nil { + return fmt.Errorf("failed to set up light client state provider: %w", err) + } + + state, err = stateProvider.State(ctx, height) + if err != nil { + return err + } + if appHash == nil { + logger.Info("warning: cannot verify appHash. Verification will happen when node boots up!") + } else { + if !bytes.Equal(appHash, state.AppHash) { + if err := blockStore.Close(); err != nil { + logger.Error("failed to close blockstore: %w", err) + } + if err := stateStore.Close(); err != nil { + logger.Error("failed to close statestore: %w", err) + } + return fmt.Errorf("the app hash returned by the light client does not match the provided appHash, expected %X, got %X", state.AppHash, appHash) + } + } + + commit, err := stateProvider.Commit(ctx, height) + if err != nil { + return err + } + + if err = stateStore.Bootstrap(state); err != nil { + return err + } + + err = blockStore.SaveSeenCommit(state.LastBlockHeight, commit) + if err != nil { + return err + } + + // Once the stores are bootstrapped, we need to set the height at which the node has finished + // statesyncing. This will allow the blocksync reactor to fetch blocks at a proper height. + // In case this operation fails, it is equivalent to a failure in online state sync where the operator + // needs to manually delete the state and blockstores and rerun the bootstrapping process. + err = stateStore.SetOfflineStateSyncHeight(state.LastBlockHeight) + if err != nil { + return fmt.Errorf("failed to set synced height: %w", err) + } + + return err +} + //------------------------------------------------------------------------------ // Node is the highest level interface to a full CometBFT node. @@ -448,10 +568,11 @@ func createBlockchainReactor(config *cfg.Config, blockStore *store.BlockStore, blockSync bool, logger log.Logger, + offlineStateSyncHeight int64, ) (bcReactor p2p.Reactor, err error) { switch config.BlockSync.Version { case "v0": - bcReactor = bc.NewReactor(state.Copy(), blockExec, blockStore, blockSync) + bcReactor = bc.NewReactorWithOfflineStateSync(state.Copy(), blockExec, blockStore, blockSync, offlineStateSyncHeight) case "v1", "v2": return nil, fmt.Errorf("block sync version %s has been deprecated. Please use v0", config.BlockSync.Version) default: @@ -718,7 +839,6 @@ func NewNode(config *cfg.Config, metricsProvider, logger, options...) } -// NewNodeWithContext is cancellable version of NewNode. func NewNodeWithContext(ctx context.Context, config *cfg.Config, privValidator types.PrivValidator, @@ -730,12 +850,13 @@ func NewNodeWithContext(ctx context.Context, logger log.Logger, options ...Option, ) (*Node, error) { + blockStore, stateDB, err := initDBs(config, dbProvider) if err != nil { return nil, err } - stateStore := sm.NewStore(stateDB, sm.StoreOptions{ + stateStore := sm.NewBootstrapStore(stateDB, sm.StoreOptions{ DiscardABCIResponses: config.Storage.DiscardABCIResponses, }) @@ -830,9 +951,15 @@ func NewNodeWithContext(ctx context.Context, evidencePool, sm.BlockExecutorWithMetrics(smMetrics), ) - + offlineStateSyncHeight := int64(0) + if blockStore.Height() == 0 { + offlineStateSyncHeight, err = stateStore.GetOfflineStateSyncHeight() + if err != nil && err.Error() != "value empty" { + panic(fmt.Sprintf("failed to retrieve statesynced height from store %s; expected state store height to be %v", err, state.LastBlockHeight)) + } + } // Make BlockchainReactor. Don't start block sync if we're doing a state sync first. - bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, blockSync && !stateSync, logger) + bcReactor, err := createBlockchainReactor(config, state, blockExec, blockStore, blockSync && !stateSync, logger, offlineStateSyncHeight) if err != nil { return nil, fmt.Errorf("could not create blockchain reactor: %w", err) } @@ -848,6 +975,10 @@ func NewNodeWithContext(ctx context.Context, config, state, blockExec, blockStore, mempool, evidencePool, privValidator, csMetrics, stateSync || blockSync, eventBus, consensusLogger, ) + err = stateStore.SetOfflineStateSyncHeight(0) + if err != nil { + panic(fmt.Sprintf("failed to reset the offline state sync height %s", err)) + } // Set up state sync reactor, and schedule a sync if requested. // FIXME The way we do phased startups (e.g. replay -> block sync -> consensus) is very messy, diff --git a/state/export_test.go b/state/export_test.go index a15414c926d..941cf32f3b6 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -45,3 +45,11 @@ func SaveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *type stateStore := dbStore{db, StoreOptions{DiscardABCIResponses: false}} return stateStore.saveValidatorsInfo(height, lastHeightChanged, valSet) } + +func Int64ToBytes(val int64) []byte { + return int64ToBytes(val) +} + +func Int64FromBytes(val []byte) int64 { + return int64FromBytes(val) +} diff --git a/state/store.go b/state/store.go index 61bdc160dac..3df10f13ef1 100644 --- a/state/store.go +++ b/state/store.go @@ -1,6 +1,7 @@ package state import ( + "encoding/binary" "errors" "fmt" @@ -41,6 +42,7 @@ func calcABCIResponsesKey(height int64) []byte { //---------------------- var lastABCIResponseKey = []byte("lastABCIResponseKey") +var offlineStateSyncHeight = []byte("offlineStateSyncHeightKey") //go:generate ../scripts/mockery_generate.sh Store @@ -94,6 +96,14 @@ type StoreOptions struct { var _ Store = (*dbStore)(nil) +func IsEmpty(store dbStore) (bool, error) { + state, err := store.Load() + if err != nil { + return false, err + } + return state.IsEmpty(), nil +} + // NewStore creates the dbStore of the state pkg. func NewStore(db dbm.DB, options StoreOptions) Store { return dbStore{db, options} @@ -658,6 +668,61 @@ func (store dbStore) saveConsensusParamsInfo(nextHeight, changeHeight int64, par return nil } +/* Custom struct to handle offline state sync + Contains reference to a store so that it can access the DB +*/ + +type BootstrapStore struct { + dbStore +} + +func NewBootstrapStore(db dbm.DB, options StoreOptions) BootstrapStore { + return BootstrapStore{ + dbStore{ + db: db, + StoreOptions: options, + }, + } +} + +func (store BootstrapStore) SetOfflineStateSyncHeight(height int64) error { + err := store.db.SetSync(offlineStateSyncHeight, int64ToBytes(height)) + if err != nil { + return err + } + return nil + +} + +// Gets the height at which the store is bootstrapped after out of band statesync +func (store BootstrapStore) GetOfflineStateSyncHeight() (int64, error) { + + buf, err := store.db.Get(offlineStateSyncHeight) + if err != nil { + return 0, err + } + + if len(buf) == 0 { + return 0, errors.New("value empty") + } + + height := int64FromBytes(buf) + if height < 0 { + return 0, errors.New("invalid value for height: height cannot be negative") + } + return height, nil +} + func (store dbStore) Close() error { return store.db.Close() } +func int64FromBytes(bz []byte) int64 { + v, _ := binary.Varint(bz) + return v +} + +func int64ToBytes(i int64) []byte { + buf := make([]byte, binary.MaxVarintLen64) + n := binary.PutVarint(buf, i) + return buf[:n] +} diff --git a/state/store_test.go b/state/store_test.go index 1c4fcbc7ce6..72ca2c038c5 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -303,3 +303,8 @@ func TestLastABCIResponses(t *testing.T) { assert.Equal(t, sm.ErrABCIResponsesNotPersisted, err) }) } +func TestIntConversion(t *testing.T) { + x := int64(10) + b := sm.Int64ToBytes(x) + require.Equal(t, x, sm.Int64FromBytes(b)) +} diff --git a/store/store.go b/store/store.go index 125e11389e2..19583496d0c 100644 --- a/store/store.go +++ b/store/store.go @@ -54,6 +54,12 @@ func NewBlockStore(db dbm.DB) *BlockStore { } } +func (bs *BlockStore) IsEmpty() bool { + bs.mtx.RLock() + defer bs.mtx.RUnlock() + return bs.base == bs.height && bs.base == 0 +} + // Base returns the first known contiguous block height, or 0 for empty block stores. func (bs *BlockStore) Base() int64 { bs.mtx.RLock() From 60ee8cab0d259b174c54d5a5fe9232d68e49a8b1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:50:35 +0200 Subject: [PATCH 36/87] indexer-respect-height-params-on-query (backport #1529) (#1543) * indexer-respect-height-params-on-query (#1529) * indexer-respect-height-params-on-query * Update .changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md Co-authored-by: Jasmina Malicevic --------- Co-authored-by: Jasmina Malicevic (cherry picked from commit 95aca040fbef5842a083b956e3bd6c297edca20d) # Conflicts: # state/indexer/query_range.go # state/txindex/kv/kv_test.go * Resolved conflicts --------- Co-authored-by: Kamen Stoykov <24619432+kstoykov@users.noreply.github.com> Co-authored-by: Jasmina Malicevic --- ...-indexer-respect-height-params-on-query.md | 2 + state/indexer/query_range.go | 3 +- state/txindex/kv/kv_test.go | 83 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 .changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md diff --git a/.changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md b/.changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md new file mode 100644 index 00000000000..d12f3eda536 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md @@ -0,0 +1,2 @@ +- `[state/indexer]` Respect both height params while querying for events + ([\#1529](https://github.com/cometbft/cometbft/pull/1529)) diff --git a/state/indexer/query_range.go b/state/indexer/query_range.go index b4af7c71698..15e523c2a59 100644 --- a/state/indexer/query_range.go +++ b/state/indexer/query_range.go @@ -87,14 +87,13 @@ func (qr QueryRange) UpperBoundValue() interface{} { func LookForRangesWithHeight(conditions []query.Condition) (queryRange QueryRanges, indexes []int, heightRange QueryRange) { queryRange = make(QueryRanges) for i, c := range conditions { - heightKey := false if IsRangeOperation(c.Op) { + heightKey := c.CompositeKey == types.BlockHeightKey || c.CompositeKey == types.TxHeightKey r, ok := queryRange[c.CompositeKey] if !ok { r = QueryRange{Key: c.CompositeKey} if c.CompositeKey == types.BlockHeightKey || c.CompositeKey == types.TxHeightKey { heightRange = QueryRange{Key: c.CompositeKey} - heightKey = true } } diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 34ae6b11c86..e8246ece04c 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -322,6 +322,89 @@ func TestTxSearchEventMatch(t *testing.T) { }) } } + +func TestTxSearchEventMatchByHeight(t *testing.T) { + + indexer := NewTxIndex(db.NewMemDB()) + + txResult := txResultWithEvents([]abci.Event{ + {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "Ana", Index: true}}}, + }) + + err := indexer.Index(txResult) + require.NoError(t, err) + + txResult10 := txResultWithEvents([]abci.Event{ + {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}, {Key: "owner", Value: "/Ivan/.test", Index: true}}}, + }) + txResult10.Tx = types.Tx("HELLO WORLD 10") + txResult10.Height = 10 + + err = indexer.Index(txResult10) + require.NoError(t, err) + + testCases := map[string]struct { + q string + resultsLength int + }{ + "Return all events from a height 1": { + q: "tx.height = 1", + resultsLength: 1, + }, + "Return all events from a height 10": { + q: "tx.height = 10", + resultsLength: 1, + }, + "Return all events from a height 5": { + q: "tx.height = 5", + resultsLength: 0, + }, + "Return all events from a height in [2; 5]": { + q: "tx.height >= 2 AND tx.height <= 5", + resultsLength: 0, + }, + "Return all events from a height in [1; 5]": { + q: "tx.height >= 1 AND tx.height <= 5", + resultsLength: 1, + }, + "Return all events from a height in [1; 10]": { + q: "tx.height >= 1 AND tx.height <= 10", + resultsLength: 2, + }, + "Return all events from a height in [1; 5] by account.number": { + q: "tx.height >= 1 AND tx.height <= 5 AND account.number=1", + resultsLength: 1, + }, + "Return all events from a height in [1; 10] by account.number 2": { + q: "tx.height >= 1 AND tx.height <= 10 AND account.number=1", + resultsLength: 2, + }, + } + + ctx := context.Background() + + for _, tc := range testCases { + tc := tc + t.Run(tc.q, func(t *testing.T) { + results, err := indexer.Search(ctx, query.MustParse(tc.q)) + assert.NoError(t, err) + + assert.Len(t, results, tc.resultsLength) + if tc.resultsLength > 0 { + for _, txr := range results { + if txr.Height == 1 { + assert.True(t, proto.Equal(txResult, txr)) + } else if txr.Height == 10 { + assert.True(t, proto.Equal(txResult10, txr)) + } else { + assert.True(t, false) + } + } + } + }) + } +} + func TestTxSearchWithCancelation(t *testing.T) { indexer := NewTxIndex(db.NewMemDB()) From f565f92a836459fc3d4ae1a0990b6d99f196cb3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:46:20 -0400 Subject: [PATCH 37/87] build(deps): Bump bufbuild/buf-setup-action from 1.27.1 to 1.27.2 (#1549) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.27.1 to 1.27.2. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.27.1...v1.27.2) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index a1cf1665c55..b98a7f1d65b 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.27.1 + - uses: bufbuild/buf-setup-action@v1.27.2 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 36c3976fc8ef98eb1ce8d79bbbca1a276d4c8d57 Mon Sep 17 00:00:00 2001 From: lasaro Date: Fri, 10 Nov 2023 17:35:07 -0300 Subject: [PATCH 38/87] Updates grpc and net dependencies to avoid https://pkg.go.dev/vuln/GO-2023-2153 and https://pkg.go.dev/vuln/GO-2023-2102 (#1590) --- go.mod | 27 +++++++++++++------------ go.sum | 62 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 43e7f2a85b8..3d2c83b02f0 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/go-kit/log v0.2.1 github.com/go-logfmt/logfmt v0.5.1 github.com/gofrs/uuid v4.3.0+incompatible - github.com/golang/protobuf v1.5.2 github.com/golangci/golangci-lint v1.50.1 github.com/google/orderedcode v0.0.1 github.com/google/uuid v1.3.0 @@ -34,9 +33,9 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.5.0 - golang.org/x/net v0.7.0 - google.golang.org/grpc v1.52.0 + golang.org/x/crypto v0.14.0 + golang.org/x/net v0.17.0 + google.golang.org/grpc v1.58.3 ) require ( @@ -52,9 +51,10 @@ require ( github.com/cometbft/cometbft-db v0.7.0 github.com/cosmos/gogoproto v1.4.1 github.com/go-git/go-git/v5 v5.5.2 + github.com/golang/protobuf v1.5.3 github.com/vektra/mockery/v2 v2.14.0 gonum.org/v1/gonum v0.8.2 - google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 + google.golang.org/protobuf v1.31.0 ) require ( @@ -85,7 +85,7 @@ require ( github.com/butuzov/ireturn v0.1.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 // indirect github.com/cloudflare/circl v1.1.0 // indirect @@ -277,13 +277,14 @@ require ( go.uber.org/zap v1.23.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.2.0 // indirect - google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 2c933987731..2acf5779204 100644 --- a/go.sum +++ b/go.sum @@ -19,15 +19,15 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -153,8 +153,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.9 h1:mPP4ucLrf/rKZiIG/a9IPXHGlh8p4CzgpyTy6EEutYk= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 h1:E7LT642ysztPWE0dfz43cWOvMiF42DyTRC+eZIaO4yI= @@ -170,6 +171,7 @@ github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtM github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -242,6 +244,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y 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/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= @@ -371,8 +374,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -939,8 +943,8 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -987,8 +991,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1036,8 +1040,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1049,7 +1053,7 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1062,8 +1066,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1147,14 +1151,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1164,8 +1168,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1254,8 +1258,8 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1330,8 +1334,10 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1349,8 +1355,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 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= @@ -1365,8 +1371,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 46951c98fee9c9ed18cfbdca41e6b111661ee5fd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 19:22:36 -0300 Subject: [PATCH 39/87] mempool: Add metric size of pool in bytes (backport #1512) (#1567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mempool: Add metric size of pool in bytes (#1512) * mempool: Add metric `SizeBytes` * Safe concurrent read of txsBytes * Add changelog (cherry picked from commit b50bca37ca0335a89ea66bde51d7e0c375602929) # Conflicts: # mempool/metrics.gen.go * Solving conflict * Updating grpc and net modules * Revert "Updating grpc and net modules" This reverts commit 3a70bfb15ac112ce1dff99efdfe07a125425b2b5. --------- Co-authored-by: Hernán Vanzetto <15466498+hvanz@users.noreply.github.com> Co-authored-by: lasarojc --- .../unreleased/features/1512-metric-mempool-size-bytes.md | 2 ++ mempool/metrics.gen.go | 7 +++++++ mempool/metrics.go | 3 +++ mempool/v0/clist_mempool.go | 2 ++ 4 files changed, 14 insertions(+) create mode 100644 .changelog/unreleased/features/1512-metric-mempool-size-bytes.md diff --git a/.changelog/unreleased/features/1512-metric-mempool-size-bytes.md b/.changelog/unreleased/features/1512-metric-mempool-size-bytes.md new file mode 100644 index 00000000000..b935dc40842 --- /dev/null +++ b/.changelog/unreleased/features/1512-metric-mempool-size-bytes.md @@ -0,0 +1,2 @@ +- `[metrics]` Add metric for mempool size in bytes `SizeBytes`. + ([\#1512](https://github.com/cometbft/cometbft/pull/1512)) \ No newline at end of file diff --git a/mempool/metrics.gen.go b/mempool/metrics.gen.go index 100c5e71cb6..83bf90fabe0 100644 --- a/mempool/metrics.gen.go +++ b/mempool/metrics.gen.go @@ -20,6 +20,12 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "size", Help: "Number of uncommitted transactions in the mempool.", }, labels).With(labelsAndValues...), + SizeBytes: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "size_bytes", + Help: "Total size of the mempool in bytes.", + }, labels).With(labelsAndValues...), TxSizeBytes: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, @@ -58,6 +64,7 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { func NopMetrics() *Metrics { return &Metrics{ Size: discard.NewGauge(), + SizeBytes: discard.NewGauge(), TxSizeBytes: discard.NewHistogram(), FailedTxs: discard.NewCounter(), RejectedTxs: discard.NewCounter(), diff --git a/mempool/metrics.go b/mempool/metrics.go index 85ca8c0cfbd..6943493fd95 100644 --- a/mempool/metrics.go +++ b/mempool/metrics.go @@ -18,6 +18,9 @@ type Metrics struct { // Number of uncommitted transactions in the mempool. Size metrics.Gauge + // Total size of the mempool in bytes. + SizeBytes metrics.Gauge + // Histogram of transaction sizes in bytes. TxSizeBytes metrics.Histogram `metrics_buckettype:"exp" metrics_bucketsizes:"1,3,7"` diff --git a/mempool/v0/clist_mempool.go b/mempool/v0/clist_mempool.go index 2f4a0264bef..e5d65637107 100644 --- a/mempool/v0/clist_mempool.go +++ b/mempool/v0/clist_mempool.go @@ -303,6 +303,7 @@ func (mem *CListMempool) reqResCb( // update metrics mem.metrics.Size.Set(float64(mem.Size())) + mem.metrics.SizeBytes.Set(float64(mem.SizeBytes())) // passed in by the caller of CheckTx, eg. the RPC if externalCb != nil { @@ -648,6 +649,7 @@ func (mem *CListMempool) Update( // Update metrics mem.metrics.Size.Set(float64(mem.Size())) + mem.metrics.SizeBytes.Set(float64(mem.SizeBytes())) return nil } From 5b1e711593387caa2a4b6084ee1ab2f43cd24dcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:06:11 +0200 Subject: [PATCH 40/87] build(deps): Bump bufbuild/buf-setup-action from 1.27.2 to 1.28.0 (#1601) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.27.2 to 1.28.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.27.2...v1.28.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index b98a7f1d65b..41509e9974f 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.27.2 + - uses: bufbuild/buf-setup-action@v1.28.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 47ffffadffe42cf0d6ac6e07ab84b31336b37a42 Mon Sep 17 00:00:00 2001 From: lasaro Date: Wed, 15 Nov 2023 12:20:14 -0300 Subject: [PATCH 41/87] Backports #1558 and #1584 to 0.38.x (#1592) (#1611) * Experimental - Reduce # of connections effectively used to gossip transactions out (#1558) * maxpeers for mempool * mempool: fix max_peers bcast routine active flag * Use semaphore to limit concurrency * Rename MaxPeers to MaxOutboundPeers * Add max_outbound_peers to config toml template * Rename in error message * Renams the parameter to highlight its experimental nature. Extend the AddPeer method to return an error. Moves the semaphone to outside the broadcast routine * reverting the addition of error to AddPeer. It fails if the context is done and handling this case will be done some other time, when an actual context is passed into acquire. * reverting the addition of error to AddPeer. It fails if the context is done and handling this case will be done some other time, when an actual context is passed into acquire. * Fixing lint issue * renaming semaphore to something more meaningful * make default value 0, which is the same as the current behavior. 10 is the recommended value. * adding new flag to manifest.go * Adding changelog * Improve the description of the parameter in the generated config file. * Add metric to track the current number of active connections. * Change metric to gauge type and rename it. * e2e: Allow disabling the PEX reactor on all nodes in the testnet * Apply suggestions from code review * Update config/config.go comment * fix lint error * Improve config description * Rename metric (remove experimental prefix) * Add unit test * Improve unit test * Update mempool/reactor.go comment --------- * Updating test file, leaving it broken for now * mempool: Limit gossip connections to persistent and non-persistent peers (experimental) (#1584) * Ignore persistent peers from limiting of outbound connections * Update 1558-experimental-gossip-limiting.md Update changeling * Fix typo in mempool/metrics.go * Use two independent configs and semaphores for persistent and non-persistent peers * Forgot to rename in test * Update metric description * Rename semaphores * Add comment to unit test --------- * Reverting to old way of reporting errors * Reverting change that shouldn't have been included in cherry-pick * Reverting tests to use older functions * fix rebase merge --------- Co-authored-by: Adi Seredinschi Co-authored-by: Ethan Buchman Co-authored-by: Daniel Cason Co-authored-by: hvanz Co-authored-by: Andy Nogueira Co-authored-by: Sergio Mena --- .../1558-experimental-gossip-limiting.md | 9 ++++ abci/example/kvstore/helpers.go | 27 ++++++++++ config/config.go | 29 ++++++++-- config/toml.go | 14 +++++ mempool/metrics.gen.go | 21 +++++--- mempool/metrics.go | 4 ++ mempool/v0/clist_mempool_test.go | 22 +++++--- mempool/v0/reactor.go | 44 ++++++++++++++- mempool/v0/reactor_test.go | 53 +++++++++++++++++++ test/e2e/pkg/manifest.go | 4 ++ test/e2e/pkg/testnet.go | 44 ++++++++------- test/e2e/runner/setup.go | 2 + 12 files changed, 235 insertions(+), 38 deletions(-) create mode 100644 .changelog/unreleased/improvements/1558-experimental-gossip-limiting.md diff --git a/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md b/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md new file mode 100644 index 00000000000..6931cef8274 --- /dev/null +++ b/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md @@ -0,0 +1,9 @@ +- `[mempool]` Add experimental feature to limit the number of persistent peers and non-persistent + peers to which the node gossip transactions. + ([\#1558](https://github.com/cometbft/cometbft/pull/1558)) + ([\#1584](https://github.com/cometbft/cometbft/pull/1584)) +- `[config]` Add mempool parameters `experimental_max_gossip_connections_to_persistent_peers` and + `experimental_max_gossip_connections_to_non_persistent_peers` for limiting the number of peers to + which the node gossip transactions. + ([\#1558](https://github.com/cometbft/cometbft/pull/1558)) + ([\#1584](https://github.com/cometbft/cometbft/pull/1584)) diff --git a/abci/example/kvstore/helpers.go b/abci/example/kvstore/helpers.go index 3508495b6b5..373a0dedc27 100644 --- a/abci/example/kvstore/helpers.go +++ b/abci/example/kvstore/helpers.go @@ -1,6 +1,9 @@ package kvstore import ( + "fmt" + "strings" + "github.com/cometbft/cometbft/abci/types" cmtrand "github.com/cometbft/cometbft/libs/rand" ) @@ -34,3 +37,27 @@ func InitKVStore(app *PersistentKVStoreApplication) { Validators: RandVals(1), }) } + +// Create a new transaction +func NewTx(key, value string) []byte { + return []byte(strings.Join([]string{key, value}, "=")) +} + +func NewRandomTx(size int) []byte { + if size < 4 { + panic("random tx size must be greater than 3") + } + return NewTx(cmtrand.Str(2), cmtrand.Str(size-3)) +} + +func NewRandomTxs(n int) [][]byte { + txs := make([][]byte, n) + for i := 0; i < n; i++ { + txs[i] = NewRandomTx(10) + } + return txs +} + +func NewTxFromID(i int) []byte { + return []byte(fmt.Sprintf("%d=%d", i, i)) +} diff --git a/config/config.go b/config/config.go index 7af22c83a16..9ab361bc39a 100644 --- a/config/config.go +++ b/config/config.go @@ -761,6 +761,19 @@ type MempoolConfig struct { // Including space needed by encoding (one varint per transaction). // XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 MaxBatchBytes int `mapstructure:"max_batch_bytes"` + // Experimental parameters to limit gossiping txs to up to the specified number of peers. + // We use two independent upper values for persistent peers and for non-persistent peers. + // Unconditional peers are not affected by this feature. + // If we are connected to more than the specified number of persistent peers, only send txs to + // the first ExperimentalMaxGossipConnectionsToPersistentPeers of them. If one of those + // persistent peers disconnects, activate another persistent peer. Similarly for non-persistent + // peers, with an upper limit of ExperimentalMaxGossipConnectionsToNonPersistentPeers. + // If set to 0, the feature is disabled for the corresponding group of peers, that is, the + // number of active connections to that group of peers is not bounded. + // For non-persistent peers, if enabled, a value of 10 is recommended based on experimental + // performance results using the default P2P configuration. + ExperimentalMaxGossipConnectionsToPersistentPeers int `mapstructure:"experimental_max_gossip_connections_to_persistent_peers"` + ExperimentalMaxGossipConnectionsToNonPersistentPeers int `mapstructure:"experimental_max_gossip_connections_to_non_persistent_peers"` // TTLDuration, if non-zero, defines the maximum amount of time a transaction // can exist for in the mempool. @@ -794,10 +807,12 @@ func DefaultMempoolConfig() *MempoolConfig { WalPath: "", // Each signature verification takes .5ms, Size reduced until we implement // ABCI Recheck - Size: 5000, - MaxTxsBytes: 1024 * 1024 * 1024, // 1GB - CacheSize: 10000, - MaxTxBytes: 1024 * 1024, // 1MB + Size: 5000, + MaxTxsBytes: 1024 * 1024 * 1024, // 1GB + CacheSize: 10000, + MaxTxBytes: 1024 * 1024, // 1MB + ExperimentalMaxGossipConnectionsToNonPersistentPeers: 0, + ExperimentalMaxGossipConnectionsToPersistentPeers: 0, TTLDuration: 0 * time.Second, TTLNumBlocks: 0, } @@ -835,6 +850,12 @@ func (cfg *MempoolConfig) ValidateBasic() error { if cfg.MaxTxBytes < 0 { return errors.New("max_tx_bytes can't be negative") } + if cfg.ExperimentalMaxGossipConnectionsToPersistentPeers < 0 { + return errors.New("experimental_max_gossip_connections_to_persistent_peers can't be negative") + } + if cfg.ExperimentalMaxGossipConnectionsToNonPersistentPeers < 0 { + return errors.New("experimental_max_gossip_connections_to_non_persistent_peers can't be negative") + } return nil } diff --git a/config/toml.go b/config/toml.go index 4ff8eecca2a..2cf16f5917e 100644 --- a/config/toml.go +++ b/config/toml.go @@ -390,6 +390,20 @@ ttl-duration = "{{ .Mempool.TTLDuration }}" # it's insertion time into the mempool is beyond ttl-duration. ttl-num-blocks = {{ .Mempool.TTLNumBlocks }} +# Experimental parameters to limit gossiping txs to up to the specified number of peers. +# We use two independent upper values for persistent peers and for non-persistent peers. +# Unconditional peers are not affected by this feature. +# If we are connected to more than the specified number of persistent peers, only send txs to +# the first experimental_max_gossip_connections_to_persistent_peers of them. If one of those +# persistent peers disconnects, activate another persistent peer. Similarly for non-persistent +# peers, with an upper limit of experimental_max_gossip_connections_to_non_persistent_peers. +# If set to 0, the feature is disabled for the corresponding group of peers, that is, the +# number of active connections to that group of peers is not bounded. +# For non-persistent peers, if enabled, a value of 10 is recommended based on experimental +# performance results using the default P2P configuration. +experimental_max_gossip_connections_to_persistent_peers = {{ .Mempool.ExperimentalMaxGossipConnectionsToPersistentPeers }} +experimental_max_gossip_connections_to_non_persistent_peers = {{ .Mempool.ExperimentalMaxGossipConnectionsToNonPersistentPeers }} + ####################################################### ### State Sync Configuration Options ### ####################################################### diff --git a/mempool/metrics.gen.go b/mempool/metrics.gen.go index 83bf90fabe0..cd41c2ebc42 100644 --- a/mempool/metrics.gen.go +++ b/mempool/metrics.gen.go @@ -58,17 +58,24 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "recheck_times", Help: "Number of times transactions are rechecked in the mempool.", }, labels).With(labelsAndValues...), + ActiveOutboundConnections: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "active_outbound_connections", + Help: "Number of connections being actively used for gossiping transactions (experimental feature).", + }, labels).With(labelsAndValues...), } } func NopMetrics() *Metrics { return &Metrics{ - Size: discard.NewGauge(), - SizeBytes: discard.NewGauge(), - TxSizeBytes: discard.NewHistogram(), - FailedTxs: discard.NewCounter(), - RejectedTxs: discard.NewCounter(), - EvictedTxs: discard.NewCounter(), - RecheckTimes: discard.NewCounter(), + Size: discard.NewGauge(), + SizeBytes: discard.NewGauge(), + TxSizeBytes: discard.NewHistogram(), + FailedTxs: discard.NewCounter(), + RejectedTxs: discard.NewCounter(), + EvictedTxs: discard.NewCounter(), + RecheckTimes: discard.NewCounter(), + ActiveOutboundConnections: discard.NewGauge(), } } diff --git a/mempool/metrics.go b/mempool/metrics.go index 6943493fd95..689d6f496dd 100644 --- a/mempool/metrics.go +++ b/mempool/metrics.go @@ -43,4 +43,8 @@ type Metrics struct { // Number of times transactions are rechecked in the mempool. RecheckTimes metrics.Counter + + // Number of connections being actively used for gossiping transactions + // (experimental feature). + ActiveOutboundConnections metrics.Gauge } diff --git a/mempool/v0/clist_mempool_test.go b/mempool/v0/clist_mempool_test.go index 35fc0663171..37c8b42886b 100644 --- a/mempool/v0/clist_mempool_test.go +++ b/mempool/v0/clist_mempool_test.go @@ -1,7 +1,6 @@ package v0 import ( - "crypto/rand" "encoding/binary" "fmt" mrand "math/rand" @@ -96,16 +95,27 @@ func ensureFire(t *testing.T, ch <-chan struct{}, timeoutMS int) { } } +func callCheckTx(t *testing.T, mp mempool.Mempool, txs types.Txs) { + txInfo := mempool.TxInfo{SenderID: 0} + for i, tx := range txs { + if err := mp.CheckTx(tx, nil, txInfo); err != nil { + // Skip invalid txs. + // TestMempoolFilters will fail otherwise. It asserts a number of txs + // returned. + if mempool.IsPreCheckError(err) { + continue + } + t.Fatalf("CheckTx failed: %v while checking #%d tx", err, i) + } + } +} + func checkTxs(t *testing.T, mp mempool.Mempool, count int, peerID uint16) types.Txs { txs := make(types.Txs, count) txInfo := mempool.TxInfo{SenderID: peerID} for i := 0; i < count; i++ { - txBytes := make([]byte, 20) + txBytes := kvstore.NewRandomTx(20) txs[i] = txBytes - _, err := rand.Read(txBytes) - if err != nil { - t.Error(err) - } if err := mp.CheckTx(txBytes, nil, txInfo); err != nil { // Skip invalid txs. // TestMempoolFilters will fail otherwise. It asserts a number of txs diff --git a/mempool/v0/reactor.go b/mempool/v0/reactor.go index 508d76666f8..b4169d5cf9a 100644 --- a/mempool/v0/reactor.go +++ b/mempool/v0/reactor.go @@ -1,6 +1,7 @@ package v0 import ( + "context" "errors" "fmt" "time" @@ -13,6 +14,7 @@ import ( "github.com/cometbft/cometbft/p2p" protomem "github.com/cometbft/cometbft/proto/tendermint/mempool" "github.com/cometbft/cometbft/types" + "golang.org/x/sync/semaphore" ) // Reactor handles mempool tx broadcasting amongst peers. @@ -23,6 +25,12 @@ type Reactor struct { config *cfg.MempoolConfig mempool *CListMempool ids *mempoolIDs + + // Semaphores to keep track of how many connections to peers are active for broadcasting + // transactions. Each semaphore has a capacity that puts an upper bound on the number of + // connections for different groups of peers. + activePersistentPeersSemaphore *semaphore.Weighted + activeNonPersistentPeersSemaphore *semaphore.Weighted } type mempoolIDs struct { @@ -96,6 +104,9 @@ func NewReactor(config *cfg.MempoolConfig, mempool *CListMempool) *Reactor { ids: newMempoolIDs(), } memR.BaseReactor = *p2p.NewBaseReactor("Mempool", memR) + memR.activePersistentPeersSemaphore = semaphore.NewWeighted(int64(memR.config.ExperimentalMaxGossipConnectionsToPersistentPeers)) + memR.activeNonPersistentPeersSemaphore = semaphore.NewWeighted(int64(memR.config.ExperimentalMaxGossipConnectionsToNonPersistentPeers)) + return memR } @@ -143,7 +154,37 @@ func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor { // It starts a broadcast routine ensuring all txs are forwarded to the given peer. func (memR *Reactor) AddPeer(peer p2p.Peer) { if memR.config.Broadcast { - go memR.broadcastTxRoutine(peer) + go func() { + // Always forward transactions to unconditional peers. + if !memR.Switch.IsPeerUnconditional(peer.ID()) { + if peer.IsPersistent() && memR.config.ExperimentalMaxGossipConnectionsToPersistentPeers > 0 { + // Block sending transactions to peer until one of the connections become + // available in the semaphore. + if err := memR.activePersistentPeersSemaphore.Acquire(context.TODO(), 1); err != nil { + memR.Logger.Error("Failed to acquire semaphore: %v", err) + return + } + // Release semaphore to allow other peer to start sending transactions. + defer memR.activePersistentPeersSemaphore.Release(1) + defer memR.mempool.metrics.ActiveOutboundConnections.Add(-1) + } + + if !peer.IsPersistent() && memR.config.ExperimentalMaxGossipConnectionsToNonPersistentPeers > 0 { + // Block sending transactions to peer until one of the connections become + // available in the semaphore. + if err := memR.activeNonPersistentPeersSemaphore.Acquire(context.TODO(), 1); err != nil { + memR.Logger.Error("Failed to acquire semaphore: %v", err) + return + } + // Release semaphore to allow other peer to start sending transactions. + defer memR.activeNonPersistentPeersSemaphore.Release(1) + defer memR.mempool.metrics.ActiveOutboundConnections.Add(-1) + } + } + + memR.mempool.metrics.ActiveOutboundConnections.Add(1) + memR.broadcastTxRoutine(peer) + }() } } @@ -203,6 +244,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) { if !memR.IsRunning() || !peer.IsRunning() { return } + // This happens because the CElement we were looking at got garbage // collected (removed). That is, .NextWait() returned nil. Go ahead and // start from the beginning. diff --git a/mempool/v0/reactor_test.go b/mempool/v0/reactor_test.go index aa0de104618..4c0e516e771 100644 --- a/mempool/v0/reactor_test.go +++ b/mempool/v0/reactor_test.go @@ -293,6 +293,51 @@ func TestDontExhaustMaxActiveIDs(t *testing.T) { } } +// Test the experimental feature that limits the number of outgoing connections for gossiping +// transactions (only non-persistent peers). +// Note: in this test we know which gossip connections are active or not because of how the p2p +// functions are currently implemented, which affects the order in which peers are added to the +// mempool reactor. +func TestMempoolReactorMaxActiveOutboundConnections(t *testing.T) { + config := cfg.TestConfig() + config.Mempool.ExperimentalMaxGossipConnectionsToNonPersistentPeers = 1 + reactors := makeAndConnectReactors(config, 4) + defer func() { + for _, r := range reactors { + if err := r.Stop(); err != nil { + assert.NoError(t, err) + } + } + }() + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + peer.Set(types.PeerStateKey, peerState{1}) + } + } + + // Add a bunch transactions to the first reactor. + txs := newUniqueTxs(100) + callCheckTx(t, reactors[0].mempool, txs) + + // Wait for all txs to be in the mempool of the second reactor; the other reactors should not + // receive any tx. (The second reactor only sends transactions to the first reactor.) + waitForTxsOnReactor(t, txs, reactors[1], 0) + for _, r := range reactors[2:] { + require.Zero(t, r.mempool.Size()) + } + + // Disconnect the second reactor from the first reactor. + firstPeer := reactors[0].Switch.Peers().List()[0] + reactors[0].Switch.StopPeerGracefully(firstPeer) + + // Now the third reactor should start receiving transactions from the first reactor; the fourth + // reactor's mempool should still be empty. + waitForTxsOnReactor(t, txs, reactors[2], 0) + for _, r := range reactors[3:] { + require.Zero(t, r.mempool.Size()) + } +} + // mempoolLogger is a TestingLogger which uses a different // color for each validator ("validator" key must exist). func mempoolLogger() log.Logger { @@ -328,6 +373,14 @@ func makeAndConnectReactors(config *cfg.Config, n int) []*Reactor { return reactors } +func newUniqueTxs(n int) types.Txs { + txs := make(types.Txs, n) + for i := 0; i < n; i++ { + txs[i] = kvstore.NewTxFromID(i) + } + return txs +} + func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { // wait for the txs in all mempools wg := new(sync.WaitGroup) diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 364a836e1c4..55b8e23ed4f 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -80,6 +80,10 @@ type Manifest struct { // Enable or disable Prometheus metrics on all nodes. // Defaults to false (disabled). Prometheus bool `toml:"prometheus"` + + // Maximum number of peers to which the node gossip transactions + ExperimentalMaxGossipConnectionsToPersistentPeers uint `toml:"experimental_max_gossip_connections_to_persistent_peers"` + ExperimentalMaxGossipConnectionsToNonPersistentPeers uint `toml:"experimental_max_gossip_connections_to_non_persistent_peers"` } // ManifestNode represents a node in a testnet manifest. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index eb65c0e7500..ecc9a5c1662 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -65,26 +65,28 @@ const ( // Testnet represents a single testnet. type Testnet struct { - Name string - File string - Dir string - IP *net.IPNet - InitialHeight int64 - InitialState map[string]string - Validators map[*Node]int64 - ValidatorUpdates map[int64]map[*Node]int64 - Nodes []*Node - KeyType string - Evidence int - LoadTxSizeBytes int - LoadTxBatchSize int - LoadTxConnections int - ABCIProtocol string - PrepareProposalDelay time.Duration - ProcessProposalDelay time.Duration - CheckTxDelay time.Duration - UpgradeVersion string - Prometheus bool + Name string + File string + Dir string + IP *net.IPNet + InitialHeight int64 + InitialState map[string]string + Validators map[*Node]int64 + ValidatorUpdates map[int64]map[*Node]int64 + Nodes []*Node + KeyType string + Evidence int + LoadTxSizeBytes int + LoadTxBatchSize int + LoadTxConnections int + ABCIProtocol string + PrepareProposalDelay time.Duration + ProcessProposalDelay time.Duration + CheckTxDelay time.Duration + UpgradeVersion string + Prometheus bool + ExperimentalMaxGossipConnectionsToPersistentPeers uint + ExperimentalMaxGossipConnectionsToNonPersistentPeers uint } // Node represents a CometBFT node in a testnet. @@ -150,6 +152,8 @@ func LoadTestnet(manifest Manifest, fname string, ifd InfrastructureData) (*Test CheckTxDelay: manifest.CheckTxDelay, UpgradeVersion: manifest.UpgradeVersion, Prometheus: manifest.Prometheus, + ExperimentalMaxGossipConnectionsToPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToPersistentPeers, + ExperimentalMaxGossipConnectionsToNonPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToNonPersistentPeers, } if len(manifest.KeyType) != 0 { testnet.KeyType = manifest.KeyType diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 0ec74fe8c80..fcb7a24b065 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -169,6 +169,8 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { cfg.P2P.AddrBookStrict = false cfg.DBBackend = node.Database cfg.StateSync.DiscoveryTime = 5 * time.Second + cfg.Mempool.ExperimentalMaxGossipConnectionsToNonPersistentPeers = int(node.Testnet.ExperimentalMaxGossipConnectionsToNonPersistentPeers) + cfg.Mempool.ExperimentalMaxGossipConnectionsToPersistentPeers = int(node.Testnet.ExperimentalMaxGossipConnectionsToPersistentPeers) switch node.ABCIProtocol { case e2e.ProtocolUNIX: From 8b258934502d163532accbed469d396c63c8416e Mon Sep 17 00:00:00 2001 From: lasaro Date: Thu, 16 Nov 2023 06:13:29 -0300 Subject: [PATCH 42/87] This commit makes the test be the same as in main, that is, it ignores the order of transactions in the receiving reactor. (#1629) --- mempool/v0/reactor_test.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/mempool/v0/reactor_test.go b/mempool/v0/reactor_test.go index 4c0e516e771..f4e5f46bd9e 100644 --- a/mempool/v0/reactor_test.go +++ b/mempool/v0/reactor_test.go @@ -321,7 +321,7 @@ func TestMempoolReactorMaxActiveOutboundConnections(t *testing.T) { // Wait for all txs to be in the mempool of the second reactor; the other reactors should not // receive any tx. (The second reactor only sends transactions to the first reactor.) - waitForTxsOnReactor(t, txs, reactors[1], 0) + checkTxsInMempool(t, txs, reactors[1], 0) for _, r := range reactors[2:] { require.Zero(t, r.mempool.Size()) } @@ -332,7 +332,7 @@ func TestMempoolReactorMaxActiveOutboundConnections(t *testing.T) { // Now the third reactor should start receiving transactions from the first reactor; the fourth // reactor's mempool should still be empty. - waitForTxsOnReactor(t, txs, reactors[2], 0) + checkTxsInMempool(t, txs, reactors[2], 0) for _, r := range reactors[3:] { require.Zero(t, r.mempool.Size()) } @@ -388,7 +388,7 @@ func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { wg.Add(1) go func(r *Reactor, reactorIndex int) { defer wg.Done() - waitForTxsOnReactor(t, txs, r, reactorIndex) + checkTxsInOrder(t, txs, r, reactorIndex) }(reactor, i) } @@ -406,13 +406,30 @@ func waitForTxsOnReactors(t *testing.T, txs types.Txs, reactors []*Reactor) { } } -func waitForTxsOnReactor(t *testing.T, txs types.Txs, reactor *Reactor, reactorIndex int) { - mempool := reactor.mempool - for mempool.Size() < len(txs) { +// Wait until the mempool has a certain number of transactions. +func waitForNumTxsInMempool(numTxs int, reactor *Reactor) { + for reactor.mempool.Size() < numTxs { time.Sleep(time.Millisecond * 100) } +} + +// Wait until all txs are in the mempool and check that the number of txs in the +// mempool is as expected. +func checkTxsInMempool(t *testing.T, txs types.Txs, reactor *Reactor, _ int) { + waitForNumTxsInMempool(len(txs), reactor) + + reapedTxs := reactor.mempool.ReapMaxTxs(len(txs)) + require.Equal(t, len(txs), len(reapedTxs)) + require.Equal(t, len(txs), reactor.mempool.Size()) +} + +// Wait until all txs are in the mempool and check that they are in the same +// order as given. +func checkTxsInOrder(t *testing.T, txs types.Txs, reactor *Reactor, reactorIndex int) { + waitForNumTxsInMempool(len(txs), reactor) - reapedTxs := mempool.ReapMaxTxs(len(txs)) + // Check that all transactions in the mempool are in the same order as txs. + reapedTxs := reactor.mempool.ReapMaxTxs(len(txs)) for i, tx := range txs { assert.Equalf(t, tx, reapedTxs[i], "txs at index %d on reactor %d don't match: %v vs %v", i, reactorIndex, tx, reapedTxs[i]) From 9d26fa4eb51f00aad831e33b0f31cb8a87481026 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Thu, 16 Nov 2023 06:18:43 -0500 Subject: [PATCH 43/87] v0.37.x: Bump Go version to v1.21 (#1625) * Bump Go version to v1.21 in go.mod Signed-off-by: Thane Thomson * Bump minimum Go version in Dockerfiles Signed-off-by: Thane Thomson * Bump all Go versions used in CI Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson Co-authored-by: lasaro --- .github/workflows/build.yml | 6 ++-- .github/workflows/check-generated.yml | 4 +-- .github/workflows/e2e-long-37x.yml | 2 +- .github/workflows/e2e-manual-multiversion.yml | 2 +- .github/workflows/e2e-manual.yml | 2 +- .github/workflows/e2e-nightly-37x.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release-version.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- DOCKER/Dockerfile | 2 +- go.mod | 4 +-- go.sum | 28 +++++++++++++++++++ test/e2e/docker/Dockerfile | 2 +- 19 files changed, 50 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6688465a7a7..7e5657361fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: @@ -43,7 +43,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: @@ -65,7 +65,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index ce242887ea4..377f3378b7d 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 with: diff --git a/.github/workflows/e2e-long-37x.yml b/.github/workflows/e2e-long-37x.yml index 9b55794681d..54d874afecb 100644 --- a/.github/workflows/e2e-long-37x.yml +++ b/.github/workflows/e2e-long-37x.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 with: diff --git a/.github/workflows/e2e-manual-multiversion.yml b/.github/workflows/e2e-manual-multiversion.yml index e1ecd3dc37f..771c5675e49 100644 --- a/.github/workflows/e2e-manual-multiversion.yml +++ b/.github/workflows/e2e-manual-multiversion.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index 1eefdec5131..fd809ebd541 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e-nightly-37x.yml b/.github/workflows/e2e-nightly-37x.yml index 68f7062bb85..8549ede868e 100644 --- a/.github/workflows/e2e-nightly-37x.yml +++ b/.github/workflows/e2e-nightly-37x.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 with: diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index 5e99df58223..ab9f6dc796d 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index eef8a21060c..3cfc0f18605 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index 4416820bb69..da8320cef94 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: actions/checkout@v4 diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 7e95736e302..305170a50c0 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" check-latest: true - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ff1e624dd95..078316040f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index aa34290e7df..7c66cbaa3c7 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' # Similar check to ./release-version.yml, but enforces this when pushing # tags. The ./release-version.yml check can be bypassed and is mainly diff --git a/.github/workflows/release-version.yml b/.github/workflows/release-version.yml index 09b442ed7b0..73d5e2f8679 100644 --- a/.github/workflows/release-version.yml +++ b/.github/workflows/release-version.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - name: Check version run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ce400b4f04..7ae9b172295 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' # Similar check to ./release-version.yml, but enforces this when pushing # tags. The ./release-version.yml check can be bypassed and is mainly diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index be011bb6807..ada56192705 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: diff --git a/DOCKER/Dockerfile b/DOCKER/Dockerfile index cf997137572..3649cc5a0b3 100644 --- a/DOCKER/Dockerfile +++ b/DOCKER/Dockerfile @@ -1,6 +1,6 @@ # Use a build arg to ensure that both stages use the same, # hopefully current, go version. -ARG GOLANG_BASE_IMAGE=golang:1.20-alpine +ARG GOLANG_BASE_IMAGE=golang:1.21-alpine # stage 1 Generate CometBFT Binary FROM --platform=$BUILDPLATFORM $GOLANG_BASE_IMAGE as builder diff --git a/go.mod b/go.mod index 3d2c83b02f0..fb327ec6697 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cometbft/cometbft -go 1.20 +go 1.21 require ( github.com/BurntSushi/toml v1.2.1 @@ -53,6 +53,7 @@ require ( github.com/go-git/go-git/v5 v5.5.2 github.com/golang/protobuf v1.5.3 github.com/vektra/mockery/v2 v2.14.0 + golang.org/x/sync v0.3.0 gonum.org/v1/gonum v0.8.2 google.golang.org/protobuf v1.31.0 ) @@ -278,7 +279,6 @@ require ( golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index 2acf5779204..3b453f8ecfd 100644 --- a/go.sum +++ b/go.sum @@ -27,7 +27,9 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -56,6 +58,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= @@ -76,6 +79,7 @@ github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= @@ -101,6 +105,7 @@ github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBF github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -149,6 +154,7 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -172,6 +178,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -217,6 +224,7 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0 github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -224,6 +232,7 @@ github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KP github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= @@ -245,6 +254,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m 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/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= @@ -266,6 +276,7 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -310,6 +321,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= @@ -344,7 +356,9 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -436,6 +450,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -461,6 +476,7 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -553,6 +569,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -600,6 +617,7 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -655,11 +673,13 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -672,6 +692,7 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -751,6 +772,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -884,8 +906,11 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= @@ -921,6 +946,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= @@ -1054,6 +1080,7 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1174,6 +1201,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile index 2ffe56d0b93..677490d251d 100644 --- a/test/e2e/docker/Dockerfile +++ b/test/e2e/docker/Dockerfile @@ -1,7 +1,7 @@ # We need to build in a Linux environment to support C libraries, e.g. RocksDB. # We use Debian instead of Alpine, so that we can use binary database packages # instead of spending time compiling them. -FROM golang:1.20-bullseye +FROM golang:1.21-bullseye RUN apt-get -qq update -y && apt-get -qq upgrade -y >/dev/null RUN apt-get -qq install -y libleveldb-dev librocksdb-dev >/dev/null From 4c6e83d4dec999a3cf4ee6fc60f74da12ec97933 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 06:25:37 -0500 Subject: [PATCH 44/87] Update SECURITY.md (backport #1626) (#1634) * Update SECURITY.md (#1626) Signed-off-by: Thane Thomson (cherry picked from commit 62a97f2c9713c716ef203052eb32069f917737f9) # Conflicts: # SECURITY.md * Resolve conflicts Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson Co-authored-by: Thane Thomson --- SECURITY.md | 225 ++++++---------------------------------------------- 1 file changed, 25 insertions(+), 200 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 01b989c6b1f..2a5c5666415 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,208 +1,33 @@ -# Security +# How to Report a Security Bug -## Reporting a Bug +If you believe you have found a security vulnerability in the Interchain Stack, +you can report it to our primary vulnerability disclosure channel, the [Cosmos +HackerOne Bug Bounty program][h1]. -As part of our Coordinated Vulnerability Disclosure Policy (link will be added -once this policy is finalized for CometBFT), we operate a [bug -bounty][hackerone]. 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. +If you prefer to report an issue via email, you may send a bug report to + with the issue details, reproduction, impact, and other +information. Please submit only one unique email thread per vulnerability. Any +issues reported via email are ineligible for bounty rewards. -### Guidelines +Artifacts from an email report are saved at the time the email is triaged. +Please note: our team is not able to monitor dynamic content (e.g. a Google Docs +link that is edited after receipt) throughout the lifecycle of a report. If you +would like to share additional information or modify previous information, +please include it in an additional reply as an additional attachment. -We require that all researchers: +Please **DO NOT** file a public issue in this repository to report a security +vulnerability. -* 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 CometBFT engineering team until the issue has been - resolved and disclosed -* Avoid posting personally identifiable information, privately or publicly +## Coordinated Vulnerability Disclosure Policy and Safe Harbor -If you follow these guidelines when reporting an issue to us, we commit to: +For the most up-to-date version of the policies that govern vulnerability +disclosure, please consult the [HackerOne program page][h1-policy]. -* 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 +The policy hosted on HackerOne is the official Coordinated Vulnerability +Disclosure policy and Safe Harbor for the Interchain Stack, and the teams and +infrastructure it supports, and it supersedes previous security policies that +have been used in the past by individual teams and projects with targets in +scope of the program. -## Disclosure Process - -CometBFT uses the following disclosure process: - -1. Once a security report is received, the CometBFT team works to verify the - issue and confirm its severity level using CVSS. -2. The CometBFT 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 CometBFT 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. -6. 24 hours following this notification, the fixes are applied publicly and new - releases are issued. -7. Cosmos SDK and Gaia update their CometBFT dependencies to use these releases, - and then themselves issue new releases. -8. Once releases are available for CometBFT, 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 CometBFT and its downstream dependent projects--including but not -limited to Gaia and the Cosmos Hub--as secure as possible. - -### 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. - -#### 24+ Hours Before Release Time - -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 (CometBFT ENG, CometBFT LEAD) -4. Test fixes on a testnet (CometBFT ENG, COSMOS SDK ENG) -5. Write “Security Advisory” for forum (CometBFT LEAD) - -#### 24 Hours Before Release Time - -1. Post “Security Advisory” pre-notification on forum (CometBFT 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 - -1. Cut CometBFT releases for eligible versions (CometBFT ENG, CometBFT - LEAD) -2. Cut Cosmos SDK release for eligible versions (COSMOS ENG) -3. Cut Gaia release for eligible versions (GAIA ENG) -4. Post “Security releases” on forum (CometBFT 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) - -#### After Release Time - -1. Write forum post with exploit details (CometBFT LEAD) -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) -2. Publish forum post with exploit details (CometBFT ENG, CometBFT LEAD) - -## Supported Releases - -The CometBFT 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 CometBFT, we encourage you to -upgrade at your earliest opportunity so that you can receive security patches -directly from the CometBFT 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][hackerone]. 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 -* Findings derived from social engineering (e.g., phishing) - -## 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! - -### Specification - -* Conceptual flaws -* Ambiguities, inconsistencies, or incorrect statements -* Mis-match between specification and implementation of any component - -### Consensus - -Assuming less than 1/3 of the voting power is Byzantine (malicious): - -* Validation of blockchain data structures, including blocks, block parts, - votes, and so on -* Execution of blocks -* Validator set changes -* Proposer round robin -* Two nodes committing conflicting blocks for the same height (safety failure) -* A correct node signing conflicting votes -* 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 - -* Authenticated encryption (MITM, information leakage) -* Eclipse attacks -* Sybil attacks -* Long-range attacks -* Denial-of-Service - -### RPC - -* Write-access to anything besides sending transactions -* Denial-of-Service -* Leakage of secrets - -### Denial-of-Service - -Attacks may come through the P2P network or the RPC layer: - -* Amplification attacks -* Resource abuse -* Deadlocks and race conditions - -### Libraries - -* Serialization -* Reading/Writing files and databases - -### Cryptography - -* Elliptic curves for validator signatures -* Hash algorithms and Merkle trees for block validation -* Authenticated encryption for P2P connections - -### Light Client - -* Core verification -* Bisection/sequential algorithms - -[hackerone]: https://hackerone.com/cosmos +[h1]: https://hackerone.com/cosmos?type=team +[h1-policy]: https://hackerone.com/cosmos?type=team&view_policy=true From 7a084a12a56db20d58c8e836de55999cb9b75810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A1n=20Vanzetto?= <15466498+hvanz@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:53:20 +0100 Subject: [PATCH 45/87] Comment that feature only applies to v0 mempool (#1631) --- .../improvements/1558-experimental-gossip-limiting.md | 2 +- config/config.go | 1 + config/toml.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md b/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md index 6931cef8274..f3d84dddc75 100644 --- a/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md +++ b/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md @@ -1,5 +1,5 @@ - `[mempool]` Add experimental feature to limit the number of persistent peers and non-persistent - peers to which the node gossip transactions. + peers to which the node gossip transactions (only for "v0" mempool). ([\#1558](https://github.com/cometbft/cometbft/pull/1558)) ([\#1584](https://github.com/cometbft/cometbft/pull/1584)) - `[config]` Add mempool parameters `experimental_max_gossip_connections_to_persistent_peers` and diff --git a/config/config.go b/config/config.go index 9ab361bc39a..5f2c5559530 100644 --- a/config/config.go +++ b/config/config.go @@ -762,6 +762,7 @@ type MempoolConfig struct { // XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 MaxBatchBytes int `mapstructure:"max_batch_bytes"` // Experimental parameters to limit gossiping txs to up to the specified number of peers. + // This feature is only available for the default mempool (version config set to "v0"). // We use two independent upper values for persistent peers and for non-persistent peers. // Unconditional peers are not affected by this feature. // If we are connected to more than the specified number of persistent peers, only send txs to diff --git a/config/toml.go b/config/toml.go index 2cf16f5917e..5b22847831f 100644 --- a/config/toml.go +++ b/config/toml.go @@ -391,6 +391,7 @@ ttl-duration = "{{ .Mempool.TTLDuration }}" ttl-num-blocks = {{ .Mempool.TTLNumBlocks }} # Experimental parameters to limit gossiping txs to up to the specified number of peers. +# This feature is only available for the default mempool (version config set to "v0"). # We use two independent upper values for persistent peers and for non-persistent peers. # Unconditional peers are not affected by this feature. # If we are connected to more than the specified number of persistent peers, only send txs to From b640900c21368dba58e8f014fb447e9c7d39bde5 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 17 Nov 2023 06:21:07 -0500 Subject: [PATCH 46/87] Release v0.37.3 (#1640) * version: Bump to v0.37.3 Signed-off-by: Thane Thomson * Build changelog Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson --- .../breaking-changes/1113-rm-upnp.md | 0 ...-indexer-respect-height-params-on-query.md | 0 .../features/1057-bootstrap-state-api.md | 0 .../1512-metric-mempool-size-bytes.md | 0 .../improvements/1210-close-evidence-db.md | 0 .../1558-experimental-gossip-limiting.md | 0 .../improvements/475-upgrade-go-schnorrkel.md | 0 .../857-make-handshake-cancelable.md | 0 .changelog/v0.37.3/summary.md | 5 ++ CHANGELOG.md | 48 +++++++++++++++++-- version/version.go | 2 +- 11 files changed, 50 insertions(+), 5 deletions(-) rename .changelog/{unreleased => v0.37.3}/breaking-changes/1113-rm-upnp.md (100%) rename .changelog/{unreleased => v0.37.3}/bug-fixes/1529-indexer-respect-height-params-on-query.md (100%) rename .changelog/{unreleased => v0.37.3}/features/1057-bootstrap-state-api.md (100%) rename .changelog/{unreleased => v0.37.3}/features/1512-metric-mempool-size-bytes.md (100%) rename .changelog/{unreleased => v0.37.3}/improvements/1210-close-evidence-db.md (100%) rename .changelog/{unreleased => v0.37.3}/improvements/1558-experimental-gossip-limiting.md (100%) rename .changelog/{unreleased => v0.37.3}/improvements/475-upgrade-go-schnorrkel.md (100%) rename .changelog/{unreleased => v0.37.3}/improvements/857-make-handshake-cancelable.md (100%) create mode 100644 .changelog/v0.37.3/summary.md diff --git a/.changelog/unreleased/breaking-changes/1113-rm-upnp.md b/.changelog/v0.37.3/breaking-changes/1113-rm-upnp.md similarity index 100% rename from .changelog/unreleased/breaking-changes/1113-rm-upnp.md rename to .changelog/v0.37.3/breaking-changes/1113-rm-upnp.md diff --git a/.changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md b/.changelog/v0.37.3/bug-fixes/1529-indexer-respect-height-params-on-query.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1529-indexer-respect-height-params-on-query.md rename to .changelog/v0.37.3/bug-fixes/1529-indexer-respect-height-params-on-query.md diff --git a/.changelog/unreleased/features/1057-bootstrap-state-api.md b/.changelog/v0.37.3/features/1057-bootstrap-state-api.md similarity index 100% rename from .changelog/unreleased/features/1057-bootstrap-state-api.md rename to .changelog/v0.37.3/features/1057-bootstrap-state-api.md diff --git a/.changelog/unreleased/features/1512-metric-mempool-size-bytes.md b/.changelog/v0.37.3/features/1512-metric-mempool-size-bytes.md similarity index 100% rename from .changelog/unreleased/features/1512-metric-mempool-size-bytes.md rename to .changelog/v0.37.3/features/1512-metric-mempool-size-bytes.md diff --git a/.changelog/unreleased/improvements/1210-close-evidence-db.md b/.changelog/v0.37.3/improvements/1210-close-evidence-db.md similarity index 100% rename from .changelog/unreleased/improvements/1210-close-evidence-db.md rename to .changelog/v0.37.3/improvements/1210-close-evidence-db.md diff --git a/.changelog/unreleased/improvements/1558-experimental-gossip-limiting.md b/.changelog/v0.37.3/improvements/1558-experimental-gossip-limiting.md similarity index 100% rename from .changelog/unreleased/improvements/1558-experimental-gossip-limiting.md rename to .changelog/v0.37.3/improvements/1558-experimental-gossip-limiting.md diff --git a/.changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md b/.changelog/v0.37.3/improvements/475-upgrade-go-schnorrkel.md similarity index 100% rename from .changelog/unreleased/improvements/475-upgrade-go-schnorrkel.md rename to .changelog/v0.37.3/improvements/475-upgrade-go-schnorrkel.md diff --git a/.changelog/unreleased/improvements/857-make-handshake-cancelable.md b/.changelog/v0.37.3/improvements/857-make-handshake-cancelable.md similarity index 100% rename from .changelog/unreleased/improvements/857-make-handshake-cancelable.md rename to .changelog/v0.37.3/improvements/857-make-handshake-cancelable.md diff --git a/.changelog/v0.37.3/summary.md b/.changelog/v0.37.3/summary.md new file mode 100644 index 00000000000..f1e5c7f755c --- /dev/null +++ b/.changelog/v0.37.3/summary.md @@ -0,0 +1,5 @@ +*November 17, 2023* + +This release contains, among other things, an opt-in, experimental feature to +help reduce the bandwidth consumption associated with the mempool's transaction +gossip. diff --git a/CHANGELOG.md b/CHANGELOG.md index e26955a191a..2836c792f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # CHANGELOG +## v0.37.3 + +*November 17, 2023* + +This release contains, among other things, an opt-in, experimental feature to +help reduce the bandwidth consumption associated with the mempool's transaction +gossip. + +### BREAKING CHANGES + +- `[p2p]` Remove unused UPnP functionality + ([\#1113](https://github.com/cometbft/cometbft/issues/1113)) + +### BUG FIXES + +- `[state/indexer]` Respect both height params while querying for events + ([\#1529](https://github.com/cometbft/cometbft/pull/1529)) + +### FEATURES + +- `[node/state]` Add Go API to bootstrap block store and state store to a height + ([\#1057](https://github.com/tendermint/tendermint/pull/#1057)) (@yihuang) +- `[metrics]` Add metric for mempool size in bytes `SizeBytes`. + ([\#1512](https://github.com/cometbft/cometbft/pull/1512)) + +### IMPROVEMENTS + +- `[crypto/sr25519]` Upgrade to go-schnorrkel@v1.0.0 ([\#475](https://github.com/cometbft/cometbft/issues/475)) +- `[node]` Make handshake cancelable ([cometbft/cometbft\#857](https://github.com/cometbft/cometbft/pull/857)) +- `[node]` Close evidence.db OnStop ([cometbft/cometbft\#1210](https://github.com/cometbft/cometbft/pull/1210): @chillyvee) +- `[mempool]` Add experimental feature to limit the number of persistent peers and non-persistent + peers to which the node gossip transactions (only for "v0" mempool). + ([\#1558](https://github.com/cometbft/cometbft/pull/1558)) + ([\#1584](https://github.com/cometbft/cometbft/pull/1584)) +- `[config]` Add mempool parameters `experimental_max_gossip_connections_to_persistent_peers` and + `experimental_max_gossip_connections_to_non_persistent_peers` for limiting the number of peers to + which the node gossip transactions. + ([\#1558](https://github.com/cometbft/cometbft/pull/1558)) + ([\#1584](https://github.com/cometbft/cometbft/pull/1584)) + ## v0.37.2 *June 14, 2023* @@ -9,14 +49,14 @@ security issues. ### BUG FIXES -- `[state/kvindex]` Querying event attributes that are bigger than int64 is now - enabled. We are not supporting reading floats from the db into the indexer - nor parsing them into BigFloats to not introduce breaking changes in minor - releases. ([\#771](https://github.com/cometbft/cometbft/pull/771)) - `[pubsub]` Pubsub queries are now able to parse big integers (larger than int64). Very big floats are also properly parsed into very big integers instead of being truncated to int64. ([\#771](https://github.com/cometbft/cometbft/pull/771)) +- `[state/kvindex]` Querying event attributes that are bigger than int64 is now + enabled. We are not supporting reading floats from the db into the indexer + nor parsing them into BigFloats to not introduce breaking changes in minor + releases. ([\#771](https://github.com/cometbft/cometbft/pull/771)) ### IMPROVEMENTS diff --git a/version/version.go b/version/version.go index 0c9057a7acc..1df77825d47 100644 --- a/version/version.go +++ b/version/version.go @@ -5,7 +5,7 @@ const ( // The default version of TMCoreSemVer is the value used as the // fallback version of CometBFT when not using git describe. // It is formatted with semantic versioning. - TMCoreSemVer = "0.37.2" + TMCoreSemVer = "0.37.3" // ABCISemVer is the semantic version of the ABCI protocol ABCISemVer = "1.0.0" ABCIVersion = ABCISemVer From 8c17e632a90ac779030b714a96e6e2da8db293b4 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 17 Nov 2023 15:43:15 -0500 Subject: [PATCH 47/87] proto: Prepare for publishing v0.37.x protos to Buf registry (#1646) Signed-off-by: Thane Thomson --- proto/buf.lock | 3 ++- proto/buf.yaml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/proto/buf.lock b/proto/buf.lock index f2b69369858..51b78ffe35a 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -4,4 +4,5 @@ deps: - remote: buf.build owner: cosmos repository: gogo-proto - commit: 6652e3443c3b4504bb3bf82e73a7e409 + commit: 5e5b9fdd01804356895f8f79a6f1ddc1 + digest: shake256:0b85da49e2e5f9ebc4806eae058e2f56096ff3b1c59d1fb7c190413dd15f45dd456f0b69ced9059341c80795d2b6c943de15b120a9e0308b499e43e4b5fc2952 diff --git a/proto/buf.yaml b/proto/buf.yaml index c6e0660f147..a646c2030a7 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -1,4 +1,5 @@ version: v1 +name: buf.build/tendermint/tendermint deps: - buf.build/cosmos/gogo-proto breaking: From f47426767f957897e5e76dd6fdda436d078c43f0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:08:34 -0500 Subject: [PATCH 48/87] proto: Update README (backport #1648) (#1652) * proto: Update README (#1648) * proto: Update README Signed-off-by: Thane Thomson * Add versions to table for clarity Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson (cherry picked from commit ee99bf534a35f56f0e5ad463d93bbe1d796bf9f0) * Update title to reflect version Signed-off-by: Thane Thomson * Update spec link to v0.37.x branch Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson Co-authored-by: Thane Thomson --- proto/README.md | 97 ++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/proto/README.md b/proto/README.md index fcce452a244..24c9131c217 100644 --- a/proto/README.md +++ b/proto/README.md @@ -1,41 +1,56 @@ -# Protocol Buffers - -This sections defines the types and messages shared across implementations. The -definition of the data structures are located in the -[core/data\_structures](../spec/core/data_structures.md) for the core data types -and ABCI definitions are located in the [ABCI](../spec/abci/README.md) section. - -## Process of Updates - -The `.proto` files within this section are core to the protocol and updates must -be treated as such. - -### Steps - -1. Make an issue with the proposed change. Within in the issue members from - both the CometBFT and tendermint-rs team will leave comments. If there is not - consensus on the change an [RFC](../docs/rfc/README.md) may be requested. - 1. Submission of an RFC as a pull request should be made to facilitate - further discussion. - 2. Merge the RFC. -2. Make the necessary changes to the `.proto` file(s), [core data - structures](../spec/core/data_structures.md) and/or [ABCI - protocol](../spec/abci). -3. Open issues within CometBFT and Tendermint-rs repos. This is used to notify - the teams that a change occurred in the spec. - 1. Tag the issue with a spec version label. This will notify the team the - changed has been made on master but has not entered a release. - -### Versioning - -The spec repo aims to be versioned. Once it has been versioned, updates to the -protobuf files will live on master. After a certain amount of time, decided on -by CometBFT and tendermint-rs team leads, a release will be made on the spec -repo. The spec may contain minor releases as well, depending on the -implementation these changes may lead to a breaking change. If so, the -implementation team should open an issue within the spec repo requiring a major -release of the spec. - -If the steps above were followed each implementation should have issues tagged -with a spec change label. Once all issues have been completed the team should -signify their readiness for release. + +# CometBFT v0.37.x Protocol Buffers Definitions + +This is the set of [Protobuf][protobuf] definitions of types used by various +parts of [CometBFT]: + +- The [Application Blockchain Interface][abci] (ABCI), especially in the context + of _remote_ applications. +- The P2P layer, in how CometBFT nodes interact with each other over the + network. +- In interaction with remote signers ("privval"). +- The RPC, in that the native JSON serialization of certain Protobuf types is + used when accepting and responding to RPC requests. +- The storage layer, in how data is serialized to and deserialized from on-disk + storage. + +The canonical Protobuf definitions live in the `proto` folder of the relevant +release branch of CometBFT. These definitions are published to the [Buf +registry][buf] for integrators' convenience. + +## Why does CometBFT use `tendermint` Protobuf definitions? + +This is as a result of CometBFT being a fork of [Tendermint Core][tmcore] and +wanting to provide integrators with as painless a way as possible of +transitioning from Tendermint Core to CometBFT. + +As of CometBFT v1, however, the project will transition to using and providing a +`cometbft` package of Protobuf definitions (see [\#1330]). + +## How are `tendermint` Protobuf definitions versioned? + +At present, the canonical source of Protobuf definitions for all CometBFT v0.x +releases is on each respective release branch. Each respective release's +Protobuf definitions are also, for convenience, published to a corresponding +branch in the `tendermint/tendermint` Buf repository. + +| CometBFT version | Canonical Protobufs | Buf registry | +|------------------|---------------------------------------------|-------------------------------------------| +| v0.38.x | [v0.38.x Protobuf definitions][v038-protos] | [Buf repository v0.38.x branch][v038-buf] | +| v0.37.x | [v0.37.x Protobuf definitions][v037-protos] | [Buf repository v0.37.x branch][v037-buf] | +| v0.34.x | [v0.34.x Protobuf definitions][v034-protos] | [Buf repository v0.34.x branch][v034-buf] | + +[protobuf]: https://protobuf.dev/ +[CometBFT]: https://github.com/cometbft/cometbft +[abci]: https://github.com/cometbft/cometbft/tree/v0.37.x/spec/abci +[buf]: https://buf.build/tendermint/tendermint +[tmcore]: https://github.com/tendermint/tendermint +[\#1330]: https://github.com/cometbft/cometbft/issues/1330 +[v034-protos]: https://github.com/cometbft/cometbft/tree/v0.34.x/proto +[v034-buf]: https://buf.build/tendermint/tendermint/docs/v0.34.x +[v037-protos]: https://github.com/cometbft/cometbft/tree/v0.37.x/proto +[v037-buf]: https://buf.build/tendermint/tendermint/docs/v0.37.x +[v038-protos]: https://github.com/cometbft/cometbft/tree/v0.38.x/proto +[v038-buf]: https://buf.build/tendermint/tendermint/docs/v0.38.x From 9e9064f1f6ddb39103a22f4a2d43c70cc3b2613c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 05:41:54 -0500 Subject: [PATCH 49/87] build(deps): Bump docker/build-push-action from 5.0.0 to 5.1.0 (#1656) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5.0.0...v5.1.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 93628994c3e..99cb523d7d4 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.1.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index 6199c6cecb9..e426397657c 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.1.0 with: context: . file: ./test/e2e/docker/Dockerfile From cf52e519b03dc8bcc2bd90e60f76c9f969301fcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:00:08 -0500 Subject: [PATCH 50/87] build(deps): Bump bufbuild/buf-setup-action from 1.28.0 to 1.28.1 (#1657) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.28.0 to 1.28.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.28.0...v1.28.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 41509e9974f..998aa305db9 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.28.0 + - uses: bufbuild/buf-setup-action@v1.28.1 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From c89ad9822996c71cd1f37e9ebe87cb3935cb9396 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 20 Nov 2023 09:08:33 -0500 Subject: [PATCH 51/87] v0.37.x: Restore minimum Go version (#1669) --- go.mod | 2 +- go.sum | 28 ---------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/go.mod b/go.mod index fb327ec6697..b9051faa8e7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cometbft/cometbft -go 1.21 +go 1.20 require ( github.com/BurntSushi/toml v1.2.1 diff --git a/go.sum b/go.sum index 3b453f8ecfd..2acf5779204 100644 --- a/go.sum +++ b/go.sum @@ -27,9 +27,7 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= -cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -58,7 +56,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= @@ -79,7 +76,6 @@ github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= @@ -105,7 +101,6 @@ github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBF github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -154,7 +149,6 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -178,7 +172,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -224,7 +217,6 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0 github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= -github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -232,7 +224,6 @@ github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KP github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= @@ -254,7 +245,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m 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/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= @@ -276,7 +266,6 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -321,7 +310,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= @@ -356,9 +344,7 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -450,7 +436,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -476,7 +461,6 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -569,7 +553,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -617,7 +600,6 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= -github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -673,13 +655,11 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= -github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -692,7 +672,6 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= -github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -772,7 +751,6 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -906,11 +884,8 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= @@ -946,7 +921,6 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= @@ -1080,7 +1054,6 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1201,7 +1174,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From ba3c8d8233dc5eed8f74921ec7dcd1c0998f2722 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:15:46 +0400 Subject: [PATCH 52/87] mempool: add `nop` mempool (backport #1643) (#1681) * mempool: add `nop` mempool (#1643) * add `nop` mempool See [ADR-111](https://github.com/cometbft/cometbft/pull/1585) * implement NopMempool and NopMempoolReactor modify node.go logic I had to add NopMempoolReactor to pass it to RPC in order not to change it. * check config instead of asserting for nil * start writing docs * add changelog * move changelog * expand docs * remove unused func arguments * add simple test * make linter happy again * doc fixes Co-authored-by: Sergio Mena * rename mempoolReactor to waitSyncP2PReactor * improve changelog message * allow empty string for backwards compatibility https://github.com/cometbft/cometbft/pull/1643#discussion_r1400378375 * make ErrNotAllowed private * mention `create_empty_blocks` in toml https://github.com/cometbft/cometbft/pull/1643/files#r1400434715 * return nil instead of closed channel https://github.com/cometbft/cometbft/pull/1643/files#r1400252575 The reader will block forever, which is exactly what we need. * grammar fixes Co-authored-by: lasaro * update changelog entry * adapt ADR to implementation * remove old ToC entry --------- Co-authored-by: Andy Nogueira Co-authored-by: Sergio Mena Co-authored-by: lasaro (cherry picked from commit bc835036aa0f6bd9d8b681ff44ed536df97d950e) # Conflicts: # config/config.go # config/toml.go # docs/architecture/README.md # docs/architecture/adr-111-nop-mempool.md # docs/core/configuration.md # node/node.go # node/setup.go * fix merge conflicts * add a missing ToC line --------- Co-authored-by: Anton Kaliaev --- .../unreleased/features/1643-nop-mempool.md | 17 + config/config.go | 24 ++ config/config_test.go | 8 + config/toml.go | 10 + docs/architecture/README.md | 1 + docs/architecture/adr-111-nop-mempool.md | 324 ++++++++++++++++++ docs/core/configuration.md | 13 + docs/core/mempool.md | 57 ++- mempool/nop_mempool.go | 110 ++++++ mempool/nop_mempool_test.go | 38 ++ node/node.go | 102 +++--- 11 files changed, 657 insertions(+), 47 deletions(-) create mode 100644 .changelog/unreleased/features/1643-nop-mempool.md create mode 100644 docs/architecture/adr-111-nop-mempool.md create mode 100644 mempool/nop_mempool.go create mode 100644 mempool/nop_mempool_test.go diff --git a/.changelog/unreleased/features/1643-nop-mempool.md b/.changelog/unreleased/features/1643-nop-mempool.md new file mode 100644 index 00000000000..e12ec43fc1a --- /dev/null +++ b/.changelog/unreleased/features/1643-nop-mempool.md @@ -0,0 +1,17 @@ +- `[mempool]` Add `nop` mempool ([\#1643](https://github.com/cometbft/cometbft/pull/1643)) + + If you want to use it, change mempool's `type` to `nop`: + + ```toml + [mempool] + + # The type of mempool for this node to use. + # + # Possible types: + # - "flood" : concurrent linked list mempool with flooding gossip protocol + # (default) + # - "nop" : nop-mempool (short for no operation; the ABCI app is responsible + # for storing, disseminating and proposing txs). "create_empty_blocks=false" + # is not supported. + type = "nop" + ``` \ No newline at end of file diff --git a/config/config.go b/config/config.go index 5f2c5559530..88edd1e3a53 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,9 @@ const ( // Default is v0. MempoolV0 = "v0" MempoolV1 = "v1" + + MempoolTypeFlood = "flood" + MempoolTypeNop = "nop" ) // NOTE: Most of the structs & relevant comments + the @@ -151,6 +154,9 @@ func (cfg *Config) ValidateBasic() error { if err := cfg.Instrumentation.ValidateBasic(); err != nil { return fmt.Errorf("error in [instrumentation] section: %w", err) } + if !cfg.Consensus.CreateEmptyBlocks && cfg.Mempool.Type == MempoolTypeNop { + return fmt.Errorf("`nop` mempool does not support create_empty_blocks = false") + } return nil } @@ -721,6 +727,17 @@ type MempoolConfig struct { // 1) "v0" - (default) FIFO mempool. // 2) "v1" - prioritized mempool (deprecated; will be removed in the next release). Version string `mapstructure:"version"` + + // The type of mempool for this node to use. + // + // Possible types: + // - "flood" : concurrent linked list mempool with flooding gossip protocol + // (default) + // - "nop" : nop-mempool (short for no operation; the ABCI app is + // responsible for storing, disseminating and proposing txs). + // "create_empty_blocks=false" is not supported. + Type string `mapstructure:"type"` + // RootDir is the root directory for all data. This should be configured via // the $CMTHOME env variable or --home cmd flag rather than overriding this // struct field. @@ -802,6 +819,7 @@ type MempoolConfig struct { // DefaultMempoolConfig returns a default configuration for the CometBFT mempool func DefaultMempoolConfig() *MempoolConfig { return &MempoolConfig{ + Type: MempoolTypeFlood, Version: MempoolV0, Recheck: true, Broadcast: true, @@ -839,6 +857,12 @@ func (cfg *MempoolConfig) WalEnabled() bool { // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *MempoolConfig) ValidateBasic() error { + switch cfg.Type { + case MempoolTypeFlood, MempoolTypeNop: + case "": // allow empty string to be backwards compatible + default: + return fmt.Errorf("unknown mempool type: %q", cfg.Type) + } if cfg.Size < 0 { return errors.New("size can't be negative") } diff --git a/config/config_test.go b/config/config_test.go index 86b32c7687e..ac455a5c458 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -37,6 +37,11 @@ func TestConfigValidateBasic(t *testing.T) { // tamper with timeout_propose cfg.Consensus.TimeoutPropose = -10 * time.Second assert.Error(t, cfg.ValidateBasic()) + cfg.Consensus.TimeoutPropose = 3 * time.Second + + cfg.Consensus.CreateEmptyBlocks = false + cfg.Mempool.Type = MempoolTypeNop + assert.Error(t, cfg.ValidateBasic()) } func TestTLSConfiguration(t *testing.T) { @@ -121,6 +126,9 @@ func TestMempoolConfigValidateBasic(t *testing.T) { assert.Error(t, cfg.ValidateBasic()) reflect.ValueOf(cfg).Elem().FieldByName(fieldName).SetInt(0) } + + reflect.ValueOf(cfg).Elem().FieldByName("Type").SetString("invalid") + assert.Error(t, cfg.ValidateBasic()) } func TestStateSyncConfigValidateBasic(t *testing.T) { diff --git a/config/toml.go b/config/toml.go index 5b22847831f..c6c0b39253c 100644 --- a/config/toml.go +++ b/config/toml.go @@ -345,6 +345,16 @@ dial_timeout = "{{ .P2P.DialTimeout }}" # 2) "v1" - prioritized mempool (deprecated; will be removed in the next release). version = "{{ .Mempool.Version }}" +# The type of mempool for this node to use. +# +# Possible types: +# - "flood" : concurrent linked list mempool with flooding gossip protocol +# (default) +# - "nop" : nop-mempool (short for no operation; the ABCI app is responsible +# for storing, disseminating and proposing txs). "create_empty_blocks=false" is +# not supported. +type = "flood" + recheck = {{ .Mempool.Recheck }} broadcast = {{ .Mempool.Broadcast }} wal_dir = "{{ js .Mempool.WalPath }}" diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 7a2ad6fed4e..5e10cc7b119 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -84,6 +84,7 @@ therefore they have links to it and refer to CometBFT as "Tendermint" or "Tender - [ADR-075: RPC Event Subscription Interface](./adr-075-rpc-subscription.md) - [ADR-079: Ed25519 Verification](./adr-079-ed25519-verification.md) - [ADR-081: Protocol Buffers Management](./adr-081-protobuf-mgmt.md) +- [ADR-111: `nop` Mempool](./adr-111-nop-mempool.md) ### Deprecated diff --git a/docs/architecture/adr-111-nop-mempool.md b/docs/architecture/adr-111-nop-mempool.md new file mode 100644 index 00000000000..234cd5b9c1d --- /dev/null +++ b/docs/architecture/adr-111-nop-mempool.md @@ -0,0 +1,324 @@ +# ADR 111: `nop` Mempool + +## Changelog + +- 2023-11-07: First version (@sergio-mena) +- 2023-11-15: Addressed PR comments (@sergio-mena) +- 2023-11-17: Renamed `nil` to `nop` (@melekes) +- 2023-11-20: Mentioned that the app could reuse p2p network in the future (@melekes) +- 2023-11-22: Adapt ADR to implementation (@melekes) + +## Status + +Accepted + +[Tracking issue](https://github.com/cometbft/cometbft/issues/1666) + +## Context + +### Summary + +The current mempool built into CometBFT implements a robust yet somewhat inefficient transaction gossip mechanism. +While the CometBFT team is currently working on more efficient general-purpose transaction gossiping mechanisms, +some users have expressed their desire to manage both the mempool and the transaction dissemination mechanism +outside CometBFT (typically at the application level). + +This ADR proposes a fairly simple way for CometBFT to fulfill this use case without moving away from our current architecture. + +### In the Beginning... + +It is well understood that a dissemination mechanism +(sometimes using _Reliable Broadcast_ [\[HT94\]][HT94] but not necessarily), +is needed in a distributed system implementing State-Machine Replication (SMR). +This is also the case in blockchains. +Early designs such as Bitcoin or Ethereum include an _internal_ component, +responsible for dissemination, called mempool. +Tendermint Core chose to follow the same design given the success +of those early blockchains and, since inception, Tendermint Core and later CometBFT have featured a mempool as an internal piece of its architecture. + + +However, the design of ABCI clearly dividing the application logic (i.e., the appchain) +and the consensus logic that provides SMR semantics to the app is a unique innovation in Cosmos +that sets it apart from Bitcoin, Ethereum, and many others. +This clear separation of concerns entailed many consequences, mostly positive: +it allows CometBFT to be used underneath (currently) tens of different appchains in production +in the Cosmos ecosystem and elsewhere. +But there are other implications for having an internal mempool +in CometBFT: the interaction between the mempool, the application, and the network +becomes more indirect, and thus more complex and hard to understand and operate. + +### ABCI++ Improvements and Remaining Shortcomings + +Before the release of ABCI++, `CheckTx` was the main mechanism the app had at its disposal to influence +what transactions made it to the mempool, and very indirectly what transactions got ultimately proposed in a block. +Since ABCI 1.0 (the first part of ABCI++, shipped in `v0.37.x`), the application has +a more direct say in what is proposed through `PrepareProposal` and `ProcessProposal`. + +This has greatly improved the ability for appchains to influence the contents of the proposed block. +Further, ABCI++ has enabled many new use cases for appchains. However some issues remain with +the current model: + +* We are using the same P2P network for disseminating transactions and consensus-related messages. +* Many mempool parameters are configured on a per-node basis by node operators, + allowing the possibility of inconsistent mempool configuration across the network + with potentially serious scalability effects + (even causing unacceptable performance degradation in some extreme cases). +* The current mempool implementation uses a basic (robust but sub-optimal) flood algorithm + * the CometBFT team is working on improving it as one of our current priorities, + but any improvement we come up with must address the needs of a vast spectrum of applications, + as well as be heavily scaled-tested in various scenarios + (in an attempt to cover the applications' wide spectrum) + * a mempool designed specifically for one particular application + would reduce the search space as its designers can devise it with just their application's + needs in mind. +* The interaction with the application is still somewhat convoluted: + * the application has to decide what logic to implement in `CheckTx`, + what to do with the transaction list coming in `RequestPrepareProposal`, + whether it wants to maintain an app-side mempool (more on this below), and whether or not + to combine the transactions in the app-side mempool with those coming in `RequestPrepareProposal` + * all those combinations are hard to fully understand, as the semantics and guarantees are + often not clear + * when using exclusively an app-mempool (the approach taken in the Cosmos SDK `v0.47.x`) + for populating proposed blocks, with the aim of simplifying the app developers' life, + we still have a suboptimal model where we need to continue using CometBFT's mempool + in order to disseminate the transactions. So, we end up using twice as much memory, + as in-transit transactions need to be kept in both mempools. + +The approach presented in this ADR builds on the app-mempool design released in `v0.47.x` +of the Cosmos SDK, +and briefly discussed in the last bullet point above (see [SDK app-mempool][sdk-app-mempool] for further details of this model). + +In the app-mempool design in Cosmos SDK `v0.47.x` +an unconfirmed transaction must be both in CometBFT's mempool for dissemination and +in the app's mempool so the application can decide how to manage the mempool. +There is no doubt that this approach has numerous advantages. However, it also has some implications that need to be considered: + +* Having every transaction both in CometBFT and in the application is suboptimal in terms of memory. + Additionally, the app developer has to be careful + that the contents of both mempools do not diverge over time + (hence the crucial role `re-CheckTx` plays post-ABCI++). +* The main reason for a transaction needing to be in CometBFT's mempool is + because the design in Cosmos SDK `v0.47.x` does not consider an application + that has its own means of disseminating transactions. + It reuses the peer to peer network underneath CometBFT reactors. +* There is no point in having transactions in CometBFT's mempool if an application implements an ad-hoc design for disseminating transactions. + +This proposal targets this kind of applications: +those that have an ad-hoc mechanism for transaction dissemination that better meets the application requirements. + +The ABCI application could reuse the P2P network once this is exposed via ABCI. +But this will take some time as it needs to be implemented, and has a dependency +on bi-directional ABCI, which is also quite substantial. See +[1](https://github.com/cometbft/cometbft/discussions/1112) and +[2](https://github.com/cometbft/cometbft/discussions/494) discussions. + +We propose to introduce a `nop` (short for no operation) mempool which will effectively act as a stubbed object +internally: + +* it will reject any transaction being locally submitted or gossipped by a peer +* when a _reap_ (as it is currently called) is executed in the mempool, an empty answer will always be returned +* the application running on the proposer validator will add transactions it received + using the appchains's own mechanism via `PrepareProposal`. + +## Alternative Approaches + +These are the alternatives known to date: + +1. Keep the current model. Useful for basic apps, but clearly suboptimal for applications + with their own mechanism to disseminate transactions and particular performance requirements. +2. Provide more efficient general-purpose mempool implementations. + This is an ongoing effort (e.g., [CAT mempool][cat-mempool]), but will take some time, and R&D effort, to come up with + advanced mechanisms -- likely highly configurable and thus complex -- which then will have to be thoroughly tested. +3. A similar approach to this one ([ADR110][adr-110]) whereby the application-specific + mechanism directly interacts with CometBFT via a newly defined gRPC interface. +4. Partially adopting this ADR. There are several possibilities: + * Use the current mempool, disable transaction broadcast in `config.toml`, and accept transactions from users via `BroadcastTX*` RPC methods. + Positive: avoids transaction gossiping; app can reuse the mempool existing in ComeBFT. + Negative: requires clients to know the validators' RPC endpoints (potential security issues). + * Transaction broadcast is disabled in `config.toml`, and have the application always reject transactions in `CheckTx`. + Positive: effectively disables the mempool; does not require modifications to Comet (may be used in `v0.37.x` and `v0.38.x`). + Negative: requires apps to disseminate txs themselves; the setup for this is less straightforward than this ADR's proposal. + +## Decision + +TBD + +## Detailed Design + +What this ADR proposes can already be achieved with an unmodified CometBFT since +`v0.37.x`, albeit with a complex, poor UX (see the last alternative in section +[Alternative Approaches](#alternative-approaches)). The core of this proposal +is to make some internal changes so it is clear an simple for app developers, +thus improving the UX. + +#### `nop` Mempool + +We propose a new mempool implementation, called `nop` Mempool, that effectively disables all mempool functionality +within CometBFT. +The `nop` Mempool implements the `Mempool` interface in a very simple manner: + +* `CheckTx(tx types.Tx) (*abcicli.ReqRes, error)`: returns `nil, ErrNotAllowed` +* `RemoveTxByKey(txKey types.TxKey) error`: returns `ErrNotAllowed` +* `ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs`: returns `nil` +* `ReapMaxTxs(max int) types.Txs`: returns `nil` +* `Lock()`: does nothing +* `Unlock()`: does nothing +* `Update(...) error`: returns `nil` +* `FlushAppConn() error`: returns `nil` +* `Flush()`: does nothing +* `TxsAvailable() <-chan struct{}`: returns `nil` +* `EnableTxsAvailable()`: does nothing +* `SetTxRemovedCallback(cb func(types.TxKey))`: does nothing +* `Size() int` returns 0 +* `SizeBytes() int64` returns 0 + +Upon startup, the `nop` mempool reactor will advertise no channels to the peer-to-peer layer. + +### Configuration + +We propose the following changes to the `config.toml` file: + +```toml +[mempool] +# The type of mempool for this CometBFT node to use. +# +# Valid types of mempools supported by CometBFT: +# - "flood" : clist mempool with flooding gossip protocol (default) +# - "nop" : nop-mempool (app has implemented an alternative tx dissemination mechanism) +type = "nop" +``` + +The config validation logic will be modified to add a new rule that rejects a configuration file +if all of these conditions are met: + +* the mempool is set to `nop` +* `create_empty_blocks`, in `consensus` section, is set to `false`. + +The reason for this extra validity rule is that the `nop`-mempool, as proposed here, +does not support the "do not create empty blocks" functionality. +Here are some considerations on this: + +* The "do not create empty blocks" functionality + * entangles the consensus and mempool reactors + * is hardly used in production by real appchains (to the best of CometBFT team's knowledge) + * its current implementation for the built-in mempool has undesired side-effects + * app hashes currently refer to the previous block, + * and thus it interferes with query provability. +* If needed in the future, this can be supported by extending ABCI, + but we will first need to see a real need for this before committing to changing ABCI + (which has other, higher-impact changes waiting to be prioritized). + +### RPC Calls + +There are no changes needed in the code dealing with RPC. Those RPC paths that call methods of the `Mempool` interface, +will simply be calling the new implementation. + +### Impacted Workflows + +* *Submitting a transaction*. Users are not to submit transactions via CometBFT's RPC. + `BroadcastTx*` RPC methods will fail with a reasonable error and the 501 status code. + The application running on a full node must offer an interface for users to submit new transactions. + It could also be a distinct node (or set of nodes) in the network. + These considerations are exclusively the application's concern in this approach. +* *Time to propose a block*. The consensus reactor will call `ReapMaxBytesMaxGas` which will return a `nil` slice. + `RequestPrepareProposal` will thus contain no transactions. +* *Consensus waiting for transactions to become available*. `TxsAvailable()` returns `nil`. + `cs.handleTxsAvailable()` won't ever be executed. + At any rate, a configuration with the `nop` mempool and `create_empty_blocks` set to `false` + will be rejected in the first place. +* *A new block is decided*. + * When `Update` is called, nothing is done (no decided transaction is removed). + * Locking and unlocking the mempool has no effect. +* *ABCI mempool's connection* + CometBFT will still open a "mempool" connection, even though it won't be used. + This is to avoid doing lots of breaking changes. + +### Impact on Current Release Plans + +The changes needed for this approach, are fairly simple, and the logic is clear. +This might allow us to even deliver it as part of CometBFT `v1` (our next release) +even without a noticeable impact on `v1`'s delivery schedule. + +The CometBFT team (learning from past dramatic events) usually takes a conservative approach +for backporting changes to release branches that have already undergone a full QA cycle +(and thus are in code-freeze mode). +For this reason, although the limited impact of these changes would limit the risks +of backporting to `v0.38.x` and `v0.37.x`, a careful risk/benefit evaluation will +have to be carried out. + +Backporting to `v0.34.x` does not make sense as this version predates the release of `ABCI 1.0`, +so using the `nop` mempool renders CometBFT's operation useless. + +### Config parameter _vs._ application-enforced parameter + +In the current proposal, the parameter selecting the mempool is in `config.toml`. +However, it is not a clear-cut decision. These are the alternatives we see: + +* *Mempool selected in `config.toml` (our current design)*. + This is the way the mempool has always been selected in Tendermint Core and CometBFT, + in those versions where there were more than one mempool to choose from. + As the configuration is in `config.toml`, it is up to the node operators to configure their + nodes consistently, via social consensus. However this cannot be guaranteed. + A network with an inconsistent choice of mempool at different nodes might + result in undesirable side effects, such as peers disconnecting from nodes + that sent them messages via the mempool channel. +* *Mempool selected as a network-wide parameter*. + A way to prevent any inconsistency when selecting the mempool is to move the configuration out of `config.toml` + and have it as a network-wide application-enforced parameter, implemented in the same way as Consensus Params. + The Cosmos community may not be ready for such a rigid, radical change, + even if it eliminates the risk of operators shooting themselves in the foot. + Hence we went currently favor the previous alternative. +* *Mempool selected as a network-wide parameter, but allowing override*. + A third option, half way between the previous two, is to have the mempool selection + as a network-wide parameter, but with a special value called _local-config_ that still + allows an appchain to decide to leave it up to operators to configure it in `config.toml`. + +Ultimately, the "config parameter _vs._ application-enforced parameter" discussion +is a more general one that is applicable to other parameters not related to mempool selection. +In that sense, it is out of the scope of this ADR. + +## Consequences + +### Positive + +- Applications can now find mempool mechanisms that fit better their particular needs: + - Ad-hoc ways to add, remove, merge, reorder, modify, prioritize transactions according + to application needs. + - A way to disseminate transactions (gossip-based or other) to get the submitted transactions + to proposers. The application developers can devise simpler, efficient mechanisms tailored + to their application. + - Back-pressure mechanisms to prevent malicious users from abusing the transaction + dissemination mechanism. +- In this approach, CometBFT's peer-to-peer layer is relieved from managing transaction gossip, freeing up its resources for other reactors such as consensus, evidence, block-sync, or state-sync. +- There is no risk for the operators of a network to provide inconsistent configurations + for some mempool-related parameters. Some of those misconfigurations are known to have caused + serious performance issues in CometBFT's peer to peer network. + Unless, of course, the application-defined transaction dissemination mechanism ends up + allowing similar configuration inconsistencies. +- The interaction between the application and CometBFT at `PrepareProposal` time + is simplified. No transactions are ever provided by CometBFT, + and no transactions can ever be left in the mempool when CometBFT calls `PrepareProposal`: + the application trivially has all the information. +- UX is improved compared to how this can be done prior to this ADR. + +### Negative + +- With the `nop` mempool, it is up to the application to provide users with a way + to submit transactions and deliver those transactions to validators. + This is a considerable endeavor, and more basic appchains may consider it is not worth the hassle. +- There is a risk of wasting resources by those nodes that have a misconfigured + mempool (bandwidth, CPU, memory, etc). If there are TXs submitted (incorrectly) + via CometBFT's RPC, but those TXs are never submitted (correctly via an + app-specific interface) to the App. As those TXs risk being there until the node + is stopped. Moreover, those TXs will be replied & proposed every single block. + App developers will need to keep this in mind and panic on `CheckTx` or + `PrepareProposal` with non-empty list of transactions. +- Optimizing block proposals by only including transaction IDs (e.g. TX hashes) is more difficult. + The ABCI app could do it by submitting TX hashes (rather than TXs themselves) + in `PrepareProposal`, and then having a mechanism for pulling TXs from the + network upon `FinalizeBlock`. + +[sdk-app-mempool]: https://docs.cosmos.network/v0.47/build/building-apps/app-mempool +[adr-110]: https://github.com/cometbft/cometbft/pull/1565 +[HT94]: https://dl.acm.org/doi/book/10.5555/866693 +[cat-mempool]: https://github.com/cometbft/cometbft/pull/1472 \ No newline at end of file diff --git a/docs/core/configuration.md b/docs/core/configuration.md index cf88034d91c..2f9f011683e 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -294,6 +294,16 @@ dial_timeout = "3s" # 2) "v1" - prioritized mempool (deprecated; will be removed in the next release). version = "v0" +# The type of mempool for this node to use. +# +# Possible types: +# - "flood" : concurrent linked list mempool with flooding gossip protocol +# (default) +# - "nop" : nop-mempool (short for no operation; the ABCI app is responsible +# for storing, disseminating and proposing txs). "create_empty_blocks=false" is +# not supported. +type = "flood" + recheck = true broadcast = true wal_dir = "" @@ -487,6 +497,7 @@ namespace = "cometbft" ``` ## Empty blocks VS no empty blocks + ### create_empty_blocks = true If `create_empty_blocks` is set to `true` in your config, blocks will be created ~ every second (with default consensus parameters). You can regulate the delay between blocks by changing the `timeout_commit`. E.g. `timeout_commit = "10s"` should result in ~ 10 second blocks. @@ -500,6 +511,7 @@ Note after the block H, CometBFT creates something we call a "proof block" (only Plus, if you set `create_empty_blocks_interval` to something other than the default (`0`), CometBFT will be creating empty blocks even in the absence of transactions every `create_empty_blocks_interval.` For instance, with `create_empty_blocks = false` and `create_empty_blocks_interval = "30s"`, CometBFT will only create blocks if there are transactions, or after waiting 30 seconds without receiving any transactions. ## Consensus timeouts explained + There's a variety of information about timeouts in [Running in production](./running-in-production.md#configuration-parameters). You can also find more detailed explanation in the paper describing @@ -518,6 +530,7 @@ timeout_precommit = "1s" timeout_precommit_delta = "500ms" timeout_commit = "1s" ``` + Note that in a successful round, the only timeout that we absolutely wait no matter what is `timeout_commit`. Here's a brief summary of the timeouts: diff --git a/docs/core/mempool.md b/docs/core/mempool.md index 8dd96878196..f86083ee04d 100644 --- a/docs/core/mempool.md +++ b/docs/core/mempool.md @@ -4,7 +4,41 @@ order: 12 # Mempool -## Transaction ordering +A mempool (a contraction of memory and pool) is a node’s data structure for +storing information on uncommitted transactions. It acts as a sort of waiting +room for transactions that have not yet been committed. + +CometBFT currently supports two types of mempools: `flood` and `nop`. + +## 1. Flood + +The `flood` mempool stores transactions in a concurrent linked list. When a new +transaction is received, it first checks if there's a space for it (`size` and +`max_txs_bytes` config options) and that it's not too big (`max_tx_bytes` config +option). Then, it checks if this transaction has already been seen before by using +an LRU cache (`cache_size` regulates the cache's size). If all checks pass and +the transaction is not in the cache (meaning it's new), the ABCI +[`CheckTxAsync`][1] method is called. The ABCI application validates the +transaction using its own rules. + +If the transaction is deemed valid by the ABCI application, it's added to the linked list. + +The mempool's name (`flood`) comes from the dissemination mechanism. When a new +transaction is added to the linked list, the mempool sends it to all connected +peers. Peers themselves gossip this transaction to their peers and so on. One +can say that each transaction "floods" the network, hence the name `flood`. + +Note there are experimental config options +`experimental_max_gossip_connections_to_persistent_peers` and +`experimental_max_gossip_connections_to_non_persistent_peers` to limit the +number of peers a transaction is broadcasted to. Also, you can turn off +broadcasting with `broadcast` config option. + +After each committed block, CometBFT rechecks all uncommitted transactions (can +be disabled with the `recheck` config option) by repeatedly calling the ABCI +`CheckTxAsync`. + +### Transaction ordering Currently, there's no ordering of transactions other than the order they've arrived (via RPC or from other nodes). @@ -46,3 +80,24 @@ order/nonce/sequence number, the application can reject transactions that are out of order. So if a node receives `tx3`, then `tx1`, it can reject `tx3` and then accept `tx1`. The sender can then retry sending `tx3`, which should probably be rejected until the node has seen `tx2`. + +## 2. Nop + +`nop` (short for no operation) mempool is used when the ABCI application developer wants to +build their own mempool. When `type = "nop"`, transactions are not stored anywhere +and are not gossiped to other peers using the P2P network. + +Submitting a transaction via the existing RPC methods (`BroadcastTxSync`, +`BroadcastTxAsync`, and `BroadcastTxCommit`) will always result in an error. + +Because there's no way for the consensus to know if transactions are available +to be committed, the node will always create blocks, which can be empty +sometimes. Using `consensus.create_empty_blocks=false` is prohibited in such +cases. + +The ABCI application becomes responsible for storing, disseminating, and +proposing transactions using [`PrepareProposal`][2]. The concrete design is up +to the ABCI application developers. + +[1]: ../../spec/abci/abci++_methods.md#checktx +[2]: ../../spec/abci/abci++_methods.md#prepareproposal \ No newline at end of file diff --git a/mempool/nop_mempool.go b/mempool/nop_mempool.go new file mode 100644 index 00000000000..4a55a620d8d --- /dev/null +++ b/mempool/nop_mempool.go @@ -0,0 +1,110 @@ +package mempool + +import ( + "errors" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/service" + "github.com/cometbft/cometbft/p2p" + "github.com/cometbft/cometbft/types" +) + +// NopMempool is a mempool that does nothing. +// +// The ABCI app is responsible for storing, disseminating, and proposing transactions. +// See [ADR-111](../docs/architecture/adr-111-nop-mempool.md). +type NopMempool struct{} + +// errNotAllowed indicates that the operation is not allowed with `nop` mempool. +var errNotAllowed = errors.New("not allowed with `nop` mempool") + +var _ Mempool = &NopMempool{} + +// CheckTx always returns an error. +func (*NopMempool) CheckTx(types.Tx, func(*abci.Response), TxInfo) error { + return errNotAllowed +} + +// RemoveTxByKey always returns an error. +func (*NopMempool) RemoveTxByKey(types.TxKey) error { return errNotAllowed } + +// ReapMaxBytesMaxGas always returns nil. +func (*NopMempool) ReapMaxBytesMaxGas(int64, int64) types.Txs { return nil } + +// ReapMaxTxs always returns nil. +func (*NopMempool) ReapMaxTxs(int) types.Txs { return nil } + +// Lock does nothing. +func (*NopMempool) Lock() {} + +// Unlock does nothing. +func (*NopMempool) Unlock() {} + +// Update does nothing. +func (*NopMempool) Update( + int64, + types.Txs, + []*abci.ResponseDeliverTx, + PreCheckFunc, + PostCheckFunc, +) error { + return nil +} + +// FlushAppConn does nothing. +func (*NopMempool) FlushAppConn() error { return nil } + +// Flush does nothing. +func (*NopMempool) Flush() {} + +// TxsAvailable always returns nil. +func (*NopMempool) TxsAvailable() <-chan struct{} { + return nil +} + +// EnableTxsAvailable does nothing. +func (*NopMempool) EnableTxsAvailable() {} + +// SetTxRemovedCallback does nothing. +func (*NopMempool) SetTxRemovedCallback(func(txKey types.TxKey)) {} + +// Size always returns 0. +func (*NopMempool) Size() int { return 0 } + +// SizeBytes always returns 0. +func (*NopMempool) SizeBytes() int64 { return 0 } + +// NopMempoolReactor is a mempool reactor that does nothing. +type NopMempoolReactor struct { + service.BaseService +} + +// NewNopMempoolReactor returns a new `nop` reactor. +// +// To be used only in RPC. +func NewNopMempoolReactor() *NopMempoolReactor { + return &NopMempoolReactor{*service.NewBaseService(nil, "NopMempoolReactor", nil)} +} + +var _ p2p.Reactor = &NopMempoolReactor{} + +// WaitSync always returns false. +func (*NopMempoolReactor) WaitSync() bool { return false } + +// GetChannels always returns nil. +func (*NopMempoolReactor) GetChannels() []*p2p.ChannelDescriptor { return nil } + +// AddPeer does nothing. +func (*NopMempoolReactor) AddPeer(p2p.Peer) {} + +// InitPeer always returns nil. +func (*NopMempoolReactor) InitPeer(p2p.Peer) p2p.Peer { return nil } + +// RemovePeer does nothing. +func (*NopMempoolReactor) RemovePeer(p2p.Peer, interface{}) {} + +// Receive does nothing. +func (*NopMempoolReactor) ReceiveEnvelope(p2p.Envelope) {} + +// SetSwitch does nothing. +func (*NopMempoolReactor) SetSwitch(*p2p.Switch) {} diff --git a/mempool/nop_mempool_test.go b/mempool/nop_mempool_test.go new file mode 100644 index 00000000000..01b169e0695 --- /dev/null +++ b/mempool/nop_mempool_test.go @@ -0,0 +1,38 @@ +package mempool + +import ( + "testing" + + "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/assert" +) + +var tx = types.Tx([]byte{0x01}) + +func TestNopMempool_Basic(t *testing.T) { + mem := &NopMempool{} + + assert.Equal(t, 0, mem.Size()) + assert.Equal(t, int64(0), mem.SizeBytes()) + + err := mem.CheckTx(tx, nil, TxInfo{}) + assert.Equal(t, errNotAllowed, err) + + err = mem.RemoveTxByKey(tx.Key()) + assert.Equal(t, errNotAllowed, err) + + txs := mem.ReapMaxBytesMaxGas(0, 0) + assert.Nil(t, txs) + + txs = mem.ReapMaxTxs(0) + assert.Nil(t, txs) + + err = mem.FlushAppConn() + assert.NoError(t, err) + + err = mem.Update(0, nil, nil, nil, nil) + assert.NoError(t, err) + + txsAvailable := mem.TxsAvailable() + assert.Nil(t, txsAvailable) +} diff --git a/node/node.go b/node/node.go index 868bd8ea008..636a9d968f4 100644 --- a/node/node.go +++ b/node/node.go @@ -494,52 +494,62 @@ func createMempoolAndMempoolReactor( memplMetrics *mempl.Metrics, logger log.Logger, ) (mempl.Mempool, p2p.Reactor) { - switch config.Mempool.Version { - case cfg.MempoolV1: - mp := mempoolv1.NewTxMempool( - logger, - config.Mempool, - proxyApp.Mempool(), - state.LastBlockHeight, - mempoolv1.WithMetrics(memplMetrics), - mempoolv1.WithPreCheck(sm.TxPreCheck(state)), - mempoolv1.WithPostCheck(sm.TxPostCheck(state)), - ) - - reactor := mempoolv1.NewReactor( - config.Mempool, - mp, - ) - if config.Consensus.WaitForTxs() { - mp.EnableTxsAvailable() - } - - return mp, reactor + switch config.Mempool.Type { + // allow empty string for backward compatibility + case cfg.MempoolTypeFlood, "": + switch config.Mempool.Version { + case cfg.MempoolV1: + mp := mempoolv1.NewTxMempool( + logger, + config.Mempool, + proxyApp.Mempool(), + state.LastBlockHeight, + mempoolv1.WithMetrics(memplMetrics), + mempoolv1.WithPreCheck(sm.TxPreCheck(state)), + mempoolv1.WithPostCheck(sm.TxPostCheck(state)), + ) + + reactor := mempoolv1.NewReactor( + config.Mempool, + mp, + ) + if config.Consensus.WaitForTxs() { + mp.EnableTxsAvailable() + } - case cfg.MempoolV0: - mp := mempoolv0.NewCListMempool( - config.Mempool, - proxyApp.Mempool(), - state.LastBlockHeight, - mempoolv0.WithMetrics(memplMetrics), - mempoolv0.WithPreCheck(sm.TxPreCheck(state)), - mempoolv0.WithPostCheck(sm.TxPostCheck(state)), - ) + return mp, reactor + + case cfg.MempoolV0: + mp := mempoolv0.NewCListMempool( + config.Mempool, + proxyApp.Mempool(), + state.LastBlockHeight, + mempoolv0.WithMetrics(memplMetrics), + mempoolv0.WithPreCheck(sm.TxPreCheck(state)), + mempoolv0.WithPostCheck(sm.TxPostCheck(state)), + ) + + mp.SetLogger(logger) + + reactor := mempoolv0.NewReactor( + config.Mempool, + mp, + ) + if config.Consensus.WaitForTxs() { + mp.EnableTxsAvailable() + } - mp.SetLogger(logger) + return mp, reactor - reactor := mempoolv0.NewReactor( - config.Mempool, - mp, - ) - if config.Consensus.WaitForTxs() { - mp.EnableTxsAvailable() + default: + return nil, nil } - - return mp, reactor - + case cfg.MempoolTypeNop: + // Strictly speaking, there's no need to have a `mempl.NopMempoolReactor`, but + // adding it leads to a cleaner code. + return &mempl.NopMempool{}, mempl.NewNopMempoolReactor() default: - return nil, nil + panic(fmt.Sprintf("unknown mempool type: %q", config.Mempool.Type)) } } @@ -705,7 +715,9 @@ func createSwitch(config *cfg.Config, p2p.SwitchPeerFilters(peerFilters...), ) sw.SetLogger(p2pLogger) - sw.AddReactor("MEMPOOL", mempoolReactor) + if config.Mempool.Type != cfg.MempoolTypeNop { + sw.AddReactor("MEMPOOL", mempoolReactor) + } sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) sw.AddReactor("EVIDENCE", evidenceReactor) @@ -933,11 +945,10 @@ func NewNodeWithContext(ctx context.Context, logNodeStartupInfo(state, pubKey, logger, consensusLogger) - // Make MempoolReactor mempool, mempoolReactor := createMempoolAndMempoolReactor(config, proxyApp, state, memplMetrics, logger) - // Make Evidence Reactor evidenceReactor, evidencePool, err := createEvidenceReactor(config, dbProvider, stateDB, blockStore, logger) + if err != nil { return nil, err } @@ -971,6 +982,7 @@ func NewNodeWithContext(ctx context.Context, } else if blockSync { csMetrics.BlockSyncing.Set(1) } + consensusReactor, consensusState := createConsensusReactor( config, state, blockExec, blockStore, mempool, evidencePool, privValidator, csMetrics, stateSync || blockSync, eventBus, consensusLogger, @@ -997,10 +1009,8 @@ func NewNodeWithContext(ctx context.Context, return nil, err } - // Setup Transport. transport, peerFilters := createTransport(config, nodeInfo, nodeKey, proxyApp) - // Setup Switch. p2pLogger := logger.With("module", "p2p") sw := createSwitch( config, transport, p2pMetrics, peerFilters, mempoolReactor, bcReactor, From 656c1e49423eec9d0550f6aa4191324422457e53 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:29:00 +0400 Subject: [PATCH 53/87] docs: various small improvements (part 2) (backport #1683) (#1686) * docs: various small improvements (part 2) (#1683) * docs: fix the number of connections CometBFT opens to the ABCI app * docs: fix link formatting * docs: make it explicit that default is TCP for ABCI * docs: replace 'supermajority' with 2/3+ 2/3+ is easier to understand. also this is the first time the reader is seeing the word 'supermajority', which might be confusing without the defition. The defition for supermajority is absent (maybe it's in the spec though) * add a link to jq * suggestions from Lasaro (cherry picked from commit 96abadac106f2c0c4d8f42170f9fa5cf16f6d6e3) # Conflicts: # docs/app-dev/indexing-transactions.md # docs/core/block-structure.md * fixes after merge --------- Co-authored-by: Anton Kaliaev --- docs/app-dev/abci-cli.md | 2 +- docs/app-dev/indexing-transactions.md | 40 ++++++++++++++------------- docs/core/block-structure.md | 19 +++++++------ docs/core/state-sync.md | 4 ++- docs/core/using-cometbft.md | 6 ++-- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/docs/app-dev/abci-cli.md b/docs/app-dev/abci-cli.md index 322d9fc2da5..e9ac6adfe6b 100644 --- a/docs/app-dev/abci-cli.md +++ b/docs/app-dev/abci-cli.md @@ -259,7 +259,7 @@ You could put the commands in a file and run Note that the `abci-cli` is designed strictly for testing and debugging. In a real deployment, the role of sending messages is taken by CometBFT, which -connects to the app using three separate connections, each with its own +connects to the app using four separate connections, each with its own pattern of messages. For examples of running an ABCI app with CometBFT, see the diff --git a/docs/app-dev/indexing-transactions.md b/docs/app-dev/indexing-transactions.md index da191fb8393..0c9922527eb 100644 --- a/docs/app-dev/indexing-transactions.md +++ b/docs/app-dev/indexing-transactions.md @@ -14,11 +14,7 @@ the block itself is never stored. Each event contains a type and a list of attributes, which are key-value pairs denoting something about what happened during the method's execution. For more -details on `Events`, see the - -[ABCI](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/abci/abci++_basic_concepts.md#events) - -documentation. +details on `Events`, see the [ABCI][abci-events] documentation. An `Event` has a composite key associated with it. A `compositeKey` is constructed by its type and key separated by a dot. @@ -36,7 +32,7 @@ would be equal to the composite key of `jack.account.number`. By default, CometBFT will index all transactions by their respective hashes and height and blocks by their height. -CometBFT allows for different events within the same height to have +CometBFT allows for different events within the same height to have equal attributes. ## Configuration @@ -74,8 +70,9 @@ entirely in the future. **Implementation and data layout** -The kv indexer stores each attribute of an event individually, by creating a composite key +The kv indexer stores each attribute of an event individually, by creating a composite key with + - event type, - attribute key, - attribute value, @@ -83,7 +80,7 @@ with - the height, and - event counter. For example the following events: - + ``` Type: "transfer", Attributes: []abci.EventAttribute{ @@ -94,7 +91,7 @@ Type: "transfer", }, ``` - + ``` Type: "transfer", Attributes: []abci.EventAttribute{ @@ -105,7 +102,7 @@ Type: "transfer", }, ``` -will be represented as follows in the store, assuming these events result from the EndBlock call for height 1: +will be represented as follows in the store, assuming these events result from the EndBlock call for height 1: ``` Key value @@ -121,10 +118,11 @@ transferBalance200EndBlock12 1 transferNodeNothingEndblock12 1 ``` -The event number is a local variable kept by the indexer and incremented when a new event is processed. -It is an `int64` variable and has no other semantics besides being used to associate attributes belonging to the same events within a height. + +The event number is a local variable kept by the indexer and incremented when a new event is processed. +It is an `int64` variable and has no other semantics besides being used to associate attributes belonging to the same events within a height. This variable is not atomically incremented as event indexing is deterministic. **Should this ever change**, the event id generation -will be broken. +will be broken. #### PostgreSQL @@ -237,18 +235,22 @@ curl "localhost:26657/block_search?query=\"block.height > 10 AND val_set.num_cha ``` -Storing the event sequence was introduced in CometBFT 0.34.26. Before that, up until Tendermint Core 0.34.26, -the event sequence was not stored in the kvstore and events were stored only by height. That means that queries -returned blocks and transactions whose event attributes match within the height but can match across different -events on that height. +Storing the event sequence was introduced in CometBFT 0.34.26. Before that, up until Tendermint Core 0.34.26, +the event sequence was not stored in the kvstore and events were stored only by height. That means that queries +returned blocks and transactions whose event attributes match within the height but can match across different +events on that height. This behavior was fixed with CometBFT 0.34.26+. However, if the data was indexed with earlier versions of Tendermint Core and not re-indexed, that data will be queried as if all the attributes within a height occurred within the same event. # Event attribute value types + Users can use anything as an event value. However, if the even attrbute value is a number, the following restrictions apply: + - Negative numbers will not be properly retrieved when querying the indexer - When querying the events using `tx_search` and `block_search`, the value given as part of the condition cannot be a float. - Any event value retrieved from the database will be represented as a `BigInt` (from `math/big`) -- Floating point values are not read from the database even with the introduction of `BigInt`. This was intentionally done -to keep the same beheaviour as was historically present and not introduce breaking changes. This will be fixed in the 0.38 series. \ No newline at end of file +- Floating point values are not read from the database even with the introduction of `BigInt`. This was intentionally done +to keep the same beheaviour as was historically present and not introduce breaking changes. This will be fixed in the 0.38 series. + +[abci-events]: https://github.com/cometbft/cometbft/blob/v0.37.x/spec/abci/abci++_basic_concepts.md#events diff --git a/docs/core/block-structure.md b/docs/core/block-structure.md index 9d9a983c028..ef904da7827 100644 --- a/docs/core/block-structure.md +++ b/docs/core/block-structure.md @@ -4,13 +4,16 @@ order: 8 # Block Structure -The CometBFT consensus engine records all agreements by a -supermajority of nodes into a blockchain, which is replicated among all -nodes. This blockchain is accessible via various RPC endpoints, mainly -`/block?height=` to get the full block, as well as -`/blockchain?minHeight=_&maxHeight=_` to get a list of headers. But what -exactly is stored in these blocks? +The CometBFT consensus engine records all agreements by a 2/3+ of nodes +into a blockchain, which is replicated among all nodes. This blockchain is +accessible via various RPC endpoints, mainly `/block?height=` to get the full +block, as well as `/blockchain?minHeight=_&maxHeight=_` to get a list of +headers. But what exactly is stored in these blocks? -The [specification](https://github.com/cometbft/cometbft/blob/v0.37.x/spec/core/data_structures.md) contains a detailed description of each component - that's the best place to get started. +The [specification][data_structures] contains a detailed description of each +component - that's the best place to get started. -To dig deeper, check out the [types package documentation](https://godoc.org/github.com/cometbft/cometbft/types). +To dig deeper, check out the [types package documentation][types]. + +[data_structures]: https://github.com/cometbft/cometbft/blob/v0.37.x/spec/core/data_structures.md +[types]: https://pkg.go.dev/github.com/cometbft/cometbft/types diff --git a/docs/core/state-sync.md b/docs/core/state-sync.md index 8400a6ef9de..8237e109578 100644 --- a/docs/core/state-sync.md +++ b/docs/core/state-sync.md @@ -30,7 +30,7 @@ The next information you will need to acquire it through publicly exposed RPC's - `trust_period`: Trust period is the period in which headers can be verified. > :warning: This value should be significantly smaller than the unbonding period. -If you are relying on publicly exposed RPC's to get the need information, you can use `curl`. +If you are relying on publicly exposed RPC's to get the need information, you can use `curl` and [`jq`][jq]. Example: @@ -46,3 +46,5 @@ The response will be: "hash": "188F4F36CBCD2C91B57509BBF231C777E79B52EE3E0D90D06B1A25EB16E6E23D" } ``` + +[jq]: https://jqlang.github.io/jq/ diff --git a/docs/core/using-cometbft.md b/docs/core/using-cometbft.md index 0c8906096cb..c81124a0e0c 100644 --- a/docs/core/using-cometbft.md +++ b/docs/core/using-cometbft.md @@ -130,7 +130,7 @@ cometbft node ``` By default, CometBFT will try to connect to an ABCI application on -`127.0.0.1:26658`. If you have the `kvstore` ABCI app installed, run it in +`tcp://127.0.0.1:26658`. If you have the `kvstore` ABCI app installed, run it in another window. If you don't, kill CometBFT and run an in-process version of the `kvstore` app: @@ -139,8 +139,8 @@ cometbft node --proxy_app=kvstore ``` After a few seconds, you should see blocks start streaming in. Note that blocks -are produced regularly, even if there are no transactions. See _No Empty -Blocks_, below, to modify this setting. +are produced regularly, even if there are no transactions. See [No Empty +Blocks](#no-empty-blocks), below, to modify this setting. CometBFT supports in-process versions of the `counter`, `kvstore`, and `noop` apps that ship as examples with `abci-cli`. It's easy to compile your app From 7dae514272b2eb58cd1d98f4584c26871c3b5749 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:50:17 -0300 Subject: [PATCH 54/87] Do not block indefinitely on the semaphore (backport #1654) (#1689) * Do not block indefinitely on the semaphore (#1654) * Do not block indefinitely on the semaphore * Cancel the context, irrespective of the flow followed * Makes the code more readable * Improving comment * make linter happy * Updating comments to match * Commenting out `select` and leaving it as TODO for when Contexts are more widely used * Cleaned up comments (cherry picked from commit 2679498c9aa1b93f1d92f1104ec35e9e6bee6f54) # Conflicts: # config/config.go # config/toml.go # test/e2e/pkg/manifest.go * Backports #1558 and #1584 to 0.38.x (#1592) * Experimental - Reduce # of connections effectively used to gossip transactions out (#1558) * maxpeers for mempool * mempool: fix max_peers bcast routine active flag * Use semaphore to limit concurrency * Rename MaxPeers to MaxOutboundPeers * Add max_outbound_peers to config toml template * Rename in error message * Renams the parameter to highlight its experimental nature. Extend the AddPeer method to return an error. Moves the semaphone to outside the broadcast routine * reverting the addition of error to AddPeer. It fails if the context is done and handling this case will be done some other time, when an actual context is passed into acquire. * reverting the addition of error to AddPeer. It fails if the context is done and handling this case will be done some other time, when an actual context is passed into acquire. * Fixing lint issue * renaming semaphore to something more meaningful * make default value 0, which is the same as the current behavior. 10 is the recommended value. * adding new flag to manifest.go * Adding changelog * Improve the description of the parameter in the generated config file. * Add metric to track the current number of active connections. * Change metric to gauge type and rename it. * e2e: Allow disabling the PEX reactor on all nodes in the testnet * Apply suggestions from code review Co-authored-by: Sergio Mena * Update config/config.go comment * fix lint error * Improve config description * Rename metric (remove experimental prefix) * Add unit test * Improve unit test * Update mempool/reactor.go comment --------- Co-authored-by: Ethan Buchman Co-authored-by: Daniel Cason Co-authored-by: lasarojc Co-authored-by: hvanz Co-authored-by: Andy Nogueira Co-authored-by: Sergio Mena * Updating test file, leaving it broken for now * mempool: Limit gossip connections to persistent and non-persistent peers (experimental) (#1584) * Ignore persistent peers from limiting of outbound connections * Update 1558-experimental-gossip-limiting.md Update changeling * Fix typo in mempool/metrics.go * Use two independent configs and semaphores for persistent and non-persistent peers * Forgot to rename in test * Update metric description * Rename semaphores * Add comment to unit test --------- Co-authored-by: hvanz * Reverting to old way of reporting errors * Reverting change that shouldn't have been included in cherry-pick * Reverting tests to use older functions * fix rebase merge --------- Co-authored-by: Adi Seredinschi Co-authored-by: Ethan Buchman Co-authored-by: Daniel Cason Co-authored-by: hvanz Co-authored-by: Andy Nogueira Co-authored-by: Sergio Mena * Fixes conflict. --------- Co-authored-by: lasaro Co-authored-by: Adi Seredinschi Co-authored-by: Ethan Buchman Co-authored-by: Daniel Cason Co-authored-by: hvanz Co-authored-by: Andy Nogueira Co-authored-by: Sergio Mena --- config/config.go | 9 +++++---- config/toml.go | 9 +++++---- mempool/v0/reactor.go | 41 ++++++++++++++++++++++------------------ test/e2e/pkg/manifest.go | 2 +- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/config/config.go b/config/config.go index 88edd1e3a53..e8a18f02d60 100644 --- a/config/config.go +++ b/config/config.go @@ -780,12 +780,13 @@ type MempoolConfig struct { MaxBatchBytes int `mapstructure:"max_batch_bytes"` // Experimental parameters to limit gossiping txs to up to the specified number of peers. // This feature is only available for the default mempool (version config set to "v0"). - // We use two independent upper values for persistent peers and for non-persistent peers. + // We use two independent upper values for persistent and non-persistent peers. // Unconditional peers are not affected by this feature. // If we are connected to more than the specified number of persistent peers, only send txs to - // the first ExperimentalMaxGossipConnectionsToPersistentPeers of them. If one of those - // persistent peers disconnects, activate another persistent peer. Similarly for non-persistent - // peers, with an upper limit of ExperimentalMaxGossipConnectionsToNonPersistentPeers. + // ExperimentalMaxGossipConnectionsToPersistentPeers of them. If one of those + // persistent peers disconnects, activate another persistent peer. + // Similarly for non-persistent peers, with an upper limit of + // ExperimentalMaxGossipConnectionsToNonPersistentPeers. // If set to 0, the feature is disabled for the corresponding group of peers, that is, the // number of active connections to that group of peers is not bounded. // For non-persistent peers, if enabled, a value of 10 is recommended based on experimental diff --git a/config/toml.go b/config/toml.go index c6c0b39253c..20fd733fa03 100644 --- a/config/toml.go +++ b/config/toml.go @@ -402,12 +402,13 @@ ttl-num-blocks = {{ .Mempool.TTLNumBlocks }} # Experimental parameters to limit gossiping txs to up to the specified number of peers. # This feature is only available for the default mempool (version config set to "v0"). -# We use two independent upper values for persistent peers and for non-persistent peers. +# We use two independent upper values for persistent and non-persistent peers. # Unconditional peers are not affected by this feature. # If we are connected to more than the specified number of persistent peers, only send txs to -# the first experimental_max_gossip_connections_to_persistent_peers of them. If one of those -# persistent peers disconnects, activate another persistent peer. Similarly for non-persistent -# peers, with an upper limit of experimental_max_gossip_connections_to_non_persistent_peers. +# ExperimentalMaxGossipConnectionsToPersistentPeers of them. If one of those +# persistent peers disconnects, activate another persistent peer. +# Similarly for non-persistent peers, with an upper limit of +# ExperimentalMaxGossipConnectionsToNonPersistentPeers. # If set to 0, the feature is disabled for the corresponding group of peers, that is, the # number of active connections to that group of peers is not bounded. # For non-persistent peers, if enabled, a value of 10 is recommended based on experimental diff --git a/mempool/v0/reactor.go b/mempool/v0/reactor.go index b4169d5cf9a..55cf185edce 100644 --- a/mempool/v0/reactor.go +++ b/mempool/v0/reactor.go @@ -157,32 +157,37 @@ func (memR *Reactor) AddPeer(peer p2p.Peer) { go func() { // Always forward transactions to unconditional peers. if !memR.Switch.IsPeerUnconditional(peer.ID()) { + // Depending on the type of peer, we choose a semaphore to limit the gossiping peers. + var peerSemaphore *semaphore.Weighted if peer.IsPersistent() && memR.config.ExperimentalMaxGossipConnectionsToPersistentPeers > 0 { - // Block sending transactions to peer until one of the connections become - // available in the semaphore. - if err := memR.activePersistentPeersSemaphore.Acquire(context.TODO(), 1); err != nil { - memR.Logger.Error("Failed to acquire semaphore: %v", err) - return - } - // Release semaphore to allow other peer to start sending transactions. - defer memR.activePersistentPeersSemaphore.Release(1) - defer memR.mempool.metrics.ActiveOutboundConnections.Add(-1) + peerSemaphore = memR.activePersistentPeersSemaphore + } else if !peer.IsPersistent() && memR.config.ExperimentalMaxGossipConnectionsToNonPersistentPeers > 0 { + peerSemaphore = memR.activeNonPersistentPeersSemaphore } - if !peer.IsPersistent() && memR.config.ExperimentalMaxGossipConnectionsToNonPersistentPeers > 0 { - // Block sending transactions to peer until one of the connections become - // available in the semaphore. - if err := memR.activeNonPersistentPeersSemaphore.Acquire(context.TODO(), 1); err != nil { - memR.Logger.Error("Failed to acquire semaphore: %v", err) - return + if peerSemaphore != nil { + for peer.IsRunning() { + // Block on the semaphore until a slot is available to start gossiping with this peer. + // Do not block indefinitely, in case the peer is disconnected before gossiping starts. + ctxTimeout, cancel := context.WithTimeout(context.TODO(), 30*time.Second) + // Block sending transactions to peer until one of the connections become + // available in the semaphore. + err := peerSemaphore.Acquire(ctxTimeout, 1) + cancel() + + if err != nil { + continue + } + + // Release semaphore to allow other peer to start sending transactions. + defer peerSemaphore.Release(1) + break } - // Release semaphore to allow other peer to start sending transactions. - defer memR.activeNonPersistentPeersSemaphore.Release(1) - defer memR.mempool.metrics.ActiveOutboundConnections.Add(-1) } } memR.mempool.metrics.ActiveOutboundConnections.Add(1) + defer memR.mempool.metrics.ActiveOutboundConnections.Add(-1) memR.broadcastTxRoutine(peer) }() } diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 55b8e23ed4f..2ca6399a8d3 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -81,7 +81,7 @@ type Manifest struct { // Defaults to false (disabled). Prometheus bool `toml:"prometheus"` - // Maximum number of peers to which the node gossip transactions + // Maximum number of peers to which the node gossips transactions ExperimentalMaxGossipConnectionsToPersistentPeers uint `toml:"experimental_max_gossip_connections_to_persistent_peers"` ExperimentalMaxGossipConnectionsToNonPersistentPeers uint `toml:"experimental_max_gossip_connections_to_non_persistent_peers"` } From cce2e5da5b7f54aed1fe6dd36c14404ad941d714 Mon Sep 17 00:00:00 2001 From: lasaro Date: Mon, 27 Nov 2023 15:05:29 -0300 Subject: [PATCH 55/87] Release v0.37.4 (#1699) * version: Bump version to v0.37.4 * Add changelog entry * unclog release * unclog build * Format changelog summary (line wrap) Signed-off-by: Thane Thomson * Add note about reverting minimum Go version change Signed-off-by: Thane Thomson * Build changelog Signed-off-by: Thane Thomson --------- Signed-off-by: Thane Thomson Co-authored-by: Thane Thomson --- .../v0.37.4/bug-fixes/1654-semaphore-wait.md | 3 ++ .../features/1643-nop-mempool.md | 0 .changelog/v0.37.4/summary.md | 9 +++++ CHANGELOG.md | 38 +++++++++++++++++++ version/version.go | 2 +- 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .changelog/v0.37.4/bug-fixes/1654-semaphore-wait.md rename .changelog/{unreleased => v0.37.4}/features/1643-nop-mempool.md (100%) create mode 100644 .changelog/v0.37.4/summary.md diff --git a/.changelog/v0.37.4/bug-fixes/1654-semaphore-wait.md b/.changelog/v0.37.4/bug-fixes/1654-semaphore-wait.md new file mode 100644 index 00000000000..9d0fb80adcc --- /dev/null +++ b/.changelog/v0.37.4/bug-fixes/1654-semaphore-wait.md @@ -0,0 +1,3 @@ +- `[mempool]` Avoid infinite wait in transaction sending routine when + using experimental parameters to limiting transaction gossiping to peers + ([\#1654](https://github.com/cometbft/cometbft/pull/1654)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1643-nop-mempool.md b/.changelog/v0.37.4/features/1643-nop-mempool.md similarity index 100% rename from .changelog/unreleased/features/1643-nop-mempool.md rename to .changelog/v0.37.4/features/1643-nop-mempool.md diff --git a/.changelog/v0.37.4/summary.md b/.changelog/v0.37.4/summary.md new file mode 100644 index 00000000000..a391feeea23 --- /dev/null +++ b/.changelog/v0.37.4/summary.md @@ -0,0 +1,9 @@ +*November 27, 2023* + +This release provides the **nop** mempool for applications that want to build +their own mempool. Using this mempool effectively disables all mempool +functionality in CometBFT, including transaction dissemination and the +`broadcast_tx_*` endpoints. + +Also fixes a small bug in the mempool for an experimental feature, and reverts +the change from v0.37.3 that bumped the minimum Go version to v1.21. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2836c792f85..cc1eac434dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # CHANGELOG +## v0.37.4 + +*November 27, 2023* + +This release provides the **nop** mempool for applications that want to build +their own mempool. Using this mempool effectively disables all mempool +functionality in CometBFT, including transaction dissemination and the +`broadcast_tx_*` endpoints. + +Also fixes a small bug in the mempool for an experimental feature, and reverts +the change from v0.37.3 that bumped the minimum Go version to v1.21. + +### BUG FIXES + +- `[mempool]` Avoid infinite wait in transaction sending routine when + using experimental parameters to limiting transaction gossiping to peers + ([\#1654](https://github.com/cometbft/cometbft/pull/1654)) + +### FEATURES + +- `[mempool]` Add `nop` mempool ([\#1643](https://github.com/cometbft/cometbft/pull/1643)) + + If you want to use it, change mempool's `type` to `nop`: + + ```toml + [mempool] + + # The type of mempool for this node to use. + # + # Possible types: + # - "flood" : concurrent linked list mempool with flooding gossip protocol + # (default) + # - "nop" : nop-mempool (short for no operation; the ABCI app is responsible + # for storing, disseminating and proposing txs). "create_empty_blocks=false" + # is not supported. + type = "nop" + ``` + ## v0.37.3 *November 17, 2023* diff --git a/version/version.go b/version/version.go index 1df77825d47..9b2a4974dde 100644 --- a/version/version.go +++ b/version/version.go @@ -5,7 +5,7 @@ const ( // The default version of TMCoreSemVer is the value used as the // fallback version of CometBFT when not using git describe. // It is formatted with semantic versioning. - TMCoreSemVer = "0.37.3" + TMCoreSemVer = "0.37.4" // ABCISemVer is the semantic version of the ABCI protocol ABCISemVer = "1.0.0" ABCIVersion = ABCISemVer From 936d5cc164efa76a38a76a46714c65e641f58c9c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 2 Dec 2023 06:32:41 +0400 Subject: [PATCH 56/87] types: validate Validator#Address field (backport #1715) (#1722) * types: validate Validator#Address field (#1715) * types: validate Validator#Address field * fix TestProposerSelection3 * add a changelog entry * fix two more tests * Update .changelog/unreleased/improvements/1715-validate-validator-address Co-authored-by: Thane Thomson --------- Co-authored-by: Thane Thomson (cherry picked from commit 63fe7bf675da59a9bb46bc1c61a9c5e8fb7dfa42) # Conflicts: # internal/state/store_test.go # internal/store/store_test.go * fix conflicts * fix test * golint --------- Co-authored-by: Anton Kaliaev --- .../improvements/1715-validate-validator-address | 1 + state/store_test.go | 4 +--- types/validator.go | 5 +++-- types/validator_set_test.go | 14 +++++++++----- types/validator_test.go | 5 +++-- 5 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 .changelog/unreleased/improvements/1715-validate-validator-address diff --git a/.changelog/unreleased/improvements/1715-validate-validator-address b/.changelog/unreleased/improvements/1715-validate-validator-address new file mode 100644 index 00000000000..ec7f2c7da6a --- /dev/null +++ b/.changelog/unreleased/improvements/1715-validate-validator-address @@ -0,0 +1 @@ +- `[types]` Validate `Validator#Address` in `ValidateBasic` ([\#1715](https://github.com/cometbft/cometbft/pull/1715)) diff --git a/state/store_test.go b/state/store_test.go index 72ca2c038c5..a8f38be2df4 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -12,9 +12,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cfg "github.com/cometbft/cometbft/config" - "github.com/cometbft/cometbft/crypto" "github.com/cometbft/cometbft/crypto/ed25519" - cmtrand "github.com/cometbft/cometbft/libs/rand" cmtstate "github.com/cometbft/cometbft/proto/tendermint/state" sm "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/types" @@ -117,7 +115,7 @@ func TestPruneStates(t *testing.T) { // Generate a bunch of state data. Validators change for heights ending with 3, and // parameters when ending with 5. - validator := &types.Validator{Address: cmtrand.Bytes(crypto.AddressSize), VotingPower: 100, PubKey: pk} + validator := &types.Validator{Address: pk.Address(), VotingPower: 100, PubKey: pk} validatorSet := &types.ValidatorSet{ Validators: []*types.Validator{validator}, Proposer: validator, diff --git a/types/validator.go b/types/validator.go index 886b32756d8..3e95c467bc7 100644 --- a/types/validator.go +++ b/types/validator.go @@ -46,8 +46,9 @@ func (v *Validator) ValidateBasic() error { return errors.New("validator has negative voting power") } - if len(v.Address) != crypto.AddressSize { - return fmt.Errorf("validator address is the wrong size: %v", v.Address) + addr := v.PubKey.Address() + if !bytes.Equal(v.Address, addr) { + return fmt.Errorf("validator address is incorrectly derived from pubkey. Exp: %v, got %v", addr, v.Address) } return nil diff --git a/types/validator_set_test.go b/types/validator_set_test.go index d2e734ff388..01e81a95995 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -300,18 +300,22 @@ func TestProposerSelection2(t *testing.T) { } func TestProposerSelection3(t *testing.T) { - vset := NewValidatorSet([]*Validator{ + vals := []*Validator{ newValidator([]byte("avalidator_address12"), 1), newValidator([]byte("bvalidator_address12"), 1), newValidator([]byte("cvalidator_address12"), 1), newValidator([]byte("dvalidator_address12"), 1), - }) + } - proposerOrder := make([]*Validator, 4) for i := 0; i < 4; i++ { - // need to give all validators to have keys pk := ed25519.GenPrivKey().PubKey() - vset.Validators[i].PubKey = pk + vals[i].PubKey = pk + vals[i].Address = pk.Address() + } + sort.Sort(ValidatorsByAddress(vals)) + vset := NewValidatorSet(vals) + proposerOrder := make([]*Validator, 4) + for i := 0; i < 4; i++ { proposerOrder[i] = vset.GetProposer() vset.IncrementProposerPriority(1) } diff --git a/types/validator_test.go b/types/validator_test.go index 5eb2ed7bf1c..954e8ec23bb 100644 --- a/types/validator_test.go +++ b/types/validator_test.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -74,7 +75,7 @@ func TestValidatorValidateBasic(t *testing.T) { Address: nil, }, err: true, - msg: "validator address is the wrong size: ", + msg: fmt.Sprintf("validator address is incorrectly derived from pubkey. Exp: %v, got ", pubKey.Address()), }, { val: &Validator{ @@ -82,7 +83,7 @@ func TestValidatorValidateBasic(t *testing.T) { Address: []byte{'a'}, }, err: true, - msg: "validator address is the wrong size: 61", + msg: fmt.Sprintf("validator address is incorrectly derived from pubkey. Exp: %v, got 61", pubKey.Address()), }, } From b9e4fe609110693f7f05e72eb4e5e6ba36d0fbe0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 07:22:55 -0500 Subject: [PATCH 57/87] fix: increase abci socket message size limit to 2GB (backport #1730) (#1746) * fix: increase abci socket message size limit to 2GB (#1730) * fix: increase abci socket message size to 2GB * fix: added .changelog * Update .changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit Co-authored-by: Anton Kaliaev * fix: use MaxInt32 as message size for 32-bit systems * Update .changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit --------- Co-authored-by: Anton Kaliaev Co-authored-by: Thane Thomson (cherry picked from commit 092b918cadd59e27c220f57481f26b5459a0cb3e) * Rename 1730-increase-abci-socket-message-size-limit to 1730-increase-abci-socket-message-size-limit.md --------- Co-authored-by: Troy Kessler <43882936+troykessler@users.noreply.github.com> Co-authored-by: Thane Thomson --- .../1730-increase-abci-socket-message-size-limit.md | 1 + abci/types/messages.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md diff --git a/.changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md b/.changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md new file mode 100644 index 00000000000..5246eb57f08 --- /dev/null +++ b/.changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md @@ -0,0 +1 @@ +- `[abci]` Increase ABCI socket message size limit to 2GB ([\#1730](https://github.com/cometbft/cometbft/pull/1730): @troykessler) diff --git a/abci/types/messages.go b/abci/types/messages.go index 74c87578514..94b49e1db0d 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -2,13 +2,14 @@ package types import ( "io" + "math" "github.com/cometbft/cometbft/libs/protoio" "github.com/cosmos/gogoproto/proto" ) const ( - maxMsgSize = 104857600 // 100MB + maxMsgSize = math.MaxInt32 // 2GB ) // WriteMessage writes a varint length-delimited protobuf message. From 68cd34aab300aabb3dbc68a6821e0eae330a28e8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 19:46:17 -0300 Subject: [PATCH 58/87] fix: Txs Validate (#1687) (#1752) * fix: The calculation method of tx size returned by calling proxyapp should be consistent with that of mempool * Revert CHANGELOG.md entries should go into .changelog --------- Co-authored-by: lasaro Co-authored-by: lasaro (cherry picked from commit eb5d9ce977605d93188f6498708c52300880f928) Co-authored-by: leven <112051166+lx-xiang@users.noreply.github.com> --- types/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/tx.go b/types/tx.go index 0ba14dbadd0..5cbb2cc40df 100644 --- a/types/tx.go +++ b/types/tx.go @@ -107,7 +107,7 @@ func ToTxs(txl [][]byte) Txs { func (txs Txs) Validate(maxSizeBytes int64) error { var size int64 for _, tx := range txs { - size += int64(len(tx)) + size += ComputeProtoSizeForTxs([]Tx{tx}) if size > maxSizeBytes { return fmt.Errorf("transaction data size exceeds maximum %d", maxSizeBytes) } From bb0c41159496916118bbcb8d7e414dd81dae49ad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:58:22 +0100 Subject: [PATCH 59/87] Update CODE_OF_CONDUCT.md (#1708) (#1767) * Update CODE_OF_CONDUCT.md Updated the contact email to an `informal.systems` one, and some nits. * Update CODE_OF_CONDUCT.md Co-authored-by: Anton Kaliaev --------- Co-authored-by: Anton Kaliaev (cherry picked from commit bde1111bc37aa03e416cce6ae6150bdb2611da58) Co-authored-by: Adi Seredinschi --- CODE_OF_CONDUCT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c25964180e6..3f93f1e5e83 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,13 +1,13 @@ # The CometBFT Code of Conduct -This code of conduct applies to all projects run by the CometBFT/Cosmos team and +This code of conduct applies to all projects run by the CometBFT team and hence to CometBFT. ---- # Conduct -## Contact: conduct@interchain.io +## Contact: conduct@informal.systems * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender, gender identity and @@ -35,7 +35,7 @@ hence to CometBFT. * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community - member, please contact one of the channel admins or the person mentioned above + member, please get in touch with one of the channel admins or the contact address above immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. From 1b0372ecdddab016969f89ecb18997219d5b3148 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:11:40 -0300 Subject: [PATCH 60/87] [e2e] Fixes prepareProposal not to return oversized set of transactions (backport #1756) (#1774) * [e2e] Fixes prepareProposal not to return oversized set of transactions (#1756) * Fixes prepareProposal not to return oversized set of transactions * Update test/e2e/app/app.go * Fix linting error * add changelog entry * Avoid marshalling the tx twice * removing unneeded changelog (cherry picked from commit 0bf3f0a3e8cf4aa7c98d48eef37a3ea25b87c613) # Conflicts: # test/e2e/app/app.go * Solve conflict --------- Co-authored-by: lasaro --- test/e2e/app/app.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index a8325417713..0abdeefb06c 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -13,6 +13,7 @@ import ( "github.com/cometbft/cometbft/abci/example/code" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" + cmttypes "github.com/cometbft/cometbft/types" "github.com/cometbft/cometbft/version" ) @@ -275,10 +276,11 @@ func (app *Application) PrepareProposal( txs := make([][]byte, 0, len(req.Txs)) var totalBytes int64 for _, tx := range req.Txs { - totalBytes += int64(len(tx)) - if totalBytes > req.MaxTxBytes { + txLen := cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{tx}) + if totalBytes+txLen > req.MaxTxBytes { break } + totalBytes += txLen txs = append(txs, tx) } From cefbecd294806d63230f0826104f01d697faaa8c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:05:19 -0300 Subject: [PATCH 61/87] Add test missing in #1687 (backport #1712) (#1760) * Add test missing in #1687 (#1712) * Experimenting the fix from lx-xiang * Fixes name in the example * Reverts fix, so it is merged from the proper branch. * Adds a test that fails because the validate is wrong and accepts a block that is larger than it should. * Add changelog for the original PR * Update internal/state/execution_test.go (cherry picked from commit ce0215c435a356e2b399827c934a448fa47ee476) # Conflicts: # abci/example/kvstore/kvstore.go * Reverting change in ABCI type name * Update type of test function * solving conflict in test * Solve conflicts * remove unused variable * Fix mocking function * revert signature change * Apply type cast * Apply type cast --------- Co-authored-by: lasaro --- .../1687-consensus-fix-block-validation.md | 3 ++ state/execution_test.go | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md diff --git a/.changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md b/.changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md new file mode 100644 index 00000000000..778f0b538b4 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md @@ -0,0 +1,3 @@ +- `[mempool]` The calculation method of tx size returned by calling proxyapp should be consistent with that of mempool + ([\#1687](https://github.com/cometbft/cometbft/pull/1687)) + diff --git a/state/execution_test.go b/state/execution_test.go index 5ae3c3c8723..076e4e05997 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -776,6 +776,59 @@ func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { mp.AssertExpectations(t) } +// TestPrepareProposalCountSerializationOverhead tests that the block creation logic returns +// an error if the ResponsePrepareProposal returned from the application is at the limit of +// its size and will go beyond the limit upon serialization. +func TestPrepareProposalCountSerializationOverhead(t *testing.T) { + const height = 2 + + state, stateDB, privVals := makeState(1, height) + // limit max block size + var bytesPerTx int64 = 4 + const nValidators = 1 + nonDataSize := 5000 - types.MaxDataBytes(5000, 0, nValidators) + state.ConsensusParams.Block.MaxBytes = bytesPerTx*1024 + nonDataSize + maxDataBytes := types.MaxDataBytes(state.ConsensusParams.Block.MaxBytes, 0, nValidators) + + stateStore := sm.NewStore(stateDB, sm.StoreOptions{ + DiscardABCIResponses: false, + }) + + evpool := &mocks.EvidencePool{} + evpool.On("PendingEvidence", mock.Anything).Return([]types.Evidence{}, int64(0)) + + txs := test.MakeNTxs(height, maxDataBytes/bytesPerTx) + mp := &mpmocks.Mempool{} + mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs(txs)) + + app := &abcimocks.Application{} + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(abci.ResponsePrepareProposal{ + Txs: types.Txs(txs).ToSliceOfBytes(), + }, nil) + + cc := proxy.NewLocalClientCreator(app) + proxyApp := proxy.NewAppConns(cc, proxy.NopMetrics()) + err := proxyApp.Start() + require.NoError(t, err) + defer proxyApp.Stop() //nolint:errcheck // ignore for tests + + blockExec := sm.NewBlockExecutor( + stateStore, + log.NewNopLogger(), + proxyApp.Consensus(), + mp, + evpool, + ) + pa, _ := state.Validators.GetByIndex(0) + commit, err := makeValidCommit(height, types.BlockID{}, state.Validators, privVals) + require.NoError(t, err) + block, err := blockExec.CreateProposalBlock(height, state, commit, pa) + require.Nil(t, block) + require.ErrorContains(t, err, "transaction data size exceeds maximum") + + mp.AssertExpectations(t) +} + // TestPrepareProposalErrorOnPrepareProposalError tests when the client returns an error // upon calling PrepareProposal on it. func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { From 4d6b50484ec504b6bc3af162bb42f4b338bab23c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:55:57 -0300 Subject: [PATCH 62/87] build(deps): Bump actions/setup-go from 4 to 5 (#1792) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 6 +++--- .github/workflows/check-generated.yml | 4 ++-- .github/workflows/e2e-long-37x.yml | 2 +- .github/workflows/e2e-manual-multiversion.yml | 2 +- .github/workflows/e2e-manual.yml | 2 +- .github/workflows/e2e-nightly-34x.yml | 2 +- .github/workflows/e2e-nightly-37x.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release-version.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- 16 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e5657361fa..b4d4200820d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: goos: ["linux"] timeout-minutes: 5 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" - uses: actions/checkout@v4 @@ -41,7 +41,7 @@ jobs: needs: build timeout-minutes: 5 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" - uses: actions/checkout@v4 @@ -63,7 +63,7 @@ jobs: needs: build timeout-minutes: 5 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" - uses: actions/checkout@v4 diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 377f3378b7d..026d1d1da60 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -16,7 +16,7 @@ jobs: check-mocks: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" @@ -40,7 +40,7 @@ jobs: check-proto: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" diff --git a/.github/workflows/e2e-long-37x.yml b/.github/workflows/e2e-long-37x.yml index 54d874afecb..845d4df93f5 100644 --- a/.github/workflows/e2e-long-37x.yml +++ b/.github/workflows/e2e-long-37x.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 120 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/e2e-manual-multiversion.yml b/.github/workflows/e2e-manual-multiversion.yml index 771c5675e49..1d0d6cbc274 100644 --- a/.github/workflows/e2e-manual-multiversion.yml +++ b/.github/workflows/e2e-manual-multiversion.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index fd809ebd541..405b6ae0484 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/e2e-nightly-34x.yml b/.github/workflows/e2e-nightly-34x.yml index 66a30b537c5..a7fc8ca6c48 100644 --- a/.github/workflows/e2e-nightly-34x.yml +++ b/.github/workflows/e2e-nightly-34x.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.18' diff --git a/.github/workflows/e2e-nightly-37x.yml b/.github/workflows/e2e-nightly-37x.yml index 8549ede868e..cf0f154ad3a 100644 --- a/.github/workflows/e2e-nightly-37x.yml +++ b/.github/workflows/e2e-nightly-37x.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index ab9f6dc796d..bf33933c1f3 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3cfc0f18605..fdab0049400 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' - uses: actions/checkout@v4 diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index da8320cef94..c70bd40a772 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -13,7 +13,7 @@ jobs: fuzz-nightly-test: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 305170a50c0..4c262bbcc57 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: govulncheck: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" check-latest: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 078316040f0..9ec28079aa1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,7 +21,7 @@ jobs: timeout-minutes: 8 steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' - uses: technote-space/get-diff-action@v6 diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 7c66cbaa3c7..b910d27771d 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/release-version.yml b/.github/workflows/release-version.yml index 73d5e2f8679..96cc598edba 100644 --- a/.github/workflows/release-version.yml +++ b/.github/workflows/release-version.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ae9b172295..5cc48eb265c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ada56192705..bd785b4eb89 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: matrix: part: ["00", "01", "02", "03", "04", "05"] steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "1.21" - uses: actions/checkout@v4 From 8b360e1bb20f61e0d88b5c37114a52402e5474f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:27:31 -0500 Subject: [PATCH 63/87] build(deps): Bump actions/stale from 8 to 9 (#1793) Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 396f41b1aba..35bfb53d77f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: "This pull request has been automatically marked as stale because it has not had From 43cfd0d2dca6ec5328fd15ae25b1d5dd5da03941 Mon Sep 17 00:00:00 2001 From: Sergio Mena Date: Wed, 13 Dec 2023 09:20:49 +0100 Subject: [PATCH 64/87] Introduce `countAllSignatures` in `VerifyCommitLight` & `VerifyCommitLightTrusting` (#1806) * [e2e] Repro evidence bug: not checking all signatures * Introduce `countAllSignatures` in `VerifyCommitLight` & `VerifyCommitLightTrusting` * Revert unneded change * Addressed @insumity's comments --------- Co-authored-by: Jasmina Malicevic --- blocksync/reactor.go | 2 +- evidence/pool.go | 10 ++--- evidence/verify.go | 5 ++- test/e2e/app/app.go | 13 ++++++ test/e2e/runner/evidence.go | 42 ++++++++++++++---- types/validator_set.go | 85 +++++++++++++++++++++++++++++++------ types/validator_set_test.go | 37 +++++++++++++--- 7 files changed, 160 insertions(+), 34 deletions(-) diff --git a/blocksync/reactor.go b/blocksync/reactor.go index 91477ceeb67..ea6937fb02d 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -368,7 +368,7 @@ FOR_LOOP: // NOTE: we can probably make this more efficient, but note that calling // first.Hash() doesn't verify the tx contents, so MakePartSet() is // currently necessary. - err = state.Validators.VerifyCommitLight( + err = state.Validators.VerifyCommitLightAllSignatures( chainID, firstID, first.Height, second.LastCommit) if err == nil { diff --git a/evidence/pool.go b/evidence/pool.go index e36b66db38e..b502341a861 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -132,11 +132,11 @@ func (evpool *Pool) Update(state sm.State, ev types.EvidenceList) { // AddEvidence checks the evidence is valid and adds it to the pool. func (evpool *Pool) AddEvidence(ev types.Evidence) error { - evpool.logger.Debug("Attempting to add evidence", "ev", ev) + evpool.logger.Info("Attempting to add evidence", "ev", ev) // We have already verified this piece of evidence - no need to do it again if evpool.isPending(ev) { - evpool.logger.Debug("Evidence already pending, ignoring this one", "ev", ev) + evpool.logger.Info("Evidence already pending, ignoring this one", "ev", ev) return nil } @@ -144,7 +144,7 @@ func (evpool *Pool) AddEvidence(ev types.Evidence) error { if evpool.isCommitted(ev) { // this can happen if the peer that sent us the evidence is behind so we shouldn't // punish the peer. - evpool.logger.Debug("Evidence was already committed, ignoring this one", "ev", ev) + evpool.logger.Info("Evidence was already committed, ignoring this one", "ev", ev) return nil } @@ -513,13 +513,13 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) { // check if we already have this evidence if evpool.isPending(dve) { - evpool.logger.Debug("evidence already pending; ignoring", "evidence", dve) + evpool.logger.Info("evidence already pending; ignoring", "evidence", dve) continue } // check that the evidence is not already committed on chain if evpool.isCommitted(dve) { - evpool.logger.Debug("evidence already committed; ignoring", "evidence", dve) + evpool.logger.Info("evidence already committed; ignoring", "evidence", dve) continue } diff --git a/evidence/verify.go b/evidence/verify.go index ca0766efda5..a8baab8f40e 100644 --- a/evidence/verify.go +++ b/evidence/verify.go @@ -106,6 +106,7 @@ func (evpool *Pool) verify(evidence types.Evidence) error { // 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 +// - all signatures must be checked as this will be used as evidence // // CONTRACT: must run ValidateBasic() on the evidence before verifying // @@ -115,7 +116,7 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, t // 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) + err := commonVals.VerifyCommitLightTrustingAllSignatures(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel) if err != nil { return fmt.Errorf("skipping verification of conflicting block failed: %w", err) } @@ -127,7 +128,7 @@ func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, 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, + if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLightAllSignatures(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID, e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil { return fmt.Errorf("invalid commit from conflicting block: %w", err) } diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 0abdeefb06c..ed79243b00a 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -163,6 +163,19 @@ func (app *Application) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDelive return abci.ResponseDeliverTx{Code: code.CodeTypeOK} } +func (app *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { + for _, ev := range req.ByzantineValidators { + app.logger.Info("Misbehavior. Slashing validator", + "validator_address", ev.GetValidator().Address, + "type", ev.GetType(), + "height", ev.GetHeight(), + "time", ev.GetTime(), + "total_voting_power", ev.GetTotalVotingPower(), + ) + } + return abci.ResponseBeginBlock{} +} + // EndBlock implements ABCI. func (app *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { valUpdates, err := app.validatorUpdates(uint64(req.Height)) diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index accb63480c4..1eadf5b0a28 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -25,7 +25,7 @@ import ( // 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence const lightClientEvidenceRatio = 4 -// InjectEvidence takes a running testnet and generates an amount of valid +// InjectEvidence takes a running testnet and generates an amount of valid/invalid // evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`. // Evidence is random and can be a mixture of LightClientAttackEvidence and // DuplicateVoteEvidence. @@ -88,10 +88,12 @@ func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amo } var ev types.Evidence - for i := 1; i <= amount; i++ { + for i := 0; i < amount; i++ { + validEv := true if i%lightClientEvidenceRatio == 0 { + validEv = i%(lightClientEvidenceRatio*2) != 0 // Alternate valid and invalid evidence ev, err = generateLightClientAttackEvidence( - ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, + ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, validEv, ) } else { ev, err = generateDuplicateVoteEvidence( @@ -103,7 +105,15 @@ func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amo } _, err := client.BroadcastEvidence(ctx, ev) - if err != nil { + if !validEv { + // The tests will count committed evidences later on, + // and only valid evidences will make it + amount++ + } + if validEv != (err == nil) { + if err == nil { + return errors.New("submitting invalid evidence didn't return an error") + } return err } } @@ -148,6 +158,7 @@ func generateLightClientAttackEvidence( vals *types.ValidatorSet, chainID string, evTime time.Time, + validEvidence bool, ) (*types.LightClientAttackEvidence, error) { // forge a random header forgedHeight := height + 2 @@ -157,7 +168,7 @@ func generateLightClientAttackEvidence( // add a new bogus validator and remove an existing one to // vary the validator set slightly - pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) + pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals, !validEvidence) if err != nil { return nil, err } @@ -172,6 +183,11 @@ func generateLightClientAttackEvidence( return nil, err } + // malleate the last signature of the commit by adding one to its first byte + if !validEvidence { + commit.Signatures[len(commit.Signatures)-1].Signature[0]++ + } + ev := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ SignedHeader: &types.SignedHeader{ @@ -286,7 +302,11 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc } } -func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, +func mutateValidatorSet( + ctx context.Context, + privVals []types.MockPV, + vals *types.ValidatorSet, + nop bool, ) ([]types.PrivValidator, *types.ValidatorSet, error) { newVal, newPrivVal, err := test.Validator(ctx, 10) if err != nil { @@ -294,10 +314,14 @@ func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *type } var newVals *types.ValidatorSet - if vals.Size() > 2 { - newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) + if nop { + newVals = types.NewValidatorSet(vals.Copy().Validators) } else { - newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) + if vals.Size() > 2 { + newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) + } else { + newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) + } } // we need to sort the priv validators with the same index as the validator set diff --git a/types/validator_set.go b/types/validator_set.go index 019834ac1d3..64536ec4879 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -717,10 +717,36 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, // VerifyCommitLight verifies +2/3 of the set had signed the given commit. // -// This method is primarily used by the light client and does not check all the +// This method is primarily used by the light client and does NOT check all the // signatures. -func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID, - height int64, commit *Commit) error { +func (vals *ValidatorSet) VerifyCommitLight( + chainID string, + blockID BlockID, + height int64, + commit *Commit, +) error { + return vals.verifyCommitLightInternal(chainID, blockID, height, commit, false) +} + +// VerifyCommitLightAllSignatures verifies +2/3 of the set had signed the given commit. +// +// This method DOES check all the signatures. +func (vals *ValidatorSet) VerifyCommitLightAllSignatures( + chainID string, + blockID BlockID, + height int64, + commit *Commit, +) error { + return vals.verifyCommitLightInternal(chainID, blockID, height, commit, true) +} + +func (vals *ValidatorSet) verifyCommitLightInternal( + chainID string, + blockID BlockID, + height int64, + commit *Commit, + countAllSignatures bool, +) error { if vals.Size() != len(commit.Signatures) { return NewErrInvalidCommitSignatures(vals.Size(), len(commit.Signatures)) @@ -738,8 +764,9 @@ func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID, talliedVotingPower := int64(0) votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 for idx, commitSig := range commit.Signatures { - // No need to verify absent or nil votes. - if !commitSig.ForBlock() { + // No need to verify absent or nil votes if not counting all signatures, + // but need to verify nil votes if counting all signatures. + if (!countAllSignatures && !commitSig.ForBlock()) || (countAllSignatures && commitSig.Absent()) { continue } @@ -756,11 +783,14 @@ func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID, talliedVotingPower += val.VotingPower // return as soon as +2/3 of the signatures are verified - if talliedVotingPower > votingPowerNeeded { + if !countAllSignatures && talliedVotingPower > votingPowerNeeded { return nil } } + if talliedVotingPower > votingPowerNeeded { + return nil + } return ErrNotEnoughVotingPowerSigned{Got: talliedVotingPower, Needed: votingPowerNeeded} } @@ -770,9 +800,37 @@ func (vals *ValidatorSet) VerifyCommitLight(chainID string, blockID BlockID, // NOTE the given validators do not necessarily correspond to the validator set // for this commit, but there may be some intersection. // -// This method is primarily used by the light client and does not check all the +// This method is primarily used by the light client and does NOT check all the // signatures. -func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *Commit, trustLevel cmtmath.Fraction) error { +func (vals *ValidatorSet) VerifyCommitLightTrusting( + chainID string, + commit *Commit, + trustLevel cmtmath.Fraction, +) error { + return vals.verifyCommitLightTrustingInternal(chainID, commit, trustLevel, false) +} + +// VerifyCommitLightTrustingAllSignatures verifies that trustLevel of the validator +// set signed this commit. +// +// NOTE the given validators do not necessarily correspond to the validator set +// for this commit, but there may be some intersection. +// +// This method DOES check all the signatures. +func (vals *ValidatorSet) VerifyCommitLightTrustingAllSignatures( + chainID string, + commit *Commit, + trustLevel cmtmath.Fraction, +) error { + return vals.verifyCommitLightTrustingInternal(chainID, commit, trustLevel, true) +} + +func (vals *ValidatorSet) verifyCommitLightTrustingInternal( + chainID string, + commit *Commit, + trustLevel cmtmath.Fraction, + countAllSignatures bool, +) error { // sanity check if trustLevel.Denominator == 0 { return errors.New("trustLevel has zero Denominator") @@ -791,8 +849,9 @@ func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *Comm votingPowerNeeded := totalVotingPowerMulByNumerator / int64(trustLevel.Denominator) for idx, commitSig := range commit.Signatures { - // No need to verify absent or nil votes. - if !commitSig.ForBlock() { + // No need to verify absent or nil votes if not counting all signatures, + // but need to verify nil votes if counting all signatures. + if (!countAllSignatures && !commitSig.ForBlock()) || (countAllSignatures && commitSig.Absent()) { continue } @@ -816,12 +875,14 @@ func (vals *ValidatorSet) VerifyCommitLightTrusting(chainID string, commit *Comm talliedVotingPower += val.VotingPower - if talliedVotingPower > votingPowerNeeded { + if !countAllSignatures && talliedVotingPower > votingPowerNeeded { return nil } } } - + if talliedVotingPower > votingPowerNeeded { + return nil + } return ErrNotEnoughVotingPowerSigned{Got: talliedVotingPower, Needed: votingPowerNeeded} } diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 01e81a95995..94232f7b0e8 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "sort" + "strconv" "strings" "testing" "testing/quick" @@ -725,7 +726,8 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { for _, tc := range testCases { tc := tc - t.Run(tc.description, func(t *testing.T) { + countAllSignatures := false + f := func(t *testing.T) { err := vset.VerifyCommit(tc.chainID, tc.blockID, tc.height, tc.commit) if tc.expErr { if assert.Error(t, err, "VerifyCommit") { @@ -735,7 +737,11 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { assert.NoError(t, err, "VerifyCommit") } - err = vset.VerifyCommitLight(tc.chainID, tc.blockID, tc.height, tc.commit) + if countAllSignatures { + err = vset.VerifyCommitLightAllSignatures(tc.chainID, tc.blockID, tc.height, tc.commit) + } else { + err = vset.VerifyCommitLight(tc.chainID, tc.blockID, tc.height, tc.commit) + } if tc.expErr { if assert.Error(t, err, "VerifyCommitLight") { assert.Contains(t, err.Error(), tc.description, "VerifyCommitLight") @@ -743,7 +749,10 @@ func TestValidatorSet_VerifyCommit_All(t *testing.T) { } else { assert.NoError(t, err, "VerifyCommitLight") } - }) + } + t.Run(tc.description+"/"+strconv.FormatBool(countAllSignatures), f) + countAllSignatures = true + t.Run(tc.description+"/"+strconv.FormatBool(countAllSignatures), f) } } @@ -772,7 +781,7 @@ func TestValidatorSet_VerifyCommit_CheckAllSignatures(t *testing.T) { } } -func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSigned(t *testing.T) { +func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajOfVotingPowerSignedIffNotAllSigs(t *testing.T) { var ( chainID = "test_chain_id" h = int64(3) @@ -783,6 +792,9 @@ func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSign commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) + err = valSet.VerifyCommitLightAllSignatures(chainID, blockID, h, commit) + assert.NoError(t, err) + // malleate 4th signature (3 signatures are enough for 2/3+) vote := voteSet.GetByIndex(3) v := vote.ToProto() @@ -793,9 +805,11 @@ func TestValidatorSet_VerifyCommitLight_ReturnsAsSoonAsMajorityOfVotingPowerSign err = valSet.VerifyCommitLight(chainID, blockID, h, commit) assert.NoError(t, err) + err = valSet.VerifyCommitLightAllSignatures(chainID, blockID, h, commit) + assert.Error(t, err) // counting all signatures detects the malleated signature } -func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotingPowerSigned(t *testing.T) { +func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelSignedIffNotAllSigs(t *testing.T) { var ( chainID = "test_chain_id" h = int64(3) @@ -806,6 +820,13 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin commit, err := MakeCommit(blockID, h, 0, voteSet, vals, time.Now()) require.NoError(t, err) + err = valSet.VerifyCommitLightTrustingAllSignatures( + chainID, + commit, + cmtmath.Fraction{Numerator: 1, Denominator: 3}, + ) + assert.NoError(t, err) + // malleate 3rd signature (2 signatures are enough for 1/3+ trust level) vote := voteSet.GetByIndex(2) v := vote.ToProto() @@ -816,6 +837,12 @@ func TestValidatorSet_VerifyCommitLightTrusting_ReturnsAsSoonAsTrustLevelOfVotin err = valSet.VerifyCommitLightTrusting(chainID, commit, cmtmath.Fraction{Numerator: 1, Denominator: 3}) assert.NoError(t, err) + err = valSet.VerifyCommitLightTrustingAllSignatures( + chainID, + commit, + cmtmath.Fraction{Numerator: 1, Denominator: 3}, + ) + assert.Error(t, err) // counting all signatures detects the malleated signature } func TestEmptySet(t *testing.T) { From e2be7372ffa522bf71f2da8d33b6b6d218604272 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 09:55:52 +0100 Subject: [PATCH 65/87] Add changelog for #1749 (#1807) (#1820) (cherry picked from commit 437391a0cb766ec0e7a2d87242a90b271ac31707) Co-authored-by: Sergio Mena --- .../bug-fixes/1749-light-client-attack-verify-all-sigs.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md diff --git a/.changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md b/.changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md new file mode 100644 index 00000000000..1115c4d195a --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md @@ -0,0 +1,4 @@ +- `[evidence]` When `VerifyCommitLight` & `VerifyCommitLightTrusting` are called as part + of evidence verification, all signatures present in the evidence must be verified + ([\#1749](https://github.com/cometbft/cometbft/pull/1749)) + From 8cba200f31339481461b562c98ae4c1f027449f2 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 18 Dec 2023 04:34:15 +0400 Subject: [PATCH 66/87] ci: fix failing golangci-lint action (#1833) * use latest golangci-lint-action * make the linter run on workflow changes too * disable 3 linters for now until we fix all the errors * comment out goconst as well * reenable depguard --- .github/workflows/lint.yml | 4 ++- .golangci.yml | 63 ++++++++++++++++++++++++++++++++++++-- Makefile | 2 +- consensus/reactor_test.go | 9 +++--- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9ec28079aa1..01e04903bfe 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,9 +30,11 @@ jobs: **/**.go go.mod go.sum + .github/** + Makefile - uses: golangci/golangci-lint-action@v3 with: - version: v1.51 + version: latest args: --timeout 10m github-token: ${{ secrets.github_token }} if: env.GIT_DIFF diff --git a/.golangci.yml b/.golangci.yml index 80e7214b2c0..d7db8f833c0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,5 @@ linters: + disable-all: true enable: - asciicheck - bodyclose @@ -7,16 +8,16 @@ linters: - dupl - errcheck - exportloopref - - goconst + # - goconst - gofmt - goimports - - revive + # - revive - gosec - gosimple - govet - ineffassign - misspell - - nakedret + # - nakedret - nolintlint - prealloc - staticcheck @@ -42,3 +43,59 @@ linters-settings: suggest-new: true misspell: locale: US + depguard: + rules: + main: + files: + - $all + - "!$test" + allow: + - $gostd + - github.com/cometbft + - github.com/cosmos + - github.com/btcsuite/btcd/btcec/v2 + - github.com/BurntSushi/toml + - github.com/go-git/go-git/v5 + - github.com/go-kit + - github.com/go-logfmt/logfmt + - github.com/gofrs/uuid + - github.com/google + - github.com/gorilla/websocket + - github.com/informalsystems/tm-load-test/pkg/loadtest + - github.com/lib/pq + - github.com/libp2p/go-buffer-pool + - github.com/Masterminds/semver/v3 + - github.com/minio/highwayhash + - github.com/oasisprotocol/curve25519-voi + - github.com/pkg/errors + - github.com/prometheus + - github.com/rcrowley/go-metrics + - github.com/rs/cors + - github.com/snikch/goodman + - github.com/spf13 + - github.com/stretchr/testify/require + - github.com/syndtr/goleveldb + - github.com/ChainSafe/go-schnorrkel + - github.com/creachadair/taskgroup + - github.com/gtank/merlin + test: + files: + - "$test" + allow: + - $gostd + - github.com/cosmos + - github.com/cometbft + - github.com/adlio/schema + - github.com/btcsuite/btcd + - github.com/fortytw2/leaktest + - github.com/go-kit + - github.com/google/uuid + - github.com/gorilla/websocket + - github.com/lib/pq + - github.com/oasisprotocol/curve25519-voi/primitives/merlin + - github.com/ory/dockertest + - github.com/pkg/errors + - github.com/prometheus/client_golang/prometheus/promhttp + - github.com/spf13 + - github.com/stretchr/testify + - github.com/gtank/merlin diff --git a/Makefile b/Makefile index 5f69579f5b4..287561f2473 100644 --- a/Makefile +++ b/Makefile @@ -271,7 +271,7 @@ format: lint: @echo "--> Running linter" - @go run github.com/golangci/golangci-lint/cmd/golangci-lint run + @go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run .PHONY: lint vulncheck: diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 793f4e29e4a..9d36ff35fa6 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -621,12 +621,11 @@ func waitForBlockWithUpdatedValsAndValidateIt( if newBlock.LastCommit.Size() == len(updatedVals) { css[j].Logger.Debug("waitForBlockWithUpdatedValsAndValidateIt: Got block", "height", newBlock.Height) break LOOP - } else { - css[j].Logger.Debug( - "waitForBlockWithUpdatedValsAndValidateIt: Got block with no new validators. Skipping", - "height", - newBlock.Height) } + css[j].Logger.Debug( + "waitForBlockWithUpdatedValsAndValidateIt: Got block with no new validators. Skipping", + "height", + newBlock.Height) } err := validateBlock(newBlock, updatedVals) From 5bbb06b8345c24fa46c3b8a564acbfa34a371309 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 18 Dec 2023 04:49:48 +0400 Subject: [PATCH 67/87] consensus: return last saved BeginBlock, not a empty one (#1782) Otherwise, the events from app's BeginBlock won't be fired. Closes #1468 Co-authored-by: forcodedancing --- consensus/replay_stubs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/consensus/replay_stubs.go b/consensus/replay_stubs.go index 5ffb20ad822..5409d6f8521 100644 --- a/consensus/replay_stubs.go +++ b/consensus/replay_stubs.go @@ -77,6 +77,10 @@ type mockProxyApp struct { abciResponses *cmtstate.ABCIResponses } +func (mock *mockProxyApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return *mock.abciResponses.BeginBlock +} + func (mock *mockProxyApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { r := mock.abciResponses.DeliverTxs[mock.txCount] mock.txCount++ From 4cb106afce627b9bb91e924edd607221a9473d56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:09:50 +0100 Subject: [PATCH 68/87] build(deps): Bump actions/upload-artifact from 3 to 4 (#1840) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/fuzz-nightly.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index c70bd40a772..a2fe5c16cf9 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -49,14 +49,14 @@ jobs: continue-on-error: true - name: Archive crashers - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: crashers path: test/fuzz/**/crashers retention-days: 3 - name: Archive suppressions - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: suppressions path: test/fuzz/**/suppressions From cde066f8a3433b4625f6bd1a5b9aab9adef12154 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:48:39 +0000 Subject: [PATCH 69/87] Updates go crypto package to v0.17.0 (backport #1859) (#1864) * Updates go crypto package to v0.17.0 (#1859) (cherry picked from commit fd87fdaf6b345af588fabef7bd1333466e9a0e30) # Conflicts: # go.mod # go.sum * Solving conflict --------- Co-authored-by: lasaro --- go.mod | 67 +++++++++---------- go.sum | 204 +++++++++++++++++++++------------------------------------ 2 files changed, 109 insertions(+), 162 deletions(-) diff --git a/go.mod b/go.mod index b9051faa8e7..5aabe63be4b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/gofrs/uuid v4.3.0+incompatible github.com/golangci/golangci-lint v1.50.1 github.com/google/orderedcode v0.0.1 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.4.0 github.com/gorilla/websocket v1.5.0 github.com/gtank/merlin v0.1.1 github.com/informalsystems/tm-load-test v1.3.0 @@ -30,12 +30,12 @@ require ( github.com/rs/cors v1.8.2 github.com/sasha-s/go-deadlock v0.3.1 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa - github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.8.1 - golang.org/x/crypto v0.14.0 - golang.org/x/net v0.17.0 - google.golang.org/grpc v1.58.3 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.1 + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.17.0 + golang.org/x/net v0.19.0 + google.golang.org/grpc v1.60.0 ) require ( @@ -53,7 +53,7 @@ require ( github.com/go-git/go-git/v5 v5.5.2 github.com/golang/protobuf v1.5.3 github.com/vektra/mockery/v2 v2.14.0 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.5.0 gonum.org/v1/gonum v0.8.2 google.golang.org/protobuf v1.31.0 ) @@ -94,10 +94,10 @@ require ( github.com/containerd/continuity v0.3.0 // indirect github.com/containerd/typeurl v1.0.2 // indirect github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.8.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/denis-tingaikin/go-header v0.4.3 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect @@ -114,10 +114,10 @@ require ( github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/go-chi/chi/v5 v5.0.7 // indirect github.com/go-critic/go-critic v0.6.5 // indirect @@ -163,7 +163,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a // indirect github.com/jgautheron/goconst v1.5.1 // indirect @@ -177,7 +177,7 @@ require ( github.com/kisielk/errcheck v1.6.2 // indirect github.com/kisielk/gotool v1.0.0 // indirect github.com/kkHAIKE/contextcheck v1.1.3 // indirect - github.com/klauspost/compress v1.15.11 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.6 // indirect @@ -186,12 +186,12 @@ require ( github.com/ldez/tagliatelle v0.3.1 // indirect github.com/leonklingele/grouper v1.1.0 // indirect github.com/lufeee/execinquery v1.2.1 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.0 // indirect github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect @@ -212,14 +212,13 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/runc v1.1.3 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/profile v1.6.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3 // indirect github.com/pointlander/jetset v1.0.1-0.20190518214125-eee7eff80bd4 // indirect github.com/polyfloyd/go-errorlint v1.0.5 // indirect @@ -232,6 +231,8 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryancurrah/gomodguard v1.2.4 // indirect github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.20.0 // indirect @@ -245,15 +246,15 @@ require ( github.com/sivchari/tenv v1.7.0 // indirect github.com/skeema/knownhosts v1.1.0 // indirect github.com/sonatard/noctx v0.0.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.6.1 // indirect - github.com/spf13/afero v1.8.2 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tdakkota/asciicheck v0.1.1 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tetafro/godot v1.4.11 // indirect @@ -269,22 +270,22 @@ require ( github.com/yeya24/promlinter v0.2.0 // indirect gitlab.com/bosi/decorder v0.2.3 // indirect go.etcd.io/bbolt v1.3.6 // indirect - go.opencensus.io v0.23.0 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3 // indirect go.opentelemetry.io/otel v1.11.0 // indirect go.opentelemetry.io/otel/trace v1.11.0 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect + go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 2acf5779204..191533411b9 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -16,17 +15,14 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= @@ -39,7 +35,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Abirdcfly/dupword v0.0.7 h1:z14n0yytA3wNO2gpCD/jVtp/acEXPGmYu0esewpBt6Q= github.com/Abirdcfly/dupword v0.0.7/go.mod h1:K/4M1kj+Zh39d2aotRwypvasonOyAMH1c/IZJzE0dmk= @@ -169,7 +164,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= @@ -192,8 +186,8 @@ github.com/cosmos/gogoproto v1.4.1 h1:WoyH+0/jbCTzpKNvyav5FL1ZTWsp1im1MxEpJEzKUB github.com/cosmos/gogoproto v1.4.1/go.mod h1:Ac9lzL4vFpBMcptJROQ6dQ4M3pOEK5Z/l0Q9p+LoCr4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -207,8 +201,9 @@ github.com/daixiang0/gci v0.8.1 h1:T4xpSC+hmsi4CSyuYfIJdMZAr9o7xZmHpQVygMghGZ4= github.com/daixiang0/gci v0.8.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= @@ -240,8 +235,6 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= @@ -255,8 +248,8 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= @@ -265,11 +258,11 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= @@ -421,7 +414,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -431,17 +423,13 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -486,12 +474,11 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/informalsystems/tm-load-test v1.3.0 h1:FGjKy7vBw6mXNakt+wmNWKggQZRsKkEYpaFk/zR64VA= github.com/informalsystems/tm-load-test v1.3.0/go.mod h1:OQ5AQ9TbT5hKWBNIwsMjn6Bf4O0U4b1kRc+0qZlQJKw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -542,17 +529,16 @@ github.com/kkHAIKE/contextcheck v1.1.3 h1:l4pNvrb8JSwRd51ojtcOxOeHJzHek+MtOyXbaR github.com/kkHAIKE/contextcheck v1.1.3/go.mod h1:PG/cwd6c0705/LM0KTr1acO2gORUxkSVWyLJOFW5qoo= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -577,8 +563,8 @@ github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QT github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.0 h1:GJY4wlzQhuBusMF1oahQCBtUV/AQ/k69IZ68vxaac2Q= @@ -588,14 +574,13 @@ github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859 github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -679,10 +664,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= @@ -697,9 +680,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3 h1:hUmXhbljNFtrH5hzV9kiRoddZ5nfPTq3K0Sb2hYYiqE= github.com/pointlander/compress v1.1.1-0.20190518213731-ff44bd196cc3/go.mod h1:q5NXNGzqj5uPnVuhGkZfmgHqNUhf15VLi6L9kW0VEc0= github.com/pointlander/jetset v1.0.1-0.20190518214125-eee7eff80bd4 h1:RHHRCZeaNyBXdYPMjZNH8/XHDBH38TZzw8izrW7dmBE= @@ -764,6 +747,10 @@ github.com/ryancurrah/gomodguard v1.2.4 h1:CpMSDKan0LtNGGhPrvupAoLeObRFjND8/tU1r github.com/ryancurrah/gomodguard v1.2.4/go.mod h1:+Kem4VjWwvFpUJRJSwa16s1tBJe+vbv02+naTow2f6M= github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= @@ -803,29 +790,29 @@ github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa h1:YJfZp12Z3AFhSBeX github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ= github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.18.1 h1:rmuU42rScKWlhhJDyXZRKJQHXFX02chSVW1IvkPGiVM= +github.com/spf13/viper v1.18.1/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= @@ -840,14 +827,14 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -907,9 +894,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3 h1:syAz40OyelLZo42+3U68Phisvrx4qh+4wpdZw7eUUdY= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3/go.mod h1:Dts42MGkzZne2yCru741+bFiTMWkIj/LLRizad7b9tw= go.opentelemetry.io/otel v1.11.0 h1:kfToEGMDq6TrVrJ9Vht84Y8y9enykSZzDDZglV0kIEk= @@ -917,13 +903,12 @@ go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+ go.opentelemetry.io/otel/trace v1.11.0 h1:20U/Vj42SX+mASlXLmSGBg6jpI1jQtv682lZtTAOVFI= go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= @@ -936,15 +921,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -958,8 +941,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 h1:Ic/qN6TEifvObMGQy72k0n1LlJr7DjWWEi+MOsDOiSk= golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -976,7 +959,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -985,14 +967,13 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1026,9 +1007,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -1040,20 +1019,16 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1066,8 +1041,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1095,7 +1070,6 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1112,20 +1086,15 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1141,7 +1110,6 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1151,29 +1119,28 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1236,18 +1203,12 @@ golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -1258,8 +1219,8 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1286,17 +1247,13 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1327,17 +1284,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1350,13 +1300,10 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= 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= @@ -1398,7 +1345,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b0127b57356eca8599f9950ff1bb253fc10f85fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 08:56:52 +0100 Subject: [PATCH 70/87] Allow blocksync to not verify all signatures (backport #1858) (#1871) * Allow blocksync to not verify all signatures (#1858) * Blocksync can skip sigs * bump (cherry picked from commit 9446e3135c28a92ac2ea9e5191e4c7da7ced7dbb) # Conflicts: # blocksync/reactor.go * Revert "Allow blocksync to not verify all signatures (#1858)" This reverts commit 4f2a211041f9fe8554d3996bcc44614047c239f6. * Allow blocksync to not verify all signatures (#1858) * Blocksync can skip sigs * bump --------- Co-authored-by: Sergio Mena --- blocksync/reactor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocksync/reactor.go b/blocksync/reactor.go index ea6937fb02d..91477ceeb67 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -368,7 +368,7 @@ FOR_LOOP: // NOTE: we can probably make this more efficient, but note that calling // first.Hash() doesn't verify the tx contents, so MakePartSet() is // currently necessary. - err = state.Validators.VerifyCommitLightAllSignatures( + err = state.Validators.VerifyCommitLight( chainID, firstID, first.Height, second.LastCommit) if err == nil { From d0b139e899180610bbe498ea0009f124cf6c0ba0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:27:20 +0800 Subject: [PATCH 71/87] docs: Fix Discord links in README (backport #1874) (#1895) * docs: Fix Discord links in README (#1874) Signed-off-by: Thane Thomson (cherry picked from commit f72d930a68386f6139838449d0653ef0621f7b29) # Conflicts: # README.md * fix conflicts --------- Co-authored-by: Thane Thomson Co-authored-by: Anton Kaliaev --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c5c37a5aa99..00a2f5be675 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,15 @@ Complete documentation can be found on the Please do not depend on `main` as your production branch. Use [releases](https://github.com/cometbft/cometbft/releases) instead. -We haven't released v1.0 yet -since we are making breaking changes to the protocol and the APIs. See below for -more details about [versioning](#versioning). - -In any case, if you intend to run CometBFT in production, we're happy to help. - -To contact us, you can also -[join the chat](https://discord.com/channels/669268347736686612/669283915743232011). +If you intend to run CometBFT in production, we're happy to help. To contact +us, in order of preference: + +- [Create a new discussion on + GitHub](https://github.com/cometbft/cometbft/discussions) +- Reach out to us via [Telegram](https://t.me/CometBFT) +- [Join the Cosmos Network Discord](https://discord.gg/cosmosnetwork) and + discuss in + [`#cometbft`](https://discord.com/channels/669268347736686612/1069933855307472906) More on how releases are conducted can be found [here](./RELEASES.md). From 14aabc31b042ab1a5b7536aae9f5ea1256a80922 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:35:29 +0800 Subject: [PATCH 72/87] build(deps): Bump bufbuild/buf-setup-action from 1.28.1 to 1.29.0 (#2168) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.28.1 to 1.29.0.
Release notes

Sourced from bufbuild/buf-setup-action's releases.

v1.29.0

Release v1.29.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bufbuild/buf-setup-action&package-manager=github_actions&previous-version=1.28.1&new-version=1.29.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 998aa305db9..6bf42245326 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.28.1 + - uses: bufbuild/buf-setup-action@v1.29.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 7b08918eeb79d49fef18fa110691340658719dcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:38:53 +0800 Subject: [PATCH 73/87] build(deps): Bump slackapi/slack-github-action from 1.24.0 to 1.25.0 (#2167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.24.0 to 1.25.0.
Release notes

Sourced from slackapi/slack-github-action's releases.

Slack Send V1.25.0

What's Changed

New Contributors

Full Changelog: https://github.com/slackapi/slack-github-action/compare/v1.24.0...v1.25.0

Commits
  • 6c661ce Automatic compilation
  • 2a8087d v1.25.0
  • a678e58 ci(security): check for pull_request_target events in the access check (#282)
  • 84a8f7d ci(security): require access checks to pass before running unit tests (#279)
  • f6aff2f Bump eslint from 8.54.0 to 8.56.0 (#275)
  • 372e934 Bump eslint-plugin-import from 2.29.0 to 2.29.1 (#274)
  • bac28df Bump @​slack/web-api from 6.9.1 to 6.11.1 (#277)
  • 0474a45 Unit tests in GitHub CI should test the PR/branch (#276)
  • 34ae0b4 Bump @​actions/github from 5.1.1 to 6.0.0 (#265)
  • e7f3840 Bump whatwg-url from 13.0.0 to 14.0.0 (#263)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=slackapi/slack-github-action&package-manager=github_actions&previous-version=1.24.0&new-version=1.25.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-long-37x.yml | 2 +- .github/workflows/e2e-nightly-34x.yml | 4 ++-- .github/workflows/e2e-nightly-37x.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 4 ++-- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release.yml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/e2e-long-37x.yml b/.github/workflows/e2e-long-37x.yml index 845d4df93f5..4ee68b90bf4 100644 --- a/.github/workflows/e2e-long-37x.yml +++ b/.github/workflows/e2e-long-37x.yml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/e2e-nightly-34x.yml b/.github/workflows/e2e-nightly-34x.yml index a7fc8ca6c48..987c86ce916 100644 --- a/.github/workflows/e2e-nightly-34x.yml +++ b/.github/workflows/e2e-nightly-34x.yml @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on success - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/e2e-nightly-37x.yml b/.github/workflows/e2e-nightly-37x.yml index cf0f154ad3a..1db3ea52a11 100644 --- a/.github/workflows/e2e-nightly-37x.yml +++ b/.github/workflows/e2e-nightly-37x.yml @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index bf33933c1f3..312960f1178 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on success - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index a2fe5c16cf9..8c21a553f9e 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index b910d27771d..53bfebd459e 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack upon pre-release - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5cc48eb265c..eeac6710be0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack upon release - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK From bd21a66a5eb0347d029b9beccc0f33b3063f9f84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:59:39 +0200 Subject: [PATCH 74/87] build(deps): Bump styfle/cancel-workflow-action from 0.12.0 to 0.12.1 (#2166) Bumps [styfle/cancel-workflow-action](https://github.com/styfle/cancel-workflow-action) from 0.12.0 to 0.12.1. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/janitor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/janitor.yml b/.github/workflows/janitor.yml index 9c28eb4fd37..29ad2ceb54b 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.12.0 + - uses: styfle/cancel-workflow-action@0.12.1 with: workflow_id: 1041851,1401230,2837803 access_token: ${{ github.token }} From f37919037f5400822859122fa9cad7103adbc994 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:26:25 +0800 Subject: [PATCH 75/87] build(deps): Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 (backport #2253) (#2256) This is an automatic backport of pull request #2253 done by [Mergify](https://mergify.com). Cherry-pick of db7a70ca28f88eace899de7354807ff17a09da82 has failed: ``` On branch mergify/bp/v0.37.x/pr-2253 Your branch is up to date with 'origin/v0.37.x'. You are currently cherry-picking commit db7a70ca2. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Unmerged paths: (use "git add ..." to mark resolution) both modified: go.mod both modified: go.sum no changes added to commit (use "git add" and/or "git commit -a") ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
--------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mikhail Zabaluev --- go.mod | 22 +++++++-------- go.sum | 84 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 5aabe63be4b..e079837d3b7 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.2 github.com/cometbft/cometbft-db v0.7.0 github.com/cosmos/gogoproto v1.4.1 - github.com/go-git/go-git/v5 v5.5.2 + github.com/go-git/go-git/v5 v5.11.0 github.com/golang/protobuf v1.5.3 github.com/vektra/mockery/v2 v2.14.0 golang.org/x/sync v0.5.0 @@ -60,6 +60,7 @@ require ( require ( 4d63.com/gochecknoglobals v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/Abirdcfly/dupword v0.0.7 // indirect github.com/Antonboom/errname v0.1.7 // indirect github.com/Antonboom/nilnil v0.1.1 // indirect @@ -67,11 +68,10 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/OpenPeeDeeP/depguard v1.1.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect - github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.3.0 // indirect @@ -89,13 +89,14 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 // indirect - github.com/cloudflare/circl v1.1.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/containerd v1.6.8 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/containerd/typeurl v1.0.2 // indirect github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/daixiang0/gci v0.8.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect @@ -121,8 +122,8 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/go-chi/chi/v5 v5.0.7 // indirect github.com/go-critic/go-critic v0.6.5 // indirect - github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.4.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-toolsmith/astcast v1.0.0 // indirect @@ -148,7 +149,7 @@ require ( github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect @@ -162,7 +163,6 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a // indirect @@ -215,7 +215,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect - github.com/pjbgf/sha1cd v0.2.3 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/profile v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -244,7 +244,7 @@ require ( github.com/sivchari/containedctx v1.0.2 // indirect github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sivchari/tenv v1.7.0 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect github.com/sonatard/noctx v0.0.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.6.1 // indirect diff --git a/go.sum b/go.sum index 191533411b9..017c7f1f488 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Abirdcfly/dupword v0.0.7 h1:z14n0yytA3wNO2gpCD/jVtp/acEXPGmYu0esewpBt6Q= github.com/Abirdcfly/dupword v0.0.7/go.mod h1:K/4M1kj+Zh39d2aotRwypvasonOyAMH1c/IZJzE0dmk= @@ -60,19 +62,17 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -87,10 +87,8 @@ github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cv github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBlWTShc= github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= @@ -140,7 +138,7 @@ github.com/bufbuild/connect-go v1.0.0 h1:htSflKUT8y1jxhoPhPYTZMrsY3ipUXjjrbcZR5O github.com/bufbuild/connect-go v1.0.0/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= @@ -161,8 +159,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= @@ -197,6 +196,8 @@ github.com/cristalhq/acmd v0.8.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBj github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daixiang0/gci v0.8.1 h1:T4xpSC+hmsi4CSyuYfIJdMZAr9o7xZmHpQVygMghGZ4= github.com/daixiang0/gci v0.8.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -230,6 +231,7 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -266,20 +268,17 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-critic/go-critic v0.6.5 h1:fDaR/5GWURljXwF8Eh31T2GZNz9X4jeboS912mWF8Uo= github.com/go-critic/go-critic v0.6.5/go.mod h1:ezfP/Lh7MA6dBNn4c6ab5ALv3sKnZVLx37tr00uuaOY= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE= -github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= -github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -409,8 +408,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -475,7 +474,6 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -487,7 +485,6 @@ github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a h1:d4+I1YEKVmWZrgkt6 github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -571,7 +568,6 @@ github.com/maratori/testpackage v1.1.0 h1:GJY4wlzQhuBusMF1oahQCBtUV/AQ/k69IZ68vx github.com/maratori/testpackage v1.1.0/go.mod h1:PeAhzU8qkCwdGEMTEupsHJNlQu2gZopMC6RjbhmHeDc= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -644,7 +640,7 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -670,8 +666,8 @@ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -733,7 +729,7 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -784,8 +780,8 @@ github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= github.com/sivchari/tenv v1.7.0 h1:d4laZMBK6jpe5PWepxlV9S+LC0yXqvYHiq8E6ceoVVE= github.com/sivchari/tenv v1.7.0/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa h1:YJfZp12Z3AFhSBeXOlv4BO55RMwPn2NoQeDsrdWnBtY= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= @@ -922,10 +918,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -972,6 +967,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1017,8 +1013,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1041,6 +1038,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1092,7 +1090,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1103,7 +1100,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1114,17 +1110,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1135,6 +1132,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1219,6 +1218,7 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1327,7 +1327,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -1345,7 +1344,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= From 0401888bbf6a7148ed039c7cca7380644496c78d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:48:23 +0800 Subject: [PATCH 76/87] feat(consensus): additional sanity checks for the size of proposed blocks (backport #1408) (#2140) This is an automatic backport of pull request #1408 done by [Mergify](https://mergify.com). Cherry-pick of 28ad4d2230134045e2f5ce9fdab6673e276f2579 has failed: ``` On branch mergify/bp/v0.37.x/pr-1408 Your branch is up to date with 'origin/v0.37.x'. You are currently cherry-picking commit 28ad4d223. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Changes to be committed: modified: consensus/state.go modified: crypto/merkle/proof.go modified: evidence/pool_test.go modified: state/execution_test.go modified: types/part_set.go modified: types/part_set_test.go Unmerged paths: (use "git add/rm ..." as appropriate to mark resolution) deleted by us: internal/consensus/errors.go deleted by us: internal/consensus/state_test.go deleted by us: internal/state/store_test.go deleted by us: internal/store/store_test.go both modified: types/event_bus_test.go ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
--------- Co-authored-by: Daniel Co-authored-by: Sergio Mena Co-authored-by: Andy Nogueira Co-authored-by: Anton Kaliaev --- consensus/state.go | 10 ++++++++++ consensus/state_test.go | 16 ++++++++++++++-- crypto/merkle/proof.go | 8 ++++---- evidence/pool_test.go | 3 +-- state/execution_test.go | 2 +- store/store_test.go | 32 ++++++++++++++++++++++---------- types/part_set.go | 13 ++++++++++++- types/part_set_test.go | 28 +++++++++++++++++++++++++++- 8 files changed, 91 insertions(+), 21 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 6fb202af23c..a2c99f8f79a 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -36,6 +36,7 @@ var ( ErrInvalidProposalPOLRound = errors.New("error invalid proposal POL round") ErrAddingVote = errors.New("error adding vote") ErrSignatureFoundInPastBlocks = errors.New("found signature from the same key") + ErrProposalTooManyParts = errors.New("proposal block has too many parts") errPubKeyIsNotSet = errors.New("pubkey is not set. Look for \"Can't get private validator pubkey\" errors") ) @@ -1887,6 +1888,15 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error { return ErrInvalidProposalSignature } + // Validate the proposed block size, derived from its PartSetHeader + maxBytes := cs.state.ConsensusParams.Block.MaxBytes + if maxBytes == -1 { + maxBytes = int64(types.MaxBlockSizeBytes) + } + if int64(proposal.BlockID.PartSetHeader.Total) > (maxBytes-1)/int64(types.BlockPartSizeBytes)+1 { + return ErrProposalTooManyParts + } + proposal.Signature = p.Signature cs.Proposal = proposal // We don't update cs.ProposalBlockParts if it is already set. diff --git a/consensus/state_test.go b/consensus/state_test.go index 298dd907705..5813bbfed44 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -253,7 +253,7 @@ func TestStateBadProposal(t *testing.T) { } func TestStateOversizedBlock(t *testing.T) { - const maxBytes = 2000 + const maxBytes = int64(types.BlockPartSizeBytes) for _, testCase := range []struct { name string @@ -299,6 +299,12 @@ func TestStateOversizedBlock(t *testing.T) { totalBytes += len(part.Bytes) } + maxBlockParts := maxBytes / int64(types.BlockPartSizeBytes) + if maxBytes > maxBlockParts*int64(types.BlockPartSizeBytes) { + maxBlockParts++ + } + numBlockParts := int64(propBlockParts.Total()) + if err := cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) } @@ -306,7 +312,8 @@ func TestStateOversizedBlock(t *testing.T) { // start the machine startTestRound(cs1, height, round) - t.Log("Block Sizes;", "Limit", cs1.state.ConsensusParams.Block.MaxBytes, "Current", totalBytes) + t.Log("Block Sizes;", "Limit", maxBytes, "Current", totalBytes) + t.Log("Proposal Parts;", "Maximum", maxBlockParts, "Current", numBlockParts) validateHash := propBlock.Hash() lockedRound := int32(1) @@ -322,6 +329,11 @@ func TestStateOversizedBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], validateHash) + // Should not accept a Proposal with too many block parts + if numBlockParts > maxBlockParts { + require.Nil(t, cs1.Proposal) + } + bps, err := propBlock.MakePartSet(partSize) require.NoError(t, err) diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 85b2db1e91a..2c53abf3d40 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -24,10 +24,10 @@ const ( // everything. This also affects the generalized proof system as // well. type Proof struct { - Total int64 `json:"total"` // Total number of items. - Index int64 `json:"index"` // Index of item to prove. - LeafHash []byte `json:"leaf_hash"` // Hash of item value. - Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. + Total int64 `json:"total"` // Total number of items. + Index int64 `json:"index"` // Index of item to prove. + LeafHash []byte `json:"leaf_hash"` // Hash of item value. + Aunts [][]byte `json:"aunts,omitempty"` // Hashes from leaf's sibling to a root's child. } // ProofsFromByteSlices computes inclusion proof for given items. diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 636bf952719..e6a32d30bcf 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -416,8 +416,7 @@ func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.Blo block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit, nil, state.Validators.Proposer.Address) block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute) block.Header.Version = cmtversion.Consensus{Block: version.BlockProtocol, App: 1} - const parts = 1 - partSet, err := block.MakePartSet(parts) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) if err != nil { return nil, err } diff --git a/state/execution_test.go b/state/execution_test.go index 076e4e05997..0c8a1685131 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -32,7 +32,7 @@ import ( var ( chainID = "execution_chain" - testPartSize uint32 = 65536 + testPartSize uint32 = types.BlockPartSizeBytes ) func TestApplyBlock(t *testing.T) { diff --git a/store/store_test.go b/store/store_test.go index 83ec298e5a8..d743e9e105b 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -2,6 +2,7 @@ package store import ( "bytes" + "encoding/json" "fmt" stdlog "log" "os" @@ -139,9 +140,10 @@ func TestMain(m *testing.M) { var cleanup cleanupFunc var err error state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - block = state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) + txs := []types.Tx{make([]byte, types.BlockPartSizeBytes)} // TX taking one block part alone + block = state.MakeBlock(state.LastBlockHeight+1, txs, new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err = block.MakePartSet(2) + partSet, err = block.MakePartSet(types.BlockPartSizeBytes) if err != nil { stdlog.Fatal(err) } @@ -169,10 +171,14 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } } - // save a block - block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) - validPartSet, err := block.MakePartSet(2) + // save a block big enough to have two block parts + txs := []types.Tx{make([]byte, types.BlockPartSizeBytes)} // TX taking one block part alone + block := state.MakeBlock(bs.Height()+1, txs, new(types.Commit), nil, state.Validators.GetProposer().Address) + validPartSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) + require.GreaterOrEqual(t, validPartSet.Total(), uint32(2)) + part2 = validPartSet.GetPart(1) + seenCommit := makeTestCommit(10, cmttime.Now()) bs.SaveBlock(block, partSet, seenCommit) require.EqualValues(t, 1, bs.Base(), "expecting the new height to be changed") @@ -380,7 +386,7 @@ func TestLoadBaseMeta(t *testing.T) { for h := int64(1); h <= 10; h++ { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestCommit(h, cmttime.Now()) bs.SaveBlock(block, partSet, seenCommit) @@ -426,7 +432,13 @@ func TestLoadBlockPart(t *testing.T) { gotPart, _, panicErr := doFn(loadPart) require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, res, "a properly saved block should return a proper block") - require.Equal(t, gotPart.(*types.Part), part1, + + // Having to do this because of https://github.com/stretchr/testify/issues/1141 + gotPartJSON, err := json.Marshal(gotPart.(*types.Part)) + require.NoError(t, err) + part1JSON, err := json.Marshal(part1) + require.NoError(t, err) + require.JSONEq(t, string(gotPartJSON), string(part1JSON), "expecting successful retrieval of previously saved block") } @@ -454,7 +466,7 @@ func TestPruneBlocks(t *testing.T) { // make more than 1000 blocks, to test batch deletions for h := int64(1); h <= 1500; h++ { block := state.MakeBlock(h, test.MakeNTxs(h, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestCommit(h, cmttime.Now()) bs.SaveBlock(block, partSet, seenCommit) @@ -573,7 +585,7 @@ func TestLoadBlockMetaByHash(t *testing.T) { bs := NewBlockStore(dbm.NewMemDB()) b1 := state.MakeBlock(state.LastBlockHeight+1, test.MakeNTxs(state.LastBlockHeight+1, 10), new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := b1.MakePartSet(2) + partSet, err := b1.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestCommit(1, cmttime.Now()) bs.SaveBlock(b1, partSet, seenCommit) @@ -590,7 +602,7 @@ func TestBlockFetchAtHeight(t *testing.T) { require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") block := state.MakeBlock(bs.Height()+1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) - partSet, err := block.MakePartSet(2) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) seenCommit := makeTestCommit(10, cmttime.Now()) bs.SaveBlock(block, partSet, seenCommit) diff --git a/types/part_set.go b/types/part_set.go index b7e31330a0a..12a0a843af0 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -18,6 +18,8 @@ import ( var ( ErrPartSetUnexpectedIndex = errors.New("error part set unexpected index") ErrPartSetInvalidProof = errors.New("error part set invalid proof") + ErrPartTooBig = errors.New("error part size too big") + ErrPartInvalidSize = errors.New("error inner part with invalid size") ) type Part struct { @@ -29,7 +31,11 @@ type Part struct { // ValidateBasic performs basic validation. func (part *Part) ValidateBasic() error { if len(part.Bytes) > int(BlockPartSizeBytes) { - return fmt.Errorf("too big: %d bytes, max: %d", len(part.Bytes), BlockPartSizeBytes) + return ErrPartTooBig + } + // All parts except the last one should have the same constant size. + if int64(part.Index) < part.Proof.Total-1 && len(part.Bytes) != int(BlockPartSizeBytes) { + return ErrPartInvalidSize } if err := part.Proof.ValidateBasic(); err != nil { return fmt.Errorf("wrong Proof: %w", err) @@ -280,6 +286,11 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { return false, nil } + // The proof should be compatible with the number of parts. + if part.Proof.Total != int64(ps.total) { + return false, ErrPartSetInvalidProof + } + // Check hash proof if part.Proof.Verify(ps.Hash(), part.Bytes) != nil { return false, ErrPartSetInvalidProof diff --git a/types/part_set_test.go b/types/part_set_test.go index c1f885260cd..260618dab1d 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -86,6 +86,22 @@ func TestWrongProof(t *testing.T) { if added || err == nil { t.Errorf("expected to fail adding a part with bad bytes.") } + + // Test adding a part with wrong proof index. + part = partSet.GetPart(2) + part.Proof.Index = 1 + added, err = partSet2.AddPart(part) + if added || err == nil { + t.Errorf("expected to fail adding a part with bad proof index.") + } + + // Test adding a part with wrong proof total. + part = partSet.GetPart(3) + part.Proof.Total = int64(partSet.Total() - 1) + added, err = partSet2.AddPart(part) + if added || err == nil { + t.Errorf("expected to fail adding a part with bad proof total.") + } } func TestPartSetHeaderValidateBasic(t *testing.T) { @@ -117,9 +133,19 @@ func TestPartValidateBasic(t *testing.T) { }{ {"Good Part", func(pt *Part) {}, false}, {"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true}, + {"Good small last part", func(pt *Part) { + pt.Index = 1 + pt.Bytes = make([]byte, BlockPartSizeBytes-1) + pt.Proof.Total = 2 + }, false}, + {"Too small inner part", func(pt *Part) { + pt.Index = 0 + pt.Bytes = make([]byte, BlockPartSizeBytes-1) + pt.Proof.Total = 2 + }, true}, {"Too big proof", func(pt *Part) { pt.Proof = merkle.Proof{ - Total: 1, + Total: 2, Index: 1, LeafHash: make([]byte, 1024*1024), } From ad304d0d7f0fc970890e124aecef3d0f8679b923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:25:32 +0800 Subject: [PATCH 77/87] build(deps): Bump golangci/golangci-lint-action from 3 to 4 (#2297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4.
Release notes

Sourced from golangci/golangci-lint-action's releases.

v4.0.0

What's Changed

Documentation

Dependencies

... (truncated)

Commits
  • 3cfe3a4 build(deps): bump @​actions/cache from 3.2.3 to 3.2.4 (#963)
  • cbc59cf build(deps-dev): bump prettier from 3.2.4 to 3.2.5 (#960)
  • 459a04b build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.19.1 to 6.20.0 ...
  • e2315b6 build(deps-dev): bump @​typescript-eslint/parser from 6.19.1 to 6.20.0 (#961)
  • d6173a4 build(deps): bump @​types/node from 20.11.10 to 20.11.16 (#962)
  • 0e8f5bf build(deps): bump @​types/node from 20.11.5 to 20.11.10 (#958)
  • 349d206 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.19.0 to 6.19.1 ...
  • 2221aee build(deps-dev): bump @​typescript-eslint/parser from 6.18.1 to 6.19.1 (#954)
  • 3b44ae5 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.18.1 to 6.19.0 ...
  • 323b871 build(deps-dev): bump prettier from 3.2.2 to 3.2.4 (#950)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 01e04903bfe..4a4390d2723 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,7 +32,7 @@ jobs: go.sum .github/** Makefile - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v4 with: version: latest args: --timeout 10m From d954826ca39aa5672746bac64f08fafd5b679f99 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:16:50 +0800 Subject: [PATCH 78/87] docs: images not rendering properly in docs (backport #2331) (#2339) This is an automatic backport of pull request #2331 done by [Mergify](https://mergify.com). ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
Co-authored-by: Andy Nogueira --- docs/imgs/light_client_bisection_alg.png | Bin 50560 -> 42191 bytes docs/imgs/sentry_layout.png | Bin 44471 -> 83613 bytes docs/imgs/sentry_local_config.png | Bin 43143 -> 66790 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/imgs/light_client_bisection_alg.png b/docs/imgs/light_client_bisection_alg.png index 2a12c7542e5f5f26789c97e532e9063c65e1614f..a960ee69f88898c76552b3a1d2d8a65995c0ef5e 100644 GIT binary patch literal 42191 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 Pfrpm5o?3~DO~^k0N{Om) literal 50560 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�F>OVYk4$mRbn5s2w>2Oo08zERL0}Y}_ZuJp zC(Dj^`=E6W55%(NS}%H37nYa(OFgd^R!;|}1$RUo?&Ip~$0Vj&LXXV-Q8L~0Rl>BP za{lZPw1nyyTg02%)wv) zvUP0eLE+V)AxEZju!zZcHQ6Ql=1#16_%osimaNsL!24_v>Z%v7kPN0GNj@c&JWRKk z3dkYP50`y#@S^|NO@kOmrBDJYm#T0w7v^j^?>b@`$>?kiNA^}sRq?{b4Tjj1PR=auKV@6%MCPvacJ) z);vLkwVVs?G>>KL!fP=|1g7ct7+TmE@|6{ve|QWVT|1pJnW+>|jeC6`nOOY=S#>NkP&%kcb*qlS{_~r z9c#K5q#a=r2Lwr(b@3Xr%~|n$w#Gw_goMy9XX{_qnO-zctmRd@I1dTtDm6yc4_}B5 zWLZ2_zlVe}i|^Hw^R{HE6`w7!cjMNM_sUfMm~s2$DsTQkV?^BH_awG^-03V%MEZ?2 z?&;1H8U+Q0nXpE9GhO5Cq}W%m4Z2!BD6_4}n%}7EEN98{^5I3edRp+1IFa-DMDtp- zHRFovAMWdIOlCV~GUC&Al3g{nh7h{xt>sR8-#_*Hn7^GfR?iquNL&)@@dE6Pxx`!p zmz-OeF0+0TazYi+hGJ(&d&f0zln9Wx`N8?W{w|e< z6EW}MeV@W|!UwTaURSEEq_#oQg_#?l83pT^!`|G*9%q=1=axTKsnwN}g`ApCH$KBl zDg0u7|DjS{F5<|k$A%++%>~G%_ys3?dE8{qN%KgUo6O%G;hCr!BCcdZ zzplf}lt`f$K0p=Fb0aO&rdzfwLRN3-22HvhzR1@&g00vmGh+agqydBSNynpDt(lo48nqt5$%u-=*1 zeg7iiq;a)HKV?k5@);47uTS`+sDSuLey}pj%GPD#b39l~{;|r)mLa`PDN1*wDgGpBq&Pa% zALUsJ>NepMU4UGKX0xZnkZyznfy^=th_n_3C3d;})v_srn?AGWmy#1wXUWVunY z(DyES60rI03FOiRt@n6-yj2xH{vBG}IE#gybnE3+6|e;8D2e=z$agHS3o6kiI{jbQ zp`4$7aNfVEniun!4sanAa9g5F@L+G%Wg2rbqQwGsNQ$87i~nmoH}ZL41`enlx)9Fk zMyU%(k1=5zr?6S~!@W3*e(-q|yM`{oN?$G&-=+J3>7M^<9GUO@;Imgci4#))WspIg z28irKV075Jz>}}oSWWwNk7J+b8zzE^o&%NA{3MwZeAayd;wg<<82gR`LXGynyn)Al zufL-uOQV*n*ETPot{%&Z{a}5v#Wd|puXbOyG)BOCA(J@> zFpKNG;7aNvFeK^w*~>w>BY~;-tIX!W%vIHsD%Q%?nLJ4LZZ{^-4^@7`Xc&B&2quJ@gR-XZEiEjmLR7=jP^jYnc zsVXFMKdeqNg3Xng4v20&E;&xgh2zXB)ndOhK^h)0Yj{h?h@s0xw3|}Gsj=}}&L0IN#!C}lp;FbpZ{A|PWnvpuhs?%`k~XpPRDOm-5|Z(0-steF zk1bj6=`^1o|K?Tr%_=2u<|`J|0TV_3x_W2!Cr#n1+jnl*hz1_&veo3t#jw{Be_%g< zg0VS8*CeY%cls`&U^*e@U3nm_IIpns_Opo?*eXi_;BCdwn|mR+E`0m4Vn}{0ujnNP zPoYj=q7H@IjjzNm?p`E z%}2IhM8s^O@E3C`9b@m_=^Ut55U2Nb5HZ$BTlNX#k-zQo6#m1UpI9nZmBj#KE*4?2 zp+8kPGG*H$h0h7{CjU+67oyUzds&ad-&RalKB`IV!0@Ph0-OB=R5~XEr%fj5#RaKU z$dL&u@-oQLH#qrDtya0qIP&f0)6MJOOXtod%e6{!=7((?T#r$JK2&CE(sfX>{oodT zAlH1}6^vG5Fm2>fzJ}(Gn?D*hHQKE^faSfEc8H&%!98!_5Eh+E4>TwmkX_|RUqwG^ z?6=X8XE|98-_IAB4!xrAfJ}=(@%HNY;iu1T?om*zclXPGtuYTjCHO>{^GDHlmWF8Il3s{U`r*! zDhoEi0?erKT4CH4DDy5I{et;lkci(ih}S#BzYEZeU%;cf6vN3!Fysn*Q)HorHPOSI zx1TVTV8|%mFH1_XVEt*L@>QYT8wTV&LzSkBp1g^g^_=nundO3j(!P0>zBWE=m^iTj z=dO6E10zv7$9-Th`-tqUS>*^JMZJzPQGmg(~Lr=NoJ0_+%;muR%)+ zdBF0#3napBtd$~SCfF>W+V618M-kS{(hnWjUms2=J=;iBE^Ckd@UD3u>9yXvv`&9wYGEr>|`{$n_WX7NEuVEm`L$4-4{hRglo^# zG*<~D%vjd!1?9t!BP(e1z75Db9Vw*4*XQiau_@#!h_LzER{-iF7>|YWG5b?ajI-e_g-j10`LBn*zRkYx6p9L1uX`X1#)HpthM(h&Aval6KAJX>Fp9@E<+Xbs zn-&yr4nmcbE9lWf^!Bgb5J64<(AJZ*sUk4c6ZMW7CC;=qsTzx-&@ zT_OL23{de{nsBL{kzvaLs-urS4jP0Sra>6Me+H2KHeliuX;j`ByLsSYIF!FK}k3bT6|X^K`LT zu+XysqG4D16(F_1v^opMEciBW+u8YrC#eCJ&r_nBSkLY1}#qb z$W`jI2|W0@3sz?s%<`+2Sk#V)WAwJZsLmj65`|(tVJ7v8A3qbbzh>)QT=fi{d@cG> zby)9gXP@+faph;V=g7gZpXV(*#lgG68XRp zhn;Q|34y}h1f7(j=xDQP*VsHDOLA(Pi*wHyHQl)p=Tpglje+d{Oi{W4!1uWbVIWiQ zU@MYFjUVAUJXPrmbd}n=0^8b2>7>O(qr2!>p=R{5WE9o!QEWhXXk{|id+i8ljJVT? zLS8gF=kzrz$%}?+>3AZhY6t(c#dgWw;fz&mH}BVFN+WRx!qN>ynVMOc8iKVeXo59j z5FGv3%mZ)A$zoZ7ZTHoTe&s(D*BL-|^9;L*O&w^jxaRuGUr6@>r z8Gyli{Yy!A|@6BdU@NggU@5xX64`eqOC1WMRnWsTIm zBNSr4wL8QEOh~AhaYJzQ!2EQUPQtBArZ+3Nn?U2=Pk|X~ho2H5MT7d~oT7t!H3@@i z;r3uRDsq+!`m{)aFr{{=c&(h}f$<^#vDq+MepOM6=jfijiHtEZJ0x|;KnFW|F&)NAKu865bD~J>Mu4syOEB`VC zmxi=oXV+66{Q)CG!q+^s(VMi%3=XPz?q-cJfut+wV+&^n5Bajs{Q<&4#lIja8JOiah|%v38Uf3ra%%_(;l|}7=|lgw9)s#Yl@wD zI1CqpvX2dCxywDh8U?P%yAL-G7XTm9JP&W)o2o+w2QKM!E;%_g1yG ze$X zbU4-}RB0+{23JFy&8b_MJ#_ffxv1@DyqqtVaN?h`*G};lndT_SKDwhaZ{Kva_}RYO zbrZK)t8e3OOKLMc_pZ)-aw}VRcJaVx5Mk>fvVH^QC+{BNk8kdkkT(g!rS+fC_3q5g z=;G-Jg*?P%U3?-JOBvC|U0Cf)zulgva&MCW?p5HmF{NKA58Od}s7t6Qn){+1)fx78 zzRB}OrkR!PWeNtO9d>{ijzBFUyyl1vqE(P@3pCetaKb@^7Cpa+h z22k3Gy^LUNkq$_9L3iWj#dc_sOZnC=eJ_j8R$|=?{?KW6KD_?miI1@zg?w?hXNt#> zP+LtKAyTUylw)tewV95*cnv9w&oMmClo0Mk-~0|#&jtoN&roxPA?@xVOTtVCbZ_ds zsps5rc;6n}X1X-W*QwI_$=2o2k;8|P6uYD_Abec_4Z5_^`N`BlspU*C?4i!!0-cnJ z%C)rYHuoU??WpR-M@94BA4u=I zwPh(|Z_KZzeQ7(f!fdGpeIp9v4)?)D&Vdg(6F-vcb+%_@g1k77?@A3<%i_ZhXGLhs z(mMzYa1nju5%Z{M^Ch@w9go(^7KcFR{=GF36?}s*VghdOy&Kk8MgTBpSTas^kNfIF z!>s2Y@^1N8N3XO6Yq#YH6|Z=~Neao~?toP5Gl1M(;aNIPP*}fgQ zX1WF~v0g`;qEHuBrIdztLIg_E7ZagzC0XEq_+I|>Z8a|28c zfJ+5CEZR^ra~?!w3#vD9%G@AVXZ6h^td zC6CF&3?Q2grd$!{>9F6}~E2c{$^zTT{CE zdZiQHZ<)v7&@xag;5;&rz&l52#PUz7eX+5ZBWhtZ!s6^4o$4gL9-hR^akJR(k=fh?h&(J2 z7HNx*NmK6TGy$M+Es6Zzrq@SK0aus5C08NLWMT{`IpgSg5AhV~A@H{iaypNwI8nL? zm24O;q|6QjKRng(cQTNRGWELtMDR)utN)J4a>)e}_BAj(Vs`SEPI2c9;gDf-mnh5PewTe77`AeDmk_(2L)i{X?IVV9ed9oRnV9A2~Fb zewkJ*MsBHKx&4r6$4Ort$YC|u8{*6}3a6&;@moau+|r+L{>0@OZ0)UJw912qlAgb( zRjkq{ojc=f7uqIe@<*q5MQScIs^mj(VbDYY0mR;Mo;t1I#Mt9@qMVb^R9Y(LYkmxS z7Ue)6!?Vtsqra7w{)>YnvJ9t#$GcXXr8^vkDQ&V~?9sVvLR3(AxXXw_r;xB{c&hk%1H4p5b!=x1txZ?~V>M5BLz0$-GybZw|)h)N? z24pSUAi5Y4ehE$$Z`UW(+``5)e(Z}B@jqX<-Z4g2ussDYycdhjJ}p);Na3Do>A557 z-&12t6Qy-o{DDNR;-sV0&@#a$3$2$O)x%iZ}6x)^(_27{_S^% z`cJLPeff2p{kHV!xk!s&8b!2kmB5Jh(ZReY|HdOK%YR1XFaPU(Q6$0)SD?O*Q&){A zk-EfNm*)<{V(sJsWH5!0s|@2STOyJg?dO)Gl}T<4Q-8M?HIo#hcX~eNX5%ue^Taob zd%!sl@vooXE`e5$Q8}{R%m&^^Z9t)orfC0gfc+yT2D84|=Yi5(yOLgCc*W&SXa5A| ztqYISZLX0)7&n|COb;oPJ}tHGX3&n4l(E!o;T5g2*0{>-#_xVLaMnxFs6S=?`rHdj zE*{}3ED8~IV?}m%@{Bx-_d+d{3#MPD(kq+XU#krt{5pyYfv^({IQ|Y5o8M{Q_tM(m z2}RpKh?z$qEc&81T!h=$8|rbXx7k&O2vad1(SFY3ziD_RpEw7C1(RYmJcrEv;4Lb` zJ8y(YFX_DaJRW}hrrd|s(TdUtBanrZ^D9&JW`9SQ?i|UNp2tOQoNqTCJWtQu7&X4{ zv^Ntq{uxDYJaf1mRpMV$wK*>KX)viE1&UCB;j6A-a^D&*ezGCa#q&=}x5L^buF`CC zLhW1Qx6cRG(>o?YIfpJUSk4#s;g9zwk#FB2+UyiFhFs}lq?yh(Oyr0Cg}Ke1KrErU z8wM($qk+n1l90Yee+uM?z$Hgl=w_NVWMdveOt>Xyz7~+J?l2x zKcAty9k>=i58o;ms)$X6(@L0fIDsw-y@V~eKK~K<@~q!Ma+~ERE+@LVPekFy(@y2Q z_|a-v!3`m)^U$=$sfO9NnVr&zJA3@y4d>)p1(0bVUujQZY;eVgb7iw;+~rKfD>k>E zXy*e-h3Dg9nHP-GveiLr7|=lK9?{li`JiVWE|Nkdw@0F5*J87HyzkdmIrmnu3I=LDR2y#{C5q57 zwa%MRbgdjtAMUTY06Qxb2{z{|)yg39$1rS|_KB@#(n<2Fj0}jB?=MOi4@~`k%>zTB z$>-s<0TI2VPjqCO^Hv!1x(F2!JTays`T4Ui4h%G&;5*Bl!iII{7GmY;ia*!)84HQ> zLiUu8YbV~#8P9xv0hcbdiiqfs*ps2Du5+Hi-ZYTmFP9TYS0Hu#F~QuN{aA%~8nziCKmU z5_tYovpn7(n{}rug@6F9kM?E~$tQBW;Iy3L$`^~A3;}`!hja+A&5!N9o8L90f*$G|1W$oeUoI~3!GATSW#3Lz$ywTzd5PKg2UNWQCk4#?YawwZn5T^|&_ zfCROF&M)u3sFGVsAURxp?#pA&`{7&tPvPa3zF5I>;fzQy;bHj9%>yj+a@BWW&LxZh2@HOeM{T*gl$;Q)w-gm~D=F&SXAzukFV5axj22nMCNIa3+fRiyITw8Sk-TM` zh|#x=P2B@k3uQ>Qrw6h|oE?3M;ceh;bI@wJz9cm^TgqE(Wz6y6Uu+Ij4%gS6rdN#^ zJ40mH^=>tW+WHE2ohV^E?d_WPmfXs0eP8GK_I9L5vogTh9Gfr@>HZ4gFIljP-7B$_T`6*g^Wx32s?m=@|rNT6Xe;U94do zPqQ{=dXK|TI5N?DZ`W$M&qY8y>QBr=DDuV@x{zI#cX%^YOLm3W8MK#{ur|VyHRmjx zwxO6q){Xwd>jMT?SK(x+64*A)s|Z6iuW(JclWJL*b~-R*WYt3C!L?RO(WC9z0WR)W zAg@oi^Y%uKgcD1kkl=OMoL?XE6PYBF#>sbvM*7WLxVFOF7>>1$t?@6=4mKKZ-*~ju z9xHFj_9wP8R2vtlFvb^Xg-=pkciz5`{$1sMltbh9UA;BP#d7dz$iCTB6@?dc3GH6| zN#pWyJLEyWN|twyT&|mSdo9b6+&IBw&H8ud1(Av7SPeRHaoA(Ete6Grfd)BpdS)$@ z+Zw0r#{y&mFCw2$=d|M8EJ+o*OLXZ*xbP!Dt&aJSB`}*lN~`W`#;uobCd<-62izat zs&Te?lNw{Cb2g?w8>p<`-;6rR)vP!q+e(a23R>M`l^jo~S(@uUBTzr%mLAu&-NyN% zE#JPlB6fOe6n7Cde!JSK!{+Tos$!}Bpk6Loj}=SuyiE4sS5rdDh5TlG1G+>zT`I-P zu4K*TNr!N9f7_eO$NJy~J>Cn9V84T=gXK?G2OOj~7shqR*Y+FZc~rZ#Rr`?*|2FAw z4Q2V3!t1GzxBAHKxc@0V^VK$|#km0X)_Ui{NB*OIRVh@FPa^mU#oFz-taSm!B5HoY z;G`;f*?a5}w>W#FQmNfU3hyg$v)D1a#F^J5xrb}B%kHmtBYR_H>(09+-nLZQl%y0t z_C(H!(VbHoh77^$5z?P-M+SdW?QAMa9im|>7-apv>tJ~hIK1{nYh&~jDo>9kJWig! zUWdreYO9aeykX3iAvW^z*kYC#vBtqE=r|sxMx>Clx(xj~BoPbc(69wO1$mTPxNkIa-@z+8@J9Ktx9QmrJ7J`}%}vO~ukcezha$JU6Pop2H?2}*@ldi8L#Th3MxwtUo)MR3MHi7v{>RJxU_LhW>Xh$Pxb^N*zfh5^yF|pT1cB5 zN^GWF=(kBlc`$y0S589id*F|}(&+ugp&2CCg%Y6W>|C8@wuZ#Gc>l(`it`tvm0!9D z!JfG3QWR1v)QIs9BLP?EQM5>s@GLYP;C{kk-;UyLP+|*XpSxTTE0}H|OU5P0unR<0 zJ8Z0Ys1tHyJ*Npd7C=uHX}vX3tsBT4k%nz&+~qzM`f;VfVteo7=#2!ZNLGmsX&FSv z=FTCF}=6q!g+?!jwk5AQ>{X0?e5Rqs0FX&&n`(WOz6 z<4;0H93bM?PEt0n@NIh`^fkNGI*Oe$TbQJB7a_|8A*GdHCYDIZ#Jlu`D+=%O`G;h# z#knQ8SwBiK(?t-Ev^`hjl_Un0B5fUJHQf2a5`G+{);e_Ere!f|-`O`6)$K&TMThHg zouQRgk(ZNIowtkV2WH%cbU{vgQANz@ed)J+0 zYm7gjcl2%XWU1#H-M^1sQmiGE-P_&UvQ>dYM2U zyYpGim8qB3a6<629K0UEB&uI(t&MJu1Qiusyf#~&@M1B!ZVL~+!#eubCxJ$$#QP(~ zWfNkROpZDf{`4$`2AxzF`cSTRF$OYIf#&9HvL|Hpm?{^x!}P%pLk)^M|5k4`ZuN=F zAy+sAWL`$o)L%`=N1Uk^x^-@bORn1Xs7}lu<*VeyiuEutK?I|TExkqU`+t;PG&3P5 z$CiM*(X05_!_Gw7bJCSKIrf{&^Dwn~VkGV~)bc`_|G^$D)Dqg3$H#uUJBX5{O%iB4 zEsS{JQlSxP_Gy1D;O+LF5#S!nj_bdko?Y*rK2A;Uq+^2YIqY0m_jSs5dRL}H+OIyB z&3}qKE=TVTOWu6>ip;E4*C8a&khpq@W?cqcmp2L`kpc6~%d}P(?w6i#E=EADKfUNf$EOa>g?Nr%>Y`?JoI|_c8A!QRkK{j>sQHR z7S|Q6{V5&qT8e=e;w|$7d-0y7zh|q4jt@Eye$Tj0G&rYwdXMIxu<3U+hIxNr{8u6& zO~_&}#i#^}pk7V6pYG^E1J>uhcs|VBR9#cKn!qx6I3^^+Zosb4tXnr-LBeAd;+$RQ zKO5?<%LaL=>Io36wC-JR@WlxeUR|FxJPlSE~P!gS;YE<_$=bS#CY*cZ80p(BVnFV z(+hr&<+19YQ!i=2eL5}=vu(I@BNku`owO@I-yhc`3AiT13>_DLxL|4E|FEpCF8q1Y z6tKqf`H*$#J8y&q&>S@%68vF4REAFuaOH&y^;1~ys`&vUloYiwmk|s zBv3=wGGo-_H^_FX;h8W~W2!t<3FmG?*nmF}yZz^$9+s9!4lE_C=^YqA!VrU?&40>C z4Us6D^s~qU@5hk%GW+wHlp6tywyb=k9d~QIC5Kq?=#zIOvgS<47MhSrA+bqydoGwC z2+J^1cJ2H8KPAIT3XPDcw(M)8Hm`K?@K^zb1~=XDt-Ic!xfajctS-MeZ_Sp)xOGKY zqn0iR2Yh5|pY5O2l+30Tygh9lytUweMNEWg^b_3F-C7_L~^hhg*#sS^-E_*v?;rm zBkoE7%#)_01GD_Fj87~QoOxoS8+4W{9y{^##Hd+9e@*FW7hzOzd-zhy%&q?il0l0o zQ%v|F<=xOgF0KQj+Z;8Oer2#(rs(PO>B}i^HUifp`umwB$Hn%H03K{#PX zx=xBbp35$Q(bR9vqm3*EzXPz(m#cJic@o*JelZD6XSXuvUz%-6$UuZ2qIlm*$BgcZ zI(0bhcb1hNNph29{q!LXfNjXIdnq;a`6guKydP~J%wNwmnC=8KsM4*E%v?Em637Q~ zONt+Fou4GcZOj$1J5jun0L16jj~s8&z-HEp&We21{To)*)EX7-bu`sqC8pz{I&0!0 zHzkyab`N3l%>bk4^B^Dx(FkjE>gRYO#gWrNOS;MUF*_-rE0M`vlI;+BzZ2KtGQDIX=5Zq?OS`DjZ>H;r`uEAM zfHHO3?3KD$kV=6xzM2Vo#wrp#c_b>Hvs-Vw6fCwepM^2zRf<*XY}ML@3l0sZoZ0Md zo4)Xmq3uv?;I9@!b*Y9e4aw8cGl)08E2)--XsCfP$HMZFGrhaBD>CaTMHIEI^>WLF zXy%ZKZmHUPyBmgXXc;BXo{*n=x2lcVs7Le=y@(SK3S|}`c1pg0HT~9SB$iwv$$t2; z%cV$3{yOm*rKZv}pyKV>XN?n-L)Gl#(3o^7$KmY7cl1wvaj-#d%>APs!q>mAAaU|g zBdAm4k#|~!j(C7!ZIrN8b;hsepL+%l|K@S~MZ*7+4SV6c{6b6q`cu_EQjy`mJH26N zL=~oh(qbq!8L1;%Y-qTA%`YL{`M$e(lUt18UZ66ngimAfFz3L}-e-sHcqvj1u}L0} zsRze1Wf3G^mHyhaz;aL@>8nS%TD|M(C^5t)mFjAAa*7_vrWcA9ohp}BuyP`f^( zb%h!tlh5s*+OaPIn`wCxdg0jmH!Kgi*-odZX+t#F30-`ay9KZ|3YZOLUMM3fB6Bp(7$H7w!o;){y)4Z>VDf#;XiaG z-a=@M`yW=54v>`;U;pEotA-Vg8)L7o3%DCJ=?0BTgq#kB&*w^UowNBW23TIq*tx0UPA_dxD58?`v86+lqH+`l9Mlz1#nzz3YsMYT42t zC?HA@$s!20ARtjP2q-j{AVD%nYBG{@21O(_IZGB136gV0au$#znFa*OlJnHTdGp>l z^X{7aXTnK6G>v-X2-PtAvfXwGGx@{ML77E}2>N+bN-6ifrh<<}0t{|?1yC@x|fulPDijl z7fJO}KcV}Wc-r?Q^Qi<70Ml4johR_bHZB3-`@-20iO?0)(e9IGB)PNIPs+naHKhj| z4|lnA+yi`bMjFhy^+jT3m-?%&U+OxV9(Ety;aNMjHlKd9=T#l^HV|knKZ335Y2REx ztLn5TZZKSbnqH8dze0Uf1|ooT3{?DVRUK7oR-L8xsA^2p?Ow4e9xK>xKzFavqr*!+ zAdK|Gf6~YOY1^+{?GwZC0nuKY4Xs#yX2v*mhU_iD_9^&ttLc8~0)rn0km&bHdh_l3 z0jK?`ZlNc-%-Jy^GN z+D?buZ>B-ymVm=mLaW|}SZR_ci4^3>5dY%fZ_qdsGX_vj_b4)2vIVy3{9Pc+W^Xmb z=rwvPolsPct2H%GYn7+s>&`U@tVq}P!~uwkw*r94!8a9Bk@>7X3ckmvMco?B2Q8!m zAvK0 zjjSua@bsPYK4Gh~Yd~Vw@NVc_+(?B4IM^#~i*cHUb0A5sZLJMFN-JgaoI!K*+7gfD zr3$1cZ2IujFF(KWY#$UE{j%afHt=cOB2)G-N!}SB@FAu6tZ$8g6H=Im^jxdcc9elE za;}L7KpnawvAu^I=M$zb;haW5&?)J zW{xT}j0q7HTf{tMhB35t;;11P_EEbfwq`%aX1Wr90?wlBR$_G7IzLwJDQ3BF-UlGy z@H0zTd0BG3yYFeOaiQvMx1~GGZv}DA8TR+FN`GpDA`Y*|R3ULhhgp<8o0M!HD#7h2 zonsw;#8MP=IB#fnNeblhFqjJ}5-48Rmw6to<9EE0=HA~qy;J#|+X6IsPT>Ul75*{7WKOSTTjRFSI#30uBF>Bbmk-__myGVnkJ#vCU+e>=_?#t?m`dF|HdxWDM2D&7I1GIk9WE0_C;=jq@LhNl!6MhE_SZc|zpxk@L2>3_> zuKecD-x*uk@ss~la(5NjBEI5aRPnnemX|Y1`pD=vv|tiOI-7d2?T0jscIZD@gb~^U z4XGm2e6<=QxO-enN*p=knc=@_V$;m%J1@wS3?INjdz`hDW03#Y?QGw(QCtg9M}Yjf zN=KR&$MAnZ3o69oeSqt(LmOo2vkxnE|EP*(j0@IRGh`p~iY>Q;GI%UlF z`U0EucPYQAmYXdj+<)r5I`xXhz4|$-oP=hlze+aW{k)K~%ttsmI7v8rj4Trig$50h z679^_9yQpD%_UQ$^uglyy{9jPl`0=sBJ!I|x<%C{32N#QL_fHlFX?zy;#kR1hg!IK zHg8em={ZmLjuxDvUF?0mswiyiK8&CDjE(p*nU0pMG_%^#rg)By6^=sHb-Xoz|6ArY z|Ba31u-8^&XlVFOhTQAeOodhRl|znj>v`E~%NS=pYBgT?OT{whp4TzwA%2NfKboJc zmQQG?-2$obBTK+$+3>;)wkQO@i61uo$ zJENLko2+OSI1fUDsBT?a8oH;k#|o!sUSsFWa5%pq%4sw z*ba(-nniFPQgMJAC@SXbJI!$mj$|31nN^j)K#CHgz5wdi9?^WkqoglITE<9MhwQ;n z`E8cZ`(%?fF3v7sV#C1wu?75(KHf-?>glX``C<2k?(D#>$-&pbJ^YDlOJgJ{YX`Rt zPrBX>a)zR!JBs`Bo;bD9FN#QeG7h2N$kXs- ztMh&NrjnXnx7*5f=8y!7^Xdeh>D$6q_)m?sAx-VhXe+L|HhjF_}j?s8LJU_RUuwV&}uiZ7gSPMJ3I!&jcc+X*lj{QN=WwFy4|L{Zz-o3IA0Mb6n^J$u4P`e#KA zuKt&b8h{Wil&edK6M(Kk$A3h=BlMKyMMa9X-_->ScgAS^YuE6YeQXUqW_4Q8gnjV% zlStk*aHARh1rk-Ebrm$vZxR9SDttNlCHzmY_45$%KVLkBioYvE{r@NjNZfUA8Tixx zCa6`mm&b!BBUoMSb-FeM<4$p=U`|7wmarL`#(hb-Lu|KwWcgn+O9|ZYL$(@44igUv*c%S-(&3 zG7yk_>3}r{_AN%{+U~&qd#Zoa#QtwDpz@b1w7{s4Ipv-&qf@LJjdADh8oTZ zczLFEgxW3FmYDP!Ddpiv==M*b^0T_(M7IsU$M3k|?QcUEIMiXlp|{wqeqh<>v?p|9 zisKe)iVQbc#Rr@<>iwuN<#pP-#Y}>$AvAr1jviT3Zr2jjxO7!H#03;8U7dTjs`acf z&JlG5f;#9ExM#E4ejpSdyN72323A;s8P}21kJs7zDm5>Td(+``xuO_uG}h}VO=jD857tDRy>4QG zr72cm2cB}Lwja<;#P-Nv^6#$bs_n?TG3>8>#XqGx8awYHX1$Wmq{IhG@uUgK|CI_} zyZ`kHUX1UZzw419%g2#Rl)j>LR}h^QS#gi7G(6eLT(TcbY3+mvr8cjeqew_x&ZtfpZfaWK!UZk<@RJEi_20p4r$=0I&z?oJMLCQg!O5%c@q{>1j4ujc`dr zXX;P|_3=AI`QE-ErI;X)J^ZpwINM+c+6WPuPA==?gHB2ZHkk`vwcFFiH)oHAv8WM~ zduyYK1!&PsN?}oq+85QZOde7w2){>%gw7*IG9s>K{8#oVUe!H0ey1Obhc|n^HEx=# zSx7Q2e16q}zeIFrC4FPG4^gK3Z0D}YLyhi4VY+&)fxWvj)aUn%=)2)f@K6E!$+?FJ zHWG9szeJcsZ6}@l% z*!JdAx8}BfVC+x&@u6C)Ru$hkAwb)2BNcVxuLQSZMNTIpS98s;EpZdn9_>E%TVLC# z%9&2szx`R`^=!Xu=v3)s#Nh_&_;i|1Z~4;kBWH_wu@h9XZVfkz>%@3gVsxtok6EJ* z`{lx=yXQ<1R-mvJkACPJSgc@cUvD8fr1`uKIcSZ0KLd<)ZSsoKN}#{C(AIAG@twzm z5YIyX`R@9-$xuaMH!N@{rg6!cEl|#qvmoS|E+!4 z+GTh5O|i%~&JI6v_N@A_-R4b?#Y*`t&R)CWdy)gPiE?fg=}7|IvKsEHaQ*;CD)T1US{;o#%CpV! zW5;0gd^~i^hySN4e*I~2viHX9{P*sEz4QX(6I@XaPz6dGRhkfm(d8Ps=p-We{?o7K zF&^j+m~fG6eCkuCK1k$_e<1NdWrgs;Lq9}%lbn=J92JyqMcD%H@Vm+T|4Oz0Zl&7! zi;Xj%cX`{qA@yTgB7_57#Xqt99g-mi@KmA?M>ui~Et!6c zI>ZpKn%|)iBHF^FW0$798JXBggzJ?+`p(Fdv3_o(sG3I_%TA#Z9B>#qsVARh<@e>c zeHW-PB2~-(vD!nBV(>^n^`0hfNR+C|kWk9rK|85O5knA-H`*o7o2g`{{?irq19LU% zs*V9~J@-FWP>b1P<9bth*x@R3ITYRBn8^srAikf)3GcaHalPmcCV@zU7n<@MLSF#qZ&)r{-yOLrXTv#*_GgwYSYcD#aGr>Vz`_@9Oa{2{q~ctJmJk0M z-+AFEwub|9nkHLC%j#~7IV;bb1|v}WrOr35(1PBhGm5YuEh9cWn5;ZlTuINwuS_ey z?^M7i@WhroFFJaq>|xSUL))>z6zAakcQrEn;_wb!Y1NKW;U889%^Rdi_G?1+jE2i4 zgq+vpS~0O{yhXfm;(m(rHS~VHSH{V?HknQ|hw_}L9VdRXr>U;MBCQ<<8;j2|{4$eP z#E9y7bJ*D>wa}}H)m{`GZ`<|7d91)O!@x?7Ze@DZ&_rl|ruci8;dY)+8_B(?slH7E z8PH^G%s8}`rA%BGW<%qiCpb@Z#3uNowlYVwkEOIql8r+D!aI8?k8! zW-i@KPhn|aO_8!V3+zATHv2r97O~YTyKJW=JXU5H$?Uv-f2{7i|LfOe)s6r#k;A{< zMeFS?hl%v?-RU6lu_G+@3N3VDuNne^KbD|H;Ri!QLpR8YVcR5mCeE5eOVpmB{U2#i zgd#Lymw``B;Q;}fgnJAe4IQ&i0`kc8M&pbl!jud`5j6BbcD5AsGpFE&8d68#vTLf| zsz`L}{Sz$;F;RGURFI8@&l4;_U!3OU=7_$j&L8uGyvhdHvq%@ZpTvcpyw@wv% zB!lpJ6EXV(_d1XST_dW=G$c}VoFXJNb9?mFXw7ZVJ2?q! zr&0syIdiZuY!9m4&C1n>eQq-DT-^;pDeoO+4t@w7T{lfR5df2r!2HrpeeY=#5L0Gn zXVV7FR&EjLS2<-ZQ4W5x>*mtB%RxJ_*~^q;mNCEmj8EIPE_dGCRLMDxE3<64?x92L zNB5>kPIPEC!$Ci|ZEX|>9mOHXQ&7-)$KOeu^cnU} zKh7SzIH1Rfc)z7e?0wzT9WIC6XUbai3pztmrsAdUo@wvqPJE?QgKM=Pe~LMI%kTDq zPR3SS_Llk>oOkpdWX=q%NN&?zDEqktQ5_p+*^~%i0&m#P&Ehn`&pmF%Jhsn@d=47nB=DfE$ z7Q<(^$Xk1r=rM300u%6FUC6*2hCD+UJutit@)5|9!i}Ndz^mUNnIWc=y8@mArhN@! zp8Vos5#U&Y7#F``JKvo7q!cQ4aUbTlNVJyGWXptND6oM)rIHEVfIJh}k^|jxtFq_~ z1b?8JQ2;Oa53vg)sX{pN$hGA%J|@VDw*;7mbcDaG@7)z)FUuTX%)KxL3LC-y%#a5s zo746amIZ=2sh8?R11>DlM$LpG4T9)(X!jpXw#2a=urDyoOo;MB)zi61`2VDWTOSbVgbm?)^g?C4h8?aZwX52jj}e0@dn zX1b`z8#-@qJ@h~Y$)y}bZtlk@L)Q15EAjbV4UHHuA${Mrq?p*~u5b1E(XVx2?L(9K zb>>sc_EL*KWYohWSPWx3-xnC@C{AfbJ$Z9ITq1&+#K}gM+^c?dG=)BiR={P#rys4w zL@8_wjG&S?Lh0&JHklpw{2v{euz}-mY8^ejyV+!Gj@2i0YsxmE!L=+Z_f8kJ1E{H3 zNpco6Gf(E1=!-ZUG6XlMKPH4QCh;JFyamCzGi9 z?f0=P+7SvE5&5*frnCliZWAAM3~1@rh~@|g2$VV zsRY_=B-dW|)lgEqD=Bxcqar}Rma+8})#aa?r=jg98Qb%DZW|F#sHr1_Wsf2a>Poc{ zN6ieSJ2rs|R;}2W1Z=8cI9#thW3Q?|KjE)DtpwyKAB2mbr z>`vr?*xY^{@puFmE6*FmL(Ql|0ud1=5q8EFcP(V+TInHg%jjZ$7zWLkMKDnph@XM^ zWSEFhdPOLKhCT>>g53x6NwM`l97bjXhD8euOT>TkMO8rxe6K5>=?=fPPnOOghngYh z%__2}&OrnP9}xl&4n(a3Ze7Gd=>7K#N*i6oO6*HudBtW4r-K3^b{xG3`&;733fY^}2asp3$aFW(`1pq|!|2`12 z8v!o>Kmy2!i>P~Lon?FKVGOVR%q{kWxm7HZRuLAdGZ~B!7$~bZl97+mLYIqrwTz~e zFy)}j_&nrLmG=RU^9~CWDnd4K)Av$y`{M4^bkv(j^X~ViLHcEQ{Ke$N89)2;IJa>}%_QxPRcDTG=TBALO}p^{8ZPtRO{Q&0_`MfDX86BBbF zw7k4LyW12S+mVu}!F&vBfzlglv!F$&c^@tK7YWmejcu+#o3GuJmh7>u) zRQu#5Z=fR|k*n*n&e{I9ps}bm!GQUxyk=F52_l#pMM=)>p3p_8I4CAi-ZaS?qf5d* zzHfY9d9f+M$j}nEmuS5suiI2b(P2H9TP5}Hht#zir*83|m~RVTEcPHBzF1dG7l*3J zaYwyQ5M$H&JP#+@wgpA+anspePcJo;5PZmmiQGi+(%Tgky0Q94ZJKOSOEs*Ic;#Bj zGzAeP@cw*sJ?fbJ)(r`yB|i2j+(g)t7Pl(7KW@9Kq^6F2i$qjk-2a-ck|TB6KkWDL zxP+XoFN?_LG~V+5_rrHdDVXp3Gr#N+ODCU2!@|isxg4U}82W=XM1BbH23qM_$m$tb zm^|m!WWNhhYaY7i#ud=mG>aK)Kl5F;#>FkdXwy1uyaFVzUl?+yZjLs|so4wjYa9me zEoq}P&#n3_EPEBpXcYXfZby?8dX)*WF^{afOEk*tiU0ih^8xx>I=ki2@NlkB;O4@@ zkJ%#{$m2x6+s5o_17)I7xIc-0N27(~`%-W%lckh4lzG(gZ{7Dx57sw#fra=h;!52q z=`Rs&M>|5hu{3EZ8Vxqng(oLA62Z5BiLWjnwpvl3dm+qoK`D1(3KDG16VJ%dQkvYR#ss9^c2xC(0qPgN}Dri zRq8rlIbPbuOGFdEL0^UgUQ!K9bqPL9S4mY4;g@2kK>h4qUjC%%I7=VHVKqlCXR8MM zbv8$DE&TS)%%}9z>TOwYHw?hQuS(`NGx-gc;~+7+8|K9_CW1@EMVv;zZzQaxVX-`# z^QBZO9NTR{hY}L+U)iTQSuaMN-6qtGF+33o2?+-W2O}er|Gju-M}Y{9ozAe^M1PX5w}PIpxA(&0;?u*!G-ZsP)#-iU-5YW58|ROdGE52n_y`0osOA`CiCYb# z$QEUq9|jy`_^odj$*r~|WW{Rp7V+P{eTx`=^!E^Xxc#b#<1IbZ>afwFoB0F*2_0_I=QrG0*oE}(XlZFhyq+B%rY0qcT5HcO z!xvTfqXDQBd0ANm01Y#saC4d z35G4Fav0Aw3mndkOsNZtQwY#bdKo&J%Vk^m;ZTV9y{{=ABlL!AKD|}!r}cJkLHi1E zR*CnsoUOOJ?!Q`QeRRxKsJiALcdeXVLdo)p{f^~yJX?(LLBq>8oFx-ugdC~g#l?k5 zuZ_iN`{(WLZLwVH#YU&+a-FfDpkPZ&i%HIFs-fnRRn-}FQOcsuo2&Rw^U)EKFXXd* z^@%AdDH$1fKv{XYapTHm_q~CO3meeX(o&%r6BGo%2G@Jv9F&!n8Fc#$4i0+z_<&LC za=BjYRz=<4@aFm&;ll@99+!P7V*es5Ej2YYUtiys-o&4knzem_uG_1roCL$8DBDq_ zPZEw#x5q$2cJ@c$_4!`2p^6q7E#1Y*7IEU7vR2u~G3NUu34q$v5G&6}`9@*WNc#wT ze0Vr)#`gYlGgVMffJQF)_t7u(o7`C59V)Kd``hd4>M9sT>F7$EkkEtC1;~6@pyP=I zRR5@qr7~}|I_5UDz^edaqN2cdUnpXQI7RgMxV(gY1xHm?)%-u2mA_v6xV;qu60#Sg zNlowidOsCb-yQr9g!aXpxd|xADk6j^7X>h+Pq8Y5XWu;Ob8=IbQS9e-R0<_&|GFdh z>uu?$`xzks7%#5^k4nJ@2M6F#QV@?t)k^}(Z?^OJW->vF#9YR5UF=Z0D=&8hhA(Xe-A+z_9oauCLiDXR3pubNQH$zwRKip;iM+m$> z5F4x1WTu}(Kfz?M<;XSGb$pj{%h$gv`}-1yEeAWz7|Jajzh5Z9#rN#~@<&Yw@@G+d zWyiDij0>0jOrgMO-+-RRnO9@|b7gJV)yf2(U2{*U#=gok5*bNDc>_mpquS>Q*;_0f z^~F{46wiW;Vwt1bB?JD)%pxW06G=|DlbNdx!zGQRj41`?jKxCm@gkzZyM7}0N&T-~he8GZ$-bq)YSYD*DSkeU zYej8jKJMZe4LH-UUA`v8oEhbBXlY(DbHl4%n*XxSvA(@nMx=nKc|b$Ky6*o}@rD|j zm#^fzGyQq6z5I7*aq(CQ=4$|>l&;;~HyFg-y*(ZRMQ|C4A1>6*YE{l8`Y;tN4o4^} z>4ifPw${8N|2ewO_xR)ugNWzUlf@V>eY{kuW{fsBK@F z_gl8fr<4VVn5qPVHbp*BkLyq_H43(bO*^}}w7XirM-Qh0!e{)PxLrI)XaIoKwdPT| zuody$V!WwkuHEk`2Fty+mZ_ns{xf^41F_lOra?h#pG#;|n*42Z1T94CLG=_%-yHEj z_Fz1lD14i!ZoU<7&xU?y5lwn};Nf|_CfT>=?cdtq!XKWwZ?B3Mk2)OEzn@UV9jpD} z7D@*JIuntbX-zb02 z=3p_4^C2%rmaTYP#4_kh?iKlI+2+&Uc8~FTd^LgiubJ~vn|2O6KuTiXCcKZ#<7QWo z?^zL2aBF0l`}~XERvzh<5;1Rk+jlI?EAOG`C8?3X5Ujip;p+0)8H)}C{hL)N>o*DH_&|CM%GG|Z)UdY}< z2g%Bs^ei zz<+h2Kl*!YjGmeK{^|-19-08z5)7nG@Z|Wb99Dy8y?0r@xgcB;?NbTnbrvreBJ)uj z&^eI{MOlzf4mpXKPbM?D6ah%?0tR8$tIC+w;wNQR-w+#PFcL+*``CCh%__X!0N z&U1&e_&g}C$21aA!?__Qg2GJM!lEQKHMQvwA+~jZIx5E@?cK24?dC*U(IUvhv7o11 zq~No$c!Bk&SbcponUOpq0iPxzbGh3f1~yDR7jdiB`<}$-w?7$BGdIX<$${pLEWE|! z4K8=7Xo*P@G4gyt+?-NeUY#`!qK*Yjhv}4a{rl*vSJ2mTZmBdw{P@B*d^Tw+b<^{_&s+5N?ax|9fQ27dCvl@}FNN9&h}ndeLTo7c}g zWKGwPmj#UULPA0-A~CU;Ip5L<&=9=jz_vUtro~RI-%`JdFk3DKJGZ%|1%w7ZK0a7jSX`W( zk#DP6sa2A?X{o8h7LA1k1v23b7OGhGTdGRJ8 z=c|0Y)@qU)s`XtA6{x!}Ic;OLwvs@rq@zcjNCnsVq}_{1pE9|G#&TA7D+$=_Rr((L zt8=+tbnH+bTEOUhMt6?w74Ee;t%;5D;Gz)Cc(o7{PaL+xE?Ke^Vo9*qek-u*uSO(? z?OOy^tc~)8V`XIYb71y#%c?9MUbDz6a`*fXKePgskE#NmCdA^E66n`Q%QFG-j#>6) z)#|T2`!ne}@*|1aa-6u?7v>3JBDfTA-$h)e^*(MmkD9TiyNz9A>6D6eF3F}L37U-~`SW5aPvr%UR0%lb5K+Q} z>Tl?sR@n0L@;0@UT~=X2V_F$`e2`hJ(MJS?s?*~T+oS@tA`si+Yu|~Pwimw_E8rGH z;{M#f@0sSulkP!BZLpRwSF-zE46DQfwEt#aLa5HK+3zATQ9uP+aLZ~J0EH;&Y#9FCf#vIpU31I-R~ ze%waZ6qUpe6Bj7uAuaV&_nUt<^F}uxKg&q^3hT**fC2qdxo+=3T3HY&`$?sX1 z$F?X(zCnfkI+M6FEs2FF661xc7(?}m;fiA=qiA?au1~Ff#m`N&u{I5SDg6=snFuOq zG*}?T?={`?QskmXjD`FrVuDy9`(9$VagDF_GB80W=iw}=I@{F3<*coa6C|#_VEn1k zuYdIx@!xHfL+>>0I_Mbqu?PHIZx2V3X&VI7_RIGi2-u(#wrr6`ckRJFxtN_finsX$ zG}}YMd5^+C|K}|aKin24F)1n7jc+(u9i?LB%%&HT#2H+Hl(q*V2FyUhNyc#%x3EJ5XHgp(RKLB_qv-*x)9&qX1DtNZVw@R zQr`5FnqWNk^O2K%XrhwQ+BYLLcGr^R&d78gps6m?(NK^Q>Gb??OIcS< zpb+cM)q-%A9i>c2NMxGDVV(5XXLleH_5dNG4j2xM+iE5;DskGBtxhq=co zqC%_~gdI-DM+5b*n*MBD?2j{zotA9L#MLimRWC;H=+i=q)#=?6(KoYEE{#~Vo6ta2 zNm$ym)cJXF4%5)#&5vc}b))<(WL$iFTO*#h#?H&U1n?=B+R5cD;{$@jL1IVlcGRLV zHs)vwOJ5Y~v_;xw`zOT5qNUqjlRYD{o-c2|g5i!Qp|S_+hQhu0e-%v2#UWMdms&47 zY8uYS6cVDNtLo{+7~>?5+F4nV#KHW#x?1TC6p9}FJ^tSo6A@u$ZEfxCTip{Gi5B*& zX718?d6znp?E6sN87dGGAFq0pB@?1|beU=m1rk*-L8M>(g#(q)OOb;YNk!d(yoQ?m zlEiESueoAZsi2nN?+I*)19|RNJBjE3cUPY6VVp-c-zu61e8L?~#INIo?C#LNPx1D9 z6^Kb|&oXiF%`O+`Jnp5ie`bx5bi%=Jv-SCX{(Z!{=`SBQ#iX{@U?Lpab}M85j~S~P zi_le1r40vfLq4Z79y(1CGJCX-Q%L9LqZ`hGl*wZJ4J4e(_M)+wIq1JTm^yikO#TvG zADo>0{kx*Rer9qKdDt1L9|uX&+na}R_m~XUGMx8isYFph0VW7ctHFHVbJP0}BoRs# zo2%{o9jY#s~B7ef7h_@zWz(sog^F zXk7?GMCvsD1LI*&~)D7Vjlbii&rZMtjt4cz{3H6L!lx^T)Y0hw$vXi zzk(c4kF1ipGdEE8SGs%v&OGbmJxIu%Uz0WwI}=tYcAPEyy7&fu6%?>Nu_fePM$~=g z6%YaWJPscA?!-aJ<6==A4J&Gpc)Y4j*3tDQ5s(P=^lXnasJ<(c@pt`a7$5)wlEaYN zF;hk9n?hGftJ$YY9u<0#qTH&vVn|>U@x7e?fWnGjV)vZXnsO@e@!up=pK1epNT3N3 z+hmv;842eDbMK^s!}oKk@EBA8JlLGpqF+P7l!PByGKXfrw@@L4X5{vP;bH0xurLQI zdy<^40@s2a7R-Td%xl~bKM%i1>e^6+wuoYBS9jd#!CjYj&&$nhR?{IRp;xzmCkxN_ zmry@z^gA_b4H!}3W##0uv$LrcGQMYLzdrptZEI`$3I(@I24%&sDCU3Qzr%P%Rgugj z=gP{}d~zf=rN~5lp;$y|aPn}QO7L-*rbcXFt+}BgL(rc$AB$eICEKRX=bsJ0!p7#} z;Q<>&uUP{xw!_VImWqabF2{n4ExXd~dvBTRk3~pmZ((t8ak1GO_)Z+~VB8<}@$1*G z=H})g9iPgipTS{0do)wn>bOOOib_I9R7%&my?EcJYbnG0)+ zRTPRAol`W{drCH!NP|XVvoRi^60tkWbnIDaFpn-%(_hVw3ULLtIqd4~Tk~eA!|`rR zBXf(|ISz(bJrE&jQ5Cvu>R^EQp8U8A|7v|mv%|?0MqLEcmx!CE@4}38!6EMHv;9~m z5Rv6_2qODI>6C}>1&4H`K{$OK^2Zn;QMtqGLFn8KN;#sg6!YeU{=;s!gydcBcrDA; z%O)x)=lbH9(gl8mexf9=J*RD7LR$lDyqqBb7IM?x;=oZ<%`&);%>*+r0>4$9O!-Qw zP^7Wic%(!IuIcX9+E8vH0MmXMAEZQx(tq>1Xp*SXo(l`{1DDC>*{ZTst?w@TYGhA{-M5UjTE7 zaLAV%L#%9&G)uVX=}=gv@L3c5Wd2>wy11p8YKoSjfW+kyzkCx*a+O^IGXj3O{t#aA zi(n~R@2gnR&efY|!=5UQ1UC}^C`wSV#zCp7QgDbC(ByrTl$5-!ct~n|1vq10Kx9jjQkS06%w}Kt$~4=O=!@Mjiit zeVYE*?jg^N8?YbeX_lkC-OI3Qdr;FcFO~J%@7;XnZLEMkmtODB2Oh7#+a(H_cV{ag zFt<$w-v2DQMkAW}(sy+ib{9S^xsTM?2KTA+%Ek;=9M+$R3?S3ShK7dL);jw5bm-;I zCf)o}D$iTx&aqq;A;Ua~t64u04P~elPhrxR9m;IN=EsJZY)QB3U8! z-mvMCThMfg-4b{}<*4O3Dj=@Asi$TNH)%7X; z(FRTUygf0%LJ;8hdzfBaO!;NB%{_T|LrzZaOki2B6_N|4$RNjg9WV425gpy{?Dz6; z1pxs8(;8sKj7iB$v3*m4URa4<7^%Cf(-MutR%F701M>WjkB=aEeS)PoVJ69xd>ZZ* zDk?n7RJp2F5Jh~3ZGc!@V3tj9VaC8KT)H_Uv4b!l76w~PT!7m#6_Id7&&sFaCIVt} z#+nVe=8m35aC8nz5YcZ&Dzu)*-2OsQjLYzyQCk}NaJVSmA#3z%-U)nvYUn5)-fC^l7bMuYK|rjl|`C8zx~eK-)$(9Gy}7yhbg7mY6b?Wc2o&DHkqF6Y z=I5z3?1cru+?rrG@bOP-LISFx;TCi63mYS2tTY-hFg%Qc!y=5cpL-d$y4FlMt`C_# z#3)25gctQgmu(6gW)_`U;z--Y2}Vu4U-c)*Acwr4K!8n9CX<*yZa})N+CBl#p0|p@ z3qTzw$Tq2`QZQW1T~5n&z~p}_5^G3UqhfJAs|ZAx*U>z_-6XqyOGGZm6|mCF^f)`? zu>RovE*(BEGnxP6$uebMbc*QkzpPT&`^%L(I=aP${`y{}m`#%6`a1jn^8!dK<<|sC z^YMghls|;TY#?G>=f0jIV4BWL+&)%?#=Q>HqS;J!)U{qnIP^ZfWhExQSmXRaA?NG; zM75N;nK$K|^*@;1Ic;&cy{KLX0pG1>cXTJ&FpA#?pLjAO#IEkh44~r?Kw^1m_^^oqrljK6q&OWSu zNq3;75l2~@FD0iqrxzyX{4uCgJZB??;$BvuHJ7QAWV|(ma*Xwr?p8UxDftd6$tRfT zWO|m7`Upz|((v8ss{>k@D2n^YdY|l?!E%ygT;h6>9QMpqKsxFwek0g)%dOE!1Kcvn zDR=mMh#1NXf4e#ct-8G8;=#4G&is7i-`{D$`PJibo{^ck2F^wp$X^MhVn;8UeCWpsllkHPz9C^VOnlG))5dyCOl;-}z8&xK z5A0Ny>`tux{aHo~Q7j$H@NbemLT+aqwK)M|!-|cKm)zh%2c_tUi0&C`^k74Cb2i7w zYhSk)yX$F2l&EEV4;GfnB#}RaFjR2Y(9lq9Y%Hk8sY?|>uks8rp2b>+BBOOKUOm2) zwo)n)EodF8nBxDYgJ#;EW&Fl)R6Evm(!9f6lO1Z1ng968`T4W1t}dcu@xOD;3-vc? zX3A|R?t!?oRme@Ut#}MrPfwqeFFy1(mwcDuQ#d>%jFnBPo=cvR$a-xa_epi0N*jJP zUFvDLS3(mWBSS-B1`rel+0|r!wyz#)bGMF!OjP~dueM1c6c{#7W0Gon-@DuEZBFcd zAP0Raz&s}Q@ z*F?c?fx9ej;`gJd#&8s-?Wx@6zhkurldm?O9>0Y)eB*KVk6GC5#+SC<-~OKDhu&!1 zUtc`+p5sT~G?3j9tku6iixdj^c>SB^`-CkQBUMIxFey+p+Q&Ev4J0GS1Q9#FQWI5A zGX;r*{Rx4M?Zi_>hg@0Fs~cX~>o_LgCBdDu@*j8!dv~`%WZG`Plgnm^dG2)o38N=i zi;L|99{F;oEomN_Q*RNjB(EqZGyCXO4)lUzV=glS#h)Fa1j3}CMVaEO0PU4vSAEGz zhszvSljy@PM*GiYj?7SCr*%Hxt#aOrVK&SeZ1%YAH-3aW!U}6*=_} zH7t_P@ap2`?CRvmNKtuDMSjcmY&kh!+*Y9+otW6Y+fnLy^z%oe*@@C#z1)9aC_gj3 zz75`~NlHe2G$tWbh2NOY|A;uu*V(R+-if59j1PZODffwJ3kQd-H2fXllno`Bj%6LH zsd3;&eq0&#PLGuEE3v;6U*i9EyLPa$qqQR<1!Lx z8ra(Eh{{jB_1m5CaFYbM6%#Mc0;a~KWjVvsPSd5%r=E4VR%?FE( z5f^>$iKG+NVWSmK5)u@msX`{R=ueWaUm^{218A8)0eSf~lYPGfMu&p&QhrZQ+Y_QU z_&@Hms`a50ZI#zlXbA;` z_vh;pK!FTVSzX=V+v7Q@I09OFdO10{y_?4mA3ppidROGbAYx53=H=)0^ETNo)r5tG z?e6X(6Y@ShJt<~zln1`QjioTIHCm#C8G~WY4bh4wkErBK#axR8E*TL>cpc z-FsUmQs)L6>fG!XZ?;C#@+fZ8i3k^F@+{QW% zixtMsY=0p1x#}@om`OOWy%qTO0f*znd|V%B^MObwYc{6gb-SCMO121?U+p_)H@j^{ z93EB_MdY7{ojB!f)BBhm!A|E^kPQU|yv6bE>}{>Bt1jZA;3dln?!L6wkyq&z_hZ ze-}E&5v13|_((Sx!wGLNtKTg3cc0AT2MCE@1Q!~)Os z^Mj+|WSe~Qybr>{0-OYh$?KY%e@s|F0ifHD02fJ8ULGlTs zWo}{nPg1{9@ZCP#KFz~X>!rnXSW4jr3z|XuOGg`ZME^QQt$d6n>~J?ScpK9#=+$`f zE!o{-zY`edjepeHD>$YLD-~0)@1Fw+HA*IE?i_9PL<2441P<46csaze)%msN@>bBh zQmQ1c-?!}+!Pq2b(Q>?^Jy0B;Tv+O=iByN^NrcAK4+r}=N0y6h(#G5LH`Q%d)G9%* zgL@+LC}cdo04EFi16oBK4#=Vmz^GCs;plA{L|jI+r`>+~u5uZ}A7&f=O=uJMBx`7hDheWYv712k$(Ht*b}O~HOm-UW6}nKjSdz!!X#Gl5R%CJQ`ERU(72bL zo{r6Iz(7k2g65HC+odBc*COAg{QduE)g%r>#);by-|W7x}11+5Qf*=nm+y9H0)`3vKnVmZg(Lr9%bk z=o_`ntsgbH=4j%htfu&q?7~A_qt*%w*&CIk%EZ1AR>*bIlLsNqxMS$cCu(#Te&%%p zRX?Quah9s1yNl1grjo#a>Aia&!W6C$yu<(v2@Q>Ox3y`#78ssz!am$!uyg%;=AV5| zHuUe0e`fY=`t_e%7RM()IEm0#mXwp!Gwsz3H+{YVncisi7gri^4gSv_#r@wj9N5aE)c>PY z=NER>n5|l^RGMB0ao6wJLRQh1@{_{RjEoGg(dcG3k&W|yq`t>x$26sz&~a@_9iAzLS3<=~%86!x6PrtYk3`{dKU*2Mshk{<|*AJ(Dv$PKJaK z-qAnL5GHj9$8*V9_D25XddB6B{nO@Qit0Fp^E!t-z|Hp0P1$wnaHMTx+~lS!iX4(w zw%bbmy_rp@TGSow@7{)XR-v4>mV5k5{u8-K2wFCyU5~Qn_KI$5SA00Y2|>+>|?n?yG*{a z4dtF)oA^DbE`&qheRT7jX>TV+S2HtNNR&oPHWXP~N3oXQ^lA)FZiN+};jfun>)8aE zfRik$$Y5h&yYnxkb1LXa^fM~3Fu__P`PDn6*!&mTBYp^w$o8 zg@=9!?TMgMk_Fk1y31kE4S>y{74&|;UjC02>>7i@pns508lnCFwoZ* z@PE2pT+|TEb+oXc@)LdRrA`YzXpS%UUlTR0{)p87;0MOap6u+6o&Ip}ErEiB1oLkp|L8hZPHMd2UZqY!~$dITu zTmbcXpymVhgc8W>6|{z^<72I-2{9!VA>9m13<=Q<$V~Be`#-nXp@0@#baORi(TX3g zZy+t(r6Krc=rAJT5T#{aJ|JPS&z8hCB24q6t>iwlWN%hVCz4e9H}mo8 z1RRhNjs73`?F$=}q2BuUN`)I4XR+nacReQpWQX`)EOnn-eq7=kji<4ib$z|OKE9A>Y{1XuBCd2!v8NEQTdzPzL1H(rp|%weof4r()%Nm9=uXk&D#4^8uE zg_eNuB(ZIb0%K>lk`2#fk9316*P!>uS=D_(XJ7xt{_AD6Jn5)?#RA!JdOUtRNMQe^ zeyDG=+IEC3sUT^Aw4hwb^+3KLvji0+z7HiFS=dDd~X{a}EcxxfaO z6AA8&w2>z-yFGN83S67|svG13P@HOMa#~^314zon&daB~w7gtPhv9Tx-q*==YO*%Y z%|cG5U$c=nw$L^!C?#AysKH;jwAne>(*IPu<`wtQmohWkAB@KCeb_~mpd_cEnM^$w z;kKg4+>e+(Lp1!S<5qImr^bV=IrCP(F+ScE9IFq9&-aI7w6m-H?|~vv=Wzig9x8hO z4Ia@|&YCu983*#3m&0@@=6J3Y1)u6sEkjR|LEyY~KMDzDL8c)9bMK!caS73zR*z8n z{QSJ#W*3i;VVsMcxPBu}20{*$@UQ`SDAOq1rrK3H$c|qNG>iSfuy*su9kynP<_UUG zBl8-HR08!x-OeYpRt^1baMv>HL7m4Kbj1h;zWLG9)BpJfC2XAj@MJD=Wr%eSdbBV8 z1@A+t*{wsqq)PZIYW{m5`zq}B8_wITG(ta-bVk4Mc^@UMW_Huvj*0KIFLjbst`Ttw z5N@6RsEysTfBpSMvB9Od0&b)Gc|#79eh$1B@DsFG;im>DNPHNmj*_eanNivBFH91v zE{op=0@885Mo)q9+(N2`)IlxY4-$AAJK`^pVEmzDaLWbC5Cd<_>4U|$io_tNslH$i zrUN)(8||*t3=A=&W)2&xi}u>}CK8|>?Fz z5J*VM&d;`;ks-V5Ly(IWTFwtftdou_r2h?yc3|JSfpBtUWCSd85X^l)Z{6DE234`2 zMl4PQyn-e4?Br*Xv zi~TCcE;Rs;jNAjB12Y@n%kE(#mT$qqH#axMGR5ryD~aS%99XHVpiYMvW(+dqcHpYQ zVU>`Ys_EoZ*W3GErcwwPfubeYI0q}MqorCyaCrcCz$klteSMUogN2C2(B^wz)7ttu zb!?-}xv;R%)Xa=xh6NrTURp`%a;??T)%B)IyK&jh1C)C*Gc%h4(x1+iNB6{JLw>A%V53VB&K>!SJ3!FV<;w&{t6SXq+z~AW$5-prx;3 zXvn;$so~vmR6T!rSFy)or{aGAMO3=Eyk1gxZzUq-zqq5cJS_IXW{yNZD@bK_(bY!(b zNOpa7MH*6DTMGy9@$s3|2h(lQbHq!YC$zR+pWcBys8l@B`T2QvLIR7)Km=%318`W3 zgiTBeo}krZKugL><6!Fx*@yUHP)q`cYbsZ0m}Pu@od5tQE);+hHoEDX!OLa0yuC+m z``Tva)+#_j0T!R!VsR`mce?!f4GvHoA)n+>Bb}=C86hZ~fX>9aIu^E^t)}n?zaD%TE1%7& zn1;Ig_mmV>=j)t=*8S}p7p5A|%r=JwUoKG;&{a6ZBF~5*PJs!?%gZ0hB8M3d(7Cvm zvn^aoGf1o?+IA|e*4Y$Pml2O%O1~0{(^8xVwq6f<>w#r}l;Cc?s-NW7nmo(VU&Tdb zJq&UV!V~Vs*SpN02MN$!=Qr$${xR?X3B<%3I5(MT-dH=b<inyOlnv%g;Iyf{`Qe4c*$;pZnla`jYzP=7p_+Yny)pWh(<<{WjbQ>$f zf&5Cs%*@QqoiBIjNs!`SQh?-0F7RF(SuSq&6XCTKFq2y=!brqL6SN!4Ng^Kc~~mm`#N zq&lEk!Gd~Nkwd0~$y}lXt(BziUEGW!I@WzXSRC@FTLL|hv%dba{jnfSVisWJY(OTq zL762z-fnv?-Qq=BoVE^G4GtR97!jH!E&u-NUnF-VN(p&DxV5ZW=sS!CA#7`p+uG1J zj4%pkF9#}b>Skvl46#ePrMT&7j}F{Yq5L|Cv!w2Qw4)cEN0b|?R(N<%<`&3`8Sxk* zgd9f+V=-Sw-!tte7E!F*-{gv2I%hG-K`_mv*^XaVBHHF z*gdnA;Yf83su(_Q5j=OM2U=e~ccdz)?AM%>&cun8g3_q&7K!_4;qZ}t<|?I~JE&|` z9HBM{Q_ScRs)h=jZt_A*%#WKqa`ApXpL0n-?jCd$^4nB5WIR}2?OSPNLW0w3Cs)<^ znRFCUqjqcajoEwp^U~)tZR|yZuOUX~XUR1Br3}_?_ik~XUZe4w^r zm!m{^p#i7Id8X@}Po|gfwUxgS5L{KA2;hKR_g<)@lgWgNpj9ydSgG9`@$I=8b`*F< zieF|hwlfLc!6g;;fCqMo=$t~xzly^^27w~pE>RM%x_YiKFZBD1zfQFhpX=psR!%|X zXA$0_00Y79ueMuZ3G-jwBG3cgw&Xh`rU}B9OPCnP`8}PW7V4dr6|honiT4U1nw;?s zniHPaPF4?FV;QTV?t7ZA@JF3Vn(IWL+FAtjTivzKZ`J@KJ8SxrG8q^)f?JL7h%1wK zVh4DA4|13SSbqnUbPp#CPW~|NkSes>Mdae1hr^ac@(ooS=%(}=5XTpZd5Ud+pJpVb;kONR^QN|RQ&tH-2E!Bl~rV8u|`|p>a zU^v>$w?P)Mw_powkSRwHouFE+Xwp@qhaudmg#$)|anfOp)ElZty^vpf0$Q;tnP2}9 z4|8sax*VPHJN}u%i~G)WYZ#8IUfAl6D{4wY=!3|V8f}aN20ED)d8%Y?Z||)he|ai3 z11l()1#`z=J5B%z3kw)u!$C3$TsL6<MGvw6<9*~`1CZ(h$zYy+)3Za3e^5c#_;`e z-bWLjpZT8v;debs4MMsiUDWCWkQGNkMAU7vLfiO8Vs^*#qi;`d>tb6&(sTGw|uE&#lhQ3+qW$_OYr@6T>rI zT=^IOpBEr=KMS6GwVMb4h{bNMgAMX zb*c7EP_Pyfa4=lAw7lf@vU|vm7FuzuGsNrJmC^BV#6D!y70l#597E3L|CFo0db?0z z@9Tje_R-1m#c}V@KRiz0@9XEwn2+f%qeBoEF&l6#nd!R7guMxWiC;MsX67+0{@7`! zKm**!;+}JT2;<{F#|awi05x_PpS<>T$ere7#JlrE852{nGPN+_GF-0Gg^}f^&Ue0z z*RA87zk?y^^qvlDp2xWu<0Dy1vvo{|?5@ZV*N`kFfrXYg~hN3DgbWU2K8UmH$?JTW$1Pgm2Q+tcld zy{W>q16cy)eTBjddh_ve_-ShQ^DYg4BQ(0wM^!$@D;ovBWhSC%O?06M+FSZer?ROi z&yb<~rsp~8#8suXVugeJI!%{=D@uY|w7rOdt*3f6l3r!F^tXM&r5o3W9p9Z}B=}ku z`Q81uMj)HV(&(C~CD;M`-wTQYYf*KP=oLgPvTX0)z&X6)`M~idhde}A|9gY=|Do$G z1EPxBcHvEmNQ_CBph$Oj4@gT&N_Tflib#WWNrQkOA>G|DNQ1O=cYVv}dEfK>Ifp;! zuxHPjz3RU2D;7G&1@s5T-+9}9`dN^|{5I})@)L-;dJq4{F|COKI{5;Pm|hiV=|0iyE`HW6_oWoMi;vG zy-zv4#N}rEb+8?*nQ;%>*>2eK%}}^AT3Ot13D3B6mYF+U2zY(lV!ZDfH_nE~8;9=C!N>gvg%_m08NRjPx}JfQ%>#R%C`Vt%c&Kia*Q8AzFnW#ylP?{ z{SA_nuIJt~Ptl$;9A!4dpj;_Sk|Jot$O|AgRs zYo1nMI$dk-M0@p9aB+2sL^#_Gqc$_|+ZetFn^rqX_|bAheM00$PUFsenQaEe(*gSD zvo6*DQ0}~jZ#m>k)8ilaWr?|*kt7^sP@Vf=&p!P9oZI!y*=~Dz*?%Y0@hc9=ZELJn z!k(VM{NQ-W{ek@%7T4uR)y$@%obsltwVxrxjJ{_ZT6`>NG@mh&C{QKv>kJCZ%}TDF z;)+A??02^|C#HKR1_p>BeEGfCCWIs$W(72s1ts)-a$&xN$CtskjHCh+M;b=KuxB5; zg5OZ_akNhKm%S0v`DVYK@QAP_A)M8`P_Qh{vL{a;qqL#G5!_}ey#_e+W zcPfV&1Pa^B@@jOv)jVpWMtLjq#~F!XA2ig})v56!XNrzuz9DmlO|-26 zo=s6dR@QHr|4S^L@&d;92@$s%iD~%UZ>HL2(4j55uv7sr5UA^RXt%d~tIF-{KyC2A z`Qhlcorucj_=qt?M^8sLHDmrRNvF=m+2Pd8rih8A$JW9M6`~yIsZG(Ew3&bV$l>#& zqf0NrrPzr%ZDqwz12EBk7N^cJuTc-X-MN*NjcXjzb#hz!+W7undAVl-3Vk4}ot|$W z>&yE^UQ^Vd{db{lts*%Y?2M8Pd~|N2i9!Bkf8hQD|7~?;mf5RL0siytGeMC6?fS6c zX$4OLI(9JzZJT%AVuoU32Z2eH3u)5PCD9)_SD%YA{rl{pZ>p&o?&~ckc~4_UCvr{i z5BCH-#u*;_Rj)r6#btKt6qN`Aix23&Y5}o-k-z{98x;)=NmyH38!+1w5+(;fq<=hscD?mV@W)D5P_)}|XRX4XrI$(oR`p-ygL;o)WbgC-#l*OR}aApTHVUh?>= zji6#^@v!(hW^)9zxh0&$Fq06O*2uZ%n^SBS`C3xW;E=rR{y2ttE^qiX3bcEO>h| ze!4w5&Jc7}{-avpe0E_nlqqjGO~__zZtF-C`n$DPe1bArqJ>9FS}KV2_!AfY73MV% z#)53JrA64F2K$zr?$urEUeE?{} zXlZE?OmxT}2VfA0sN&+|AM;%&CMAtBM93u0{AwiQ{}mRnS%gg|;CV_JTyTv=prfOk zczdPlVR1EePTlB3aF4S&{}~89KyeDxLZh)o6+^OI`!E0i;lJ`LDd}Hz4wPm93@l$G zF0;J1m#(7Wa;>^_@uSS+BJ%Hqtv2rcW9s3>WbUPvleoCrw`wl-yV22KU!C&+h>?qv zla$w~ASsC;VkM2&dCczsBLL0Yf5P#r?A>ufKiLQa8ZyNr1mT$JSGn+Y2R9&AMnzVc zCa=UZ3T8(Q1_5^o5D_4vyH|kdQRdOh=(#!V7(w-cyRiAWvxA_0T`TJ)x@V7(vsgeE z!6>C7I_$jwNDBBV4X*p7k==%@N!$@Xr>&M4mSTTh9Q8A?V4?VdAl=l&L`_W%;Ilwt zq+9Q@yWAPZPg<6fL&e2)e!btEfIojPc$>0m<$1D>1tp#+mCc({Go}zSRLWokx_qQ$ zM~$l5aD+3i9!Hdo2n?ezQ$Fdg z-NqtI3l&xLh1u1&yYV@37x>@lOF3!Rh&pD1Qk^3H*Mk3bR8ykh37Do35z9dM(To-plZ!v5J|4bZoXpOC1j? zkZTMF%Vi@uTh4u@Ej9yi{PFN~Yt^zQGq8dqI(;vlKO2dWLrD~3nckLvH>a^D+?lrf zD!9K(vMRtMFwh`MHp{S~bWTPjv%iE{x}X;w=}Y8h71HHv^9Noa`+qf2vwu>|{V<6l z&;vARINB`O$hB`KfY%s9=HLIzD_DvP^+S2O(l1g!EQ+}Co!v$vWmZ@G`bGY;GRM!3 zy}FX4s+y~O3j=abn0zQel%!GHSd-*X`#2aCtS=GqRpy@E>XA$}$0jRy%u~(x@cTPk zV)vck_2o(K45`9EjQ?C#6^_a#?$l=^;T3aVWX%aHuBBnj+okilW->oz<1)ROgnD8DcAQYOCK3z z&-p(SY()8}NLbmXJIoTlKp2BwA%cMo(zbNNSLRk2{|Ok!9KU}zPHT{~;tpH6I9lrm zq7_{g6{owKORyJ#%B0U>Cmv*XcjJ!Lj){rMjP(?w!_<%xW`Pury~$(rUd&5yvA1_~ zwKw*s$si%{7Xb?tD4-56*-cq-2DG-=-vEHNwY61JvS3V95qSnxZP@*=@}lK;e#gQE z81w#HTms$7%K1C9w#}W)^z`(yp$}fadR0jO4EgXy57s7i5{>F$IKzQU{*JuU3rJi- zqC~HmwEM892Tt5QoXUd&m6nzQI2HP{w)V<{baZ_%<)PG4xx`oml_+@}Sp9EvLiF-K z$K(M_P%B{$-4FZ*Q$bQ;4+mm18MwIGE@Q#EUJ4;&#^Q5a#|r)+EiKJYiVK03KrN%A zkGt&+4Oc2ESh3m-3=I`B1j9c}HMk!Gl6ks-r=;x`%mbZ}Xw1}tcDh)o?(>S@hlAPE zO~Cq?e?kF!A`HgS2d)}0zrFv?us?l@IFc}@?{K8UAed4|tq))T69vI$yZtZqa@h?#|nIZY0=pz%w*MH79l$YX(9_*?NZ)-e7d zUQi85L&(f8^cRb_LS28DMIP1nkJz;doF4_Rw><+e54Apk4XIsp~Z-PoO`@x_wOJd=KRIpB*$s7aBW{=^01*;$g4Vtg@PX zQEOL5rj;6l*UF6v(h8wJ5L|BEM{j1EL|; z2gADvUzyDnG568gjw2i>`H^1E4Qo=7Q$Y%Nx2dTqRyzRcg=^@S|8VcEuLpma0yWs6 zh+QrAPF!4Gbon3Wk7&Fd@S*pF{opIKevyUz2u_r?q7{_ym}Cdgk%ar0V(pPKmY)@b zb6Eb?#|OGE`5N-&Ob2n{3gSiue@rh1gNV57m4;BZLcrb8~wbpI>i9(dBe!qW$y*F46)P-E{=}bS}!5L zq9R(XcA$$95kZPr;d9-insZOhJ4?5Wwc$haT_-*=J`J-Jm13zn(|a*5sDBvy)FLiP z+2`g^intqCDFIqK3s&*>?>Sm8RU#JrjdjCxVX4%D!0;dneX?l&G|vGQwyq!!J>2Lr)F-Dp%*_dSFX@^?i&dY**>Kt} zXzS|w{2igV)SbYlm7+4wP?}owz4r#(Mew9#9tFTyK?qjsxDi3bC4a0mZieKH^Zsb7 zE1al+I6B9hht&g^O@xFH5*`QpfzTC*Wj4PeXN7~lOFvt~C}9KXGP{_?)2ZN8YUAMu z>FbH|bE@tIYdsiXeyCGRC&s+(QV7}GXATg2O^FLKZ0gmNtS>*8791mH+;J$6xw{u) zgVrUV3t^zP*+m%!1F<1+ILjeW!oVob@5Mhwk(bkL4c}$u|Fmw=HEmaQA@WAtaN*}z zeu%Lf^OP%9H&(P&HC!uhwfUy5?Fq#*YQWanSS9PFBO}P*&F_0J58kvt9b!HIAoj#o zaUY&0hDbuYl+643jqx7?F$1RrcSK3{I|;`Nb?33Qt(<EO<@m!mJzhii#Fpc}~Um>MW6qSS|OwwAiu=(U+^jd1~J;&YdK7rqA~Bs?)sJ zv!)s2#MVjH?#fGRxyu_pA7sEL?dt?eR5;^^6aJ2Dpu`S@=%iQ%GNR$B!|=Xj@_Z2? z>%}PM+LbNoyv|-^m1lagP|c}^d5tJ|T7r#%>NUxthqpM(##W;AndV#Tk48T+ z25T}u^=&zn`#-H%-~SSOETIE$qMRJ9LxjE?PGF_@=_^alI2IwpIfMKWaqIdxZj8<8 zRf`OyAD{c5`r!TWf_FEU(j6{zuN{*oeJ<;o75nw;3)-LxG@}9xj9foIWMdkCp$DJC zPE{X2H+qEGBWb3J`DurOwf+ssh{XBACOp}>S*SF4EYCXTbuH&@gIV{@Y9E!G~; zL2kubVha=d);D@9GtQAN6mr#Z6mohSjnVz186jfDE2e}pp(gn0$M#E6; zf&Pkw&jpK+y`ZS*Gx;Y_zXc9FZ{Iuvfh{&PHa-r7x0Qghlq4$&6L)YpQrGidTUvTd zMD!LWeq3ZSlzIvFbmt%u{`%}{n=L7w2YcBrZ=Bgx-oLK{DI;JxJwbhphsR|;Mumrm zSFUl>jw^&k%I^k1E>N%qayY<0#b+@H1dP(-pQg!wI@N}=F(R=tE8`B04AmoE(=sru9lAl}FTwydt1$QF%a>G7 zdO-}8qihZqJ#q0s4SGTEt4a-ez^=`E0j#55Nyl`rfTDOb)g{miPfbk$vU^=!9YD3+ zRq52ezoDo1x;V(H7P6wQYA&hFY2QcRqpbZsD|4Kt4Fa9~LtZ|rWxRw1p_!`;Le)a2 z1KF5^&gC%Eh}?qmZ`FYcpME*Mk_txy1W&-SK=NzV|Ng!TbR=M6lxqwau|D*Npe^#a zR(;td)?5(VxVyU}{DGZb{%fDhH9UXvuk@Nvo;@=%GGcc>GD1N?aaRSYp}mvSKcd&O zGn)QePqCT9gF60QocyvMWn$MkPo||DhdLMfCRMN`YH#iz@ln1gcH9mfI*V=-G$x3q z8&{w&VUkh1{z;e3$n}0g0t5?_$ijSvB^x@IrRB84j$IK$;(1?^zxc|K5+5bkN`-fy zR!wv+n(y3eo~c^@{p+%qkyAZ3Q?=cs?NI)vrm%Ys!F8*bvQj)o%lk}_LR9c};Xe2Q zmcw6VL!}xUp~3pf(LUdpazmjje~(~tjB<^u!-i%H+8{*gnq@FLw&U0z+RqM`dh|_j zxmn&sFGv05VC?Oz`NimnDw`h)d`&%;>k2wcnu!QCi>4CZCeGEPvF`U_8$9djl!F3N zE_uN-lA+nIaBKN5Uh=IIC1~n3$bPkxZxTW?DjN$hKOb9$9b2q1b-vt;MP<;{Y<7G_ ztsfO|wxu5MgTH@nyK}>YC9!XnJjj=Un{zMyr)v?R*tP5ITX9yUm7gLc7@`wfDN(G- z^k1c4v4)Dje$kurBuSh;9&U8ACF!12@vZJVqR)jkOTthhB1?6D-SHQ{t+7K?o43)l zYs0w41Y-)O%y{3^J3E4!_epItb>2z=ar+BP#LV(DigI#N`TS=@w>u|MmcQS=JLLYA zxK|I8kyHQ5`Ca#*#*pb}6bqqey1_-@r`Bwpgzu!^$CNjz69^IyQYA~6nad|ss4$}{ zDxZewQ2X?{-_ezBEfy^wDVK{MEry9IU`tSUvl#5&WN)@c8APAN)(oMv_+`2B?ov{e~Fa6VF-9egvc3%M7Qq7(2q>UBKRwCg#;E6!k~ z{7Wz6GbJ&R7_Yh?m2g^1UTBz3u)u;)Idpwo(cYc~9_vc*cC7NEIzJkIC{vw}5o4m-{#k1#lGBLMpCWoZ# zhum(%#(h!r1kLckfEB>bz_K)+Z+W3u>iw`VQj{`K5EMa#EJ2vIlJ|`RcXV zS@lWvjPHzsn09(i3|_D>(J)O4t1)O?=AJ8%16yx6MiuFCZ}=q;-*&60eWmrPX(;*@ zd-$gCWFdN;r^og1gMTaiuk!YWVP^lsodZP`EoC>I?UE=4K=ir=T>=8W#UCF~L}0Ph z+kZz4hlaiEU!EgqV4{Q2R5>Uox$2Xr56nu)3U~L56rj{=H8o<0m&d zw*?!r#6pyx0;U+M$PU>29?)@=+ox3tygR=xAkzM(7TNuY>jP?7@#&-bT^?_M44DiP1YV>Hf|ohR*=2U_ z-YT?J?$FL;jf#$#jtWSCf-36PoRneqXMS-a142X1mKAo)0*vFM&Dan@E92ki3}r zFG_VFr^A4N;D}E%a^||=$M>nkSqEaV!G+G>*kdpW(=>bb_#^b8JI|ExC-?{>n#(ua)WZb&Mq#XXF6afPUYH1k$k&Ku{ zq$bZKN2yN!4OJLNEJ&?zb(r%fzNi~y+W|%&?)PASFVu?XmCIf^5WRshGv|5>k*gF z%|+hOFRZR_5@GNdEKJN~kjX-U!NIKfSTR(E51}YrHe?8lRa_`8E)JA{qp94__jDj~ z=_62z|M>CaQ4u;WscZ}gy*wN!aT!2wxVdpV|HUI9kcOd(z(DOjh07M*57dF);c-hw zl43)sB3}32@Cl z?Cc+<>dtXziUrCXqFsFod{#iJP3Al2BoSKY^}*D1V`&Kq0)BtM3n8Z0NV)JCw5kf| zT#r%9t!Fvozin|!iFrs;JppCe_gdR~O2zLbs@m`g2tvhQM#IHPFb^-ifE|NDd4W(o zV0z_azIzI2T)?XkK@6Bym-q7W@~>XKLOgE)B)Ba_xb8nnCvZgod@D&pMqoEk@EpX? zji@ellkN*XZWdYS`4$3uR=6M4%F zh=20jLoBw2QL1VIFRT%;%NiOiEGpqtWGA^7 zyd3{uF2GJ+$U8P{Ir@ff<#AReZ`G~%-){@w=e=IZI0_VEcdG=xo%=gG|N0%6jhut@ zIYG|{s4i#0AX;ejd@Cb!^7?_uPc?kb6l+scDLH_8RsZ~yn`^8khj~P}ngZ92&-(Y% z#rS)5bu|@?4qDoxoE)RIzIepI&CLy1AzjiAvDhV=B8f>z*4VUsAEwxX1Q40S=m~#w z*qUmuYsOKX6yZ_w?OVQm)~PO%hzA9-NkguOwjWyjkN~|coy~OUEmI>Z7#Ig`4cJ7S zZl0bQyw0>#R7L<}85}07XHupu^>FCz+na57Xv*U5>`;}Oq92y5CRB#T_`Td=COg&Bv|czK@^{r zwFHo`=U=1YRIEu~w!HGWFwn=Z!@YN(DXv?oC$Xlpxfa9J=s@nHa<&L+jDXNPH$M-U zFMxj|z|XH%s$FX}T>>Y%UD zs@GZ?@2=sw#HjP%a%EM4y+-Ei4g;2(NyhBA+KgP>+-^WFbF>~1Pp7W@6qs>?Y5F8I zr>Ut)Vq6SxkOjxo5(IA!+G`d(ic3owX=uy=p&FRKfMH86J@)&z3+Oq6@(Zv6P)Rpk zEQf(!1Q(T8RwA0kau`K%$6-L~n`{E;(#1tZtN9Xj(W$8eD~AQeepJ$56B1&nYQ3+| zz-)@40?b@6%zKvXfD~;_4i`*dWWTmHQ4-uSamuYyB|g4-iLe*pqykp}Fyhs64ol$y zyO2~kq4RRb!xv#|fB(Y0-QVwIf{RGF?esx0NWk;~k}z;qb}pYD9v&VV+5@T5j8;Or zty+AYt64z9WhStU!XXnRhE^Fk1U`ztG`F$>O31>pvSy##IzY2j7krWmMkMh0_MFLz zYLEh#00{t4N`5-~-3f|yZYOZEtEYb<2E3FLWgzjHVD+oQlY`ex)t~{rG zQ1vr1a7{sVMr!vaB0Vh%@A-2Q0gn^F>P8g-8%;IGcZEsySPyIn%m~-ftkn$Bwc|%+ zyK;P$JLao+UGjoH`{K&DWko&?svllSNJxOWges!l;C2XXkB{!IPAY0@sHJ26<9N=N zG@$1@aZ3I>EkC-E@_f6jnKfwutWXuxRxwo8R#ugS4Xh`BTvbjlSV0KU4g$!a>zmAM7O`TMot7%UN`x;EA>Mj!M%&IuYW26QGn(ww1o-6#*c zeJAf%(g}T~wpBY^rR6X4vzX;R5ExRERR&7*)m-TAO5wD11V zFqWI>VcOdwCq)qNrIqfto-+~J^ERO>b(WDHUZxF|mCjbitZ!^^`PP~$cq%-=(sdRn zX=u8|e1}PQpjB$GsX5GO(R9Ax3HZpiI_T#(jmi6zklW3L{WfcW+B}@8%(6K>&R}qr zJaf&=43v|p71C3I$1QM}0`62m*Z|~i4vvmVvadth?d|L?03=zfz4z%cB`BDxD=FE7 zECFziJ32Z5^G?A1r~_~g!NR)0Rv^Z8-uN!j?qjRbr_Lu?aHH9N-!W>_#9<+Iyei4? zHarp%0kaYEat&>O73=8%^atDq9zzUPK@ja0QX4cKb`dq-U2TK%@0TxMT=wQbqXxkA z2U`+asx}`WaAdyO^Gs=yrX!3!o&0G+uy!f&&E3{y9pXM}xYIjEseV zi-9B8IU^&yuJ6W{jOWbTzO9dv&)S4Yuy261@ zK}468oLo3Je*LzZ#zi_aPf%0=9KnNxG&(v8lz-@cU~N@8zP~-!gSZ`5F`R4d0rPOS zhD#tPrS54mZMCoOy9Ygg|Nh09jBj$*oSPMSf5UuLP(nfl!|(y`0@id1@$pKXpm5ZC z@1s3<{#aEbiA3DOoosWXD(i(q#g<@f<#bvvzA%| z0dI9d>MA3cwYqUo!F`Vw#pGM*%F<_85`4-F2aFa;ylePdDNP;mz4EeG{=6|%Z0BC& zLNuOr=alaQMCDjrO5&tDsH9()rO__(nF}#yalAPpp5LJqAU^xflfWftDKKs=J?X9g zE^_%W!heIy?RIhDH``+!r=O8^asdLOaJ>(Nq8OuCccv&jhTbI&I@@%$v{Msu6N^jf{bBsb78*^XiZ}Tl<`;Qf$qJsLT zFO17&t2iq7(&H$cRRk}HApbML4&t2i9PBDYMyelKxB+43k5&YcXq!_G%-bW$E=8>w;5E2ej6vb9pbQT+CkG3ro6-cP<8 zi@NOyatg`BEi!nH#ZSMkZf2+xgWkkhB%!0Iz(d2fH8Ht)8jpzQ%otVx%^f3Asxi83 z`&Bu(Xz(pHixuzZl+>kG*SICkaVRu)SS%NL(I%Io9FZKIC!Hm zCnaw!$rdNiF60!>eKbg1pF3(Rj65BWBF(&I4zy?)vw#wVJtEQ_tl`5a$;}^2Rl_9O z9&pJgocn6xUrnd==d->uD6p4!tgNL_%b8<}Z#6&9t^wFMUK{y+3BIS{=hvuMqq_X? zdwoLD&#Liww^!|+7W5aBhh+D>ca+e8#|E90x-EyrC#{*qky5K?kih`6};e&Gy@W<=!G;Y!Hwc;EZ`D1}i>|q4K?P z%KJ*YSivis9-5=vTWcs}k|$oH%FS%lHdVMJ;z{;ZS8%}7JpHSvsLlh>d*YrvpT6DL ztF<+TJv?Vr9=FByOtUm9s>)PM@H59if~<5&esch3W+eqNgp3~PyLl?%)a_r1wofFg zsHZj0;TScSeVtJK9jD(jS=N;tPREQ*@#}K(L!FqSOvmuH>v#9qy*E+o9|*TXi|9Td zr@!71kb8l-1!WdBeA@dI_v*z%F3UOw+)45(ppVY_@R6sOBG<2B?r&w~2yFeCP-6jwg={G_yY zUEWqWynwDfw!)^%y-VtTUPerGmIqG%#72`OuZ#x++S9pC+k-`Q{gKu@U*TU7gDOt? zMv>~jJ$zl972(U`;QBk3yKZHyu%gYXPE43Ez`Q`KBB^pmWha10?__Qvy*k3iDa4N* zeO!^t@ph;6Vs>LQ!ry1Q@Q}hVZ4SN;@;7vQgHwa+8>V6ZX9fA{$5 zsaGfVmovD5Jpg>)K4xrZ6z+ncBk)@)F}N-Sw@IMWh+u?7LOrD9bpF`_7a}e0g$c?0 z-NSh$Ckdu6iR@dd#AMa=ZS06!#H8B(t;fGVJii#h5%2@;wJca6TL5)GYmWJkBbl}% z1)(4MHzSbE(eo$*VS@+2HuIAS7?&x)>u~1A7^L_ykJMCE%_)d;YM8OJIHhxyvlRas zwEs8f1u`~pnoUCkf2g>_*^VX~8yl2)SM&2{w!?3*(FBQp0H%A5(b;=w4?aqe9Gssg z$mISb6#(&-p|LR=(TI9Rljn3F%)mga-K+}?$$UJ&Bu_PX$ZL(C zhzCdMw!~JP{gEaR<@J*iBWcg32&}{$SL}Z2bN8*?JTPhoJX5Q`zcvx#dkbVU$$8YQ zfhWiS_6al#2AB8u%Wl~bQsi*TiZSQ_hE*6-6R_?;_ahU;LGz1BSXZ*0Z*s^gF$a6C zf3RH@IXCwOh>S?NY>4MSI#GN4Ja^8PKoQ`a5^@Ad0oQ_CduQhwKvlusc%T>1SX@t} zN`7vxrZt`^g?$E2#qj`kdXFAG^1a@z0cYlnk;tD5!%%4~bND{*ebmRVhV!|xBdFfI zdBeaUM-H>&PJLlJsB!e1qul*8wK~uFMLrHHD(bUm&oEGtiHK4Fk4g9PPynWkONxyI z0Si~~V8z8N`BPhG@$zMpo{*7cq@;k&M~8$In+7*B_!$N^c2!M{ZCb#p$I)&WDC3}@ zV^fl+!^qMrEaXW1VD+r#S zJD=kEIRC1_o^KT#&yEZR;#gv0;=d8!JnfvEz84l|@nPn)#D;Jw8!$cFc-8#JV&P2z zvEbCkNN0!ShuvoLwNl+Xh2^^|$NLT4Tgje&vbscg3@bn37VX|bV;I*ajX99QpYJaM z6BhpYkC_|z4zdrz%`GTGdl^d#dxQ=q1AZ*}Fs~-^+9i7HC29;a9iNc38WAwQZ+ry# zG{<8>6Kl0PMLK0t`B@+$0#$^XcbS#H1`q?{Nr1L+dIN7A9NY!!M6e2QJ6f7T`0OTE z>l2Pg+ZRCaV=`Np7!+PyN@=McDKDm6Sb6Jtpl;3UaeE_eTts;LIepQ3K%bfF30Mrs zb2Sd`1zBjR-=RYR zlDNvwhh^sqV5UtPfm?lUo;TT$YY-2Q3ZCYx=Q%k#u3#OIZ{+fsy7;MncW^*NT5Ef| z{YWsZ{&ZvHVc7T+#@B+sl-{cimeDiU*egUt)cG(vT+Vny!pvCvdOX6gFCvB;`LDDa zVD1-)-mnx`UoNhl-M2MNC7QK!t)nI@cg>0Ph8Bfm%VRZaUi@BvF3uOzV(!`ZED(pE zQ25&0)ZH#32R*#r>87_TUMAeFm~O-@=Rm?S!mt*j);&+3oNtjUjWTn^=qu!(6D?XUhLVg3i`-aqe0y!V_VhJm|>syyvC8P ze*)XJ7M!=O6|hG{PNj63Trx77j_h|dGmusaAC~zrHSu~>#0MQ_vN#UNED>3$E!s*BXK*bIO#5n;i8iUYON7hP znh++Y-krWJFF=C4_(_bE5BprXwClVJ*)o=YmXsm2oEHxXNXIEVp6MlilN4Bbnhj8$ zmMD~z$nZ77lpU-15Ra1|rrWK1j)wQloALjRm7S5V=WF9n%72uU zmN1y*ol;`~;Rgk_Au~Ru zS8BmsK{usu>6-fPLWAYFf@d%;u`l)A%*BV5;YIHHrFPu+F3JqW>*_BOHF?%@1b+Dn zY~}AtMWhONZyfE{lr1heoV*OPK08>9#oeEm^^JMK>TqFcTUt!x>tWPA6W4**ZPxiv zKDejHL;g4nRn%VErzkq~)p$$b7J-(sifFvLvt7@?tsX*LXd9=Ey;}Bek=VI9I4k;l zIg%g!ouTU?S$&#`_sD-|h5~)G(yh{UP{YhrOJwSmZ}gpEVNJnMX$fCp1j%&dmfzL9 zDcRoVhUe8M+hD73kyuDRQnQxXk+@`2-`)NGdhqrnyMJ<3!AR#|KSL!%17haT?KdI- zeuHZQhTV<$@OX6^pQM~}&dWRBGB_mqUXP}vOxe;f3PKegr?l&{m8@d?2>KEm92PR1 zm%IFJu3J|v*nG_^$Z)-%uTWV)=b?YO-CEzksumfO)>-e>Fl(&sJh$sn39ipC-&=#Q zzC!8>!HDJ+p^G)V>q|uv1Iz6osD-2a#;(_)$ka+#K8uj-V=L=U^BOD7V}UE9n93$X z2>O&MfQu-gwb2}ZBN060ebdB^4E<=!m70T zHe`mnYGh#@xx!E$T<9Vmt;>Y-Xvly8aoo=>X@aN+-#tlXK%Y=?-=ZjhjwYVUi(BON zcjwg8rrWTes_)M>Tb(D)ha9!Nu6%qn1fSMdzs#|;5;KI%=pKj^mzF@nYvhobZ))bc zf>ADSN1r3itTceG)#!B5<2igfTOq$7IXDnavyWKp!&?GT_(N36KeIoJ7!l{kV*ee~ z-v~g0nnQ4bP^$uogagQH!kgv!Bbo`=cjB{{{ zUYVS1uW3%AaXjc@f!g|`g#;UX%M|{M^g1qP^GOC)qK(6>$6^giB}od(O3qGB?YGv} zi2jfrv6*Gpt-21=XpEOdb5N0^P&A3hnP>l z=Kx78*wRJEs`kwL=W6j(y{nW$GT+woi9JTK0=gFV9F}112-(+y zhvS4O)vZx1Wna=*0iGJak?j{{|GcwS{eF(rcVFIi|1N8(*!hP`AUpDSRM!s;D}Xd9 zBiX!};~D;s+A~J)&&Oi}^z_G$$CZt|fxNim zQPmh*7QgPXlWAUXe3l45-3+y=rhqeTGh>@$ zNyku0v*OFmpD2^fv$2*B4a(G1upIk-z)ESC`{mi*)>omP(bDj}+e$1#<>fk09f1R{ zTd577csMqJG;Rf3dSz2kLa$LDrAR>-K+;)Iq~!0mI~X15=(f(4eKi8I)>&ao` z+*`|i#NzLCYKqUK+Si^Vi3t+nqR^YZe*K@=olnZKhkK_G%1Ko!33x_19r4fBYkc|# z=GsOlCZY^g;u3V;YtzTea9mx&#y}QF_>|Sdq33Te1asYR=xDMH2*z@DIR^}(Y-%O6 zz*AMpuj|!njYyeI)qOovsrt;~-z1?obVJ4TbTQGaj*qr~Csc+xykmQ}Y+3Xit9A3w z&d%RuwRbv7aVhO(h3nL6L_|bOie1vnAGo_w83FI%;G^f~KQ?aZ1PAQq#)$}6JRAAFM2P=m)?_-vF z^T+arp;$;kwlIQRxe1|~)Z=+K~}qN8yT z4?^7B-rn4v1)AdHaX-Z_pk*2w9;9JnU|_QLc=^1!t}aVgzul3T&ArLBSzpJCDviR(VEu=j3a#^7o2?A6!m$DMZ{zq5@y{!6l^j`a2 z#+f+oTU#Y=@=S7?ej)tu41L0H1R^lU1l!EKd;UruJ`P6x1G_DwlDvvar8jSA=NEp^pZ9NP90bCIv*ZFq-jyy*M^xR-ei+-y=nrPMmaHo%x$ra7A`~5vn z37msd`~aJBs$VNwDnXQ*O3QyEH*k)_D(d1hs4xeYLAkbVIOPq)?~i2M`4z>)xX z+oO%)?CN&?5*x!GxC=2tMkXel8Dt=TBqpXO8?EXLyjcTSzY`vv=uC)|*#9H;`#&A< zufzbt3~@G)A^qRO#Q$^YW+X5pO8hZRiQXjaN{&&8J|1=F25JQ>E2ZL+B%$}`MxW|S z3bQD1G{fuAWW}z6^;CV%5WSS9J_>GbY4nlT7bTleUbt`H zTACJ`7plZ$X#H@TxQgpYDZ_FZ82-i8(%F{WA08Q*&jk;B-y))a+PCxS{sDv%{BY>D zn#?aMl87$0PuyU-uz47i%e({pSC1cWVS+&w|78_lq4}egn9X7f(q5AYP`Dtl3HD1TWc++uwAkRN-?Bgk$gfAw6`Z_2{Z_3= z{ih18u_2?_%xHEQ_T}Z}D{AU-lAz$}dN)GC3cZI~vVKc$m!eq+p)())%E z0V7%I*w>8*{&y$ston|LvRwlbbb}?kz)H3_gynEkdK94*(A^+;=bD_yFC^3qE+!x# zFy~bcOg=ebM}__!iA_(R%IC@m3R*9uV}y3N*B5OO*&zxAJhHD1d!_W;E+1rExLc}9 zOn^GPY6%;#I2+!`5b#tRZ+&?JQeDUo91H#ZJ3VQnlq-2!74Qs`jQMgyy|%OxbJ$M3 z&=?CJam83**F`4a5l8+BcuM^%_q1<* zCtx!%QstWS(*Cn1iJx~Eq)cj2zUEoy@ z6XgvshvU7ZqDRx;nk1VfYXbNL(Qr{wQGja}xaKO;KvUDx6O)sRz_hKzdChbZ zkV&&^YI39P$K67IF77oS<36{^l7uJxXw}8T@YiR?2d4?>3@+ILYR^Af1rU_1A;z&>p%X&~NtcgoGj2R5-wz{qE3VF?WnH)c)3RNq*($@&=? zthlkX8+NsGmb-(Jl6|h1Dgu_v|4H0)hBd4@O|3DUX%d4^2vWkY@tjdEI3`Rj zh_OSrijBg#7wE=ltZ2qf(a~vhD3F?WY7|1?ghaupXQn^Gz1PW%@ChxMI0IJ!*iyjY zO;~Qzpn@RJpnN)!f5VRw`*;i+EZsm&mU)XjAz>bE362IFN00vkWe(ryVax8alaYx+ zpnTt5)T3}|m{!rA1^9|k;+Lr3AOba-Lswj+vr=-XmfycNt_V{*L!KWtiQF z#hP9=oZAXr1nc1r6AW|A!6AM18HV^1sOQa_{x`ocp|7v!US%&#$Q=}HfV}ofmOh5j zb2sv=zB+yO!a~W!>zGFj`I^hud=)Y5J@dMxBLoKh(Lr3vEserNb+FUpPPs25OLGDr}i_dr&>&&m>BPL=n7NHKc^b}@fB{c>OYZz`oyCI<-Fz__XvgGF))|w7_g%k{# z3f?J*7>J2{`$z*=G-LGqG-3dhjSN1?(4azdb75Z{F?Dm6mN%A`x3*7~ImPEWHB34B zl6GazD~uTbKse@WP+ocYHlgK^@#D%GlU7Y!l^=sKeD)wm_C8eNqDKvpyEGbg_cSb> zk5H!G9sb@f!E)~81n0i#_U8KRX&9L~F#H*x-lS2@cVm(!(_oF164+5E0X*yDA`sb9E4(IrARW!s? z{m#m$_-Zyim9U|kqGr* zmK76-*!VI1FyX8!F)99%ithL!r15^{Nef{D?nA@$%|E`JIAllJl19G;xG#X!F7{oj!DR_*##Kse41BSBi zw}>%VkflkzzIt#$)8|eDL*)NNq4&}%wPDHD-}RZj0EENmNd7N=g5PuSFX~4sst5c} zD0Nj|>yio)MY3Wu(L_+CK zN$Hf9QfZKG1Obum5|Hi&2?;@w?oA7bNSAcO_i*mLf4pOS49^*`*sMMGT64{Ke${fa zlmencU}^H${G!G91IrTl{^E2V4=JPjub{-8)l>Y3FJ1U{DTUPaAay$;@P7uWtF?67 z@&p-rbpfGK;@m>#sRMjf+}sT6j&I+9BNk0BAt8YRpAxbXxwyF@l}(kTP~`acO+Q*{ z5$`()pvg^tvA*dGk=c-xetEnQ0ZN|zpAq}G+!O10`S-ijmQGYgun&w8cp+vYwvDx( zeS;*1o>9p@5EKZYRWyB4V&V&3UCY<69})x;5D*-0PD@8;1CI_kMSu;0-5xe<{kbvF>0m6pjZ z8is~Ssh+{@#!OX!gXy(+>(k;Pe1T;P$!r?CkTZmah6euqaQ7Zy_+87EVnLsUp{o`3 z%E-TImZ`hw!g%PG#SsDO#+g9uOFT#PfXExJ!8*2lb&2exsA32)U!nmF2fVe)>+!R% zyyAC2S4|xG3_rY*acWW;wc~n$=%)8 z`~#yOA~{=My7kYRy;;nx&yPg=$Fco4?TY;K>Og2x$CGg_;XEFC|QZwo$n1Ab>iYssG!P?X((tv3k1gaUC z5>D^vGnxFVXctA*M1KVB!Ear5760%BF_z!k`L{-gmHU&pHX9!olvy!)tHwCrU+zu* zcq}47LaM4Z-#0@)IqTo0dKh>^mUwm|&Sljj=~;f&w@Av%a2@6O?Bc)ut(6mMOzM3v zPgq26QIXPohG?HQUVq0c1rZP7DyJ?t{MOnK~ zyWtvZNUX-iUyIAdJ_|M*67~UZ*Dq)bVkPt2GITk^${{`2m)mKIQ+#pBGP7JWwkO-r z$-v=Q!WumQ=&L`U{MMvTW4Sz%*~>Yf4ZfF%MvhG_r1thqR9i3mJg5FhnK0p-Uy;a; zc$?|HcNc?p7T%i3e%4mb?PM` zq}W96t398IZ)LZU`Nv>ZQxR9{D}lVK5z@ zm`J3TEl`)kCR*#PvaOKcxyaJIG-0_gU~YDi5sqoEvGOjH3lXWho1`x8j7|V@sTkNG zIQ6|oP2;uq`)_Nk@|_-@`jewtYa)ghf&UE8nxah7p(p~b$bGj3U)8mLg=XFE{yv<(8Saxz(qp0)=A+?n+7vi@CaE2g@Uq6ag}w zI%Nv2U&={d?h^W2Sxb`Vb3!xj^{3sQqaqloVF9$d8D;wcDRatlHhW~w_Gs9WCjcI>$PXk6V73X!D(N7yS~1j z`stINzCILme55}*9Sc~P|%ieVn5+aeZwcz=jl7# zZ1P-{dr>u~ZW!iEcpLJ3kYn2)@{5WflgE7h3>(2EX`axW@^^h}GHFz#Z@>xDZ6RP> zWL^Hj{yhv3nOE~t43ok94RI#|jQM7kA-`G|4a8=tksRE}`|&cys?j7@s-K;)tIvR+ z4>KRwB006J!6yEk>XF3!_6H1HTo-3&1F&-zcs>>{c(ECRPt5Q$SBfQqd-`F#T%qOq z`+Gx!kFJ(^%;U(#T&RE(|A>|K65_PYwFSf+J%h$4Z4Fqu+*gWEVNL!yqwq_=Z`Y@m zhgvg;ub5`svE0l$)fhg~-O9%T_dbVVeJbG^(|GkuNW(BkyzIfv@LZlP>6UphL1lVcgYg4Xe#;FML8o1YL)TEs7fs0 zs)mGz=K|S*{F-j0&h!AovpP_f^iJT5U)U7Y9>90`e``{5LV3&!$X(~3ibviHlNU?o z9pAZB(ddv4j-LXD!-J;L`l|O;3U8?=sr#+MVLfzn#-nZQ#&@=mb+>hnQkdroiGo##f-%!Pa^%TU zzO8C{7Yd)vYo^84MfVob6ikR0&| z)R+tIzN@+90@RqTO#c{mw>9gaa%Q!i^SAALQLkiCN3+#UNh*D}M3qx49H>bXryeKy zyVEM9#cJ#nsZ~`irjCX7TA#fscSZ&_{CdH0WCZN6DYXO&OG01i1nXf_^0kBR*YQ_d zHo`&fPhv8hY|D6@Yb)^9m<#g5+ecUHh~bj%lw!rnJab|WPhuUPXgXAZtGM0m!pi1V zY^u+VL+vTKM=|Q2`BXJJn#5j5ut=%jr`;_q(EqOTF27>Lj5Jj?otSwcd0cchGnc?~ zrkL*K<02I-@%-0=a{gW=IV}TwN^P0LjZrZzXVu%)6;j#sN%HO@J15eEgG87X68`0M zbO-e!SMDtloRNX#6uQnl6#U!h0YM4aw`4c90>XYwY2Gu&y-hjj8Sbfc5=ir!Tze4z zu6y_o@85X+4g(P}+xNYM1V%)0J%CWZ#qeWJ;lsA4h72~`PNa2uH)&0FAKgVxm=pO& zj2h%vNgyV6>XE;u|EeFS9jB@Bw8u{KJ;dj<(C~(kmA}#iT~+HYifToZ@%@}X{dK+` ztr+bYr5qe=$j6KaW)o0)R!uXO6_6ba7YAnEJ^AD}LZcSJ=#W%5^~;i8{~cZZAGEGW zl$){z&xEDDoyT6c8r>wMNd7Y{sDR_~%5-_AtBND8pDj`j$}4*b-h^5ok3Z5po_I0Y zI?~-Rg#Bpdc}NF8wwHtD9wqWqexZM!j&qq;_B&y-xAHe}oSXw2Xj3X^h=flPOe?Qa z-kEH;@C9DX<+JvG7QiHTj0}ujRSRsmwd}jokJ8}nPNEuo)XB4o^NxCoeC7V7Ec^7g z&+YP2FN({?CmQez)Z0v*OHd4yaq~*b78>>WbPCCw;yaFe-#^|6a1D>R~DM+kzVZ zc=m(S*bJMDlDw2J%`DVIIg-;!pYl~cFUVA5mf`Q~>0K3!`&>Y4{9g8^?P0O(d>&gX zxk*??WAiko%b3m6NKpZ|AVHq&05(?^r;RBf#$(JI&WCi8i z61BBMNeG1{4!PFyXWnaHE(HEJQ;7yMamSAz`PCQ}ShIVR2L;HyVsup|?1~q~p!Moc z;^+VqqyBzY78dKjol)=we+>Qr^3R`p{QE5i8hP;bpQtcF`8F^Rp%Y2_0o=QRkCp~B zcK{k-D)kih0XzYWQ~1U3b_=0o77L^{JXZ-SaiJ*Hhh`wJivXoAaN%$eIXO8HAcNwY z^E{!{uw`~`Zf<0x3i6u-1kPY@0=hI%zLhnfGS$2k75xUH7>I5GP1glb`=R=N4nsaL z(Mf`zfsxT};q^!hVZ#ua{Dsi}^IusBit(P5^zIrby+I?g(vsS!Cfl=V6uN9|IVx<> z6#*mfI`$I_3(gr$4|>bJ8%aqDxbx)|74|Uww0L`kkk7iWmn0`AKX@J=N+Ti+dr@|F z0ub9ZT^vr+%Tn{&iO!!$>Kj;rGeC6Wf`#|9#aNMZ{i1_)-=H9LF+AYusUO>bHm;DU zJHkSDMT#fek*h9Z>e-i)jN_+VD$2^VcQFfAAs>Ob{jaTxKLKMgXwBXy|K2;xO8fk(x9ju%%HK#dM<@}#N!o>+Ic&!ss}j)WHCAYE04iF z?rJ3~LbJ@E$#HcU^c_&%G&Tl%t17#BaYTMe$s%n3MN<{fZ{6xk7ZIVOd#5utH3ha5 zI!tl!e^m!ia3FlY&vTYp9Bs`&&#Mg!Q-H*aHd48)h<^^5W|{5*+uR}wpy)4^B0~!? zS>@>Ge`(Q?l0_8!?5C>bcMD~$3F&s1qw&S|Zu+p2xj$3x^`W{73RrR@yQPeEDxbfz zGneLQ%q6g#hH9AiIF?M4NY0slGiWT-%V*-@=&bc=%uF?b;k|HAu2uXk^Km z($fsgMvsV&%ymhb70)u>y3JB;-48JPQ3Li4`Tp2Lm?#L-DisD@Mx|r__NQ80wFNAj zu65(RLa0?{SFTuX3{!3aic+4l;7OgL{M{Iq#>Fd%=dD>?8 zP@yD~G2sf_Ll93~BnMYysgb=)bJ$^nZNivGiOq55ZwiA~_;U`vto?p6%3!BszOmEL z_`+IhlzsvE_PziO*W1W&aQ?te#}flO&Y%^}4c}T3nj45)lUyMTQdX`YX7;qW%uT(qmbzR!b|R8D!qc92$xirLQ~2Tf zKyHsYx!boGJo+=X{$sO|3y<-)1tRLGC`HX46?UNA*A-qv`tOet+Ev4YrMVDoKqcrf$QBA)Pg|~z(LfQm)Aue| zP@2$;7?zt`LLTYtkQDJ=T!Q?YMOB^#sf!LfN7ZT~ z4-um0seULDqkL>8m82yUtn0`33Z9`MnlIdFtjTjsH5w>`n9nvy8s9l24?L}QoNpSm z=Xn(XatSD@SUI5gq2|eGDM_pq6qMj6x+U(oTiOy~36s|S_J1es^5dd%wHfpfHm_>r zZh!bAxReFnBa}gG9QJM~7%be+U#Fg^s~WCydDy;Y!so{92F)p3-+EyUr{nkctFwf; z9)@Av6ZHl$B}p`WeB(TwTi?S9g(SLiuR^OA=KD>Ahn>?iLe+Z6(DbmjpQaYPH`LNq zJK892)BlxUv7jNj&wWwvHWfZcSnQpHOoh1w>nn>3A&~@cLq~*V(YcXYs$EEM+VQby*KZaGPlRMsH$bKhO zCqX6c1@kLj+^vO6D^50h@7|GfQpC!^E{S}p_G>1sHyd{cD%1qIT;~SUsz#76x(m0T zVQmjRz{OcMoW3)NRQxM^fF7@+M&NzAEKMM$v3^T~W#RJlDD{{0Xbsn^Es4#^0i4p1 zEAs}<4dKnN{m5U$eR&x$u6duX=8>0u@gf?HV;-v-_{051XZXTvO(W-LqI5ElNk0#Z z4LV?;vAI&Sxu&)8PzAC$k$$PZz>Iw~GIpd3UMXEcbx&CIdwF}otDr@mbeoOQmZ`BdIpVFjDs>lh?>_g-8%+CHFHDTyyywBemE@##$C!R|nK7vZ2ae#0;eq=G!Vb!|PfdP;YJiuY-@=&)GwgIX z4QDL#%dm%Zx3OY3`ab6#Wk%B+1%o6Z4*6f_OK#@9S#{06Wp6FJ)_M!7sHS8BXzndF4>t$d z5uM?u(PpOegrzg~R1LhqUDKC#=i~gk|HwM~Fv|L$l^?iVf2?-0ezQ89vpXc>C`)_S zj_+4L^0QQW<(Jrw3jCO1s=|0@ zLxXU1EPfwrdXH(Pw?rPX-t=sP>8k(2reWKNdQ6NUI*M;b4Ts>|W0_&))PoL-TJt#z zQ~jB!STie=AFN)VX-UQxAJ!>dHK&?^u2-k=nL^K-kELqO#8?#kYW{Lw-BS|LyOgMi z%AaVLBh%UPOqR|sbUi&<`h3T0roN0$0qaIkNC+&%vM2k|spK9NqZ;Le$l6Lje~x%b z{-$O#$HY8{nA_(Oa~~Q)%v^IX>EKp`Op?04&tV+UZ-VZSMSuEKyl@~aqsYBPN{YrA zc3`!YpB2PyDU7Y(;!Tx|{eDcg@N%YVPqUt_C_bA?KU2QqA^OtVai`C}->pzW2NxDL zCZ-0cK!D{{t<_|ve+wB$i?u~ykMKqPm+_oC_$_rO&*vUsx=Y^_9ZN5ozN#w222&A< zz&j|uJqc{UachA!?{4X#PK}+CT1V{PY!Rn&e!dH9<;vJ#b58zuxzGKD^;xVgnU^JX94`Rbv|)VIcG%z*^Kni2~!(+M-1Holwfg z^}T2>;w_)^JrEvoT)hrNS>9o@z2R&9uex$YgdEe!OJ?{Ji2p-ERQq&il4o=2^12LP zUx|1eQO!L&K|`F^Z#WJ(;_Apf%3<+fLqU;zhDVlpBlA{?AFqm-DE?hMqJKfO9-Vh3 zMI+6U?pbH#cx=xwC=74dU%S=mObtgJM(>|^AOEY@W8D_&;<8O|J_`3(Cg1oweVbgU zF3XtoDmTtUW@vHSi=i>W{x>#4X8J2)-#y}K*=V-pnP9!VX0*DenaAruR0Jrt!CegY zE)YGf%6KaI2J>m8`S3 zLriyzJsQt%cFaPl5v?WKe&uN@DL)bloB}6n%F(+a8xz?abKgg5!@Qn{3Fh|7-)_Jq zcFfxoj(y(pn$klN;rop&CHl)~A5zkS3!zNjRCJ`8-p%#4 z(jbf7&s@Jf!$Qk$^I$uOD?wgCxq8#K-EuyW-CvtxON~O3gl0~y;6lEMe62T%_os%_ zw$*4;mBGRs^Vj=AbdMMFgEx(Iyv3)Jrxp}&V69g zEsyzqmJl&~)Qp4BIO=x~bqibnZN4z0WRO-k&ZyqjHoWdpU`Xxmwa~-kLJoc?ha|&o zPt6>k9$0k#A~L+BL9E2GBmQ}v-c&7n+*qE}@JM1*OkZ2lO#7nL9&PcS>^6>YjmzUgQ`8@{u)7n+`CDRM9Lu4MQIO8iZpnxC|v46bl7vEF$yt6ffRk*{IWovfnF za_HoJ?wDV{7)Nyx$D!S7vV0BC^N*=bfs=*Wbmft;Tp}TXSxL)IN5tpDN z2H7oy7~5suuxweenf)|>_dx6l7iL_3cfpGQpWgis}`!Ja+=&(~c+M@+_s$%(toMT+wnC5sG;M-*)5=O&A{XUx5W6X;;6*17R%`KvWU58lEY;q=N0VJ?jPbil}1c zlN(@U3XrBJ5V{~=5lD#lWOw7V43eNO{o=(043mK2^0@8(ggN`F@(XtUoTnJNxS&! z2e?&ZJ1yN%LTMy0xzI;L2&KOm+zY-{d)SiVzQ>rJf{DP4>#ph;G*3|tbHa?dM-WU& zCGdv0Dk#)-FScu`dEXi;1np_WaoKx~fk6ju79V1s<_+?20VdG3x)Wx+Y06~M9h%QU z;tDI)Ec_z6?tgA6K?BoPzt|O&p|Aq1G;atZ1m7nQS5EfVZy+2_t>>6aE^COMp8gK$ zlW(q1xx0M6sb4AbVwc3qEnXY3_a+V@-%1S#?)5^JfUd@?H*fOoe>OXfC0C@P!XV|9 zVwmQAJo-w`#-U!# zgWCc1Yv3!LfR{$}RcSO(J`v!T5K76n3v5lFivulDQQsHLX(B$CV2A|TQEW-CgHbIY zQ}r3%8qpI>c7_4YFJyhxq6S#0dU}+y(LgfoVQT>#;)_%kiZ2-bg3XH=WIKsx;31#@ z)C>kj0pj+H-9(`HXJ=UBqcgp8cr>7NibuyugVJIZHo72rS#zWpl$+%k?< z!BcJRmt0827uYo@2N|PCWF$RwBIx`|mZhNl)5{x~Y4S*z$_Ip>iRtvWKW=_P zP*6||{UdOEzi|V|t&rMTYn!f^r^a{}{9yt!-@bhdsn8Ix$v{vXNG_=%0ZujmiDmNH zXW9&9L(NMeg&SyL;D!tkyD9~}?2T`M*cAB*uT4!?92=l+00_H_tsAYekJMzM zHz2d}BD3xLcNEbaRW8Ebtn@fHP8FB5tNvhro%YsNu?*$9h6a9KUNFH|6)8TzebW2& z_n$w1HaD9f9~tdIZwikRGqJRklw#tOdAI@UPOli(!gywBW#58rodSzTQ1U~9V6YLE z=gJ1;9N1!%ymp_x2Z+%8#iHrscArU0UjcUjTdusa@*0ZMV@-z&6*OKZT3%MRzz_)- zn;E+{5WkAbqEw3X7e*L=q&l<8&WZ#i@Nga!U2`MHK(i~3}}!!sbC)_KApX^V^+BmyK30P!CKO3VxP3^d{LoSedc6zu#v6k;Lu5bz?@k0(DgZ z-2<%vvh$?@zMA5Zehqiapyid?FxA*+_xOA+1rd#hQ6@{jDsbEV+ib-mI- zJOPHYv~&kNjnV&6{{W8l;sJ<;M(~a4nzw@~soKwZyF2L_(6Kqb--hc?fj4;Ui~T>X z{n)ek#R%^c*{??q6pgeC%K>9)MuN*WfvE?!uQVQ}+3W|D2r!^3aj=LHML# zLJvk?@UT4;10EkWFgzgo5Jbg{cc&&MJdZPyu+h0PA!)M#;_W^+l(Pbcg#aQWd_n($ z$)F>g3JbwZ9AdG$l5F5zzoTKm??+}Taro}Odt=@7gGQDRi5j^G5*J+$^1bz)u7~L# zp50r+$(NyrXt4&@oqH*L-15ayv9YEREu$RPX?%h=QM|a)=JlSaWq>ZhauSg**tj`1qrc~S6+A)NlY(nGgEo-&rCocxXM4*B z?xHXQ$CSwrW6QD+@f}hSJ6Mm^o>-RK@Xe(fo^^^#=|0QLDpjU9bu@QahbI`g7Ziwl zdU|4F>4LeAz6izA%Fsaf@FnB5b-7d!m|PX@4V0=gf$BUtE^hQ(oJ8OAfv)>Zi%&Xz z7nJElo6HShF9wA!#XOIr85gjrh1J^j zfZ{s=X&$2?3taS6@3jAHv#VO$qx8r z4clO72F3yS_`ORJ$pUq7fmAN=tjjH}s^S=ps2G~O z5s9yD#Rc?OO*OSDi_tHCma|**6p}Ov4;UFD3~*!_ygVnvE<5{ zK7TEipU`(YEcZht80ylLI1e8^3LaQSM@Lt4SzkA)c=1s2Q+ZXD<>=i9#G+;6kYLIb zC%4dlYa%;0&^z(#seg26W=*=r#Zv*Kg<4os~ML=5xxy7J=UjUXC+y8wa>o@Xs|u&X08_STP_mZv9xgo(7%l9fMy z#9eq-o?2O1@tm|?~ZFAT57li!dEKG{EBIbSSfD=z^CwL>M#HT)&MfFw%%O8-Q0x{94xLcE-nO4 z8d1f5ALTzFzo5p4Bx$Hp8`j!T=^HsQN=m+3t)W>ob1K?-Qa5%PBT}_%(PP`Q*pmPg z6QCqL`6ypvCBuoH{26`vdEMU~RwyjH9`ujhBYn0UnsgL}Oznyr(>(`RKSz~5WI?VR zeNZk?*JB;u_5OiEyXZ5p>J-LPuQ-@2WNTVmDdSi8+-WtS-6&~05D(pW#P8rsJ9|2_}%JGFpwv2{Q)W3>F# zsiT_6A|kTVtHGei!p$uRm&V!2DW_tys<=4RqOS(^XEQ&VL=a9%VWEYzsr73(CckEU*W01GM7$NgiK+1pX`y)L7zhCbt>VK6O%IRQUX|$aaW=+u5&@j8#v;m_E zs5U5=FU<@>U7Z;JTld29YOEg*iO;;?q3(I2Yw_K7H?Ljm3&r8<%kx*mUzQs?Sj_C! zUSYE|Ra8Zdi~OmW%L=*(%-{JTa{ljTOcz9X*;35CyG?t*k6002p3d|UQTSNcfPttC zXLbm8<$w7z0V?wfIL&ikUD#r}vWdgYzxzF;S_Tdhc>mqQF#Z`z1|y|o(1?XpJ%21< zVIZP-LK+G%>}-Qm(RXnhs*n?Er}V0N-cymAyy|U0+vY!PcKD%&!lC1+R30#w6*xlz zx+xe~^jKe4ym;@%-biAuc)yP^TTz$H;bUuQQ9lLCyLMwB-KHwK01_APIhJY#dCv5* zu&r|H|HPMiKKUhmUcdTejkIxp_&M?|IpITcYYPjLr|aAJ2gDd?&y%UnR&=vU@murP z=|<)GSgxqKW~TuRExf$DwG?2XU(}L6ndRE=wEit=n@STGHv&-DVJgWYr>G){rz6|= zPGNQ7VQlXu=CRzBDWvM9h$mLn7)$LdU1Vmsc=p2!Zv4(&Ef8P1Nam=_(p5o+FECf@)&sA!pcU}%LY?l%8BGb|LM}@Oag=!nK7Q_zb zN1u8<(()e8JD-B%%_gS`Ty1~86Z2McIku%NY7j*TF>{EJ_;d@<7*LIjTmD)%W2uwk zlRS|s@c$NCm;saJDK1P@DSXGl16%6Bg(-ZW%d3wkonFo14kGUo5s`iA>F!SRJ~hu! zK3*|$b$X+IMUL_rP;<`c zZE@&vE^{yB4VWFzfzJqJWw8q$>~zp5B}1?+xXiKn(Zc>c|5nuQ-kz7I=bbDvu${f= zpz(o%o1hM_LF%F9+Kp6R`x^)b`9x8nlxryd8Iaww#@|5E0MJ6ItVKf9r|yfX@Asv= zC&%@(1{Okjg|M&{km}g?V!B}D&l`FJ&khzQCgj&bEEm*p2l>Q?OVWUUh^C1&o9VuH}&X zYoqH!mepoG@i1U>IbovtKXuWyG2b;Lscq>uL2(M(M}7V8lM_|~xd*=J{eH(8aq!3; zUq1lAw6CWJq6H`bE48*RfekC9S|x43@Xu2j|}7{n+u0{R{zms40$ z@<0|!=&)<9Uds;X;Bo5xS|MNcdVoRpEtt}P+$L+tbAL$MqNd@?7j7;t@c&cKPzGN? zFb#^N2N%xH;^Ja32z@7N2e~lO^p=*ET;$j_q_S8_2~A$w9Ze)5NaQVGjY6(EQyd8F zBIsq;dWfi~W>&cF$woJPU>V97{}5<_fq-o%3=;-0mm})j514AMz$g@92Xu}?t?QIQ zs0oXY#vye~0tu#+vD+o3kb-qKy|qR|)PnhERfbtZoX1XPu5XSxl9B~p0gny3ZVI<` zq0b8}p8yc$d_lgOE9`S+Z_n#dG>167g zqBmpbEvr$J=lDzAM9G0WD7j(}6;@G9 zK}zYFzHj7>p_et6Dz41*9s4}!l_mQjLEv{v!#sBFA{-1A?Q2oBqw3lL)31ucJvZ5fuW6)>b(N4YO zdjEz&_5{!lJ>mcRIsR-!Izj|Pn-flBZLww8RGW{BcyD_evh@NlmD%$N`kL}$0?ys^ zU%&dr-Ahiw%+}w;x<)3oSzy$tOeIt;-ZDr0W?#GYb2!PTiS&=|9jV40)+3v-&wImlW9DQkltDC<9^7B=K-NRB^C!G!yLjJA0$I@gL_rK2G7g&B)*xgo`(!zwPdZ6R9 z{0Scg5ov0@BxDiOI`8D+CDDPPi*E z>hO)Y-pi9{pa&H52NS;1;Vur043;~$qx_ATpnsEF$|g@O6EA1Hggp&yOdy4$Y5b}ke7V3lb!awM{= zyHc-7O45aPiN$u{;ZYVEIa3yN=53a>g^4R!_bl|lGKT8weA(1 z4!v7VRsKi|0ugY+&3WyjE=5y?O)cZ1+_~YDL2-ED{A13?Qn`Imf+zuZi0b*Y{ik0Z z-tMtx3!++xFLeGn1y`ihXiaB?`cbqYL(p8r%GXU+gv^M^3Qji5-S(v6*p~KS9_mIO z2CMI8Uz)I3j8NhIl8f#jtUPcsAE22UHE41@dpgo`-^w2y^Jg;#;)wV__S{Y|?Bev! zZI!{wRL6E-W?3zBib{GR5uqE1wKdg-8yN1d(Ge4l+t0NSooz3|KPt)h2xe%Zc}@2=7Icz` zTiagJc+Vc91~Q{vZiy>&4|tZlo%F^d#n8Aka^}1R*MFXTw-zp7UKaBg%GUH>kYuKK zC6RCcm4_8yE1_ny=IOYiNS((6i_tXh#xE_ zq^z!tjZ)TWKgy|mh4XVaK5;z}L&#j65+OQg4xSc_x(;}@2u0D31dZZZtaQ{wVJ0CN zCB2STL#$onoNAu{{#X#?XU|6VfYh`SI@guGU61tc6uC)U?zs7p&zXUxq2z;ScehD& zF5a3t!wYb8W?NLWqb+~27xiF;><#OjF|FGeAy5$*KU=JRxklcG$5F`fa$+o;@o*v{ zovQ)%{%xQB!oSQ|FV)n={|YV4gnvL!B5za+T%$ZLy{YS`wmpZjs$1J=thqZ= z71T1i?i5CCZoSUy)~~wlYoOUIbW5pu*q)AVh<=97Uq)jNRsJQ-x89nt;x@iZkcG6y!` zr5(qI3)+xXB&cezC@9=T6CV*T?ml9*mfcz7y8`Di~jY5j>FHTyEkGy)TM`w ziuQ$-Upty__CyQo)hXeui1-p#5nUBM=~-q+^@Fd?;n5$MU^>k>gwr#(Gw#=?&kOc- zieEp<+&+R>~b^A+;P9hh<^UN_gAdi@HOu8aXkSyZQS6BG~tQeZsl6 zXUf7ysBTyY^al2dzSRT6(ZmLp$A-BAs7U7**h%k1l+M9cQ$AHQ3q0hG2Z>Ej;kh9G zv$pNAf`d|nuPKqs$|IDvh1VNs&iXEl8a|m?!d-^|IPkOn{@nGMv5Eub^$nzMz?rcd z9c**U$v=MGfAfD_0Cp$xnrQItTzdh%CPY;yQPu?`fAso2atezhxA2s&#~fx%*pESr zETBdFl$Ik*^29;^^DEP}8xp4@RDOK;uE#_0W?a{AwvD1R2#4$)5B~3u8n!b0p}iU5 z0i%&*w4@UoQ}^_mv0+~M^39_2POq!=EofbkIad7tHn`VUNt6)|p)1W%Loc4XQAD>_ zems)u81!;&>6zDc8^g-U*>N?$;LOgr4^aiI{?VRvJw^o+REzsCkb+A<(Ih4Hj7nlY zkH~kvfnHKgiKV}r1GwcAdQhj~dp<^eeT7-;*n>{^8obP8z4_s_AI;Vy@KOJ}J#g~p zql*D&EnX2@jBl3yY@Kxrq^yMBAVYzA&qn{KS}hxO4sfNPBo-&kUUs+7By5RB+C!f? zbmZUMeBQBO^J#Rw+OcDJ{xmF6(0%vuu~uRLJnOJ-fpG{glMJ3_FwNk4|KGRyvas3Y zu(GVt36mO$)&l6ZK?_ThEQ-+XH6hP1>1b^)OLjGAY?DV z7Pz6l9_8DAq{P8hN5HFI5N2o^4N)Q3&0!ZJr>KbYA14{=Vq;@tQ2&AeHSqL?^bn$u z_80~Qa>z`Up$CIdSoedb5Ek7ySXfyCcBYC?D8&1QEY?r&tpczF%lNnTo{$}a`1Yy_ z<=fd9TE5ahaZa{l}GZz!*UooGW(bY~9fD`1J{dUBvRd? zrKDu@VY+9f)g(J>!gJgAo%i4&SXc#$E?xEL!nw1j)_45+&i3eKy z9jWhK-RpA<95y|8vkV;C?oHm%>(Zet;O546jm2L;6wS4!t1fc!n<<79s(hgGV@Sn; z@@#2!H76tE3P5*2$?2oi6Cv9@+%tQUbdb*Sh!JT~quAfiedv5}G_H>I!1!rGkJH|6 zaf@M=W&$yh+H~TbqyQumNbz}4rawJBHK?=8I7ugduY(aohs38&xikNU9To9ewE&U? zA(!y%d~YCK(5YYvVU-mq5@5)ed3MWG;|0tndT zIMC~DXBqj@>}fZh(7=K5f~qTDu9Bu!DQkgLv!pcUW}W4KUi$^6sE z46Q=yVt2>!+r5itNhsEp(YQ#MheSi zBpKBM4Y}20VId#>%G%d+w^Qx-IsCemwT@b@E`Y3Lu`-xZYg_AOUhY$>Z|&>fS+s zGnIArz<8&mhh)l=-_$}-P!LKVXgHL|@L{(nD(SV8Zw9kd;D(>_}Y z3Uzs!1+#iJK10Y$iod0Nxb_!fTI-%A83S||t?RvOB?D^CBAtqFN8 zo*Y~bdqy4s@}%?2B2HU=*r}`dX^0 zswyf&90P(QP@`=y5hFH+?38OD$z@9q0QEO-7_Tn|+Ogi3lB|gN`FDM`ULMD7-R&dV zlmZCXhL-L5JuON-&z9-i@?WqKp}2dYkMRU}>y=V##S0=GT*DE+!KbIeJG!r;Py`!O z4DZl+EGv)sR6Hw3F6Ak^Oa`+2YPy{eg+W4p5}+h(1o#HnPWM<|r3Mex$wbF~u;kA) zMe+gQkNBPLbJ=Q&kJU5 z*#P0%a;HK2Dz(bGy9amL>|vj`&+x_(%I_|+=du^WIPO^SC@~qOMRj$gh>gw7>BHR* ztqW}DZ+*$RUOf{OzFN#64AW&;lLx&ZLRooG<{})=f}*)bO-f@Xaypn}E{BISf%IBZuKxT%Vi)CRZ>e9ezRv$KNX_1a@5ytnWkRA0-S?i9ddc zZUHaDAQQ8aXs8&dlTfrv*7(g_by&<;$fv>8Or=7Gj`1NS#zJ~ z#44`Qkf-X{Ozf?v5e@ViYF{O8{H54SPxoTsD{EEnSCCbz`!hdMG;hrVYZU)hr_z3)a_TKZ633TJDca#Wf9Ni@><;<|$KQ@e{Z2=v4#MI&9(@?&g{y0eR{Pe_; z#(nGGxG;*4pGS~dhn4;TQ|oVc9qo&Qv%^>2+nkbo^|~SgwRXd+0WpeY?t5bABB`mj zRZhcJXlmM~|CQ4Q{i@9M{Rmxa+n&c**Wm7#>sgrH{L5IQ!AbGas%1BP=O9vKbEALZ z%qNZW;E{!rds|#{GxgApeeIQ^yV0y4g?YP9*jt&%GNqRo1KnF4Gd>I@zvS0MdIsvw z{+91lkQJ=Y8czJyOE59%z$LX?8JGAA;T@`1n-9wbTV8f0-7E@-i;G$Ic67g*>K<0L z&>j=X9Eh|$M11Q?*OclbJ!zf7j)mL6W2 zEWlp7ioUVyV@Q1S?oHniiut}H^JYAGsFqB`cXsi^ z_A3wb#mV5L8i~*1&StImPK9QX{)u&6Q&p{xZ<#?EI-<#-Y@`rHlqu@q&St?#VYLod z4LwubZ4_UFyso~-9uLYN9t@7ogtVG2F2%W>jItR0c=@CNJ4|!#z9&6m#gQB__y#HH z``d;UhgJCcnl`xXbk=iPNb0?tXm6r$@=Zu*3+WqMb)-MB<-igxsjJj1bs{((^GTsE z@Topme6l&ZGhzUi#xgQ|H6j8WIa|m;c?^W!uMcs$Qft*ZB~nE>m*a10;OQ*b`Vd*I za}i#)7xyv92#Nb@ErgrUkhFVI*P8cOd~XpJE3hKU-f^?ayezA8Pb_ZWlHS}b0pk=> zila+@hRmb2nx2LuNTVBBYUcdg6);DMdFSn)=JP+tIhH_aj(? zb;?Y)YHfj->z2CSPMGtmdS<~Cc3(A2(BHcbam-9EBGrHm?;;y-hufw ztEY_k)rFAx%D=iBPZ6Jlj_b8*o7?Lj4X_-GXK8+Qb3^ehdi}3&B1FPn(iBqzab3(Q z=_n;OZ5Pt<_7I*QTf-l+JXtb8IxEVZ7F;A1+~!|1a=KOT+!fYeua-NSPoltQ=!2Vp zt_0aG=3{uxH418F7I#%dZc6H3M@WqqYhT0ufVYfLVO3HdI|NMm(_*YK++~-(l{DG1 zrR0TIwEjL#LMQBWhGd{8``g0zyac2>#2iP<;2lV{Wko2OWZx!|Xl&^Y0l4|G(pM1LuFelg`wXKOozr zM{A6{f^$PO?0U?;uY5Zs#CwJ{{Hyqr$$5%?=au?cqLbBiVt}vvmE@0-&-wlPES1dv zXNjE6;^Q>T_EnSXG*+=luF*rYNQHdI5k@-tHskxm6Wld_KE~EzAtF;fO-z-_q{kIF z@C(;6H}unIJseh=+Wn(Nk{H74Wz59ooAraz#;15Vk7JtLRrP!w1Roo7j#w~>1*+A1*p2nt& z5E;K#eL?HQXX==UuPxr|5$QaQXT!XHtuvRB^l>A17k_zJcfTy;T&wvvCu#J2mmrP& z7mwp_T}pY+X^NOQp!WJcCjEbWy#-WNU-UJ4EkGJXK~f|o z1d#?o0qK@*l#uQ&gOEnx(jfxU%>`+tL%O6Dq`Mp5;`e>uf4uj`c<+uohNzcw&)H|6 zz1Ny+t~swo4)JIZ?kgI!^Psbx-I-YbJ+G~5%r_+>9{aDwyL>rLJ}-g$;aT#$4Z1IO zc0-(n%squVWQ>(Y>#*&LzHA00LtGu=kxcJeBDQgWewy?kADkX&%!;mg7Z~KZH0N$$!DHhzw)R&4_o*C!a$r0u%7qtEY=>YqU(O> zoM^a5aU5frK@oN{Tbs>qD3>rMbn^=$!GO2MFw9JZq2MuJN#?)idk6!*@B5WLvIPI7 ze;j!5*yo;BQi~N0qVd^{S*9rKiL%F~`jk!cWEY94==nSug?rpNKSmm3$Hmy$yGb$q z@u$HxM7Yh|%0@~mP>q;BxyN+mp33DaKU(be=_!_tD_6UQKq((*OW_fJ>&xY0LJsH} z6pgM9aik$b2Zdt~zq>oz6f?2}bd8!19UQ3nD?0JZrFS(8X?fQO&6-rS1rT*1Z;487^>&(#jyh7S>3y26n!-bHC|+u(hSY+$m^pUYVzEvofZCn7{PYvsz3AH=Jd` zyl-hX2VtPmit6239{MaJG*foET6r5SmVd2}DwaaW(_3SgAH~of=&{gQ^uqAXz^n}! zCId#}bPp+lBqRXP7VsKtOXj!nQ>I?rBER+nR2&`8X?r{W_P;cIiM;jbK3%%U@w%|n zuj%Qaj@hKM)lIanX03^;t4kJMYkQzU-W!M|$nY3}P|jivhkw0@C{&YtmZvU>iD!j5 zV_SG2S$>UO&-$5rQA zcDDvc_|8_#C=&3@XpvG~S&{Cny$sE#evCDg9~1aUh|RBZb;ST064^>6w(p_t@7)5EMOmIThG_gxK)S_!U!)*?banMmLk#0S zf%BV}6n{&E)nQ%@eb+LR^A>+CujMH}ejYR+@;;y@Ju0d!@5k7bgocu`nvSBDin;L{ zDxsx7aP9!CYk-1H1S5^R5%OjyKfavfcqz?}C%5==$Z5&wg-o(Ug9I zkN3NWo9mI|?#8{>t4j+j8?!4yhFf>;@GlKv(+8VcwUt#ol|9{{AVEWrqC_$ya-vHo zqgM}erDd>orGsUL&7F$so0`ys`s>siaD&j{9$^9EA*-ZhT^(TaAZqNWH>w@5}{1l_Ia_3b0xKgNgVvwie%*7MIY_v zhQFu-Tas~X#Sf#B{Vr56?((4$APvpQ^e3% z%IT$)oa#%ff}vu44OO|wFG_i4Avn~&EH~&y+liL<`3;RTgC~5@zFKgZBl@ycH9JPX zdsd`7NSYa{RsON9M?nzyXX|LlkW^0BJ0fZeFV<##V=(lm0cgf2%geBna97}RSID#A zRz}Sz?K<+y$*Bewmx^g9(dQNpESHCS-45@4)s9TYClkTY6oe7!AaHf<$W$h)@vrP4 z;<%3}=9`SDzhe|h`r(1OFS4b|jLy&RW9xx@Si!%oaulIkbA;5GlwJLlNAZ}ihQ`wR zs>|l)>fetN*B}z+YV0I6q(c;s=|m2#?(s7-gJwBHJ(3k)OrinxEx|3JZE@FXecfuE z+;VM=Pp4kxVW+%|nRzw^JnJKkCq(f{E~69~+|iht%4_>^t3E^v8Cny+ndVU!KhuV) z4Mv=C5$u*{oQS1qj|llJ9146LU8R344@j`Zz2ytV)8hnf#GPJFQ=$8;PPr&L+j|QF(hPyJH{hPcnv;gFx-mY>oDLDlee-+cvgy9)kbz*e`R#_RU zvJ6#LS>jS&TJbsPrLtY9%ELmWBzPQ)q-8TQ-#}1?+bkL=;ET1HT0>^83PHClak$Mh zgMY7XhzqIunC{eR2R>1cdToO#I5{OKICXxVPe*82c9v!9Ti7zecw!5!byB^Ck{Y|` z*(=SCK(&ekkVN-P96zm8mXJ^I-W-{)Z)$y_3ErJJd4=XEQghJ)<=d*pjkz3XA3I)` zptQIU8WZC@OJ7){RIr$Xe36-rotRZRBatr6CJxylZ6nh^Tc>l>SfYb0G9pQHG+-xI zz1x`!F3yr<+S&r>=q_wF6MFK}en~3?bUB|Hk(kEu*P%T`rG;t#2$g8Dob>f?oQENNA|i<>zvcn=KmHSS3$~8B1;=F;FtgTUMBQ~P*!&#bW9MqiPqn!*y z0~T$8vGSwdAasN^B_(&ofwEMn3X2*e5toQzsN!4K+o`>k4z^p%Bi$p@pi(ZSJ3yn> zKHr#YZm7!j-1+^{t(yp5SQd~ z(zA)Oq?qH;hWJ^sjHhDX6GW=Arb{D%jMPM<60JE9+#AV-&1>eUUxTa z{fIA;eEVt-{4G$0>tlbi#gIjI@OCJ-`M1SwmqH`@Hn-E`D!1eBr)V7G{4@jsO{#gS z%7J1`pNWWxNDfx_-7|Fxm2>y_bqiF?tz?V^y}WLSPSu`G9Dln?nQqaGW(U7^_*~cd3Y3}3wUTSqOpMX?+o+LoyWVeo)Jq!rzs(nF8ANYZ@ zN858MucML3mcJnXuL(5)@|AawUVT&Sz;78JCq)25pM;dO+T+j~oU5R@3N9^*_C6uu zP9qi@HkD8ke`!UEgWO-B9VJNQj z!tk8znMKlqj-3>0HD9j|dZsjrMulCs5C|2fyH2)xdX$OXDbUj?#b1z8qy=QNEIe0TQ7q_!a2YC2VLcr426oilikcQ^aEefRtUyU7=rbo_+V|K;BQ z1ML1E0O9{J39n|Oli7bMn@F|{OyeuS;{OvH{{KIOQMDsK1yB%tBj&FT^osURsoG$e8) zZiI{C|E#YEA4KS|-t!B`7cse^?T^o)WcaZa@$P#yi!d`C4>Jo+a>D=N0_3SNoi3iV zZ2U~Ce^CcMLLmEb4I%9(n-g)HS!UA91DBH9U{1jUy4>fq$9`*1Xk6CrPl=u?w9wyG zXXGlcCxi7`7Y5vDU@dv73Et@8nUo6|)4@V6wWea1lY1)kQBIzRYL`ErUw%osUi9vy zNcquS2$xko!1<0ZT3%$k9_Xoc?Xw$9cm3- zprncD?CM&^y~*{sSpD*c-Z>6l_}ZEEUUOE)Yo&tngDji+J-oeLytTi?Ly7J&ZI!o| z1z0jR*%7=Rj+NX5tNFk;rTBfMjo)LIerZ4fqCTK|yi^^xA;K zMYf%3_{PEJ^b}ihY(Y9Rs;zaTe~^v$bvrM_t4MV3-?i+P5&DNI$C_qsE;g!pL(|?u zQy%-=(SK;S2Fz7Bs=hvL3JVWMe+Ttc#|0_atDj+j-c#7M{*Sa$>guCl?*Z*Cp`=_v z8@7EuAQb)CHFUf%hpD?)x}~Jf#ti*q z>o&BFP2VNV+b9fvVCAI4C0;cGtV19YhJX?(I{O`%;RLe&)#b2sA2i_!PZk@BLMe1^ zc6JFD{aixMN%Mu8w4R!?@k`ap=ROS^&pc}aNK^LrunzXH(B9SbFY@Z71lUBVrLPFM zwhzyQ8<{Tf@(G~%?d@S zd4E(NL^S1PW%u^hGW3&{O30I@C{}QCh2cunU8w3&1*p*V{?vK~se#d%B5hl?rfnAP z*cAs>;p&>1h`%Wp&#pbsQ@VRM#?dj`&(9B*@rR$^LZKU!BsP}@p^R?_d*dI}s9qv8 z@kUUKg32o}uy;YU#PU-k6P3e$noSNad!p`|n^ zfI~|%kOgqbdAHWa%C5hExA93a*=cnoS-4u5hGwEW@N%y?jixPp_1-{b4(<@*K|xMu zuk<5UCUOB|HTjkE4u@%;6P~)SUuWY@Qvw46VT-DQ?#IFqSVi=|bKiGoq@~UH^5uDY zKhRK`y1PRke1Z<^x!)eIwxDMc_<4GTLqd$7y-|m=|Ej2#e^b(t{wxn1^zaS!HvM@f z9JYQ>U0A36*T~Pz!*@ywLOiiUtA$Ao2F;T26tM%l{lf$3GjKoLDCmESiHZ5&T;YP% zup3%f&){;eoP5rc#KgpoPEPpY#e4Po^NIS^9k7O_pi+OO+VkMg^7greg6r4AcLdDS zn$GK~`I*dW6}gOcdTgI1#U6f^r0wkO&9Z;};X!N2{oQ^ED!C-yk~h=W!0#LZ0U#!j z&7qGJWC6ijBs*I&Yp|a}NnJg0(R}>@HMxo$#q&s`3467B!O)m8zqq&vBDaf+Z6;+C z1oSYmuV8Hs^#%$_AqQTtEAZkl>U&EC4(}M$4t`$nUowkf8Op$8S<`S?JfPEwrG%9P z$OOK>dQ%&WlE!0iB4}@`Ol)k(xfES~7Io0Tpf*YYpsxLw&iS*|qaI(0!_7;fjU5}! zWGX!PI)SU0i5HAC3=9n1-QAs?ouMBdhR49*i%9=b!a8ZF|CLhhfZg`mOoIv9S@v6ONCLBr^wKdPA!+cwdMXs0p9sr^#k2--oB+?p-7xS zz!&858Xk z%E}5TEs~Lfwtpb50_)^kP~)4~apAEo?=_uh4LERlx@K4wKjbfdZrjMpNwNBj8mbqV76e_^7BhsTQH>A4g4Y2Szkg8gLqoqEgxk97RiaQw~P&(5>8 zxBtq`OCDy(fV|DMpa1?UB1Ed5KLhb;I;^n~w(gOU3M92~#g>n@KWD*PLkN?H5dZhs zaG|sFn{^%g%Yp<@vKML3|DPP?6rz;EqY)>HNF!827^A)_vn=nSZf7Ws^}&dD{foz5 zsSbgzVLwqDpBx};484NKpV1PlH>aKoosT>zkiM`jszd3q<{d5Cwg?A+L0(B~Gd!MB zSC6Mo37=DC6^c}MSAttXIfdeILY=R~XGxp8U;JZpyO3?1!B()Pc4b>VKDDpxovT2H z`Jh2`b$CS$uYeet(RO~|G<_~S!8aJE$9gAI@2cC6lHXr2Fc6-~H9%4#syJvxwVZ;r zz%eCqb?;DlCOB2r`!J5hb?@7Q4FH1h%BhemmpYVa)>Y}a~Ag@5L`Cs`7OyBm9?Se6tO|vRlt0jeV zMsv>iztmEZ&`Pbd_nf*gTE_3TDbMIgG|&EcjKKt^pg8Mm`}}wvvJ{Kw9zl?S_XJ$s zK-??epzu*I{C)Pt&GdCS|Fy}r>;9xP;MV&UI6oNiPngNVibZe{J6bs9mIUm^s$qKR zinn*2%&W9?M9haTrl+0Bl^@<48B$^XR~nO=Y*0!;`o}7EML;s=W?{5zdJ~bT% zVYI|vhoAI-@wx#dLEU9aB4+{+@xtG^wH32P#s?%$$wgLcN6XEev$2{T8Hnhs_DqaO|wb#)acA-kJe1qm^%I{MnoIo+~7 zJ+jn3U|ix#Q9y-S!Z^7R$7)_sAKE4i!Dp(-K7 z*)vVr{O9j(-W0?5^5Gq??LE_E-r^wTwc*^C9u$>t0}9p^tP1pOET$?d#{d5Ca~jn> zZ2UQW(@6B)O~Q8hp3U$#kBsj>xRbKE=)J*^l(d_|z^$X&NX%4B{{2{{VOxO#9uk44N{kUvV!+wOr8nv&cp_e+9vDGy}youFbIPWei10$?Dsg^ z(f`!;6?1y`skUn`n@c|XhpYeJ`0qeZ^39rWNF}RbjaIhDG*OhT{UG_8tmQqMnXHK? zpZza#6J_?Ck856ZfxLv$nj@`Uv;~Ij$*m?Tw9%K_mkDWK75vh@MIOrvQqA4HkX? zb$UQ_prP@2cd^ym7*1Y@y_^#9)Wqz3wD07k2e%-$Yrbf_+!H1HEU~-i*RS%*%JAUe zx!q%kVZhytVtZ(=EgV=FQE_o3L`1&$4-3)e|NYa@)`md-OVnos7`yyLQX{G#38ki{ zq@=*67ZHJf|9+MG{>mK&`3Ol?PkP%b9Wt`Sj0{@xHarUcW|J~-d>{cbF989FT1Cai z1{nSZJ(eMJC-8g#pzE(b76RdjhQ@Prja~mXp3H%z=O=tj%8@rM{sivNVl}SNT3Pxi zk>k^#QbooLaATv@(@J{&|Uu5H6DSyBr##F9<|^cS8>j4ni-1n{LE0rU-3l z@2p{r-`!6?<0{unYw2?@(O>~58%O@Ajv)sf1j0c6OD{gcL^d&PpEs=GbfX3wHeqq< zFbWd8l4*G@7I*C-HWrr0$zIMiSW6KUXf5#Zd_y#I)v|fsXz5U`P#Ry*GIA4~27$dR z!7ETGnwnzMt91o!HcUVJi!-;^uAW+2q|oqQZWbj=584Tfw!AJIlgEDps33w+WTf>U zyOMwKIADQPfs<1uy}!Aw?G1@DWMaO)Hy_8=xb2z%R!Kxu4vJflp~0>3@wxoM-o5f@dYHqFACjg!2_W#?=YbLZnp?}U+b=E4S*TWpx|5T-Ge zr8|v5@l%oZ&dywD@ZDCtjg)o*5Y%q2wWYb46Fa=Ye!ded{$g1)zbfZ^BG?C=2?*P4 zKCMbYsJ+;m3LvH|y#xFPECCW>y^x-B0VhEZyH7=X%9%}W`Z?p5A%PxOj?mC-eY?$@tkPc3h zc?6GviQ2ahsLLQ_1m+wdC1T70!dSi8Dl5UnY?D({fTI>e=nfcppco+qMV-OV>#(!3 z(roYUf-H7!b~d1Upf66&ZK>hvdgKQ`>Q-m;zXd_85#W;|rC&b$>?%kZqIC-aMnhV7 z?~cdHwhbGbB1ckmPsaKpls396gT)@^sEpU%;7aXnvrB6F>-d21{quMDuS+y=w1J2& zOJ7!5*~T97`(bRCVevD>kLVXL*);jL8jd7*u<%91&@NS7zqC;KWBUv?x!S>OJbwQg z1L5FgbQr8n>=s(da@6~y?~z_B)F%+Xh_06Gl2_=FMM<3xDI3l=|JXgP4JE?^!$O*? zMaKqQymfgKC_-|>A=x&9^ULEv+XJq>KLe@^iY zEPafb6xQ#T1;)K*+xW3>BOGq#+Gk&lDZ1_VxTb`WcOc6N4$|J*KM zlS0NU5^A$(6mqA0oNs(+cY)P*?diqzUG?Cd)JML?IAm)l{^Q4CrT_Mjx3R?B!gKX? zEaygwSY3QiGf?b=gmGMwbO`?2kr?GN5<%2eI(pCzuA!l!sHmu;ldqOHCCL^rzB|F| zYYv#i&udkGjOEwRoA32*;iwg!yJe|$9o@Rq}I~)tlnQN z^HA5GggW|8jgC@Rr9st+*v3fqzvWY!iLhcZX>Z#5zo$oUC8R4d78Vx5nzFZb^@4_i z*05}u_bMww50HB|BePHYEC`kKY64{Guj&|}8rVFm?jj1kKC4jSwXv}=HElEEc$QQ3 zaov2}TI^9_Zg*YN9%YN|HZsO|kYchx=QJ2AMUr`keT_=hLH9*uX_$h`zt=`ggDxcE z-*!?dRfl#6MC@Nk^^_oK6X?Xh&6*pNWjPpU2c#`-U91ZFiGW6x<*(+kUoXI1DE>W%1Sll_AWDC zv0~_hm&zk`7qZA+`KNUfd^cs89(Oq}QTgNNy4vUe)y$>K8~t@jqr}QOCWK*l zyeQN*EFF~jOhl6IF4r18aWtn*)yqQc{kF|terBb6FVg#=XMzoO#M8Y&1p<*WlWu-c zXasz~S%)4|`F1&)vXo4O*cYy9>96;K(m*2))=t<70cikb{=@Z&5g%cl0+kU}GY5K2 zRYTo=2Z5QAyWwHuhslhcKD2WUmab}#2xnhPS(?r`680J%ZZX+Z%daq>Z)JLuzhI z()H;@NXtDZe1EVaOL*>OeANmA>$0nWtReX0$ zE)=@dukmk!1|T3YfbziV4S`S!mqiS8iIBpH+z)-V3~Ez&cz8U{V&Vv?oBZiFW+aNl zqoAB-1JXWC{x}{-TZZWJo+fjyr5Tf51*Yg>!+bBSRg5E6GEw})v zyirk5SgmPk_ zv(32g{3lx^CqI{fNPTOVG_?0iPI~Oc+xD`L^e2a#sW8*ej~YrNi1 z8{D`G|Gx%=4QSOM+L3}oDfqjqE4Xd${{I}BFcu05-CWKrXxF7~I?8ly(y)M7(1QmL zSXkCT8W0T&*U=5I8IaVFmXq5*Jmi5-MkXX+=G=5F)XuzG57Dh6KLKnCNqZ$vJ_^gR zqpJ&xdhcJyoj2&Oc_XBsQ&p=ny&$~i)khyF35Uj@25l(7S`UA`F2NT8l(`kR+4ceQ zoseUm3hX2&U*rf;>>t7I8G(3ycn!iQc>Nb(!=HTxb>0c`>wig6$|{eEkxVU~I_U!! z&t;r#(6SVEVTY2vE!<_WZa|AU-1)Pyo*{Hh^fU_gJ;aavPk+So8Y1IzA$#;g&Ao}(lb4doW{~6=qDUvwr{he^U``-z_qjt(A$?d3qk8i!o zC8FLRNBXr)$wK!tq7PlCfXGBI_O>3Ax%AyxOkd7%s#SdUDQW}p-Sy!|i@C3Q$ciZl zhg$=!7p=P}%!X;6pYC4&!psz(O+S}gTOp?ODmNMfzY!%o;x#G3>b*cbbtbO$*{s-^ zodany*JwLCejJiER%Py^*LN+M$^Vny07+nel%lVhdbE?O)_Txq^6Ocjm$~`*Y{+QO z@>imF84dV}M1LSwg7l)NCj#*0)1GR2N5PwA?U`ewJ}yWOu~_d7;T9@&Sp@wXX1i}W z)00Z{&N;`^PC3r)F147;*6Wjy*Eb!^WQmi@*Mv4`L@UyjI_5uI_lv_rMqIlZpJ4_T zkx!TZFocEFPS5?8)jinGZQ z$04zIcI-G)48&YiwCPZ6=7463?ia%AKQw&r ztE-f1oeJHlc}{l|`&n9dh}fRXlW zkztBq>6-@^JLa7_j-juddfY8goi}A{`OrU$YfjkxgZi4miLDL|(Xe#Jz&#&k_Ke(V z|1b>1Zn|=g?^n`zk^b2-bB6kw_cyNd8*{frM4R)tNLU}(1QT(Y6|6T2loxm0>nXk^ z|8c?jC?jTVn$e(|j;x;LT@@1}>&Wl#`#TuUW#u9yBeOzo4L*}c3Ia1v^1Y*Q2q_Q? z)LaaC5Vgbc`^JSXf6@GMI}(vutVjEVv*epYBuZ!e(%85~mP~tAAyj^lRrur1W#?+>bzn@<|dx6Jjh0EuwHw& z{HKApF#M+)wg7JEPs{B`q2ju1$hTT6h0=cH9L(R8v9BSpMGGTLV^pvc&TP>6ks=mV zr^#_H`oC%}SMWlNhCu~ZJC>pl`#HMGC`N8{`9(U-m?K)?K1l`oYGCh}eP@)m!ws%^ZW2$NP{egAGjwvuG_2)8n{ zN<%kJ`b^4Yo7)rnzoWnAnwOF;MbRZ64T{p6l~%;pF*~l@{KFMwL3_NM)--m?JP@$pTi zl&%?cMt|CQNYmXP7I+nY)K!dk{CVd4SHBh&G*`h?3C)yHWXCD`OQpi@@^@PBJUbtJsuKRvV1e=nqk_9J7-)~a;jtX1Ole0M^E z`hx(TZa`q69NP2BukcC4QiUUw2bZjWt##rfo->r{Ej)Q-4v>F$kA0QSs}9pu0eZx* zAd#G3?OjfG_KjMaOW$s%Wyi3_YC_4qk6IBMb!&wFhVF0(8x^U*O~kK6)W_4L^|98l z>r4KRC8FqPX#xJgZequt`!B~g8~qEmf+A^(0o$AmQ5|RJ=TVW7<&&vDm*?==Z&c@A z^LjO+F2l@$+#hbmy@_~APkc{`nS)C@u;j+I1qss@MNp&y`NfTyhVVJwusMi=zzV6i zyZa>u)bx52xUiavf%c-Ru1@0%wW|_{*)A^*8%X?Liitsy)pDl(89RG1bg{xo0FUPE z;&c;gT)d$3)!r`omB68!92sxa>L(4n4VVJ6F7;TVnt#sWROa zxzbyH_^@$g#2F$g@PL-iEN#gKXDC@&?EdklfUTPQBw>u3-u>M+Vs3}}4a|rych891 zf~~!2h>1uA@D02jofCgeOeFR$QVF`+LYm z$inH%Ss|eu-=W;#?KuuHM?cQi< zjgO-&0$IUFcB;xD3sg)4n)=^z?(gpdNL6Vw1458qM_Yftz6@^vr@nggQ_E}L2=R34 z$N&ED_L!6f(se+Gu{XqqaQ!KQZaWakLKbA^sSKjKpj^5)>Mm=J;)nV!M0(9-Vpt%_ z9TO8HCZR+cDxNh6`Bhb|n+m90_w@7tF~&dTHVZdu=XHCf*z4RwZ^D&%i|{{DnG-!d|O1B%%J+B2X^-f6_a zge1LtcUX`~D0Opw{w3#KsLkTzE2cWACZan2HGcijrT;J^O~BPnenJ-Y*Kv!ggs$K& zPpdilQlcz98L#ah=p6&@94BcvSYxT@YrN1Y9xC+%r)|&_;OQl%01W`J%(w(;eKZ`> zjD|8qpJ9Z=7hV2g({^eD|IG<>GVd+9IQDBc|7I>Pc$D^7G)sMdgD5;2Hpx5uppX!q zG7}uTApl!p%E>(Z3`wdjITaLNo9_AcfwV=O>;j}`LPvi-(wiOv6~W{1aYQ*d| zBI~->!}?7=yJTx->OLG8Doj7fJ$6@%8g^W3v$Gk=+n|}>SA;sCX$(9#;JE)e`ejRJ zg5g{fyCs;<0I?42ddKTLfI`BZ+8Z7oPK@c-Tn3p=J=AZ(02~_2gD*(Mw-!@kezrQ(W0`Noth=k`AG!FoXG#dnDuX*17iUtNqASkbAWo5k~8F{S& z*fAvL)`o_EAyaySotJOf#sp%TH~$g5C4NPv1*Lh{*?Vyvgx~ZcDMG&S9zE(bjl zdUe}t?1*-nzQ^D6ug{?!1HWK7-aj1kQ zgY|OfE%(1`X(rq2MR?y&!@a=d$k}kA&82f0aUs#n>tGcSEf|<=IM^Aho8{7$wSLhm zDv&Brr4gR^SFY><*;ULO*2xWUO&v|1>`B%)tV=kKC{+H0C`n zuLv)Hc!xS8s_K*do7k@0UDV8s$Fz0zoyy%sEA6(RG#oPS^6mOYyXdWAqfPzYizoTn zGn6<_e@cJaI*!aUe)(|LZZAtzRJ^K^cgAvpt;*_aEjr2#FD~@nydonJP|BcM(9;vP zG%1cB+=A)%p~Fa-(dyixV}7Sqf&d<};Fj5KR~)Z7z2M%(Io_VXXI{Ip81G2F_lc{( zBK+-}KL|;Bm{&O}s19s?D3>APA2b%@{I&V=?VJ}V`)=raxw(!y%qkGOOvs_rlky9# zubr;+@17Rj`yJ*f?CKat>^IOD{;P+U0Me?n?>ZXol)8|hzn0dqWCfS4>7z^?hhyFc!RS3k>L|j?G;fc17+E~MVCU;1v z|Mj2}kHdT}4WBvM2Q64c^}j8re5Uobw&Pvr2e6j_li{svCr@K9(#6Rr=)(6IH z9#~XfOjbHWrm1A>c0=To$*SegoeV8LgN%E*-eiX_I0Z%*CRT$|AiG5g=Xg*2+^#L7 z%E=HR3#aZ8o#Fbizdn*ZoO>2IOIo6lqm26A^r@b0{T!QIrN)>}y>&x|v8TxDx5(Z# zn#Y9^?Ie{&O@)_F_dC!$(D~<0Zxh*boiJZK)a%~B!TV%dcf#w-w#D<>bsXcr7rQf& z6#;QGjc*d#FD~Z?^Z{Y*rOKyx>5nnxA|G9&f@P3mdNxZL84lOg8R-Hyae(gK9V<(X9tP~c-fKXgEb<^~Vc7L+LtXoDl ztI~+7qE%8Fp*b7H899UY@4ckGTiY6RcRKR)J1v52<+^5OOb|`AjSXDR_}PW^@{-1Q ziKvR5vF(wAexoG0Zr%x3ncj}nG57ExAII^=mF!Bk9cex8I-;TH1&w8|a?7>n``Qyc zw>e{CHP07sQ4)&cov7v9CG^FQA0K&$Bt?=_i$RHjDC(!v_fv{nG-g9a@k8Z8Dt6@R zdA3d-AIy`))Tv=UN%LVw)biaq?S>Wz^5V&uC^^Pe;i zO|YBL{HnV>+h)ejhZlSNaYp$aF#WP?=@%=!HtlpbDI2sN7j{>L;&+T#Xpq$`7e{%I z*WdrHm2-ZvaYxwjBy3=K%&8DpU8`Ch$}^b05wf|}PVZfgjZEN%Wu)-BI)&NI4)%m! zdhPsE8}#EiIWP@iTWecC&0YQFkF&1bU&?3p@>}+UlxK{XH1`Y3Dh!A2GPGL+y{&jg z>Mq9BW^lT~6rOQrIUV@z>3DfT?^hZkW{2d-0Y?GzIIKqveufyy5@nWdM8?S(^7D)k(?W$Sj-jeLq|YDYc4$wzJ(Lw(XnE`w*)bw2(C~dm z@wQ)xJ)ZvW@Tn%PMbk8P%a^qtT$-a+?l=zj>%|t=$!19GqxAo>>#Kk5W%cEfudrYw1-Qz(}S?7p8N%>sAFVKNl?8)+IImQ5i@mJr$N@c38^S<8OF4K&q4Tc9Ys9TB7^K z6C-*1uDoeim#p);O{9}5>n1g;^whJ=zig3yTnzuL0xq875T%Zg^QrxBmqiTf7nyrI z9lzLO^iIt$Z{Eoda6X^8;eXeui!Ewj3Be`)Rm)vst$RKLA1igmSy0;;X7zPz zKfS*$hQ{no$sae}U^0^{%F!Q)V|Cnl8pmYFRg^6`coJf4(tlpamvGC?^Ly`+EtPMC za!!>Gm(N6&i=6G-^A4)0pdE{b-_P{)Zz<9=XA-;^r@Ffx&`DLdKpHsu%jQuGt8cW+ zf+$iY`&?>&0=Mu{?FkB5rXSvS*9hy}8xzIakda-Ri!7pq6?lX2Nodac&6#g#L03)h z?PlfQJ`$7u;T7_ls~gYPE>IKce*4eK_|SXJ=t;(=lc0utNcQ|s_h&Cn^G^o8oZ}bh zoVfLlP5o_x>vOtaQod`rl%LEqz3EAlOiKjPmuErRF&c?pH7KtHmj(xngatzLvp*<_ z%*a+>yEfnW*{y+3OAl_Oex4Whn-`wnKShbCPl-DX;)?Gi9EakLFDG#rcE{nd-AmQa z$|WwZP*9pge|b4ME42LTTQAv3m>h#_H3?XxL6#-pyariTt<$PnQ)8j--KxWA;?aXH zI!JqnLOSJdILj$*X0>r%-RN80NNgSt%%rlp2fjpHcbjpsD=G^{ZQHJu zvBQeQu6weoFK9bLJQ7A}ZU!xmR${DNiGm!WLkd*~)$Ha3HjFe-xTbvOP7bCf zKtO~uXJlgw4-IXp?u^w@{GITH{an6uj4o&8mhmAT=kZkUA6NTD%id@8(rWwRLWu|7 zcXY(-B}(7DV4f0}NyQ@#<*)gZsW`O4D}gszzi8woZcNIFDfM?AyfoMnEGxH3m!8aH z`Sx`Mldb3hCn@7h=aF#b>^5+)SzW$Qd?%57_#jHAOs_t=Jwe`W*dW_QD9>vyC!{qa zIBh|LWdsDgp#TvmoF$j&Kj#|z$Uos#rNyNA;j804-tFZ{-?~eU(IF2DQUN!Dnz#D^F?Z*&hdMp`>ih*)7_jJ^oiiOnk{C!>D(3%-)Ux3%@cYTIj4A9a) zkD5~Hj?c2dCK;BQlY+oe>tN~&F4r*@fAn*3r#7~lN( z-xQBTxap27_%sTW@?_@31^cFx1VU*8+BX;b00bF+i5z^4s z-!GOwCv|v5M?XK?l(LUhdfgtTJ30L_8T)x>ul&@Vk=Jx$hQHf$u~xg3@kRDCrR)tK z#ZQYAiN5H}9GZHy5*)QpnP+x%emDc=AP}XR1X~LdfqQjx>XoQ^9`T3a8s+NLOg8F? zn1%xMj#i%`!P}p@p&-*3Lbfd2V^**$VSKin^oBXGjjyCDQAGqNdbTqpbz#J4dPqI` z+X^JDkOX<1dz;A95=t6Hay#4ZP;LmAzZX&IFEla!@S#WSrDMcsTLk}8Mt@*wIINDa z5(fI>Lz|Pdf&y`HOGQNmFw((tgx%S(9bo;9IiZ19bKZ05GXjd@XEyzK$?kYgbX@T~ zZVW=tUAvr$rI})47IQO!swS<>%-XBkwW4Lgbooo`MJJutEpt*%QaMO=K7MQhT8}~! z@8{T99&lX<6~Fr8$FaeNS8Z1SeuPqddyErYm9g#qs_{PUUu@;y2j(Y^qjfR)q2p^4 zbUgKgsunGyl}^Xl=For!JRG2T$Vf|nV&x6Hgi0rgzadG5VG~-_WJp^K&)-R&p~H1A z`$_^a%ABQcdi)hFHzSiP%?%@tcnu#C>ShBHpHi(GYz%>wLW194>TY(%mp{xqI;j{* z$sFge3iFy7w|aMg6Ei(+0ol>K3B60jM7Y?)mbNw;Uoez_t}QQsJ~A>gz%2`MF<35l5Fht1PeA$!=hjW+r=!a8pAf36;x^$cC(I%#4gAfFy;@fm%MZdLgiif9%Zb zE-ut0=gs`EeVXL(RrFU8$;v|XJ!b{w$O6`7tqp~;RdfBFtH)mOn}A#Kb$aAQRqD~; zapfBh<2Fu?+%@kdW;x7Yilf*I?Th5;)yM&j>+RkR9zpno#6p#;PkY`Lb-F@s2!x*c;{~$+ngWLRy*;^=)b&k` zua7?1yB0+Kv}yZ(#Zg<&fiAm6X|7<~HN&-^N;tt9C1>=I=@$MK7ND1`A9r2sG&N;P zO@4vVPvK$jFTt&MGn!XAJzA}qir|~$yfr5@m_TCdDI`q$Z@BgoC~{7KX@*Gq;NTtV za!55j4mZ{zo;7Pb+_3={{8mRk~`?0 zwWukWJZDYzP_N^iDVEK%LQL2e{rv5`K0dtV3f0=sn#YzCBP*ytl_8StRY_ zV!I~tWJuFpD-pXKt1h zsWBJ0?<2$Wki$~3E0|FPB4YuUcKWGCi-y06uZ1Y2y>+sWRy)|l^m?ONPgBNbWmoL- z9OEyOvGJMN>l*fb@i}7}7V=52hb}a}@SNVM^MhK8{jSIC*vZ%Oldlb3tE46EqZ425 zpl%GNDORwt$#RDSh0>(eZApMmy}tA{XY!U@dZapoUZH%g?Ae$@r$mJw%}P*IP+_gf zpAgx{PWMUDVm031cGx1job9Edix1bTxi=8!ch}*^kSf|I=@}MFa!u+@N#$9V<(Q%w zZ7kTOxR(k>?!}<#QkLm!ygmzEiIlZ+u5fbnd6Qp{rtd;XBjBHhv`AT1yuNOvl^APpiRC6clrsDzY+ln6+7mvo5; zNOwyk(h~2*Isf;a`QDlDoB0@Lj%V3jpMCau?)$oa*DpS4sqa|p+fskq*Wt)7`^DQ@ zHa+rP@Y1)dtxX;~@j|AuCfFE}I-lukVFpFj9kZGLOKWi`~M-xex~^UXMko)=O%U>*SWv~sCx9cDWKT;9b4i_U@i3)~X+ zwN)<3i=uP);J{~pJSL0OnQZw6(?tpi(Q+qzt0E$z zpSYS=yW4W>FgTEL#BAasBCd{od*^`K|G{bv=luTw9vPOl^cuWR!6y<>P1mid#Kgp& zPG+KPQuodHJ-A^*j|u8=FOg_>_(Y%?0_MN$=yiWTYOU z;trHCCx<#`{IF6;9TJp2{0nBrDKSgRu@X-KC=2rnfD?s6{~Vpb!RF~7tTH@&^5l~N z=Q+gd0m;^%;211VfiP6zdE}(3+6z=#m@`ndgTM#+M|q-P=ed?EEG(IT7q_*w0iNly zKH@VmaOM93XyW1GN+J#a2hwa|;()kSQc~G(M6SRZUQ&mlP)l2zhr2r+8i3IUwIq=@ zh0L{QqO`S-xRR3pqI8F_aDd8TXKHB*826t!v5I|UHiw)fGHWnxoKf(sX?J}W3hR6zCjhldx1zC_@Ue;O=uu~CIK`JbsBsb zFuOw1bAA{X%?4H+Fl`F-$`lyh<53Dcg*nZ)kF?<{dsH^x`ksGnxgOeZjMd)Fvo&~C z;qzR!4Ge2v5;YHXb~WDlVM!(v=vH%2UQR9+GH*erE*aEg0Y1G>KfkDt(I!Pi%q=Zh z*xMIWAm0Boo-ls;^!D%O`7)(@!5vyx8yvTeLSFWc_#q5eJ>);PS78MmNkfucnEy2( za>{k_QrN%mUIb& z!(I=n_R*&T5WI#|2H61E^TNEuu+m``l8m02{s|i;hv_CY71a+^H+|0FD)?=KOq!^d z=nlAZ>+9)hWNCqROeSq$1(`~c_9i%Zw8EY?QKbRaA<#HOz8d~s7!E=*{NV<69jk6wDZ{0N^iyq=0|`tlSxWRxK>IF`BtQ z4WGkTCnYHfZT8Q?0C!h-Z=kk;Owg*{S1WQBlaFEp`dV0=uHxe}QGlaGQAx=y7$+J- zsT>W~4D9?6PxeB~Q*63>OPxN&DYi0L{kc z>cfX&nC$fxn*W|_-KslCl1bBrN*`vCz(9ZQ^x#PhS0_Pt2-#wkQ`7!1 z?&LOxbmt|fs*vdeKtCWYT3$s(U|$jAGCvIEK%+q=W(;hFL<;hI0N;+%VhA!{+^i*&>T;$>^H>EN2s*ti6(H%Kh zC#=Ry`%!A&6AtU3xLHRy&|Xy%1TJ#Y^s1fN9Lb0J`+0yFAS@g|64GNK?s-@Us~0ew zf1fD9{2ziy+pbU}ZV*sWos5EBf4dO}YKq`DZ>qsAMQZtasv0W+653Y1yn5FSifDz! z-|iY&rJd^;(B|*$?{5wlB;Reqk!S}>A-Ql4sn@TXkEvU(Gmqo*N>n_Eeq~d?d+3Un zGYfv*9>AquQSoJ2VEIFa3t_^};`!q!Bj(6^A|h~0dO$8)OH6h) zJ8a;vp~Hau=Z{qXXY$PM!34#HYu4)$XXQYEfQ1z@Gr@R1>-TN0`yRVi{Y*jKi<{#W zVX&hJIX(e-FSxKkB>^#L1&RB3h{*`;580y2fVPKw37eY=xB2_? zHXXqAw~O;dOw8~{?rXUSHp{Y zP3wJV0^2`OaHNsYcjg<_RUctrHw=RUfX7U3{w@jzy0C$Gx5w}N{tdU_(3-G_#3R<2 z+cD=+*Ql&)Cbz(VFc%#ZkX(N__ZB*3w&X9mvGJ_oNHztuX*aEL}-2U$L@YLzeA>Hzp9g`7hp01<7E^IrTVm}xELra z)L>8)XabN#2`pmI&>p}W`T*&*+>B??e88E2s8*?|mD4rezI}V~AgphA%|cWvGBTGP*MZ&)e}&x>b{(KqtKyB}q+E3X z1O~PiH#eY2n3Rlav4c5}lh8*<+yhr02t*GI49wQbhOl+KW(c&53=fA{HNYgd|LK$N z;{64^6xgJsefqSzwg#&!H|Vnd&(;AHRDmYL`G#;rz<@a3UBO>d!ZIdFA>(nWaovbe zNXIX6U_isG+uxC2Kxb6IQ~>1AfHeO#j5GcH{fioq0ZkAp2pElWJl_iN&V@n?3~Pq^ z`tFL0`+(XVQUqCsih)tEySE1$6nt*TNrYkyb~TVffdn4$286S(_;nm~dZ z0Q2{BHCC_}Vo^;hEiM*@aBO0!lYntM*z^n9@9VqjffSX|tgs4B_GkYSFz4<0N)%mJ1Teq8_AisT^hIDO zKn?LhO`OH|h2N77Q9e!&U_?Ly#sdR3;L>aY!-j&3W1#iN%I=c*UAO?w z_6mGJ{09eLQ&lxOGV;T!6y&G8HPcMF`y> z3mhFABRVar-WIn$(9+fIoOOWW@dcD6z=!+C830sl39+$O>pymbR5Jg#*FNyS%s)y) zMRl7f7z!$gtppIBgPolWuMIj4;FbkQW)&2iFMdFMPr5&T=pdX3l1OuV`>lDPGDkgR z4EmP@R9ANj@So4&EU>z8fX@X~8MxR5ZXaE#js=$rs5u;ks8ZE}2pGIQ6&Q(3+Xr=j z_>JMg=LL8)P}~hTaJIvox7tj(orUX61X`Kn!y+pR3okbLi_q^jO3G_2VqJuipnZhT z%_KlXyNFDMYj1O;XdWPUPqFg>Rq>6-w06c_TI_?c!-J&mAHoR+1_qEHAT3)+=n%Gh ziVzE^m7z_tuiFEO1^-9pwU8U2z{H8CfD}tmhF-J;!4G%9YscOFm$!DT>pFdyYE7qW*rD5TY5k$oL*Z?MZW;Vz;y{c9F5MX zo{N=iJ)?Vrup*)L$zA_OtBt_iK9>uL1J6}_YCN^H?|9(K0koWqQAeW$#!rwD%Ydfn zwZ)NGCOp-+*Z}{1C#A9xmp12rv=Kb04AJB~0eQ4F9|cqi;GG2ZKRR&LI77~uxKfXe zl%zXA`puXVPsjrc?kt^ac*|mBok&yR&50^rs1!)v!gn|5l|k9)NTtz96{oey*Egwg zC8EQ#ll=unz(c+0%$VW#cXG)?jga(;!wA#%bUPvwq0nSFBj70mYZk~!uvA$P4=FvB=xbS6V!}8Ob8b&f^y+*qq|U zX}2QEMi+};)@^quzAW0zBz^pgUYZw?3I1f_g^dsxzCaxn6%`d2tl%flR#hbx>z&sr zgLkVhvC6sK$Sgixk2$}`EBAea#TkeG=5~^%*zgN?@UVjm5XfgAP~N%_#q)h!kThI@ zxufNICg_~x7zVUFz4VordYCCi!e#fp+G|2lc@^A*Ha_Q`MY}4+wyq+pUXBSz@yuS5G z&$RtplY5dE+O8slKETDv@dESX`8DAfTK_!;Ovd+FoyRVI8U~~8Z~t_-QT=VuesuEb zq|qK%ovDf6S@!ztuWV3074+WHFW-8F+9S5Xi9p(l5pdqRWjfaq44)9LZ(w^dB=ihP zeM#}c<;*2*@Emi0`}Z@M?ZHegYI=ZmQQ5VPJHl3)iy@WSa?Q7J=1=d+mn(^`vD)z=SPnS=FWO>*JZ z0r8MRdjIh7hvr#hGJz*8NU}AnhkMfUA>uu3cnL<)M7~xXadXn89*t_Dj!ESNeAj4u#?9U{&Te&`Z4m< z(!Ir|wPpG}A~vHutls@1Q#>MOv9yAkqoO{Ew*oj*OiF(vMt=c48KQElJ0 zU%pnym>60n4PF4q>@IxP=H|2`-0c)Yx@f#_K~)3TpCj@mh{%%+QYC(AM_bR|RntkF z_Pdb0pgz)Pb(F5%&rT(a=p29l&#{0&e4bsoM%%@dqsWFxCe>+rOtQ?8-Dfu;w_2^6 zSREOV=2Eoxe*aq#TxtWvmr0f^as{9lLY3cgKw95=a0pk z)tqVT*sOuy{Poz*O$Q#U%4VqO4OsEjzR9*;+fu{h|8o=3hFOIrv24v3uShB5Aw0VB znV)Ld?86lc&%JQTzU+cfj8g7+yjVJ(!6##FINE%}HYP4C33HDg#aPGACL}jz-urcp z&JFQPlqi^Qqxbw`@Q1MMrDheI*$16gPS1M>c=Y45*zpFb~@^orRQ*A*A<%r?IU z<0Vh1gHs+*qn70uZr2sxieM+*U7dYnvhl_IChon0O5|NCW-o`Ifg6LfV1V(CL^BI$ z5vT@lAYen;3M-Pmr>CclO?FIV_Jd8|XL8FZc^HDF+c#x7Jt&~rd}uFvhwugZ?j(zk z!_Thox_`0o)AS9<<6$F%<$+S1S*M^5*dVYaLt4@>%4gEuhpN3jgp!nmBqlEIs7G)b zx=L_H1=B(kGFPa0VRR518w=%FhzzgAw~M+JGTR}vt!;Wbxpx^Fg=I(;Toz0;?CR{a zhV{+JDDjlBtaR(H?R=v3xpEQhsYyLq`jVM8ZZ_QKf9~^Bgs5pn>L2b{>pAszKS>C8 z0}@A4$q}Jvy#F;`4|eb}1!I79Xn=qSd|~9(-JO%2-OuCRL2c~3(USuG!S3dye!2!Y z!;&@^LYaA!zSMc8A3AmbvHw^50wf14Po_RQs_EmMsogy5Yo_8|H)h#J<4#t)1a^WR z?Oq&(Fscjhv=>ApC4v)tjc_lZx1Z4g)mTg{$^ByZ4GmC;48RFAb0|7d(G!4-E-=+( z(BJ~_qI?$ZeZ0aFr&JEWLEs%2uWIHD4i_}6o`8!8kkfPetU{^o?EukzPUx(gtARij zY-WRn%Cy+`{(&pAzN1oP(r?RG#XT1nI0{SbRe2^?U(2!V{Zzh7IXzcpc^Pvuj_otV zf2kWA(@Fk<{you0I$Y#y_*lXN_Gp{+ks|1UKyM2!5pN0#?4cTiFHbTRfG4fU1;9hI zis~bPuAsZ0R%=OmgUgfBXZ=sh)#;riBi0^Ikf94tSDHA#ePRXk5r|d;UOx0h50Up_ z>chko&|^VLN;)s(4PeICuM%Z?lZ6cubjBs4AbM$cJOPvgnF=j2khsBDPa$1HEE&vV zX8_tA92#OkZ%);{op%SMJE4aJi4Esm^s>ZT_}D_BmS$#jXy_Dz z=Y@)PkD*C(r1JSa7XW@E8X82Q^RKT_KPee)(bkyvW{R_?arq!71w*S;wUN=&G#0RJ z10RapByUwR1;yb(m{!0gFCdzulXD5v55xhm>1na2Yh)4QO5Y|?WF!(!{BFiWc@4Pe zfm;+Axp@jl?xMeAHz&I7=XJ4g5Y_&iaSD4!Y`W)Ouid{<2$v1lpepLb5!6F|csBn3 zu}dkTSEdUS&Zr@lGE6EiXWtu;uhbhX449p&*;;0KM@EFONag&iyI;-}QV~13EQFA; z+!V{u+vk~Gv4Xy8sQrb0>RdCOWbcCuUGT<=UncdD~r9#veRHV zGH_`JnU5EDKOjZaZj#s_&HA@)r5Lt^?ZcHYC%5|Pv69?ywmK4f0W(oVY+C+%SqJlu z+D?wQ8sdpR)vlPB;YuE&%I--zcp-gter8PAo?HoiFBftHhn_L$e17pk$&ukzUv=j= zQJ&Nz+6OP;h5hxhDi)7R+8df_#FLdOASESZsZ|*rZn-PCk2M_?vk-$C-o$5ceSZ#CoakFSt1LwQqabK9rNA@sMB-T8sP~K|v5amaF zMT4&if}5`_H>u*~3&UsnITR~5gJmk#M~BBmMxMUO9;WlGTt}L?eF=rcoM%_P$p>n25dn1~lHGbuM@KSMl zUePT~#4nj6+5(a<6&r#PW{up>xHm4rdHE{A7(m`+IZPl)7C=$C5n9v?sRiGf1ksP>M2#zt$pne0vz6;LU1wj)n`FSO<7@2gm-`!K?pG(PJ``cC(18gH zB4u*n_qM30_|)_7e`Tt}Lbk~$vm6hiV(|;vq}2^P zF(*&jh31ey1XJ-azHal;c4kl3f3KyEuNHmM+3oZPQAl8pLw%oxUZqUg1+#2xs~&9S4cxP*sXiX>dAgvvIv zKfSVyuq_!{5zhA>xK~}CF_!Ccsh831QBsMe0HQJpbU9|0zWjTJS!`#6bxU>)D;25+ z^=+n4(GzkGdXw7H>1#CP6MLcYeVUrL(VZgf^_dy&+wKz_w8t%sT}UA!f7=}C0=etQ zo0G-!h(gS+aA9rp+T5Prs@ascVGZpg$dDTUJLK~YeEFOCXQmVWO``46FID4LDoet3 zrQOU;RuaOzyRSGFc+T92G^-GeNyEcfZO++SC9Y$*uZJ{jA8f1JDoebdJ77d8Nmec5 zQT^8bSa;-A`$)%#Hu0Nq+O=|~%u~gk2 zscm^Kxj{}N$CK4vlsAK51hL=*7w_{Q%q<-RDPp1$_#@%Nhin7h?_I`gEbCZQZbTr&)i5PsPGuylcg%Xf}zILMPPg#p163EoCWj{Tg)Raa11*@s5FV~?W$s#41^_l+_; znYMPr#?(vM+DlotIlVZhO;ON`TYY|xLhP=qrpXHLfwNPCHy^4dFPQi)f!dSPu^qj@zw{xluz=!i(naF{nW~ zx|HY(AcmSmrF;{u{yfj-F(Q$~o^pNk&8L=0#A zj?=QehSJDTPyM6i>+Yl8*!zn|ZkOHm4DBRYC0YHIzUC zN4F-Mon)>PHxH!RooOCF4?91(KF!0Wjq?l`Z|hDPwu+VOl(3x{dSY zQp07p8j;e>uyyvc@gl$aXWb2=3>Gr0UaFy25`SpNEZ?_gRz|Af%LF1$8s5x_w%bWc z2DOdt_$N1r3qWS!*@oBf)P8Xc94~EcodR-pZdG6_X|bZmrR~xZLi-aQ#-X0uYuAHv z=TWxX+U`5Pd0Zwx)#eA3_4;G7vSK5s8gk#Z zajxp>xw0!2;#LcIsev8kuWyZQV@{)NRN9-fiS`9X#e!=GrSe}1)dLYo`j#wz5i?l_ zcP7g)dv_P>zBvD4e*_r~2+JL_=1#1Vbo+`H*4+=TH+XA}_tS|Wef6cuG3nsm=?+a{ z$2zUkpYo`mX%QQJfS4eH7xj%9aEZLar9OM2SbnQl6Crf{;=%DrdXZPc(IiPDRg3te zY={Z$f+4KcpHpL6>Yf={_0?6sHEajW{f*q3uQ1cUl5A>M{YXK>?~>A>svu{nW~dil z!aKj&%3+3su^qxH<*QIyA#m!lRj!ZH(oCwUTWtiokwrn``PPv#TeP0Z-Fc;Mnt07x?MXMXqpKTwuydHj+ za)mr3u(_s!B&qnQ|3+cV;nq~uc50@1CW0Fq7CNcp{PO2qQ%84L9&(-@jcjpIZVuBN z?uPmFiu25Ag)cr7o}EM>E)B~+e4te|eFj5PWmjB;;2d0gfd*b!H->4|CngH0xefn< zvM#uL7_Z_=bdt!?{wfn6J&v~eM-~xGUwXJ(#+~-?l^!2_u=SFw+P}r{p^k*d$K9>{ z+QymYzAWX_a6(rNLp{2SCvPM8jh`=xId7(4nj14*=`b${sWa`w&m`ZiZtK43GRU;Y zpKiF3S9d?LZZgrsuwn8~laBL=9cCJlIkFJv(e&4rU8!YMO}o$!`)88Oky+Kb+Y>7v z-&}q1O7`HRf1hzUHO5y0cVCJ^TEmy!M}`T13izC@63i|AuzyeM7j8F37i%Ia32#aH z-iIakaITlqqk7B?$4^OJlgLc=-;<_~TJ&^UojLDP@l&VgVK#EbMo7&FkQ;MU$%={8 zpWhl~FdM}?(=DLF=BhwpIG7A zQ9*-M1BZ~8JE<~KM;)aZU$oDb_P#0`<1f4@rXPQoTD;KE759s*Tc+#UxOikDrUL_P zXBwH;ecJBeKYpaIC;A<{Chpf_zG^nh^tCxUD)HqoetvOqj`GjVWPNP{RDxot zG1SE2>duO~bZ}KC;uXG9eEh=ZER)Ek6z_{?KHE%kJNuDj=9#_*4vn zs{Z7}8osy@NtPDTlXYP@2P~50n7OylN0QP;X}eS~Wcg2%mrjjWN-j=X0Gu?Gz3TF7 zN~6`ypIz55aY}y8lD;^^E4(;r4!FXT>kq0Q6hu;EU!$|J>7Prk&ES^BqQipkI&Ud3 zK2d+j)s0&LovIw199`}iEi=y`f}E*^&u~9hDdWWSWhm{)GqXu$fIb`?UG%pL#N|J_ zd|0PLz%#t!lrmo3_zTbY?sV{7KFv|S{!jh5Car_XB^~3RDR>zDWFdiFSC7({RdkFx zhVgehL$UO)Ag?JZRoPnX9MsjGnpt98tR39d@WlNgX1nL7gBijU5x1NUwsFY?9Ss)D zRJl;2y8kY^2nU;aju4rA6^#` z5wW}L491->DcIR@l2(VoAJ8z>!4HsCK~GN)n0#+;axC>G+w)Ptl>DajXqoM!2M-zn zngy_3Uq@%?=g+8wgq%U1w}OZS;~_4@EpSu^`34|nU}n)jFaUE&;Hd#eXZc&4rlFzN zuOIaE4_#f$j}`z$n{aHW2Fuu`Fz{JtVP*!fL_e($8yovuqDTd(=y`cHkwxympMWtq z7>NKc1sImPB^Cheb{?BzstLkw{0`k`MYU7eX#8Vs59oMc2nX+{tEx1DQ_jM$K(jak0v7{v$C}X wOVoqi-P*mdit=(?`d9w`=Wv3*uAW`*9eytG`M9|0q9>M@R(Y5&Y5eNH0JoE7jsO4v literal 44471 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 >(gu7ZzTObZfNJ>I( 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 t-~igq)fMw%3f01OsN=#m~NZ8=z{{R#3`uqR@ literal 43143 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 fI1vD!=;OPpj>QCJ@1Ga Date: Thu, 29 Feb 2024 21:58:54 +0100 Subject: [PATCH 79/87] ci: check metrics generation in CI checks (backport #2483) (#2486) This is an automatic backport of pull request #2483 done by [Mergify](https://mergify.com). ---

Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
Co-authored-by: Sergio Mena --- .github/workflows/check-generated.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 026d1d1da60..372d8348022 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -13,7 +13,7 @@ permissions: contents: read jobs: - check-mocks: + check-mocks-metrics: runs-on: ubuntu-latest steps: - uses: actions/setup-go@v5 @@ -22,17 +22,18 @@ jobs: - uses: actions/checkout@v4 - - name: "Check generated mocks" + - name: "Check generated mocks and metrics" run: | set -euo pipefail - make mockery + make mockery metrics - if ! git diff --stat --exit-code ; then + git add . + if ! git diff HEAD --stat --exit-code ; then echo ">> ERROR:" echo ">>" - echo ">> Generated mocks require update (either Mockery or source files may have changed)." - echo ">> Ensure your tools are up-to-date, re-run 'make mockery' and update this PR." + echo ">> Generated mocks and/or metrics require update (either Mockery or source files may have changed)." + echo ">> Ensure your tools are up-to-date, re-run 'make mockery metrics' and update this PR." echo ">>" exit 1 fi @@ -54,7 +55,8 @@ jobs: make proto-gen - if ! git diff --stat --exit-code ; then + git add . + if ! git diff HEAD --stat --exit-code ; then echo ">> ERROR:" echo ">>" echo ">> Protobuf generated code requires update (either tools or .proto files may have changed)." From 0641e473ca963c19c3203299dcf4be20572f9a7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:50:41 +0800 Subject: [PATCH 80/87] build(deps): Bump docker/setup-buildx-action from 3.0.0 to 3.1.0 (#2510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.0.0 to 3.1.0.
Commits
  • 0d103c3 Merge pull request #300 from crazy-max/cache-binary
  • f19477a chore: update generated content
  • a4180f8 cache-binary input to enable/disable caching binary to GHA cache backend
  • 5243153 Merge pull request #299 from docker/dependabot/npm_and_yarn/docker/actions-to...
  • 3679a54 chore: update generated content
  • 37a22a2 build(deps): bump @​docker/actions-toolkit from 0.14.0 to 0.17.0
  • 65afe61 Merge pull request #297 from docker/dependabot/npm_and_yarn/undici-5.28.3
  • fcb8f72 chore: update generated content
  • f62b9a1 Merge pull request #298 from crazy-max/bump-gha
  • 74c5b71 bump codecov/codecov-action from 3 to 4
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/setup-buildx-action&package-manager=github_actions&previous-version=3.0.0&new-version=3.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index 99cb523d7d4..c9b1fd5db71 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.1.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index e426397657c..f396dd7817e 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -41,7 +41,7 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.1.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} From a940dc81bb7ae88836711ff5c5f4186800a53c7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:32:10 +0100 Subject: [PATCH 81/87] build(deps): Bump docker/build-push-action from 5.1.0 to 5.2.0 (#2556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.1.0 to 5.2.0.
Release notes

Sourced from docker/build-push-action's releases.

v5.2.0

Full Changelog: https://github.com/docker/build-push-action/compare/v5.1.0...v5.2.0

Commits
  • af5a7ed Merge pull request #1074 from crazy-max/build-cmd-debug
  • 2a85189 chore: update generated content
  • 6c20794 disable quotes detection for "outputs" input
  • afdf0c0 chore: debug build cmd and args
  • 00ae31a Merge pull request #1070 from docker/dependabot/npm_and_yarn/docker/actions-t...
  • 701942b chore: update generated content
  • 90e54d0 chore(deps): Bump @​docker/actions-toolkit from 0.14.0 to 0.18.0
  • 831ca17 Merge pull request #1066 from crazy-max/ci-local-cache
  • 6bd0e54 ci: local-cache job to test local cache feature
  • b3eddbb Merge pull request #1057 from docker/dependabot/npm_and_yarn/undici-5.28.3
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/build-push-action&package-manager=github_actions&previous-version=5.1.0&new-version=5.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cometbft-docker.yml | 2 +- .github/workflows/testapp-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cometbft-docker.yml b/.github/workflows/cometbft-docker.yml index c9b1fd5db71..82469edf7ad 100644 --- a/.github/workflows/cometbft-docker.yml +++ b/.github/workflows/cometbft-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v5.2.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/testapp-docker.yml b/.github/workflows/testapp-docker.yml index f396dd7817e..050c4566968 100644 --- a/.github/workflows/testapp-docker.yml +++ b/.github/workflows/testapp-docker.yml @@ -51,7 +51,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v5.2.0 with: context: . file: ./test/e2e/docker/Dockerfile From 3caf4a46b16810f495c79c2fa8a0ab0eca9969f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:39:04 +0100 Subject: [PATCH 82/87] build(deps): Bump bufbuild/buf-setup-action from 1.29.0 to 1.30.0 (#2555) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.29.0 to 1.30.0.
Release notes

Sourced from bufbuild/buf-setup-action's releases.

v1.30.0

Release v1.30.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bufbuild/buf-setup-action&package-manager=github_actions&previous-version=1.29.0&new-version=1.30.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 6bf42245326..88a1c147db2 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.29.0 + - uses: bufbuild/buf-setup-action@v1.30.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' From 1bb0bd5cb85733fbe7dbaac41a9fc50b2177267c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:10:18 -0400 Subject: [PATCH 83/87] fix(docker-compose): fix subnet (backport #2383) (#2582) This is an automatic backport of pull request #2383 done by [Mergify](https://mergify.com). ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
Co-authored-by: Anton Kaliaev --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ee582871976..f6c37165abc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,4 +63,4 @@ networks: ipam: driver: default config: - - subnet: 192.167.10.0/16 + - subnet: 192.167.0.0/16 From fd52ab7e4827a87750ef275d2d67e26af088a293 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 12 Mar 2024 12:27:22 +0800 Subject: [PATCH 84/87] =?UTF-8?q?feat(blocksync):=20set=20the=20max=20numb?= =?UTF-8?q?er=20of=20(concurrently)=20downloaded=20bloc=E2=80=A6=20(#2574)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …ks (backport #2467) (#2515) Manual backport of https://github.com/cometbft/cometbft/pull/2467 Co-authored-by: Andy Nogueira --- .../2467-decrease-n-of-requested-blocks.md | 3 ++ blocksync/pool.go | 47 +++++++------------ blocksync/pool_test.go | 21 +++++---- blocksync/reactor.go | 5 +- 4 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 .changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md diff --git a/.changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md b/.changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md new file mode 100644 index 00000000000..3b5ea17ce5a --- /dev/null +++ b/.changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md @@ -0,0 +1,3 @@ +- `[blocksync]` make the max number of downloaded blocks dynamic. + Previously it was a const 600. Now it's `peersCount * maxPendingRequestsPerPeer (20)` + [\#2467](https://github.com/cometbft/cometbft/pull/2467) diff --git a/blocksync/pool.go b/blocksync/pool.go index 9a95fe56214..9e6d7fe2998 100644 --- a/blocksync/pool.go +++ b/blocksync/pool.go @@ -29,8 +29,6 @@ eg, L = latency = 0.1s const ( requestIntervalMS = 2 - maxTotalRequesters = 600 - maxPendingRequests = maxTotalRequesters maxPendingRequestsPerPeer = 20 requestRetrySeconds = 30 @@ -41,9 +39,6 @@ const ( // Assuming a DSL connection (not a good choice) 128 Kbps (upload) ~ 15 KB/s, // sending data across atlantic ~ 7.5 KB/s. minRecvRate = 7680 - - // Maximum difference between current and new block's height. - maxDiffBetweenCurrentAndReceivedBlockHeight = 100 ) var peerTimeout = 15 * time.Second // not const so we can override with tests @@ -108,24 +103,27 @@ func (pool *BlockPool) OnStart() error { func (pool *BlockPool) makeRequestersRoutine() { for { if !pool.IsRunning() { - break + return } - _, numPending, lenRequesters := pool.GetStatus() + pool.mtx.Lock() + var ( + maxRequestersCreated = len(pool.requesters) >= len(pool.peers)*maxPendingRequestsPerPeer + + nextHeight = pool.height + int64(len(pool.requesters)) + maxPeerHeightReached = nextHeight > pool.maxPeerHeight + ) + pool.mtx.Unlock() + switch { - case numPending >= maxPendingRequests: - // sleep for a bit. + case maxRequestersCreated: // If we have enough requesters, wait for them to finish. time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers pool.removeTimedoutPeers() - case lenRequesters >= maxTotalRequesters: - // sleep for a bit. + case maxPeerHeightReached: // If we're caught up, wait for a bit so reactor could finish or a higher height is reported. time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers - pool.removeTimedoutPeers() default: // request for more blocks. - pool.makeNextRequester() + pool.makeNextRequester(nextHeight) } } } @@ -261,7 +259,8 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int if diff < 0 { diff *= -1 } - if diff > maxDiffBetweenCurrentAndReceivedBlockHeight { + const maxDiff = 100 // maximum difference between current and received block height + if diff > maxDiff { pool.sendError(errors.New("peer sent us a block we didn't expect with a height too far ahead/behind"), peerID) } return @@ -372,30 +371,20 @@ func (pool *BlockPool) pickIncrAvailablePeer(height int64) *bpPeer { return nil } -func (pool *BlockPool) makeNextRequester() { +func (pool *BlockPool) makeNextRequester(nextHeight int64) { pool.mtx.Lock() defer pool.mtx.Unlock() - nextHeight := pool.height + pool.requestersLen() - if nextHeight > pool.maxPeerHeight { - return - } - request := newBPRequester(pool, nextHeight) pool.requesters[nextHeight] = request atomic.AddInt32(&pool.numPending, 1) - err := request.Start() - if err != nil { + if err := request.Start(); err != nil { request.Logger.Error("Error starting request", "err", err) } } -func (pool *BlockPool) requestersLen() int64 { - return int64(len(pool.requesters)) -} - func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) { if !pool.IsRunning() { return @@ -418,7 +407,7 @@ func (pool *BlockPool) debug() string { defer pool.mtx.Unlock() str := "" - nextHeight := pool.height + pool.requestersLen() + nextHeight := pool.height + int64(len(pool.requesters)) for h := pool.height; h < nextHeight; h++ { if pool.requesters[h] == nil { str += fmt.Sprintf("H(%v):X ", h) diff --git a/blocksync/pool_test.go b/blocksync/pool_test.go index dd7c02b9dae..e9184ecf918 100644 --- a/blocksync/pool_test.go +++ b/blocksync/pool_test.go @@ -78,10 +78,12 @@ func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { } func TestBlockPoolBasic(t *testing.T) { - start := int64(42) - peers := makePeers(10, start+1, 1000) - errorsCh := make(chan peerError, 1000) - requestsCh := make(chan BlockRequest, 1000) + var ( + start = int64(42) + peers = makePeers(10, start, 1000) + errorsCh = make(chan peerError) + requestsCh = make(chan BlockRequest) + ) pool := NewBlockPool(start, requestsCh, errorsCh) pool.SetLogger(log.TestingLogger()) @@ -138,10 +140,13 @@ func TestBlockPoolBasic(t *testing.T) { } func TestBlockPoolTimeout(t *testing.T) { - start := int64(42) - peers := makePeers(10, start+1, 1000) - errorsCh := make(chan peerError, 1000) - requestsCh := make(chan BlockRequest, 1000) + var ( + start = int64(42) + peers = makePeers(10, start, 1000) + errorsCh = make(chan peerError) + requestsCh = make(chan BlockRequest) + ) + pool := NewBlockPool(start, requestsCh, errorsCh) pool.SetLogger(log.TestingLogger()) err := pool.Start() diff --git a/blocksync/reactor.go b/blocksync/reactor.go index 91477ceeb67..32f8cc09159 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -77,7 +77,10 @@ func NewReactorWithOfflineStateSync(state sm.State, blockExec *sm.BlockExecutor, panic(fmt.Sprintf("state (%v) and store (%v) height mismatch, stores were left in an inconsistent state", state.LastBlockHeight, storeHeight)) } - requestsCh := make(chan BlockRequest, maxTotalRequesters) + + // It's okay to block since sendRequest is called from a separate goroutine + // (bpRequester#requestRoutine; 1 per each peer). + requestsCh := make(chan BlockRequest) const capacity = 1000 // must be bigger than peers count errorsCh := make(chan peerError, capacity) // so we don't block in #Receive#pool.AddBlock From 15d4d915b7fde3cd1ccc7b670f777474e0c47998 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:28:58 +0800 Subject: [PATCH 85/87] feat(blocksync): sort peers by download rate & multiple requests for closer blocks (backport #2475) (#2577) This is an automatic backport of pull request #2475 done by [Mergify](https://mergify.com). Cherry-pick of f8366fc4290e7ab5109c8943dfadf9d75c6ca2f0 has failed: ``` On branch mergify/bp/v0.37.x/pr-2475 Your branch is up to date with 'origin/v0.37.x'. You are currently cherry-picking commit f8366fc42. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Changes to be committed: new file: .changelog/unreleased/improvements/2475-blocksync-2nd-request.md new file: .changelog/unreleased/improvements/2475-blocksync-no-block-response.md new file: .changelog/unreleased/improvements/2475-blocksync-sort-peers.md Unmerged paths: (use "git add ..." to mark resolution) both modified: blocksync/pool.go both modified: blocksync/reactor.go ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
Co-authored-by: Anton Kaliaev --- .../2475-blocksync-2nd-request.md | 3 + .../2475-blocksync-no-block-response.md | 3 + .../improvements/2475-blocksync-sort-peers.md | 2 + blocksync/pool.go | 415 +++++++++++++----- blocksync/reactor.go | 5 +- 5 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 .changelog/unreleased/improvements/2475-blocksync-2nd-request.md create mode 100644 .changelog/unreleased/improvements/2475-blocksync-no-block-response.md create mode 100644 .changelog/unreleased/improvements/2475-blocksync-sort-peers.md diff --git a/.changelog/unreleased/improvements/2475-blocksync-2nd-request.md b/.changelog/unreleased/improvements/2475-blocksync-2nd-request.md new file mode 100644 index 00000000000..67614a8e35f --- /dev/null +++ b/.changelog/unreleased/improvements/2475-blocksync-2nd-request.md @@ -0,0 +1,3 @@ +- `[blocksync]` Request a block from peer B if we are approaching pool's height + (less than 50 blocks) and the current peer A is slow in sending us the + block [\#2475](https://github.com/cometbft/cometbft/pull/2475) diff --git a/.changelog/unreleased/improvements/2475-blocksync-no-block-response.md b/.changelog/unreleased/improvements/2475-blocksync-no-block-response.md new file mode 100644 index 00000000000..d01b3679866 --- /dev/null +++ b/.changelog/unreleased/improvements/2475-blocksync-no-block-response.md @@ -0,0 +1,3 @@ +- `[blocksync]` Request the block N from peer B immediately after getting + `NoBlockResponse` from peer A + [\#2475](https://github.com/cometbft/cometbft/pull/2475) diff --git a/.changelog/unreleased/improvements/2475-blocksync-sort-peers.md b/.changelog/unreleased/improvements/2475-blocksync-sort-peers.md new file mode 100644 index 00000000000..5c544401ba6 --- /dev/null +++ b/.changelog/unreleased/improvements/2475-blocksync-sort-peers.md @@ -0,0 +1,2 @@ +- `[blocksync]` Sort peers by download rate (the fastest peer is picked first) + [\#2475](https://github.com/cometbft/cometbft/pull/2475) diff --git a/blocksync/pool.go b/blocksync/pool.go index 9e6d7fe2998..11e4c9201aa 100644 --- a/blocksync/pool.go +++ b/blocksync/pool.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "sort" "sync/atomic" "time" @@ -36,9 +37,20 @@ const ( // enough. If a peer is not sending us data at at least that rate, we // consider them to have timedout and we disconnect. // - // Assuming a DSL connection (not a good choice) 128 Kbps (upload) ~ 15 KB/s, - // sending data across atlantic ~ 7.5 KB/s. - minRecvRate = 7680 + // Based on the experiments with [Osmosis](https://osmosis.zone/), the + // minimum rate could be as high as 500 KB/s. However, we're setting it to + // 128 KB/s for now to be conservative. + minRecvRate = 128 * 1024 // 128 KB/s + + // peerConnWait is the time that must have elapsed since the pool routine + // was created before we start making requests. This is to give the peer + // routine time to connect to peers. + peerConnWait = 3 * time.Second + + // If we're within minBlocksForSingleRequest blocks of the pool's height, we + // send 2 parallel requests to 2 peers for the same block. If we're further + // away, we send a single request. + minBlocksForSingleRequest = 50 ) var peerTimeout = 15 * time.Second // not const so we can override with tests @@ -57,7 +69,8 @@ var peerTimeout = 15 * time.Second // not const so we can override with tests // BlockPool keeps track of the block sync peers, block requests and block responses. type BlockPool struct { service.BaseService - startTime time.Time + startTime time.Time + startHeight int64 mtx cmtsync.Mutex // block requests @@ -65,7 +78,8 @@ type BlockPool struct { height int64 // the lowest key in requesters. // peers peers map[p2p.ID]*bpPeer - maxPeerHeight int64 // the biggest reported height + sortedPeers []*bpPeer // sorted by curRate, highest first + maxPeerHeight int64 // the biggest reported height // atomic numPending int32 // number of requests pending assignment or block response @@ -80,9 +94,10 @@ func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- p bp := &BlockPool{ peers: make(map[p2p.ID]*bpPeer), - requesters: make(map[int64]*bpRequester), - height: start, - numPending: 0, + requesters: make(map[int64]*bpRequester), + height: start, + startHeight: start, + numPending: 0, requestsCh: requestsCh, errorsCh: errorsCh, @@ -94,8 +109,8 @@ func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- p // OnStart implements service.Service by spawning requesters routine and recording // pool's start time. func (pool *BlockPool) OnStart() error { - go pool.makeRequestersRoutine() pool.startTime = time.Now() + go pool.makeRequestersRoutine() return nil } @@ -106,6 +121,14 @@ func (pool *BlockPool) makeRequestersRoutine() { return } + // Check if we are within peerConnWait seconds of start time + // This gives us some time to connect to peers before starting a wave of requests + if time.Since(pool.startTime) < peerConnWait { + // Calculate the duration to sleep until peerConnWait seconds have passed since pool.startTime + sleepDuration := peerConnWait - time.Since(pool.startTime) + time.Sleep(sleepDuration) + } + pool.mtx.Lock() var ( maxRequestersCreated = len(pool.requesters) >= len(pool.peers)*maxPendingRequestsPerPeer @@ -122,8 +145,9 @@ func (pool *BlockPool) makeRequestersRoutine() { case maxPeerHeightReached: // If we're caught up, wait for a bit so reactor could finish or a higher height is reported. time.Sleep(requestIntervalMS * time.Millisecond) default: - // request for more blocks. pool.makeNextRequester(nextHeight) + // Sleep for a bit to make the requests more ordered. + time.Sleep(requestIntervalMS * time.Millisecond) } } } @@ -145,11 +169,16 @@ func (pool *BlockPool) removeTimedoutPeers() { "minRate", fmt.Sprintf("%d KB/s", minRecvRate/1024)) peer.didTimeout = true } + + peer.curRate = curRate } + if peer.didTimeout { pool.removePeer(peer.id) } } + + pool.sortPeers() } // GetStatus returns pool's height, numPending requests and the number of @@ -201,45 +230,69 @@ func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) return } -// PopRequest pops the first block at pool.height. -// It must have been validated by 'second'.Commit from PeekTwoBlocks(). +// PopRequest removes the requester at pool.height and increments pool.height. func (pool *BlockPool) PopRequest() { pool.mtx.Lock() defer pool.mtx.Unlock() - if r := pool.requesters[pool.height]; r != nil { - /* The block can disappear at any time, due to removePeer(). - if r := pool.requesters[pool.height]; r == nil || r.block == nil { - PanicSanity("PopRequest() requires a valid block") - } - */ - if err := r.Stop(); err != nil { - pool.Logger.Error("Error stopping requester", "err", err) - } - delete(pool.requesters, pool.height) - pool.height++ - } else { + r := pool.requesters[pool.height] + if r == nil { panic(fmt.Sprintf("Expected requester to pop, got nothing at height %v", pool.height)) } + + if err := r.Stop(); err != nil { + pool.Logger.Error("Error stopping requester", "err", err) + } + delete(pool.requesters, pool.height) + pool.height++ + + // Notify the next minBlocksForSingleRequest requesters about new height, so + // they can potentially request a block from the second peer. + for i := int64(0); i < minBlocksForSingleRequest && i < int64(len(pool.requesters)); i++ { + pool.requesters[pool.height+i].newHeight(pool.height) + } } -// RedoRequest invalidates the block at pool.height, -// Remove the peer and redo request from others. +// RemovePeerAndRedoAllPeerRequests retries the request at the given height and +// all the requests made to the same peer. The peer is removed from the pool. // Returns the ID of the removed peer. -func (pool *BlockPool) RedoRequest(height int64) p2p.ID { +func (pool *BlockPool) RemovePeerAndRedoAllPeerRequests(height int64) p2p.ID { pool.mtx.Lock() defer pool.mtx.Unlock() request := pool.requesters[height] - peerID := request.getPeerID() - if peerID != p2p.ID("") { - // RemovePeer will redo all requesters associated with this peer. - pool.removePeer(peerID) - } + peerID := request.gotBlockFromPeerID() + // RemovePeer will redo all requesters associated with this peer. + pool.removePeer(peerID) return peerID } -// AddBlock validates that the block comes from the peer it was expected from and calls the requester to store it. +// RedoRequestFrom retries the request at the given height. It does not remove the +// peer. +func (pool *BlockPool) RedoRequestFrom(height int64, peerID p2p.ID) { + pool.mtx.Lock() + defer pool.mtx.Unlock() + + if requester, ok := pool.requesters[height]; ok { // If we requested this block + if requester.didRequestFrom(peerID) { // From this specific peer + requester.redo(peerID) + } + } +} + +// Deprecated: use RemovePeerAndRedoAllPeerRequests instead. +func (pool *BlockPool) RedoRequest(height int64) p2p.ID { + return pool.RemovePeerAndRedoAllPeerRequests(height) +} + +// AddBlock validates that the block comes from the peer it was expected from +// and calls the requester to store it. +// +// This requires an extended commit at the same height as the supplied block - +// the block contains the last commit, but we need the latest commit in case we +// need to switch over from block sync to consensus at this height. If the +// height of the extended commit and the height of the block do not match, we +// do not add the block and return an error. // TODO: ensure that blocks come in order for each peer. func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) { pool.mtx.Lock() @@ -247,37 +300,44 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int requester := pool.requesters[block.Height] if requester == nil { - pool.Logger.Info( - "peer sent us a block we didn't expect", - "peer", - peerID, - "curHeight", - pool.height, - "blockHeight", - block.Height) - diff := pool.height - block.Height - if diff < 0 { - diff *= -1 - } - const maxDiff = 100 // maximum difference between current and received block height - if diff > maxDiff { - pool.sendError(errors.New("peer sent us a block we didn't expect with a height too far ahead/behind"), peerID) + // Because we're issuing 2nd requests for closer blocks, it's possible to + // receive a block we've already processed from a second peer. Hence, we + // can't punish it. But if the peer sent us a block we clearly didn't + // request, we disconnect. + if block.Height > pool.height || block.Height < pool.startHeight { + err := fmt.Errorf("peer sent us block #%d we didn't expect (current height: %d, start height: %d)", + block.Height, pool.height, pool.startHeight) + pool.sendError(err, peerID) + pool.Logger.Error("failed to add block", "peer", peerID, "err", err) + return } + + err := fmt.Errorf("got an already committed block #%d (possibly from the slow peer %s)", block.Height, peerID) + pool.Logger.Error("failed to add block", "peer", peerID, "err", err) return } - if requester.setBlock(block, peerID) { - atomic.AddInt32(&pool.numPending, -1) - peer := pool.peers[peerID] - if peer != nil { - peer.decrPending(blockSize) - } - } else { - pool.Logger.Info("invalid peer", "peer", peerID, "blockHeight", block.Height) - pool.sendError(errors.New("invalid peer"), peerID) + if !requester.setBlock(block, peerID) { + err := fmt.Errorf("requested block #%d from %v, not %s", block.Height, requester.requestedFrom(), peerID) + pool.sendError(err, peerID) + pool.Logger.Error("failed to add block", "peer", peerID, "err", err) + return + } + + atomic.AddInt32(&pool.numPending, -1) + peer := pool.peers[peerID] + if peer != nil { + peer.decrPending(blockSize) } } +// Height returns the pool's height. +func (pool *BlockPool) Height() int64 { + pool.mtx.Lock() + defer pool.mtx.Unlock() + return pool.height +} + // MaxPeerHeight returns the highest reported height. func (pool *BlockPool) MaxPeerHeight() int64 { pool.mtx.Lock() @@ -298,6 +358,9 @@ func (pool *BlockPool) SetPeerRange(peerID p2p.ID, base int64, height int64) { peer = newBPPeer(pool, peerID, base, height) peer.setLogger(pool.Logger.With("peer", peerID)) pool.peers[peerID] = peer + // no need to sort because curRate is 0 at start. + // just add to the beginning so it's picked first by pickIncrAvailablePeer. + pool.sortedPeers = append([]*bpPeer{peer}, pool.sortedPeers...) } if height > pool.maxPeerHeight { @@ -316,7 +379,7 @@ func (pool *BlockPool) RemovePeer(peerID p2p.ID) { func (pool *BlockPool) removePeer(peerID p2p.ID) { for _, requester := range pool.requesters { - if requester.getPeerID() == peerID { + if requester.didRequestFrom(peerID) { requester.redo(peerID) } } @@ -328,6 +391,12 @@ func (pool *BlockPool) removePeer(peerID p2p.ID) { } delete(pool.peers, peerID) + for i, p := range pool.sortedPeers { + if p.id == peerID { + pool.sortedPeers = append(pool.sortedPeers[:i], pool.sortedPeers[i+1:]...) + break + } + } // Find a new peer with the biggest height and update maxPeerHeight if the // peer's height was the biggest. @@ -350,11 +419,14 @@ func (pool *BlockPool) updateMaxPeerHeight() { // Pick an available peer with the given height available. // If no peers are available, returns nil. -func (pool *BlockPool) pickIncrAvailablePeer(height int64) *bpPeer { +func (pool *BlockPool) pickIncrAvailablePeer(height int64, excludePeerID p2p.ID) *bpPeer { pool.mtx.Lock() defer pool.mtx.Unlock() - for _, peer := range pool.peers { + for _, peer := range pool.sortedPeers { + if peer.id == excludePeerID { + continue + } if peer.didTimeout { pool.removePeer(peer.id) continue @@ -368,9 +440,19 @@ func (pool *BlockPool) pickIncrAvailablePeer(height int64) *bpPeer { peer.incrPending() return peer } + return nil } +// Sort peers by curRate, highest first. +// +// CONTRACT: pool.mtx must be locked. +func (pool *BlockPool) sortPeers() { + sort.Slice(pool.sortedPeers, func(i, j int) bool { + return pool.sortedPeers[i].curRate > pool.sortedPeers[j].curRate + }) +} + func (pool *BlockPool) makeNextRequester(nextHeight int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -423,6 +505,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { didTimeout bool + curRate int64 numPending int32 height int64 base int64 @@ -495,27 +578,41 @@ func (peer *bpPeer) onTimeout() { //------------------------------------- +// bpRequester requests a block from a peer. +// +// If the height is within minBlocksForSingleRequest blocks of the pool's +// height, it will send an additional request to another peer. This is to avoid +// a situation where blocksync is stuck because of a single slow peer. Note +// that it's okay to send a single request when the requested height is far +// from the pool's height. If the peer is slow, it will timeout and be replaced +// with another peer. type bpRequester struct { service.BaseService - pool *BlockPool - height int64 - gotBlockCh chan struct{} - redoCh chan p2p.ID // redo may send multitime, add peerId to identify repeat - mtx cmtsync.Mutex - peerID p2p.ID - block *types.Block + pool *BlockPool + height int64 + gotBlockCh chan struct{} + redoCh chan p2p.ID // redo may got multiple messages, add peerId to identify repeat + newHeightCh chan int64 + + mtx cmtsync.Mutex + peerID p2p.ID + secondPeerID p2p.ID // alternative peer to request from (if close to pool's height) + gotBlockFrom p2p.ID + block *types.Block } func newBPRequester(pool *BlockPool, height int64) *bpRequester { bpr := &bpRequester{ - pool: pool, - height: height, - gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan p2p.ID, 1), - - peerID: "", - block: nil, + pool: pool, + height: height, + gotBlockCh: make(chan struct{}, 1), + redoCh: make(chan p2p.ID, 1), + newHeightCh: make(chan int64, 1), + + peerID: "", + secondPeerID: "", + block: nil, } bpr.BaseService = *service.NewBaseService(nil, "bpRequester", bpr) return bpr @@ -526,14 +623,20 @@ func (bpr *bpRequester) OnStart() error { return nil } -// Returns true if the peer matches and block doesn't already exist. +// Returns true if the peer(s) match and block doesn't already exist. func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool { bpr.mtx.Lock() - if bpr.block != nil || bpr.peerID != peerID { + if bpr.peerID != peerID && bpr.secondPeerID != peerID { bpr.mtx.Unlock() return false } + if bpr.block != nil { + bpr.mtx.Unlock() + return true // getting a block from both peers is not an error + } + bpr.block = block + bpr.gotBlockFrom = peerID bpr.mtx.Unlock() select { @@ -549,23 +652,54 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() p2p.ID { +// Returns the IDs of peers we've requested a block from. +func (bpr *bpRequester) requestedFrom() []p2p.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() - return bpr.peerID + peerIDs := make([]p2p.ID, 0, 2) + if bpr.peerID != "" { + peerIDs = append(peerIDs, bpr.peerID) + } + if bpr.secondPeerID != "" { + peerIDs = append(peerIDs, bpr.secondPeerID) + } + return peerIDs } -// This is called from the requestRoutine, upon redo(). -func (bpr *bpRequester) reset() { +// Returns true if we've requested a block from the given peer. +func (bpr *bpRequester) didRequestFrom(peerID p2p.ID) bool { bpr.mtx.Lock() defer bpr.mtx.Unlock() + return bpr.peerID == peerID || bpr.secondPeerID == peerID +} - if bpr.block != nil { +// Returns the ID of the peer who sent us the block. +func (bpr *bpRequester) gotBlockFromPeerID() p2p.ID { + bpr.mtx.Lock() + defer bpr.mtx.Unlock() + return bpr.gotBlockFrom +} + +// Removes the block (IF we got it from the given peer) and resets the peer. +func (bpr *bpRequester) reset(peerID p2p.ID) (removedBlock bool) { + bpr.mtx.Lock() + defer bpr.mtx.Unlock() + + // Only remove the block if we got it from that peer. + if bpr.gotBlockFrom == peerID { + bpr.block = nil + bpr.gotBlockFrom = "" + removedBlock = true atomic.AddInt32(&bpr.pool.numPending, 1) } - bpr.peerID = "" - bpr.block = nil + if bpr.peerID == peerID { + bpr.peerID = "" + } else { + bpr.secondPeerID = "" + } + + return removedBlock } // Tells bpRequester to pick another peer and try again. @@ -578,34 +712,75 @@ func (bpr *bpRequester) redo(peerID p2p.ID) { } } +func (bpr *bpRequester) pickPeerAndSendRequest() { + bpr.mtx.Lock() + secondPeerID := bpr.secondPeerID + bpr.mtx.Unlock() + + var peer *bpPeer +PICK_PEER_LOOP: + for { + if !bpr.IsRunning() || !bpr.pool.IsRunning() { + return + } + peer = bpr.pool.pickIncrAvailablePeer(bpr.height, secondPeerID) + if peer == nil { + bpr.Logger.Debug("No peers currently available; will retry shortly", "height", bpr.height) + time.Sleep(requestIntervalMS * time.Millisecond) + continue PICK_PEER_LOOP + } + break PICK_PEER_LOOP + } + bpr.mtx.Lock() + bpr.peerID = peer.id + bpr.mtx.Unlock() + + bpr.pool.sendRequest(bpr.height, peer.id) +} + +// Picks a second peer and sends a request to it. If the second peer is already +// set, does nothing. +func (bpr *bpRequester) pickSecondPeerAndSendRequest() { + bpr.mtx.Lock() + if bpr.secondPeerID != "" { + bpr.mtx.Unlock() + return + } + peerID := bpr.peerID + bpr.mtx.Unlock() + + secondPeer := bpr.pool.pickIncrAvailablePeer(bpr.height, peerID) + if secondPeer != nil { + bpr.mtx.Lock() + bpr.secondPeerID = secondPeer.id + bpr.mtx.Unlock() + + bpr.pool.sendRequest(bpr.height, secondPeer.id) + } +} + +// Informs the requester of a new pool's height. +func (bpr *bpRequester) newHeight(height int64) { + select { + case bpr.newHeightCh <- height: + default: + } +} + // Responsible for making more requests as necessary // Returns only when a block is found (e.g. AddBlock() is called) func (bpr *bpRequester) requestRoutine() { + gotBlock := false + OUTER_LOOP: for { - // Pick a peer to send request to. - var peer *bpPeer - PICK_PEER_LOOP: - for { - if !bpr.IsRunning() || !bpr.pool.IsRunning() { - return - } - peer = bpr.pool.pickIncrAvailablePeer(bpr.height) - if peer == nil { - bpr.Logger.Debug("No peers currently available; will retry shortly", "height", bpr.height) - time.Sleep(requestIntervalMS * time.Millisecond) - continue PICK_PEER_LOOP - } - break PICK_PEER_LOOP + bpr.pickPeerAndSendRequest() + + poolHeight := bpr.pool.Height() + if bpr.height-poolHeight < minBlocksForSingleRequest { + bpr.pickSecondPeerAndSendRequest() } - bpr.mtx.Lock() - bpr.peerID = peer.id - bpr.mtx.Unlock() - to := time.NewTimer(requestRetrySeconds * time.Second) - // Send request and wait. - bpr.pool.sendRequest(bpr.height, peer.id) - WAIT_LOOP: for { select { case <-bpr.pool.Quit(): @@ -615,22 +790,34 @@ OUTER_LOOP: return case <-bpr.Quit(): return - case <-to.C: - bpr.Logger.Debug("Retrying block request after timeout", "height", bpr.height, "peer", bpr.peerID) - // Simulate a redo - bpr.reset() - continue OUTER_LOOP + case <-time.After(requestRetrySeconds * time.Second): + if !gotBlock { + bpr.Logger.Debug("Retrying block request(s) after timeout", "height", bpr.height, "peer", bpr.peerID, "secondPeerID", bpr.secondPeerID) + bpr.reset(bpr.peerID) + bpr.reset(bpr.secondPeerID) + continue OUTER_LOOP + } case peerID := <-bpr.redoCh: - if peerID == bpr.peerID { - bpr.reset() + if bpr.didRequestFrom(peerID) { + removedBlock := bpr.reset(peerID) + if removedBlock { + gotBlock = false + } + } + // If both peers returned NoBlockResponse or bad block, reschedule both + // requests. If not, wait for the other peer. + if len(bpr.requestedFrom()) == 0 { continue OUTER_LOOP - } else { - continue WAIT_LOOP + } + case newHeight := <-bpr.newHeightCh: + if !gotBlock && bpr.height-newHeight < minBlocksForSingleRequest { + // The operation is a noop if the second peer is already set. The cost is checking a mutex. + bpr.pickSecondPeerAndSendRequest() } case <-bpr.gotBlockCh: + gotBlock = true // We got a block! // Continue the for-loop and wait til Quit. - continue WAIT_LOOP } } } diff --git a/blocksync/reactor.go b/blocksync/reactor.go index 32f8cc09159..761e3946ac7 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -247,6 +247,7 @@ func (bcR *Reactor) ReceiveEnvelope(e p2p.Envelope) { bcR.pool.SetPeerRange(e.Src.ID(), msg.Base, msg.Height) case *bcproto.NoBlockResponse: bcR.Logger.Debug("Peer does not have requested block", "peer", e.Src, "height", msg.Height) + bcR.pool.RedoRequestFrom(msg.Height, e.Src.ID()) default: bcR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } @@ -381,14 +382,14 @@ FOR_LOOP: if err != nil { bcR.Logger.Error("Error in validation", "err", err) - peerID := bcR.pool.RedoRequest(first.Height) + peerID := bcR.pool.RemovePeerAndRedoAllPeerRequests(first.Height) peer := bcR.Switch.Peers().Get(peerID) if peer != nil { // NOTE: we've already removed the peer's request, but we // still need to clean up the rest. bcR.Switch.StopPeerForError(peer, fmt.Errorf("Reactor validation error: %v", err)) } - peerID2 := bcR.pool.RedoRequest(second.Height) + peerID2 := bcR.pool.RemovePeerAndRedoAllPeerRequests(second.Height) peer2 := bcR.Switch.Peers().Get(peerID2) if peer2 != nil && peer2 != peer { // NOTE: we've already removed the peer's request, but we From 17419f9e6ef51b888d76c17792b6f14c6783307a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:42:25 +0800 Subject: [PATCH 86/87] fix(blocksync): use timer instead of time.After (backport #2584) (#2588) This is an automatic backport of pull request #2584 done by [Mergify](https://mergify.com). ---
Mergify commands and options
More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport ` will backport this PR on `` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com
Co-authored-by: Anton Kaliaev --- blocksync/pool.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/blocksync/pool.go b/blocksync/pool.go index 11e4c9201aa..01a1a211d5f 100644 --- a/blocksync/pool.go +++ b/blocksync/pool.go @@ -740,11 +740,11 @@ PICK_PEER_LOOP: // Picks a second peer and sends a request to it. If the second peer is already // set, does nothing. -func (bpr *bpRequester) pickSecondPeerAndSendRequest() { +func (bpr *bpRequester) pickSecondPeerAndSendRequest() (picked bool) { bpr.mtx.Lock() if bpr.secondPeerID != "" { bpr.mtx.Unlock() - return + return false } peerID := bpr.peerID bpr.mtx.Unlock() @@ -756,7 +756,10 @@ func (bpr *bpRequester) pickSecondPeerAndSendRequest() { bpr.mtx.Unlock() bpr.pool.sendRequest(bpr.height, secondPeer.id) + return true } + + return false } // Informs the requester of a new pool's height. @@ -781,6 +784,9 @@ OUTER_LOOP: bpr.pickSecondPeerAndSendRequest() } + retryTimer := time.NewTimer(requestRetrySeconds * time.Second) + defer retryTimer.Stop() + for { select { case <-bpr.pool.Quit(): @@ -790,7 +796,7 @@ OUTER_LOOP: return case <-bpr.Quit(): return - case <-time.After(requestRetrySeconds * time.Second): + case <-retryTimer.C: if !gotBlock { bpr.Logger.Debug("Retrying block request(s) after timeout", "height", bpr.height, "peer", bpr.peerID, "secondPeerID", bpr.secondPeerID) bpr.reset(bpr.peerID) @@ -807,12 +813,21 @@ OUTER_LOOP: // If both peers returned NoBlockResponse or bad block, reschedule both // requests. If not, wait for the other peer. if len(bpr.requestedFrom()) == 0 { + retryTimer.Stop() continue OUTER_LOOP } case newHeight := <-bpr.newHeightCh: if !gotBlock && bpr.height-newHeight < minBlocksForSingleRequest { // The operation is a noop if the second peer is already set. The cost is checking a mutex. - bpr.pickSecondPeerAndSendRequest() + // + // If the second peer was just set, reset the retryTimer to give the + // second peer a chance to respond. + if picked := bpr.pickSecondPeerAndSendRequest(); picked { + if !retryTimer.Stop() { + <-retryTimer.C + } + retryTimer.Reset(requestRetrySeconds * time.Second) + } } case <-bpr.gotBlockCh: gotBlock = true From 07493f4a8d706aeaee244c78568b895eb7c18d14 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 13 Mar 2024 13:18:37 +0800 Subject: [PATCH 87/87] Release v0.37.5 (#2591) [CHANGELOG](https://github.com/cometbft/cometbft/blob/4b09c0dd417988ca2ef4f1604650e5db54ce9572/CHANGELOG.md#v0375) --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments - [ ] Title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) spec --- .../1687-consensus-fix-block-validation.md | 0 ...749-light-client-attack-verify-all-sigs.md | 0 .../1715-validate-validator-address.md} | 0 ...increase-abci-socket-message-size-limit.md | 0 .../2467-decrease-n-of-requested-blocks.md | 0 .../2475-blocksync-2nd-request.md | 0 .../2475-blocksync-no-block-response.md | 0 .../improvements/2475-blocksync-sort-peers.md | 0 .changelog/v0.37.5/summary.md | 5 +++ CHANGELOG.md | 40 +++++++++++++++++-- version/version.go | 2 +- 11 files changed, 42 insertions(+), 5 deletions(-) rename .changelog/{unreleased => v0.37.5}/bug-fixes/1687-consensus-fix-block-validation.md (100%) rename .changelog/{unreleased => v0.37.5}/bug-fixes/1749-light-client-attack-verify-all-sigs.md (100%) rename .changelog/{unreleased/improvements/1715-validate-validator-address => v0.37.5/improvements/1715-validate-validator-address.md} (100%) rename .changelog/{unreleased => v0.37.5}/improvements/1730-increase-abci-socket-message-size-limit.md (100%) rename .changelog/{unreleased => v0.37.5}/improvements/2467-decrease-n-of-requested-blocks.md (100%) rename .changelog/{unreleased => v0.37.5}/improvements/2475-blocksync-2nd-request.md (100%) rename .changelog/{unreleased => v0.37.5}/improvements/2475-blocksync-no-block-response.md (100%) rename .changelog/{unreleased => v0.37.5}/improvements/2475-blocksync-sort-peers.md (100%) create mode 100644 .changelog/v0.37.5/summary.md diff --git a/.changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md b/.changelog/v0.37.5/bug-fixes/1687-consensus-fix-block-validation.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1687-consensus-fix-block-validation.md rename to .changelog/v0.37.5/bug-fixes/1687-consensus-fix-block-validation.md diff --git a/.changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md b/.changelog/v0.37.5/bug-fixes/1749-light-client-attack-verify-all-sigs.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1749-light-client-attack-verify-all-sigs.md rename to .changelog/v0.37.5/bug-fixes/1749-light-client-attack-verify-all-sigs.md diff --git a/.changelog/unreleased/improvements/1715-validate-validator-address b/.changelog/v0.37.5/improvements/1715-validate-validator-address.md similarity index 100% rename from .changelog/unreleased/improvements/1715-validate-validator-address rename to .changelog/v0.37.5/improvements/1715-validate-validator-address.md diff --git a/.changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md b/.changelog/v0.37.5/improvements/1730-increase-abci-socket-message-size-limit.md similarity index 100% rename from .changelog/unreleased/improvements/1730-increase-abci-socket-message-size-limit.md rename to .changelog/v0.37.5/improvements/1730-increase-abci-socket-message-size-limit.md diff --git a/.changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md b/.changelog/v0.37.5/improvements/2467-decrease-n-of-requested-blocks.md similarity index 100% rename from .changelog/unreleased/improvements/2467-decrease-n-of-requested-blocks.md rename to .changelog/v0.37.5/improvements/2467-decrease-n-of-requested-blocks.md diff --git a/.changelog/unreleased/improvements/2475-blocksync-2nd-request.md b/.changelog/v0.37.5/improvements/2475-blocksync-2nd-request.md similarity index 100% rename from .changelog/unreleased/improvements/2475-blocksync-2nd-request.md rename to .changelog/v0.37.5/improvements/2475-blocksync-2nd-request.md diff --git a/.changelog/unreleased/improvements/2475-blocksync-no-block-response.md b/.changelog/v0.37.5/improvements/2475-blocksync-no-block-response.md similarity index 100% rename from .changelog/unreleased/improvements/2475-blocksync-no-block-response.md rename to .changelog/v0.37.5/improvements/2475-blocksync-no-block-response.md diff --git a/.changelog/unreleased/improvements/2475-blocksync-sort-peers.md b/.changelog/v0.37.5/improvements/2475-blocksync-sort-peers.md similarity index 100% rename from .changelog/unreleased/improvements/2475-blocksync-sort-peers.md rename to .changelog/v0.37.5/improvements/2475-blocksync-sort-peers.md diff --git a/.changelog/v0.37.5/summary.md b/.changelog/v0.37.5/summary.md new file mode 100644 index 00000000000..3e31f8c58c3 --- /dev/null +++ b/.changelog/v0.37.5/summary.md @@ -0,0 +1,5 @@ +*March 12, 2024* + +This release fixes a security bug in the light client. It also introduces many +improvements to the block sync in collaboration with the +[Osmosis](https://osmosis.zone/) team. diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1eac434dc..a1f219ecc34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # CHANGELOG +## v0.37.5 + +*March 12, 2024* + +This release fixes a security bug in the light client. It also introduces many +improvements to the block sync in collaboration with the +[Osmosis](https://osmosis.zone/) team. + +### BUG FIXES + +- `[mempool]` The calculation method of tx size returned by calling proxyapp should be consistent with that of mempool + ([\#1687](https://github.com/cometbft/cometbft/pull/1687)) +- `[evidence]` When `VerifyCommitLight` & `VerifyCommitLightTrusting` are called as part + of evidence verification, all signatures present in the evidence must be verified + ([\#1749](https://github.com/cometbft/cometbft/pull/1749)) + +### IMPROVEMENTS + +- `[types]` Validate `Validator#Address` in `ValidateBasic` ([\#1715](https://github.com/cometbft/cometbft/pull/1715)) +- `[abci]` Increase ABCI socket message size limit to 2GB ([\#1730](https://github.com/cometbft/cometbft/pull/1730): @troykessler) +- `[blocksync]` make the max number of downloaded blocks dynamic. + Previously it was a const 600. Now it's `peersCount * maxPendingRequestsPerPeer (20)` + [\#2467](https://github.com/cometbft/cometbft/pull/2467) +- `[blocksync]` Request a block from peer B if we are approaching pool's height + (less than 50 blocks) and the current peer A is slow in sending us the + block [\#2475](https://github.com/cometbft/cometbft/pull/2475) +- `[blocksync]` Request the block N from peer B immediately after getting + `NoBlockResponse` from peer A + [\#2475](https://github.com/cometbft/cometbft/pull/2475) +- `[blocksync]` Sort peers by download rate (the fastest peer is picked first) + [\#2475](https://github.com/cometbft/cometbft/pull/2475) + ## v0.37.4 *November 27, 2023* @@ -87,14 +119,14 @@ security issues. ### BUG FIXES -- `[pubsub]` Pubsub queries are now able to parse big integers (larger than - int64). Very big floats are also properly parsed into very big integers - instead of being truncated to int64. - ([\#771](https://github.com/cometbft/cometbft/pull/771)) - `[state/kvindex]` Querying event attributes that are bigger than int64 is now enabled. We are not supporting reading floats from the db into the indexer nor parsing them into BigFloats to not introduce breaking changes in minor releases. ([\#771](https://github.com/cometbft/cometbft/pull/771)) +- `[pubsub]` Pubsub queries are now able to parse big integers (larger than + int64). Very big floats are also properly parsed into very big integers + instead of being truncated to int64. + ([\#771](https://github.com/cometbft/cometbft/pull/771)) ### IMPROVEMENTS diff --git a/version/version.go b/version/version.go index 9b2a4974dde..e6a7d8adb91 100644 --- a/version/version.go +++ b/version/version.go @@ -5,7 +5,7 @@ const ( // The default version of TMCoreSemVer is the value used as the // fallback version of CometBFT when not using git describe. // It is formatted with semantic versioning. - TMCoreSemVer = "0.37.4" + TMCoreSemVer = "0.37.5" // ABCISemVer is the semantic version of the ABCI protocol ABCISemVer = "1.0.0" ABCIVersion = ABCISemVer
Release notes

Sourced from docker/setup-buildx-action's releases.

v3.1.0

Full Changelog: https://github.com/docker/setup-buildx-action/compare/v3.0.0...v3.1.0