Compare commits

...

11 Commits

Author SHA1 Message Date
Jae Kwon
66ac0bf8b0 fix inaccuracies (#10144) 2025-03-06 15:39:57 -08:00
Callum Waters
85a584dd2e add retraction notice for v0.35 (#9308) 2022-08-24 15:57:05 +02:00
Daniel
d0c3457343 Documentation of v0.35 p2p layer: peer manager (#8982)
* Doc: documentation of new p2p layer, first commit

* Doc: p2p peer manager abstraction, first commit

* Doc: life cycle of a peer, first part

* Doc: life cycle of a p2p peer, picture added

* typos

* Doc: life cycle of a p2p peer picture updated

* Doc: life cycle of a p2p peer section refactored

* Doc: p2p connection policy and connection slots

* Doc: peer manager defines the connection policy

* Doc: peer manager connection slots upgrading

* Doc: peer manager eviction procedure introduced

* Doc: several corrections in peer manager documentation

* Doc: peer ranking mechanism documented

* Doc: EvictNext peer manager transition documented

* Doc: concept of candidate peer added to peer manager

* Doc: peer manager documentation, aesthetic changes

* Apply suggestions from code review (again)

Co-authored-by: Sergio Mena <sergio@informal.systems>

* Spec of v0.35 p2p layer moved to spec/p2p/v0.35

* Spec: p2p markdown links fixed

* Spec: addressing more issues on peer manager spec

* Spec: p2p peer manager DialNext algorithm

* Spec: p2p peer manager Dial and Accepted algorithms

* Spec: p2p router dialing peers thread

* Spec: p2p router accept peers threads

* Spec: p2p router evict peers routine

* Spec: p2p router routing messages routines

* Spec: p2p v0.35 readme points to other documents

* Spec: fixing markdown links

* Apply suggestions from Josef's code review

* They state that this is a work in progress, that has been interrupted to focus on the specification of the p2p layer adopted by Tendermint v0.34.

Co-authored-by: Josef Widder <44643235+josef-widder@users.noreply.github.com>

* Spc: p2p v0.35 spec mentions new p2p layer

Co-authored-by: Jasmina Malicevic <jasmina.dustinac@gmail.com>
Co-authored-by: Sergio Mena <sergio@informal.systems>
Co-authored-by: Josef Widder <44643235+josef-widder@users.noreply.github.com>
Co-authored-by: Daniel Cason <daniel.cason@informal.systems>
2022-08-08 11:40:50 +02:00
Mark Rushakoff
d433ebe68d Improve handling of -short flag in tests (#9075)
As a small developer quality of life improvement, I found many individual unit tests that take longer than around a second to complete, and set them to skip when run under `go test -short`.

On my machine, the wall timings for tests (with `go test -count=1 ./...` and optionally `-short` and `-race`) are roughly:

- Long tests, no race detector: about 1m42s
- Short tests, no race detector: about 17s
- Long tests, race detector enabled: about 2m1s
- Short tests, race detector enabled: about 28s

This PR is split into many commits each touching a single package, with commit messages detailing the approximate timing change per package.
2022-07-29 13:41:54 +00:00
Sam Kleinman
48147e1fb9 logging: implement lazy sprinting (#8898)
shout out to @joeabbey for the inspiration. This makes the lazy
functions internal by default to prevent potential misuse by external
callers.

Should backport cleanly into 0.36 and I'll handle a messy merge into 0.35
2022-07-27 19:16:51 +00:00
William Banfield
b9d6bb4cd1 RELEASES: add a set of pre-release steps for Tendermint versions (#8786)
{[rendered](https://github.com/tendermint/tendermint/blob/wb/release-document/RELEASES.md)}

This pull request adds a proposed set of steps to perform before each Tendermint minor version release. This represents an initial set of ideas that derives from conversations among members of the Tendermint team. If you have additional suggestions for pre-release steps, please leave a comment explaining the specific suggestion and detailing how it would help build confidence in the quality of the release of Tendermint.
2022-07-27 15:03:36 +00:00
Callum Waters
7a84425aec update dependabot frequencies (#9087) 2022-07-26 12:05:48 +02:00
dependabot[bot]
488e1d4bd2 build(deps): Bump github.com/creachadair/tomledit from 0.0.22 to 0.0.23 (#9084)
Bumps [github.com/creachadair/tomledit](https://github.com/creachadair/tomledit) from 0.0.22 to 0.0.23.
<details>
<summary>Commits</summary>
<ul>
<li><a href="e7b6512f75"><code>e7b6512</code></a> Release v0.0.23.</li>
<li><a href="fdb62db21d"><code>fdb62db</code></a> Add tomldiff command-line tool.</li>
<li><a href="ce86b667b5"><code>ce86b66</code></a> Fix a comment typo.</li>
<li><a href="3834adf9ee"><code>3834adf</code></a> Fix documentation.</li>
<li><a href="525c9c12f6"><code>525c9c1</code></a> cli: escape as basic strings by default</li>
<li>See full diff in <a href="https://github.com/creachadair/tomledit/compare/v0.0.22...v0.0.23">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/creachadair/tomledit&package-manager=go_modules&previous-version=0.0.22&new-version=0.0.23)](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)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2022-07-25 12:53:08 +00:00
dependabot[bot]
89246e993a build(deps): Bump docker/build-push-action from 3.0.0 to 3.1.0 (#9082) 2022-07-25 12:07:29 +02:00
Steven Ferrer
6c302218e3 Documentation: update go tutorials (#9048) 2022-07-25 10:15:18 +02:00
dependabot[bot]
023c21f307 build(deps): Bump github.com/golangci/golangci-lint from 1.47.1 to 1.47.2 (#9070)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.47.1 to 1.47.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/golangci/golangci-lint/releases">github.com/golangci/golangci-lint's releases</a>.</em></p>
<blockquote>
<h2>v1.47.2</h2>
<h2>Changelog</h2>
<ul>
<li>61673b34 revive: ignore slow rules (<a href="https://github-redirect.dependabot.com/golangci/golangci-lint/issues/2999">#2999</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md">github.com/golangci/golangci-lint's changelog</a>.</em></p>
<blockquote>
<h3>v1.47.2</h3>
<ol>
<li>updated linters:
<ul>
<li><code>revive</code>: ignore slow rules</li>
</ul>
</li>
</ol>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="61673b3436"><code>61673b3</code></a> revive: ignore slow rules (<a href="https://github-redirect.dependabot.com/golangci/golangci-lint/issues/2999">#2999</a>)</li>
<li><a href="3fb60a3e8a"><code>3fb60a3</code></a> build(deps): bump terser from 5.12.0 to 5.14.2 in /docs (<a href="https://github-redirect.dependabot.com/golangci/golangci-lint/issues/2998">#2998</a>)</li>
<li><a href="dc0237854f"><code>dc02378</code></a> docs: Update documentation and assets (<a href="https://github-redirect.dependabot.com/golangci/golangci-lint/issues/2996">#2996</a>)</li>
<li>See full diff in <a href="https://github.com/golangci/golangci-lint/compare/v1.47.1...v1.47.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/golangci/golangci-lint&package-manager=go_modules&previous-version=1.47.1&new-version=1.47.2)](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)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2022-07-22 14:16:44 +00:00
63 changed files with 1603 additions and 538 deletions

View File

@@ -4,7 +4,6 @@ updates:
directory: "/"
schedule:
interval: weekly
day: monday
target-branch: "master"
open-pull-requests-limit: 10
labels:
@@ -15,7 +14,6 @@ updates:
directory: "/"
schedule:
interval: weekly
day: monday
target-branch: "v0.34.x"
open-pull-requests-limit: 10
labels:
@@ -26,7 +24,6 @@ updates:
directory: "/"
schedule:
interval: weekly
day: monday
target-branch: "v0.35.x"
open-pull-requests-limit: 10
labels:
@@ -37,7 +34,6 @@ updates:
directory: "/"
schedule:
interval: weekly
day: monday
target-branch: "v0.36.x"
open-pull-requests-limit: 10
labels:
@@ -48,7 +44,6 @@ updates:
directory: "/docs"
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 10
###################################
@@ -58,7 +53,7 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
interval: weekly
target-branch: "master"
open-pull-requests-limit: 10
labels:
@@ -68,7 +63,7 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
interval: weekly
target-branch: "v0.34.x"
open-pull-requests-limit: 10
labels:
@@ -78,7 +73,7 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
interval: weekly
target-branch: "v0.35.x"
open-pull-requests-limit: 10
labels:
@@ -88,7 +83,7 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
interval: weekly
target-branch: "v0.36.x"
open-pull-requests-limit: 10
labels:

View File

@@ -49,7 +49,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish to Docker Hub
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: .
file: ./DOCKER/Dockerfile

View File

@@ -28,7 +28,7 @@ jobs:
- name: Install generator dependencies
run: |
apk add --no-cache make bash git npm
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
# We need to fetch full history so the backport branches for previous
# versions will be available for the build.
@@ -37,7 +37,7 @@ jobs:
run: |
git config --global --add safe.directory "$PWD"
make build-docs
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: build-output
path: ~/output/
@@ -49,8 +49,8 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-output
path: ~/output

View File

@@ -224,7 +224,7 @@ build-docs:
@cd docs && \
while read -r branch path_prefix; do \
( git checkout $${branch} && npm ci --quiet && \
VUEPRESS_BASE="/$${path_prefix}/" npm run build --quiet ) ; \
VUEPRESS_BASE="/$${path_prefix}/" NODE_OPTIONS="--openssl-legacy-provider" npm run build --quiet ) ; \
mkdir -p ~/output/$${path_prefix} ; \
cp -r .vuepress/dist/* ~/output/$${path_prefix}/ ; \
cp ~/output/$${path_prefix}/index.html ~/output ; \

View File

@@ -36,20 +36,17 @@ Please do not depend on master as your production branch. Use [releases](https:/
Tendermint has been in the production of private and public environments, most notably the blockchains of the Cosmos Network. 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 Tendermint in production, we're happy to help. You can
contact us [over email](mailto:hello@interchain.io) or [join the chat](https://discord.gg/cosmosnetwork).
In any case, if you intend to run Tendermint in production, we're happy to help.
You can contact us [over email](mailto:hello@newtendermint.org) or [join the
chat](https://discord.gg/gnoland).
More on how releases are conducted can be found [here](./RELEASES.md).
## Security
To report a security vulnerability, see our [bug bounty
program](https://hackerone.com/cosmos).
To report a security vulnerability, please [email us](mailto:security@newtendermint.org).
For examples of the kinds of bugs we're looking for, see [our security policy](SECURITY.md).
We also maintain a dedicated mailing list for security updates. We will only ever use this mailing list
to notify you of vulnerabilities and fixes in Tendermint Core. You can subscribe [here](http://eepurl.com/gZ5hQD).
## Minimum requirements
| Requirement | Notes |
@@ -139,9 +136,9 @@ We keep a public up-to-date version of our roadmap [here](./docs/roadmap/roadmap
## Join us!
Tendermint Core is maintained by [Interchain GmbH](https://interchain.berlin).
If you'd like to work full-time on Tendermint Core, [we're hiring](https://interchain-gmbh.breezy.hr/)!
The development of Tendermint Core was led primarily by All in Bits, Inc. The
Tendermint trademark is owned by New Tendermint, LLC. If you'd like to work
full-time on Tendermint2 or [gno.land](https://gno.land), [we're
hiring](mailto:hiring@newtendermint.org)!
Funding for Tendermint Core development comes primarily from the [Interchain Foundation](https://interchain.io),
a Swiss non-profit. The Tendermint trademark is owned by [Tendermint Inc.](https://tendermint.com), the for-profit entity
that also maintains [tendermint.com](https://tendermint.com).

View File

@@ -1,8 +1,9 @@
# Releases
Tendermint uses [semantic versioning](https://semver.org/) with each release following
a `vX.Y.Z` format. The `master` branch is used for active development and thus it's
advisable not to build against it.
Tendermint uses modified [semantic versioning](https://semver.org/) with each
release following a `vX.Y.Z` format. Tendermint is currently on major version
0 and uses the minor version to signal breaking changes. The `master` branch is
used for active development and thus it is not advisable to build against it.
The latest changes are always initially merged into `master`.
Releases are specified using tags and are built from long-lived "backport" branches
@@ -29,8 +30,8 @@ merging the pull request.
### Creating a backport branch
If this is the first release candidate for a major release, you get to have the
honor of creating the backport branch!
If this is the first release candidate for a minor version release, e.g.
v0.25.0, you get to have the honor of creating the backport branch!
Note that, after creating the backport branch, you'll also need to update the
tags on `master` so that `go mod` is able to order the branches correctly. You
@@ -77,7 +78,8 @@ the 0.35.x line.
After doing these steps, go back to `master` and do the following:
1. Tag `master` as the dev branch for the _next_ major release and push it up to GitHub.
1. Tag `master` as the dev branch for the _next_ minor version release and push
it up to GitHub.
For example:
```sh
git tag -a v0.36.0-dev -m "Development base for Tendermint v0.36."
@@ -99,7 +101,7 @@ After doing these steps, go back to `master` and do the following:
## Release candidates
Before creating an official release, especially a major release, we may want to create a
Before creating an official release, especially a minor release, we may want to create a
release candidate (RC) for our friends and partners to test out. We use git tags to
create RCs, and we build them off of backport branches.
@@ -109,7 +111,7 @@ Tags for RCs should follow the "standard" release naming conventions, with `-rcX
(Note that branches and tags _cannot_ have the same names, so it's important that these branches
have distinct names from the tags/release names.)
If this is the first RC for a major release, you'll have to make a new backport branch (see above).
If this is the first RC for a minor release, you'll have to make a new backport branch (see above).
Otherwise:
1. Start from the backport branch (e.g. `v0.35.x`).
@@ -140,11 +142,13 @@ Note that this process should only be used for "true" RCs--
release candidates that, if successful, will be the next release.
For more experimental "RCs," create a new, short-lived branch and tag that instead.
## Major release
## Minor release
This major release process assumes that this release was preceded by release candidates.
This minor release process assumes that this release was preceded by release candidates.
If there were no release candidates, begin by creating a backport branch, as described above.
Before performing these steps, be sure the [Minor Release Checklist](#minor-release-checklist) has been completed.
1. Start on the backport branch (e.g. `v0.35.x`)
2. Run integration tests (`make test_integrations`) and the e2e nightlies.
3. Prepare the release:
@@ -176,16 +180,16 @@ If there were no release candidates, begin by creating a backport branch, as des
- Commit these changes to `master` and backport them into the backport
branch for this release.
## Minor release (point releases)
## Patch release
Minor releases are done differently from major releases: They are built off of
Patch releases are done differently from minor releases: They are built off of
long-lived backport branches, rather than from master. As non-breaking changes
land on `master`, they should also be backported into these backport branches.
Minor releases don't have release candidates by default, although any tricky
Patch releases don't have release candidates by default, although any tricky
changes may merit a release candidate.
To create a minor release:
To create a patch release:
1. Checkout the long-lived backport branch: `git checkout v0.35.x`
2. Run integration tests (`make test_integrations`) and the nightlies.
@@ -197,11 +201,143 @@ To create a minor release:
- Bump the TMDefaultVersion in `version.go`
- Bump the ABCI version number, if necessary.
(Note that ABCI follows semver, and that ABCI versions are the only versions
which can change during minor releases, and only field additions are valid minor changes.)
which can change during patch releases, and only field additions are valid patch changes.)
4. Open a PR with these changes that will land them back on `v0.35.x`
5. Once this change has landed on the backport branch, make sure to pull it locally, then push a tag.
- `git tag -a v0.35.1 -m 'Release v0.35.1'`
- `git push origin v0.35.1`
6. Create a pull request back to master with the CHANGELOG & version changes from the latest release.
- Remove all `R:minor` labels from the pull requests that were included in the release.
- Remove all `R:patch` labels from the pull requests that were included in the release.
- Do not merge the backport branch into master.
## Minor Release Checklist
The following set of steps are performed on all releases that increment the
_minor_ version, e.g. v0.25 to v0.26. These steps ensure that Tendermint is
well tested, stable, and suitable for adoption by the various diverse projects
that rely on Tendermint.
### Feature Freeze
Ahead of any minor version release of Tendermint, the software enters 'Feature
Freeze' for at least two weeks. A feature freeze means that _no_ new features
are added to the code being prepared for release. No code changes should be made
to the code being released that do not directly improve pressing issues of code
quality. The following must not be merged during a feature freeze:
* Refactors that are not related to specific bug fixes.
* Dependency upgrades.
* New test code that does not test a discovered regression.
* New features of any kind.
* Documentation or spec improvements that are not related to the newly developed
code.
This period directly follows the creation of the [backport
branch](#creating-a-backport-branch). The Tendermint team instead directs all
attention to ensuring that the existing code is stable and reliable. Broken
tests are fixed, flakey-tests are remedied, end-to-end test failures are
thoroughly diagnosed and all efforts of the team are aimed at improving the
quality of the code. During this period, the upgrade harness tests are run
repeatedly and a variety of in-house testnets are run to ensure Tendermint
functions at the scale it will be used by application developers and node
operators.
### Nightly End-To-End Tests
The Tendermint team maintains [a set of end-to-end
tests](https://github.com/tendermint/tendermint/blob/master/test/e2e/README.md#L1)
that run each night on the latest commit of the project and on the code in the
tip of each supported backport branch. These tests start a network of containerized
Tendermint processes and run automated checks that the network functions as
expected in both stable and unstable conditions. During the feature freeze,
these tests are run nightly and must pass consistently for a release of
Tendermint to be considered stable.
### Upgrade Harness
> TODO(williambanfield): Change to past tense and clarify this section once
> upgrade harness is complete.
The Tendermint team is creating an upgrade test harness to exercise the
workflow of stopping an instance of Tendermint running one version of the
software and starting up the same application running the next version. To
support upgrade testing, we will add the ability to terminate the Tendermint
process at specific pre-defined points in its execution so that we can verify
upgrades work in a representative sample of stop conditions.
### Large Scale Testnets
The Tendermint end-to-end tests run a small network (~10s of nodes) to exercise
basic consensus interactions. Real world deployments of Tendermint often have over
a hundred nodes just in the validator set, with many others acting as full
nodes and sentry nodes. To gain more assurance before a release, we will also run
larger-scale test networks to shake out emergent behaviors at scale.
Large-scale test networks are run on a set of virtual machines (VMs). Each VM
is equipped with 4 Gigabytes of RAM and 2 CPU cores. The network runs a very
simple key-value store application. The application adds artificial delays to
different ABCI calls to simulate a slow application. Each testnet is briefly
run with no load being generated to collect a baseline performance. Once
baseline is captured, a consistent load is applied across the network. This
load takes the form of 10% of the running nodes all receiving a consistent
stream of two hundred transactions per minute each.
During each test net, the following metrics are monitored and collected on each
node:
* Consensus rounds per height
* Maximum connected peers, Minimum connected peers, Rate of change of peer connections
* Memory resident set size
* CPU utilization
* Blocks produced per minute
* Seconds for each step of consensus (Propose, Prevote, Precommit, Commit)
* Latency to receive block proposals
For these tests we intentionally target low-powered host machines (with low core
counts and limited memory) to ensure we observe similar kinds of resource contention
and limitation that real-world deployments of Tendermint experience in production.
#### 200 Node Testnet
To test the stability and performance of Tendermint in a real world scenario,
a 200 node test network is run. The network comprises 5 seed nodes, 100
validators and 95 non-validating full nodes. All nodes begin by dialing
a subset of the seed nodes to discover peers. The network is run for several
days, with metrics being collected continuously. In cases of changes to performance
critical systems, testnets of larger sizes should be considered.
#### Rotating Node Testnet
Real-world deployments of Tendermint frequently see new nodes arrive and old
nodes exit the network. The rotating node testnet ensures that Tendermint is
able to handle this reliably. In this test, a network with 10 validators and
3 seed nodes is started. A rolling set of 25 full nodes are started and each
connects to the network by dialing one of the seed nodes. Once the node is able
to blocksync to the head of the chain and begins producing blocks using
Tendermint consensus it is stopped. Once stopped, a new node is started and
takes its place. This network is run for several days.
#### Network Partition Testnet
Tendermint is expected to recover from network partitions. A partition where no
subset of the nodes is left with the super-majority of the stake is expected to
stop making blocks. Upon alleviation of the partition, the network is expected
to once again become fully connected and capable of producing blocks. The
network partition testnet ensures that Tendermint is able to handle this
reliably at scale. In this test, a network with 100 validators and 95 full
nodes is started. All validators have equal stake. Once the network is
producing blocks, a set of firewall rules is deployed to create a partitioned
network with 50% of the stake on one side and 50% on the other. Once the
network stops producing blocks, the firewall rules are removed and the nodes
are monitored to ensure they reconnect and that the network again begins
producing blocks.
#### Absent Stake Testnet
Tendermint networks often run with _some_ portion of the voting power offline.
The absent stake testnet ensures that large networks are able to handle this
reliably. A set of 150 validator nodes and three seed nodes is started. The set
of 150 validators is configured to only possess a cumulative stake of 67% of
the total stake. The remaining 33% of the stake is configured to belong to
a validator that is never actually run in the test network. The network is run
for multiple days, ensuring that it is able to produce blocks without issue.

View File

@@ -3,7 +3,6 @@ package abciclient
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
@@ -65,7 +64,7 @@ RETRY_LOOP:
if cli.mustConnect {
return err
}
cli.logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr), "err", err)
cli.logger.Error("abci.grpcClient failed to connect, Retrying...", "addr", cli.addr, "err", err)
timer.Reset(time.Second * dialRetryIntervalSeconds)
select {
case <-ctx.Done():

View File

@@ -67,8 +67,11 @@ func (cli *socketClient) OnStart(ctx context.Context) error {
if cli.mustConnect {
return err
}
cli.logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying after %vs...",
cli.addr, dialRetryIntervalSeconds), "err", err)
cli.logger.Error("abci.socketClient failed to connect, retrying after",
"retry_after", dialRetryIntervalSeconds,
"target", cli.addr,
"err", err)
timer.Reset(time.Second * dialRetryIntervalSeconds)
select {
@@ -77,7 +80,6 @@ func (cli *socketClient) OnStart(ctx context.Context) error {
case <-timer.C:
continue
}
}
cli.conn = conn

View File

@@ -15,6 +15,10 @@ import (
)
func TestRollbackIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
var height int64
dir := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())

View File

@@ -106,7 +106,7 @@ module.exports = {
}
],
smallprint:
'The development of Tendermint Core is led primarily by [Interchain GmbH](https://interchain.berlin/). Funding for this development comes primarily from the Interchain Foundation, a Swiss non-profit. The Tendermint trademark is owned by Tendermint Inc, the for-profit entity that also maintains this website.',
'The development of Tendermint Core was led primarily by All in Bits, Inc. The Tendermint trademark is owned by New Tendermint, LLC.',
links: [
{
title: 'Documentation',

View File

@@ -31,3 +31,4 @@ To find out about the Tendermint ecosystem you can go [here](https://github.com/
## Contribute
To contribute to the documentation, see [this file](https://github.com/tendermint/tendermint/blob/master/docs/DOCS_README.md) for details of the build process and considerations when making changes.

View File

@@ -247,8 +247,8 @@ Similarly, you could put the commands in a file and run
Want to write an app in your favorite language?! We'd be happy
to add you to our [ecosystem](https://github.com/tendermint/awesome#ecosystem)!
See [funding](https://github.com/interchainio/funding) opportunities from the
[Interchain Foundation](https://interchain.io/) for implementations in new languages and more.
TODO link to bounties page.
The `abci-cli` is designed strictly for testing and debugging. In a real
deployment, the role of sending messages is taken by Tendermint, which

View File

@@ -4,4 +4,4 @@ order: false
# Validators
This file has moved to the [node section](../nodes/validators.md).
_This file has moved to the [node section](../nodes/validators.md)._

View File

@@ -23,8 +23,6 @@ yourself with the syntax.
By following along with this guide, you'll create a Tendermint Core project
called kvstore, a (very) simple distributed BFT key-value store.
> Note: please use a released version of Tendermint with this guide. The guides will work with the latest version. Please, do not use master.
## Built-in app vs external app
Running your application inside the same process as Tendermint Core will give
@@ -36,28 +34,27 @@ through a TCP, Unix domain socket or gRPC.
## 1.1 Installing Go
Please refer to [the official guide for installing
Go](https://golang.org/doc/install).
Go](https://go.dev/doc/install).
Verify that you have the latest version of Go installed:
```bash
```sh
$ go version
go version go1.16.x darwin/amd64
go version go1.18.x darwin/amd64
```
## 1.2 Creating a new Go project
We'll start by creating a new Go project.
We'll start by creating a new Go project. First, initialize the project folder with `go mod init`. Running this command should create the `go.mod` file.
```bash
mkdir kvstore
cd kvstore
go mod init github.com/<github_username>/<repo_name>
```sh
$ mkdir kvstore
$ cd kvstore
$ go mod init github.com/<username>/kvstore
go: creating new go.mod: module github.com/<username>/kvstore
```
Inside the example directory create a `main.go` file with the following content:
> Note: there is no need to clone or fork Tendermint in this tutorial.
Inside the project directory, create a `main.go` file with the following content:
```go
package main
@@ -73,7 +70,7 @@ func main() {
When run, this should print "Hello, Tendermint Core" to the standard output.
```bash
```sh
$ go run main.go
Hello, Tendermint Core
```
@@ -152,16 +149,43 @@ func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk
}
```
Now I will go through each method explaining when it's called and adding
Now, we will go through each method and explain when it is executed while adding
required business logic.
### 1.3.1 CheckTx
### 1.3.1 Key-value store setup
When a new transaction is added to the Tendermint Core, it will ask the
application to check it (validate the format, signatures, etc.).
For the underlying key-value store we'll use the latest version of [badger](https://github.com/dgraph-io/badger), which is an embeddable, persistent and fast key-value (KV) database.
```sh
$ go get github.com/dgraph-io/badger/v3
go: added github.com/dgraph-io/badger/v3 v3.2103.2
```
```go
import "bytes"
import "github.com/dgraph-io/badger/v3"
type KVStoreApplication struct {
db *badger.DB
currentBatch *badger.Txn
}
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
return &KVStoreApplication{
db: db,
}
}
```
### 1.3.2 CheckTx
When a new transaction is added to the Tendermint Core, it will ask the application to check it (validate the format, signatures, etc.).
```go
import (
"bytes"
...
)
func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {
// check format
@@ -214,26 +238,7 @@ Valid transactions will eventually be committed given they are not too big and
have enough gas. To learn more about gas, check out ["the
specification"](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#gas).
For the underlying key-value store we'll use
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
persistent and fast key-value (KV) database.
```go
import "github.com/dgraph-io/badger"
type KVStoreApplication struct {
db *badger.DB
currentBatch *badger.Txn
}
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
return &KVStoreApplication{
db: db,
}
}
```
### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
### 1.3.3 BeginBlock -> DeliverTx -> EndBlock -> Commit
When Tendermint Core has decided on the block, it's transfered to the
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
@@ -290,7 +295,7 @@ func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
}
```
### 1.3.3 Query
### 1.3.4 Query
Now, when the client wants to know whenever a particular key/value exist, it
will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call
@@ -348,17 +353,15 @@ import (
"path/filepath"
"syscall"
"github.com/dgraph-io/badger"
"github.com/dgraph-io/badger/v3"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
cfg "github.com/tendermint/tendermint/config"
tmflags "github.com/tendermint/tendermint/libs/cli/flags"
"github.com/tendermint/tendermint/libs/log"
nm "github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/internal/p2p"
"github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/proxy"
abciclient "github.com/tendermint/tendermint/abci/client"
abcitypes "github.com/tendermint/tendermint/abci/types"
tmconfig "github.com/tendermint/tendermint/config"
tmlog "github.com/tendermint/tendermint/libs/log"
tmservice "github.com/tendermint/tendermint/libs/service"
tmnode "github.com/tendermint/tendermint/node"
)
var configFile string
@@ -395,57 +398,42 @@ func main() {
<-c
}
func newTendermint(app abci.Application, configFile string) (*nm.Node, error) {
// read config
config := cfg.DefaultValidatorConfig()
config.RootDir = filepath.Dir(filepath.Dir(configFile))
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("viper failed to read config file: %w", err)
}
if err := viper.Unmarshal(config); err != nil {
return nil, fmt.Errorf("viper failed to unmarshal config: %w", err)
}
if err := config.ValidateBasic(); err != nil {
return nil, fmt.Errorf("config is invalid: %w", err)
}
func newTendermint(app abcitypes.Application, configFile string) (tmservice.Service, error) {
// read config
config := tmconfig.DefaultValidatorConfig()
config.SetRoot(filepath.Dir(filepath.Dir(configFile)))
// create logger
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
var err error
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel)
if err != nil {
return nil, fmt.Errorf("failed to parse log level: %w", err)
}
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("viper failed to read config file: %w", err)
}
if err := viper.Unmarshal(config); err != nil {
return nil, fmt.Errorf("viper failed to unmarshal config: %w", err)
}
if err := config.ValidateBasic(); err != nil {
return nil, fmt.Errorf("config is invalid: %w", err)
}
// read private validator
pv := privval.LoadFilePV(
config.PrivValidatorKeyFile(),
config.PrivValidatorStateFile(),
)
// create logger
logger, err := tmlog.NewDefaultLogger(tmlog.LogFormatPlain, config.LogLevel, false)
if err != nil {
return nil, fmt.Errorf("failed to create logger: %w", err)
}
// read node key
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
if err != nil {
return nil, fmt.Errorf("failed to load node's key: %w", err)
}
// create node
node, err := tmnode.New(
config,
logger,
abciclient.NewLocalCreator(app),
nil,
)
if err != nil {
return nil, fmt.Errorf("failed to create new Tendermint node: %w", err)
}
// create node
node, err := nm.NewNode(
config,
pv,
nodeKey,
abcicli.NewLocalClientCreator(app),
nm.DefaultGenesisDocProviderFunc(config),
nm.DefaultDBProvider,
nm.DefaultMetricsProvider(config.Instrumentation),
logger)
if err != nil {
return nil, fmt.Errorf("failed to create new Tendermint node: %w", err)
}
return node, nil
return node, nil
}
```
This is a huge blob of code, so let's break it down into pieces.
@@ -469,7 +457,7 @@ This can be avoided by setting the truncate option to true, like this:
db, err := badger.Open(badger.DefaultOptions("/tmp/badger").WithTruncate(true))
```
Then we use it to create a Tendermint Core `Node` instance:
Then we use it to create a Tendermint Core [Service](https://github.com/tendermint/tendermint/blob/v0.35.8/libs/service/service.go#L24) instance:
```go
flag.Parse()
@@ -483,75 +471,48 @@ if err != nil {
...
// create node
node, err := nm.NewNode(
config,
pv,
nodeKey,
abcicli.NewLocalClientCreator(app),
nm.DefaultGenesisDocProviderFunc(config),
nm.DefaultDBProvider,
nm.DefaultMetricsProvider(config.Instrumentation),
logger)
node, err := tmnode.New(
config,
logger,
abciclient.NewLocalCreator(app),
nil,
)
if err != nil {
return nil, fmt.Errorf("failed to create new Tendermint node: %w", err)
return nil, fmt.Errorf("failed to create new Tendermint node: %w", err)
}
```
`NewNode` requires a few things including a configuration file, a private
validator, a node key and a few others in order to construct the full node.
[tmnode.New](https://github.com/tendermint/tendermint/blob/v0.35.8/node/public.go#L29) requires a few things including a configuration file, a logger and a few others in order to construct the full node.
Note we use `abcicli.NewLocalClientCreator` here to create a local client instead
of one communicating through a socket or gRPC.
Note that we use [abciclient.NewLocalCreator](https://github.com/tendermint/tendermint/blob/v0.35.8/abci/client/creators.go#L15) here to create a local client instead of one communicating through a socket or gRPC.
[viper](https://github.com/spf13/viper) is being used for reading the config,
which we will generate later using the `tendermint init` command.
```go
config := cfg.DefaultValidatorConfig()
config.RootDir = filepath.Dir(filepath.Dir(configFile))
// read config
config := tmconfig.DefaultValidatorConfig()
config.SetRoot(filepath.Dir(filepath.Dir(configFile)))
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("viper failed to read config file: %w", err)
return nil, fmt.Errorf("viper failed to read config file: %w", err)
}
if err := viper.Unmarshal(config); err != nil {
return nil, fmt.Errorf("viper failed to unmarshal config: %w", err)
return nil, fmt.Errorf("viper failed to unmarshal config: %w", err)
}
if err := config.ValidateBasic(); err != nil {
return nil, fmt.Errorf("config is invalid: %w", err)
return nil, fmt.Errorf("config is invalid: %w", err)
}
```
We use `FilePV`, which is a private validator (i.e. thing which signs consensus
messages). Normally, you would use `SignerRemote` to connect to an external
[HSM](https://kb.certus.one/hsm.html).
As for the logger, we use the built-in library, which provides a nice
abstraction over [zerolog](https://github.com/rs/zerolog).
```go
pv := privval.LoadFilePV(
config.PrivValidatorKeyFile(),
config.PrivValidatorStateFile(),
)
```
`nodeKey` is needed to identify the node in a p2p network.
```go
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
// create logger
logger, err := tmlog.NewDefaultLogger(tmlog.LogFormatPlain, config.LogLevel, true)
if err != nil {
return nil, fmt.Errorf("failed to load node's key: %w", err)
}
```
As for the logger, we use the build-in library, which provides a nice
abstraction over [go-kit's
logger](https://github.com/go-kit/kit/tree/master/log).
```go
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
var err error
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())
if err != nil {
return nil, fmt.Errorf("failed to parse log level: %w", err)
return nil, fmt.Errorf("failed to create logger: %w", err)
}
```
@@ -570,40 +531,41 @@ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
```
## 1.5 Getting Up and Running
## 1.5 Getting up and running
We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
dependency management.
```bash
export GO111MODULE=on
go mod init github.com/me/example
```
This should create a `go.mod` file. The current tutorial only works with
the master branch of Tendermint. so let's make sure we're using the latest version:
Make sure to enable [Go modules](https://github.com/golang/go/wiki/Modules). Run `go mod tidy` to download and add dependencies in `go.mod` file.
```sh
go get github.com/tendermint/tendermint@master
$ go mod tidy
...
```
Let's make sure we're using the latest version of Tendermint (currently `v0.35.8`).
```sh
$ go get github.com/tendermint/tendermint@latest
...
```
This will populate the `go.mod` with a release number followed by a hash for Tendermint.
```go
module github.com/me/example
module github.com/<username>/kvstore
go 1.15
go 1.18
require (
github.com/dgraph-io/badger v1.6.2
github.com/tendermint/tendermint <vX>
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/tendermint/tendermint v0.35.8
...
)
```
Now we can build the binary:
Now, we can build the binary:
```bash
go build
```sh
$ go build
...
```
To create a default configuration, nodeKey and private validator files, let's
@@ -614,66 +576,87 @@ installing from source, don't forget to checkout the latest release (`git
checkout vX.Y.Z`). Don't forget to check that the application uses the same
major version.
```bash
$ rm -rf /tmp/example
$ TMHOME="/tmp/example" tendermint init validator
```sh
$ rm -rf /tmp/kvstore /tmp/badger
$ TMHOME="/tmp/kvstore" tendermint init validator
I[2019-07-16|18:40:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json
I[2019-07-16|18:40:36.481] Generated node key module=main path=/tmp/example/config/node_key.json
I[2019-07-16|18:40:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
I[2019-07-16|18:40:36.483] Generated config module=main mode=validator
2022-07-20T17:04:41+08:00 INFO Generated private validator keyFile=/tmp/kvstore/config/priv_validator_key.json module=main stateFile=/tmp/kvstore/data/priv_validator_state.json
2022-07-20T17:04:41+08:00 INFO Generated node key module=main path=/tmp/kvstore/config/node_key.json
2022-07-20T17:04:41+08:00 INFO Generated genesis file module=main path=/tmp/kvstore/config/genesis.json
2022-07-20T17:04:41+08:00 INFO Generated config mode=validator module=main
```
Feel free to explore the generated files, which can be found at
`/tmp/kvstore/config` directory. Documentation on the config can be found
[here](https://docs.tendermint.com/master/tendermint-core/configuration.html).
We are ready to start our application:
```bash
$ ./example -config "/tmp/example/config/config.toml"
```sh
$ ./kvstore -config "/tmp/kvstore/config/config.toml"
badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0s
badger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0
badger 2019/07/16 18:42:25 INFO: Replay took: 695.227s
E[2019-07-16|18:42:25.818] Couldn't connect to any seeds module=p2p
I[2019-07-16|18:42:26.853] Executed block module=state height=1 validTxs=0 invalidTxs=0
I[2019-07-16|18:42:26.865] Committed state module=state height=1 txs=0 appHash=
badger 2022/07/16 13:55:59 INFO: All 0 tables opened in 0s
badger 2022/07/16 13:55:59 INFO: Replaying file id: 0 at offset: 0
badger 2022/07/16 13:55:59 INFO: Replay took: 3.052µs
badger 2022/07/16 13:55:59 DEBUG: Value log discard stats empty
2022-07-16T13:55:59+08:00 INFO starting service impl=multiAppConn module=proxy service=multiAppConn
2022-07-16T13:55:59+08:00 INFO starting service connection=query impl=localClient module=abci-client service=localClient
2022-07-16T13:55:59+08:00 INFO starting service connection=snapshot impl=localClient module=abci-client service=localClient
2022-07-16T13:55:59+08:00 INFO starting service connection=mempool impl=localClient module=abci-client service=localClient
2022-07-16T13:55:59+08:00 INFO starting service connection=consensus impl=localClient module=abci-client service=localClient
2022-07-16T13:55:59+08:00 INFO starting service impl=EventBus module=events service=EventBus
2022-07-16T13:55:59+08:00 INFO starting service impl=PubSub module=pubsub service=PubSub
2022-07-16T13:55:59+08:00 INFO starting service impl=IndexerService module=txindex service=IndexerService
2022-07-16T13:55:59+08:00 INFO ABCI Handshake App Info hash= height=0 module=consensus protocol-version=0 software-version=
2022-07-16T13:55:59+08:00 INFO ABCI Replay Blocks appHeight=0 module=consensus stateHeight=0 storeHeight=0
2022-07-16T13:55:59+08:00 INFO Completed ABCI Handshake - Tendermint and App are synced appHash= appHeight=0 module=consensus
2022-07-16T13:55:59+08:00 INFO Version info block=11 mode=validator p2p=8 tmVersion=0.35.8
```
Now open another tab in your terminal and try sending a transaction:
Let's try sending a transaction. Open another terminal and execute the below command.
```bash
```sh
$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
{
"check_tx": {
"gasWanted": "1",
...
},
"deliver_tx": { ... },
"hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6",
"height": "128"
...
"result": {
"check_tx": {
...
"gas_wanted": "1",
...
},
"deliver_tx": {...},
"hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6",
"height": "91"
}
}
```
Response should contain the height where this transaction was committed.
Now let's check if the given key now exists and its value:
Let's check if the given key now exists and its value:
```json
```sh
$ curl -s 'localhost:26657/abci_query?data="tendermint"'
{
"response": {
"code": 0,
"log": "exists",
"info": "",
"index": "0",
"key": "dGVuZGVybWludA==",
"value": "cm9ja3M=",
"proofOps": null,
"height": "6",
"codespace": ""
...
"result": {
"response": {
"code": 0,
"log": "exists",
"info": "",
"index": "0",
"key": "dGVuZGVybWludA==",
"value": "cm9ja3M=",
"proofOps": null,
"height": "0",
"codespace": ""
}
}
}
```
"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of
`dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of
"tendermint" and "rocks" accordingly.
## Outro

View File

@@ -37,25 +37,27 @@ Core will not have access to application's state.
## 1.1 Installing Go
Please refer to [the official guide for installing
Go](https://golang.org/doc/install).
Go](https://go.dev/doc/install).
Verify that you have the latest version of Go installed:
```bash
```sh
$ go version
go version go1.16.x darwin/amd64
go version go1.18.x darwin/amd64
```
## 1.2 Creating a new Go project
We'll start by creating a new Go project.
We'll start by creating a new Go project. Initialize the folder with `go mod init`. Running this command should create the `go.mod` file.
```bash
mkdir kvstore
cd kvstore
```sh
$ mkdir kvstore
$ cd kvstore
$ go mod init github.com/<username>/kvstore
go: creating new go.mod: module github.com/<username>/kvstore
```
Inside the example directory create a `main.go` file with the following content:
Inside the project directory, create a `main.go` file with the following content:
```go
package main
@@ -71,8 +73,8 @@ func main() {
When run, this should print "Hello, Tendermint Core" to the standard output.
```bash
go run main.go
```sh
$ go run main.go
Hello, Tendermint Core
```
@@ -150,10 +152,34 @@ func (KVStoreApplication) ApplySnapshotChunk(abcitypes.RequestApplySnapshotChunk
}
```
Now I will go through each method explaining when it's called and adding
Now, we will go through each method and explain when it is executed while adding
required business logic.
### 1.3.1 CheckTx
### 1.3.1 Key-value store setup
For the underlying key-value store we'll use the latest version of [badger](https://github.com/dgraph-io/badger), which is an embeddable, persistent and fast key-value (KV) database.
```sh
$ go get github.com/dgraph-io/badger/v3
go: added github.com/dgraph-io/badger/v3 v3.2103.2
```
```go
import "github.com/dgraph-io/badger/v3"
type KVStoreApplication struct {
db *badger.DB
currentBatch *badger.Txn
}
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
return &KVStoreApplication{
db: db,
}
}
```
### 1.3.2 CheckTx
When a new transaction is added to the Tendermint Core, it will ask the
application to check it (validate the format, signatures, etc.).
@@ -212,26 +238,8 @@ Valid transactions will eventually be committed given they are not too big and
have enough gas. To learn more about gas, check out ["the
specification"](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#gas).
For the underlying key-value store we'll use
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
persistent and fast key-value (KV) database.
```go
import "github.com/dgraph-io/badger"
type KVStoreApplication struct {
db *badger.DB
currentBatch *badger.Txn
}
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
return &KVStoreApplication{
db: db,
}
}
```
### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
### 1.3.3 BeginBlock -> DeliverTx -> EndBlock -> Commit
When Tendermint Core has decided on the block, it's transferred to the
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
@@ -287,7 +295,7 @@ func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
}
```
### 1.3.3 Query
### 1.3.4 Query
Now, when the client wants to know whenever a particular key/value exist, it
will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call
@@ -344,7 +352,7 @@ import (
"os/signal"
"syscall"
"github.com/dgraph-io/badger"
"github.com/dgraph-io/badger/v3"
abciserver "github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/libs/log"
@@ -353,7 +361,7 @@ import (
var socketAddr string
func init() {
flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address")
flag.StringVar(&socketAddr, "socket-addr", "unix://kvstore.sock", "Unix domain socket address")
}
func main() {
@@ -426,40 +434,41 @@ signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
```
## 1.5 Getting Up and Running
## 1.5 Getting up and running
We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
dependency management.
```bash
export GO111MODULE=on
go mod init github.com/me/example
```
This should create a `go.mod` file. The current tutorial only works with
the master branch of Tendermint, so let's make sure we're using the latest version:
Make sure to enable [Go modules](https://github.com/golang/go/wiki/Modules). Run `go mod tidy` to download and add dependencies in `go.mod` file.
```sh
go get github.com/tendermint/tendermint@97a3e44e0724f2017079ce24d36433f03124c09e
$ go mod tidy
...
```
Let's make sure we're using the latest version of Tendermint (currently `v0.35.8`).
```sh
$ go get github.com/tendermint/tendermint@latest
...
```
This will populate the `go.mod` with a release number followed by a hash for Tendermint.
```go
module github.com/me/example
module github.com/<username>/kvstore
go 1.16
go 1.18
require (
github.com/dgraph-io/badger v1.6.2
github.com/tendermint/tendermint <vX>
github.com/dgraph-io/badger/v3 v3.2103.2
github.com/tendermint/tendermint v0.35.8
...
)
```
Now we can build the binary:
Now, we can build the binary:
```bash
go build
```sh
$ go build
...
```
To create a default configuration, nodeKey and private validator files, let's
@@ -470,94 +479,112 @@ installing from source, don't forget to checkout the latest release (`git
checkout vX.Y.Z`). Don't forget to check that the application uses the same
major version.
```bash
rm -rf /tmp/example
TMHOME="/tmp/example" tendermint init validator
```sh
$ rm -rf /tmp/kvstore /tmp/badger
$ TMHOME="/tmp/kvstore" tendermint init validator
I[2019-07-16|18:20:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json
I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json
I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
I[2019-07-16|18:20:36.483] Generated config module=main mode=validator
2022-07-20T17:04:41+08:00 INFO Generated private validator keyFile=/tmp/kvstore/config/priv_validator_key.json module=main stateFile=/tmp/kvstore/data/priv_validator_state.json
2022-07-20T17:04:41+08:00 INFO Generated node key module=main path=/tmp/kvstore/config/node_key.json
2022-07-20T17:04:41+08:00 INFO Generated genesis file module=main path=/tmp/kvstore/config/genesis.json
2022-07-20T17:04:41+08:00 INFO Generated config mode=validator module=main
```
Feel free to explore the generated files, which can be found at
`/tmp/example/config` directory. Documentation on the config can be found
`/tmp/kvstore/config` directory. Documentation on the config can be found
[here](https://docs.tendermint.com/master/tendermint-core/configuration.html).
We are ready to start our application:
```bash
rm example.sock
./example
```sh
$ rm kvstore.sock
$ ./kvstore
badger 2019/07/16 18:25:11 INFO: All 0 tables opened in 0s
badger 2019/07/16 18:25:11 INFO: Replaying file id: 0 at offset: 0
badger 2019/07/16 18:25:11 INFO: Replay took: 300.4s
I[2019-07-16|18:25:11.523] Starting ABCIServer impl=ABCIServ
badger 2022/07/20 17:07:17 INFO: All 1 tables opened in 9ms
badger 2022/07/20 17:07:17 INFO: Replaying file id: 0 at offset: 256
badger 2022/07/20 17:07:17 INFO: Replay took: 9.077µs
badger 2022/07/20 17:07:17 DEBUG: Value log discard stats empty
2022-07-20T17:07:17+08:00 INFO starting service impl=ABCIServer service=ABCIServer
2022-07-20T17:07:17+08:00 INFO Waiting for new connection...
```
Then we need to start Tendermint Core and point it to our application. Staying
within the application directory execute:
Then, we need to start Tendermint Core and point it to our application. Staying
within the project directory, open another terminal and execute the command below:
```bash
TMHOME="/tmp/example" tendermint node --proxy-app=unix://example.sock
```sh
$ TMHOME="/tmp/kvstore" tendermint node --proxy-app=unix://kvstore.sock
I[2019-07-16|18:26:20.362] Version info module=main software=0.32.1 block=10 p2p=7
I[2019-07-16|18:26:20.383] Starting Node module=main impl=Node
E[2019-07-16|18:26:20.392] Couldn't connect to any seeds module=p2p
I[2019-07-16|18:26:20.394] Started node module=main nodeInfo="{ProtocolVersion:{P2P:7 Block:10 App:0} ID_:8dab80770ae8e295d4ce905d86af78c4ff634b79 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-nIO96P Version:0.32.1 Channels:4020212223303800 Moniker:app48.fun-box.ru Other:{TxIndex:on RPCAddress:tcp://127.0.0.1:26657}}"
I[2019-07-16|18:26:21.440] Executed block module=state height=1 validTxs=0 invalidTxs=0
I[2019-07-16|18:26:21.446] Committed state module=state height=1 txs=0 appHash=
2022-07-20T17:10:22+08:00 INFO starting service impl=multiAppConn module=proxy service=multiAppConn
2022-07-20T17:10:22+08:00 INFO starting service connection=query impl=socketClient module=abci-client service=socketClient
2022-07-20T17:10:22+08:00 INFO starting service connection=snapshot impl=socketClient module=abci-client service=socketClient
2022-07-20T17:10:22+08:00 INFO starting service connection=mempool impl=socketClient module=abci-client service=socketClient
2022-07-20T17:10:22+08:00 INFO starting service connection=consensus impl=socketClient module=abci-client service=socketClient
2022-07-20T17:10:22+08:00 INFO starting service impl=EventBus module=events service=EventBus
2022-07-20T17:10:22+08:00 INFO starting service impl=PubSub module=pubsub service=PubSub
2022-07-20T17:10:22+08:00 INFO starting service impl=IndexerService module=txindex service=IndexerService
...
2022-07-20T17:10:22+08:00 INFO starting service impl=Node module=main service=Node
2022-07-20T17:10:22+08:00 INFO Starting RPC HTTP server on 127.0.0.1:26657 module=rpc-server
2022-07-20T17:10:22+08:00 INFO p2p service legacy_enabled=false module=main
2022-07-20T17:10:22+08:00 INFO starting service impl=router module=p2p service=router
2022-07-20T17:10:22+08:00 INFO starting router channels=402021222330386061626300 listen_addr=tcp://0.0.0.0:26656 module=p2p net_addr={"id":"715727499e94f8fcaef1763192ebcc8460f44666","ip":"0.0.0.0","port":26656} node_id=715727499e94f8fcaef1763192ebcc8460f44666
...
```
This should start the full node and connect to our ABCI application.
```sh
I[2019-07-16|18:25:11.525] Waiting for new connection...
I[2019-07-16|18:26:20.329] Accepted a new connection
I[2019-07-16|18:26:20.329] Waiting for new connection...
I[2019-07-16|18:26:20.330] Accepted a new connection
I[2019-07-16|18:26:20.330] Waiting for new connection...
I[2019-07-16|18:26:20.330] Accepted a new connection
2022-07-20T17:09:55+08:00 INFO Waiting for new connection...
2022-07-20T17:10:22+08:00 INFO Accepted a new connection
2022-07-20T17:10:22+08:00 INFO Waiting for new connection...
2022-07-20T17:10:22+08:00 INFO Accepted a new connection
2022-07-20T17:10:22+08:00 INFO Waiting for new connection...
2022-07-20T17:10:22+08:00 INFO Accepted a new connection
```
Now open another tab in your terminal and try sending a transaction:
Let's try sending a transaction. Open another terminal and execute the below command.
```json
```sh
$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
{
"check_tx": {
"gasWanted": "1",
...
},
"deliver_tx": { ... },
"hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB",
"height": "33"
...
"result": {
"check_tx": {
...
"gas_wanted": "1",
...
},
"deliver_tx": { ... },
"hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6",
"height": "15"
}
}
```
Response should contain the height where this transaction was committed.
Now let's check if the given key now exists and its value:
Let's check if the given key now exists and its value:
```json
```sh
$ curl -s 'localhost:26657/abci_query?data="tendermint"'
{
"response": {
"code": 0,
"log": "exists",
"info": "",
"index": "0",
"key": "dGVuZGVybWludA==",
"value": "cm9ja3M=",
"proofOps": null,
"height": "6",
"codespace": ""
...
"result": {
"response": {
"code": 0,
"log": "exists",
"info": "",
"index": "0",
"key": "dGVuZGVybWludA==",
"value": "cm9ja3M=",
"proofOps": null,
"height": "0",
"codespace": ""
}
}
}
```
"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of
`dGVuZGVybWludA==` and `cm9ja3M=` are the base64-encoding of the ASCII of
"tendermint" and "rocks" accordingly.
## Outro

View File

@@ -1,4 +1,4 @@
master master
v0.33.x v0.33
v0.34.x v0.34
v0.35.x v0.35
master master

8
go.mod
View File

@@ -40,7 +40,7 @@ require (
github.com/bufbuild/buf v1.4.0
github.com/creachadair/atomicfile v0.2.6
github.com/creachadair/taskgroup v0.3.2
github.com/golangci/golangci-lint v1.47.1
github.com/golangci/golangci-lint v1.47.2
github.com/google/go-cmp v0.5.8
github.com/vektra/mockery/v2 v2.14.0
gotest.tools v2.2.0+incompatible
@@ -240,8 +240,12 @@ require (
)
require (
github.com/creachadair/tomledit v0.0.22
github.com/creachadair/tomledit v0.0.23
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.37.0
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca
)
retract (
[v0.35.0,v0.35.9] // See https://github.com/tendermint/tendermint/discussions/9155
)

8
go.sum
View File

@@ -245,8 +245,8 @@ github.com/creachadair/atomicfile v0.2.6/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2
github.com/creachadair/command v0.0.0-20220426235536-a748effdf6a1/go.mod h1:bAM+qFQb/KwWyCc9MLC4U1jvn3XyakqP5QRkds5T6cY=
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/creachadair/tomledit v0.0.22 h1:lRtepmrwhzDq+g1gv5ftVn5itgo7CjYbm6abKTToqJ4=
github.com/creachadair/tomledit v0.0.22/go.mod h1:cIu/4x5L855oSRejIqr+WRFh+mv9g4fWLiUFaApYn/Y=
github.com/creachadair/tomledit v0.0.23 h1:ohYJjMsxwzj4dDzKaBWFbWH5J+3LO/8CYnlVY+baBWA=
github.com/creachadair/tomledit v0.0.23/go.mod h1:cIu/4x5L855oSRejIqr+WRFh+mv9g4fWLiUFaApYn/Y=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -449,8 +449,8 @@ github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=
github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.47.1 h1:hbubHskV2Ppwz4ZZE2lc0/Pw9ZhqLuzm2dT7ZVpLA6Y=
github.com/golangci/golangci-lint v1.47.1/go.mod h1:lpS2pjBZtRyXewUcOY7yUL3K4KfpoWz072yRN8AuhHg=
github.com/golangci/golangci-lint v1.47.2 h1:qvMDVv49Hrx3PSEXZ0bD/yhwSbhsOihQjFYCKieegIw=
github.com/golangci/golangci-lint v1.47.2/go.mod h1:lpS2pjBZtRyXewUcOY7yUL3K4KfpoWz072yRN8AuhHg=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA=
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=

View File

@@ -469,12 +469,10 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh
lastAdvance = r.pool.LastAdvance()
)
r.logger.Debug(
"consensus ticker",
r.logger.Debug("consensus ticker",
"num_pending", numPending,
"total", lenRequesters,
"height", height,
)
"height", height)
switch {

View File

@@ -33,6 +33,10 @@ import (
// Byzantine node sends two different prevotes (nil and blockID) to the same
// validator.
func TestByzantinePrevoteEquivocation(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// empirically, this test either passes in <1s or hits some
// kind of deadlock and hit the larger timeout. This timeout
// can be extended a bunch if needed, but it's good to avoid

View File

@@ -10,6 +10,7 @@ import (
cstypes "github.com/tendermint/tendermint/internal/consensus/types"
"github.com/tendermint/tendermint/internal/eventbus"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/internal/p2p"
sm "github.com/tendermint/tendermint/internal/state"
"github.com/tendermint/tendermint/libs/bits"
@@ -1113,7 +1114,7 @@ func (r *Reactor) handleDataMessage(ctx context.Context, envelope *p2p.Envelope,
}
if r.WaitSync() {
logger.Info("ignoring message received during sync", "msg", fmt.Sprintf("%T", msgI))
logger.Info("ignoring message received during sync", "msg", tmstrings.LazySprintf("%T", msgI))
return nil
}

View File

@@ -779,6 +779,10 @@ func TestReactorRecordsVotesAndBlockParts(t *testing.T) {
}
func TestReactorVotingPowerChange(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
@@ -885,6 +889,10 @@ func TestReactorVotingPowerChange(t *testing.T) {
}
func TestReactorValidatorSetChanges(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

View File

@@ -118,6 +118,10 @@ func sendTxs(ctx context.Context, t *testing.T, cs *State) {
// TestWALCrash uses crashing WAL to test we can recover from any WAL failure.
func TestWALCrash(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
testCases := []struct {
name string
initFn func(dbm.DB, *State, context.Context)

View File

@@ -21,6 +21,7 @@ import (
"github.com/tendermint/tendermint/internal/eventbus"
"github.com/tendermint/tendermint/internal/jsontypes"
"github.com/tendermint/tendermint/internal/libs/autofile"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
sm "github.com/tendermint/tendermint/internal/state"
tmevents "github.com/tendermint/tendermint/libs/events"
"github.com/tendermint/tendermint/libs/log"
@@ -778,11 +779,9 @@ func (cs *State) updateToState(state sm.State) {
// signal the new round step, because other services (eg. txNotifier)
// depend on having an up-to-date peer state!
if state.LastBlockHeight <= cs.state.LastBlockHeight {
cs.logger.Debug(
"ignoring updateToState()",
cs.logger.Debug("ignoring updateToState()",
"new_height", state.LastBlockHeight+1,
"old_height", cs.state.LastBlockHeight+1,
)
"old_height", cs.state.LastBlockHeight+1)
cs.newStep()
return
}
@@ -1038,12 +1037,10 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) {
}
if err != nil && msg.Round != cs.Round {
cs.logger.Debug(
"received block part from wrong round",
cs.logger.Debug("received block part from wrong round",
"height", cs.Height,
"cs_round", cs.Round,
"block_round", msg.Round,
)
"block_round", msg.Round)
err = nil
}
@@ -1073,7 +1070,7 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) {
// We could make note of this and help filter in broadcastHasVoteMessage().
default:
cs.logger.Error("unknown msg type", "type", fmt.Sprintf("%T", msg))
cs.logger.Error("unknown msg type", "type", tmstrings.LazySprintf("%T", msg))
return
}
@@ -1184,10 +1181,10 @@ func (cs *State) enterNewRound(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != cstypes.RoundStepNewHeight) {
logger.Debug(
"entering new round with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering new round with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
@@ -1195,7 +1192,10 @@ func (cs *State) enterNewRound(ctx context.Context, 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", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering new round",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
// increment validators if necessary
validators := cs.Validators
@@ -1274,10 +1274,10 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPropose <= cs.Step) {
logger.Debug(
"entering propose step with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering propose step with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
@@ -1291,7 +1291,10 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
}
}
logger.Debug("entering propose step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering propose step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
defer func() {
// Done enterPropose:
@@ -1333,17 +1336,13 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
}
if cs.isProposer(addr) {
logger.Debug(
"propose step; our turn to propose",
"proposer", addr,
)
logger.Debug("propose step; our turn to propose",
"proposer", addr)
cs.decideProposal(ctx, height, round)
} else {
logger.Debug(
"propose step; not our turn to propose",
"proposer", cs.Validators.GetProposer().Address,
)
logger.Debug("propose step; not our turn to propose",
"proposer", cs.Validators.GetProposer().Address)
}
}
@@ -1480,10 +1479,10 @@ func (cs *State) enterPrevote(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevote <= cs.Step) {
logger.Debug(
"entering prevote step with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering prevote step with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
@@ -1493,7 +1492,10 @@ func (cs *State) enterPrevote(ctx context.Context, height int64, round int32) {
cs.newStep()
}()
logger.Debug("entering prevote step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering prevote step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
// Sign and broadcast vote as necessary
cs.doPrevote(ctx, height, round)
@@ -1533,14 +1535,10 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
//TODO: Remove this temporary fix when the complete solution is ready. See #8739
if !cs.replayMode && cs.Proposal.POLRound == -1 && cs.LockedRound == -1 && !cs.proposalIsTimely() {
logger.Debug("prevote step: Proposal is not timely; prevoting nil",
"proposed",
tmtime.Canonical(cs.Proposal.Timestamp).Format(time.RFC3339Nano),
"received",
tmtime.Canonical(cs.ProposalReceiveTime).Format(time.RFC3339Nano),
"msg_delay",
sp.MessageDelay,
"precision",
sp.Precision)
"proposed", tmtime.Canonical(cs.Proposal.Timestamp).Format(time.RFC3339Nano),
"received", tmtime.Canonical(cs.ProposalReceiveTime).Format(time.RFC3339Nano),
"msg_delay", sp.MessageDelay,
"precision", sp.Precision)
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
@@ -1625,8 +1623,8 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
blockID, ok := cs.Votes.Prevotes(cs.Proposal.POLRound).TwoThirdsMajority()
if ok && cs.ProposalBlock.HashesTo(blockID.Hash) && cs.Proposal.POLRound >= 0 && cs.Proposal.POLRound < cs.Round {
if cs.LockedRound <= cs.Proposal.POLRound {
logger.Debug("prevote step: ProposalBlock is valid and received a 2/3" +
"majority in a round later than the locked round; prevoting the proposal")
logger.Debug("prevote step: ProposalBlock is valid and received a 2/3 majority in a round later than the locked round",
"outcome", "prevoting the proposal")
cs.signAddVote(ctx, tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
return
}
@@ -1637,8 +1635,8 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
}
}
logger.Debug("prevote step: ProposalBlock is valid but was not our locked block or " +
"did not receive a more recent majority; prevoting nil")
logger.Debug("prevote step: ProposalBlock is valid but was not our locked block or did not receive a more recent majority",
"outcome", "prevoting nil")
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
}
@@ -1647,10 +1645,10 @@ func (cs *State) enterPrevoteWait(height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrevoteWait <= cs.Step) {
logger.Debug(
"entering prevote wait step with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering prevote wait step with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
@@ -1661,7 +1659,10 @@ func (cs *State) enterPrevoteWait(height int64, round int32) {
))
}
logger.Debug("entering prevote wait step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering prevote wait step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
defer func() {
// Done enterPrevoteWait:
@@ -1679,17 +1680,21 @@ func (cs *State) enterPrevoteWait(height int64, round int32) {
// Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round)
// else, precommit nil otherwise.
func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
logger := cs.logger.With("new_height", height, "new_round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommit <= cs.Step) {
logger.Debug(
"entering precommit step with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering precommit step with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
logger.Debug("entering precommit step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering precommit step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
defer func() {
// Done enterPrecommit:
@@ -1796,14 +1801,13 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32)
// Enter: any +2/3 precommits for next round.
func (cs *State) enterPrecommitWait(height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
logger := cs.logger.With("new_height", height, "new_round", round)
if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) {
logger.Debug(
"entering precommit wait step with invalid args",
logger.Debug("entering precommit wait step with invalid args",
"triggered_timeout", cs.TriggeredTimeoutPrecommit,
"current", fmt.Sprintf("%v/%v", cs.Height, cs.Round),
)
"height", cs.Height,
"round", cs.Round)
return
}
@@ -1814,7 +1818,10 @@ func (cs *State) enterPrecommitWait(height int64, round int32) {
))
}
logger.Debug("entering precommit wait step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering precommit wait step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
defer func() {
// Done enterPrecommitWait:
@@ -1828,17 +1835,20 @@ func (cs *State) enterPrecommitWait(height int64, round int32) {
// Enter: +2/3 precommits for block
func (cs *State) enterCommit(ctx context.Context, height int64, commitRound int32) {
logger := cs.logger.With("height", height, "commit_round", commitRound)
logger := cs.logger.With("new_height", height, "commit_round", commitRound)
if cs.Height != height || cstypes.RoundStepCommit <= cs.Step {
logger.Debug(
"entering commit step with invalid args",
"current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step),
)
logger.Debug("entering commit step with invalid args",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
return
}
logger.Debug("entering commit step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
logger.Debug("entering commit step",
"height", cs.Height,
"round", cs.Round,
"step", cs.Step)
defer func() {
// Done enterCommit:
@@ -1892,12 +1902,12 @@ func (cs *State) enterCommit(ctx context.Context, height int64, commitRound int3
// If we have the block AND +2/3 commits for it, finalize.
func (cs *State) tryFinalizeCommit(ctx context.Context, height int64) {
logger := cs.logger.With("height", height)
if cs.Height != height {
panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height))
}
logger := cs.logger.With("height", height)
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
if !ok || blockID.IsNil() {
logger.Error("failed attempt to finalize commit; there was no +2/3 majority or +2/3 was for nil")
@@ -1907,9 +1917,8 @@ func (cs *State) tryFinalizeCommit(ctx context.Context, height int64) {
if !cs.ProposalBlock.HashesTo(blockID.Hash) {
// TODO: this happens every time if we're not a validator (ugly logs)
// TODO: ^^ wait, why does it matter that we're a validator?
logger.Debug(
"failed attempt to finalize commit; we do not have the commit block",
"proposal_block", cs.ProposalBlock.Hash(),
logger.Debug("failed attempt to finalize commit; we do not have the commit block",
"proposal_block", tmstrings.LazyBlockHash(cs.ProposalBlock),
"commit_block", blockID.Hash,
)
return
@@ -1951,11 +1960,10 @@ func (cs *State) finalizeCommit(ctx context.Context, height int64) {
logger.Info(
"finalizing commit of block",
"hash", block.Hash(),
"hash", tmstrings.LazyBlockHash(block),
"root", block.AppHash,
"num_txs", len(block.Txs),
)
logger.Debug(fmt.Sprintf("%v", block))
// Save to blockStore.
if cs.blockStore.Height() < block.Height {
@@ -2052,8 +2060,11 @@ func (cs *State) RecordMetrics(height int64, block *types.Block) {
address types.Address
)
if commitSize != valSetLen {
cs.logger.Error(fmt.Sprintf("commit size (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
commitSize, valSetLen, block.Height, block.LastCommit.Signatures, cs.LastValidators.Validators))
cs.logger.Error("commit size doesn't match valset",
"size", commitSize,
"valset_len", valSetLen,
"height", block.Height,
"extra", tmstrings.LazySprintf("\n%v\n\n%v", block.LastCommit.Signatures, cs.LastValidators.Validators))
return
}
@@ -2187,8 +2198,7 @@ func (cs *State) addProposalBlockPart(
cs.metrics.BlockGossipPartsReceived.With("matches_current", "false").Add(1)
// NOTE: this can happen when we've gone to a higher round and
// then receive parts from the previous round - not necessarily a bad peer.
cs.logger.Debug(
"received a block part when we are not expecting any",
cs.logger.Debug("received a block part when we are not expecting any",
"height", height,
"round", round,
"index", part.Index,
@@ -2248,11 +2258,9 @@ func (cs *State) handleCompleteProposal(ctx context.Context, height int64) {
blockID, hasTwoThirds := prevotes.TwoThirdsMajority()
if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) {
if cs.ProposalBlock.HashesTo(blockID.Hash) {
cs.logger.Debug(
"updating valid block to new proposal block",
cs.logger.Debug("updating valid block to new proposal block",
"valid_round", cs.Round,
"valid_block_hash", cs.ProposalBlock.Hash(),
)
"valid_block_hash", tmstrings.LazyBlockHash(cs.ProposalBlock))
cs.ValidRound = cs.Round
cs.ValidBlock = cs.ProposalBlock
@@ -2291,23 +2299,19 @@ func (cs *State) tryAddVote(ctx context.Context, vote *types.Vote, peerID types.
}
if bytes.Equal(vote.ValidatorAddress, cs.privValidatorPubKey.Address()) {
cs.logger.Error(
"found conflicting vote from ourselves; did you unsafe_reset a validator?",
cs.logger.Error("found conflicting vote from ourselves; did you unsafe_reset a validator?",
"height", vote.Height,
"round", vote.Round,
"type", vote.Type,
)
"type", vote.Type)
return added, err
}
// report conflicting votes to the evidence pool
cs.evpool.ReportConflictingVotes(voteErr.VoteA, voteErr.VoteB)
cs.logger.Debug(
"found and sent conflicting votes to the evidence pool",
cs.logger.Debug("found and sent conflicting votes to the evidence pool",
"vote_a", voteErr.VoteA,
"vote_b", voteErr.VoteB,
)
"vote_b", voteErr.VoteB)
return added, err
} else if errors.Is(err, types.ErrVoteNonDeterministicSignature) {
@@ -2331,13 +2335,11 @@ func (cs *State) addVote(
vote *types.Vote,
peerID types.NodeID,
) (added bool, err error) {
cs.logger.Debug(
"adding vote",
cs.logger.Debug("adding vote",
"vote_height", vote.Height,
"vote_type", vote.Type,
"val_index", vote.ValidatorIndex,
"cs_height", cs.Height,
)
"cs_height", cs.Height)
if vote.Height < cs.Height || (vote.Height == cs.Height && vote.Round < cs.Round) {
cs.metrics.MarkLateVote(vote.Type)
@@ -2458,11 +2460,9 @@ func (cs *State) addVote(
cs.ValidBlock = cs.ProposalBlock
cs.ValidBlockParts = cs.ProposalBlockParts
} else {
cs.logger.Debug(
"valid block we do not know about; set ProposalBlock=nil",
"proposal", cs.ProposalBlock.Hash(),
"block_id", blockID.Hash,
)
cs.logger.Debug("valid block we do not know about; set ProposalBlock=nil",
"proposal", tmstrings.LazyBlockHash(cs.ProposalBlock),
"block_id", blockID.Hash)
// we're getting the wrong block
cs.ProposalBlock = nil

View File

@@ -3,8 +3,94 @@ package strings
import (
"fmt"
"strings"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
)
type lazyStringf struct {
tmpl string
args []interface{}
out string
}
func (s *lazyStringf) String() string {
if s.out == "" && s.tmpl != "" {
s.out = fmt.Sprintf(s.tmpl, s.args)
s.args = nil
s.tmpl = ""
}
return s.out
}
// LazySprintf creates a fmt.Stringer implementation with similar
// semantics as fmt.Sprintf, *except* that the string is built when
// String() is called on the object. This means that format arguments
// are resolved/captured into string format when String() is called,
// and not, as in fmt.Sprintf when that function returns.
//
// As a result, if you use this type in go routines or defer
// statements it's possible to pass an argument to LazySprintf which
// has one value at the call site and a different value when the
// String() is evaluated, which may lead to unexpected outcomes. In
// these situations, either be *extremely* careful about the arguments
// passed to this function or use fmt.Sprintf.
//
// The implementation also caches the output of the underlying
// fmt.Sprintf statement when String() is called, so subsequent calls
// will produce the same result.
func LazySprintf(t string, args ...interface{}) fmt.Stringer {
return &lazyStringf{tmpl: t, args: args}
}
type lazyStringer struct {
val fmt.Stringer
out string
}
func (l *lazyStringer) String() string {
if l.out == "" && l.val != nil {
l.out = l.val.String()
l.val = nil
}
return l.out
}
// LazyStringer captures a fmt.Stringer implementation resolving the
// underlying string *only* when the String() method is called and
// caching the result for future use.
func LazyStringer(v fmt.Stringer) fmt.Stringer { return &lazyStringer{val: v} }
type lazyBlockHash struct {
block interface{ Hash() tmbytes.HexBytes }
out string
}
// LazyBlockHash defers block Hash until the Stringer interface is invoked.
// This is particularly useful for avoiding calling Sprintf when debugging is not
// active.
//
// As a result, if you use this type in go routines or defer
// statements it's possible to pass an argument to LazyBlockHash that
// has one value at the call site and a different value when the
// String() is evaluated, which may lead to unexpected outcomes. In
// these situations, either be *extremely* careful about the arguments
// passed to this function or use fmt.Sprintf.
//
// The implementation also caches the output of the string form of the
// block hash when String() is called, so subsequent calls will
// produce the same result.
func LazyBlockHash(block interface{ Hash() tmbytes.HexBytes }) fmt.Stringer {
return &lazyBlockHash{block: block}
}
func (l *lazyBlockHash) String() string {
if l.out == "" && l.block != nil {
l.out = l.block.Hash().String()
l.block = nil
}
return l.out
}
// SplitAndTrimEmpty slices s into all subslices separated by sep and returns a
// slice of the string s with all leading and trailing Unicode code points
// contained in cutset removed. If sep is empty, SplitAndTrim splits after each

View File

@@ -14,6 +14,7 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/libs/clist"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/types"
)
@@ -540,7 +541,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, checkTxRes *abci.Respon
}
txmp.logger.Debug("evicting lower-priority transactions",
"new_tx", fmt.Sprintf("%X", wtx.tx.Hash()),
"new_tx", tmstrings.LazySprintf("%X", wtx.tx.Hash()),
"new_priority", priority,
)
@@ -562,7 +563,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, checkTxRes *abci.Respon
txmp.logger.Debug(
"evicted valid existing transaction; mempool full",
"old_tx", fmt.Sprintf("%X", w.tx.Hash()),
"old_tx", tmstrings.LazySprintf("%X", w.tx.Hash()),
"old_priority", w.priority,
)
txmp.removeTxByElement(vic)
@@ -588,7 +589,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, checkTxRes *abci.Respon
txmp.logger.Debug(
"inserted new valid transaction",
"priority", wtx.Priority(),
"tx", fmt.Sprintf("%X", wtx.tx.Hash()),
"tx", tmstrings.LazySprintf("%X", wtx.tx.Hash()),
"height", txmp.height,
"num_txs", txmp.Size(),
)

View File

@@ -132,6 +132,10 @@ func convertTex(in []testTx) types.Txs {
}
func TestTxMempool_TxsAvailable(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -540,6 +544,10 @@ func TestTxMempool_CheckTxSameSender(t *testing.T) {
}
func TestTxMempool_ConcurrentTxs(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -9,6 +9,7 @@ import (
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/internal/libs/clist"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/internal/p2p"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
@@ -330,9 +331,8 @@ func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID, m
return
}
r.logger.Debug(
"gossiped tx to peer",
"tx", fmt.Sprintf("%X", memTx.tx.Hash()),
r.logger.Debug("gossiped tx to peer",
"tx", tmstrings.LazySprintf("%X", memTx.tx.Hash()),
"peer", peerID,
)
}

View File

@@ -313,7 +313,7 @@ func (c *MConnection) Send(chID ChannelID, msgBytes []byte) bool {
// Send message to channel.
channel, ok := c.channelsIdx[chID]
if !ok {
c.logger.Error(fmt.Sprintf("Cannot send bytes, unknown channel %X", chID))
c.logger.Error("Cannot send bytes to unknown channel", "channel", chID)
return false
}

View File

@@ -315,6 +315,10 @@ func TestMConnectionMultiplePings(t *testing.T) {
}
func TestMConnectionPingPongs(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// check that we are not leaking any go-routines
t.Cleanup(leaktest.CheckTimeout(t, 10*time.Second))
@@ -558,6 +562,10 @@ func TestMConnectionReadErrorUnknownMsgType(t *testing.T) {
}
func TestMConnectionTrySend(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
server, client := net.Pipe()
t.Cleanup(closeAll(t, client, server))
ctx, cancel := context.WithCancel(context.Background())
@@ -606,6 +614,10 @@ func TestConnVectors(t *testing.T) {
}
func TestMConnectionChannelOverflow(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
chOnErr := make(chan struct{})
chOnRcv := make(chan struct{})

View File

@@ -296,6 +296,10 @@ func TestPeerManager_DialNext(t *testing.T) {
}
func TestPeerManager_DialNext_Retry(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -358,7 +358,8 @@ func (r *Reactor) calculateNextRequestTime(added int) time.Duration {
// If the peer store is nearly full, wait the maximum interval.
if ratio := r.peerManager.PeerRatio(); ratio >= 0.95 {
r.logger.Debug("Peer manager is nearly full",
"sleep_period", fullCapacityInterval, "ratio", ratio)
"sleep_period", fullCapacityInterval,
"ratio", ratio)
return fullCapacityInterval
}

View File

@@ -208,13 +208,11 @@ func (s *pqScheduler) process(ctx context.Context) {
} else {
pqEnvTmpChIDStr := strconv.Itoa(int(pqEnvTmp.envelope.ChannelID))
s.metrics.PeerQueueDroppedMsgs.With("ch_id", pqEnvTmpChIDStr).Add(1)
s.logger.Debug(
"dropped envelope",
s.logger.Debug("dropped envelope",
"ch_id", pqEnvTmpChIDStr,
"priority", pqEnvTmp.priority,
"msg_size", pqEnvTmp.size,
"capacity", s.capacity,
)
"capacity", s.capacity)
s.metrics.PeerPendingSendBytes.With("peer_id", string(pqEnvTmp.envelope.To)).Add(float64(-pqEnvTmp.size))
@@ -238,13 +236,11 @@ func (s *pqScheduler) process(ctx context.Context) {
// There is not sufficient capacity to drop lower priority Envelopes,
// so we drop the incoming Envelope.
s.metrics.PeerQueueDroppedMsgs.With("ch_id", chIDStr).Add(1)
s.logger.Debug(
"dropped envelope",
s.logger.Debug("dropped envelope",
"ch_id", chIDStr,
"priority", pqEnv.priority,
"msg_size", pqEnv.size,
"capacity", s.capacity,
)
"capacity", s.capacity)
}
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/tendermint/tendermint/crypto"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
"github.com/tendermint/tendermint/types"
@@ -461,7 +462,7 @@ func (r *Router) acceptPeers(ctx context.Context, transport Transport) {
closeErr := conn.Close()
r.logger.Debug("rate limiting incoming peer",
"err", err,
"ip", incomingIP.String(),
"ip", tmstrings.LazyStringer(incomingIP),
"close_err", closeErr,
)

View File

@@ -41,6 +41,10 @@ func echoReactor(ctx context.Context, channel p2p.Channel) {
}
func TestRouter_Network(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -162,6 +166,10 @@ func TestRouter_Channel_Basic(t *testing.T) {
// Channel tests are hairy to mock, so we use an in-memory network instead.
func TestRouter_Channel_SendReceive(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -224,6 +232,10 @@ func TestRouter_Channel_SendReceive(t *testing.T) {
}
func TestRouter_Channel_Broadcast(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
ctx, cancel := context.WithCancel(context.Background())
@@ -255,6 +267,10 @@ func TestRouter_Channel_Broadcast(t *testing.T) {
}
func TestRouter_Channel_Wrapper(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
ctx, cancel := context.WithCancel(context.Background())
@@ -443,6 +459,11 @@ func TestRouter_AcceptPeers(t *testing.T) {
}
func TestRouter_AcceptPeers_Errors(t *testing.T) {
if testing.Short() {
// Each subtest takes more than one second due to the time.Sleep call,
// so just skip from the parent test in short mode.
t.Skip("skipping test in short mode")
}
for _, err := range []error{io.EOF, context.Canceled, context.DeadlineExceeded} {
t.Run(err.Error(), func(t *testing.T) {
@@ -480,9 +501,7 @@ func TestRouter_AcceptPeers_Errors(t *testing.T) {
router.Stop()
mockTransport.AssertExpectations(t)
})
}
}
@@ -811,6 +830,10 @@ func TestRouter_EvictPeers(t *testing.T) {
}
func TestRouter_ChannelCompatability(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -59,6 +59,10 @@ func TestMConnTransport_AcceptBeforeListen(t *testing.T) {
}
func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -126,6 +126,10 @@ func TestBlockQueueWithFailures(t *testing.T) {
// Test that when all the blocks are retrieved that the queue still holds on to
// it's workers and in the event of failure can still fetch the failed block
func TestBlockQueueBlocks(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
peerID, err := types.NewNodeID("0011223344556677889900112233445566778899")
require.NoError(t, err)
queue := newBlockQueue(startHeight, stopHeight, 1, stopTime, 2)
@@ -176,6 +180,10 @@ loop:
}
func TestBlockQueueAcceptsNoMoreBlocks(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
peerID, err := types.NewNodeID("0011223344556677889900112233445566778899")
require.NoError(t, err)
queue := newBlockQueue(startHeight, stopHeight, 1, stopTime, 1)

View File

@@ -686,37 +686,31 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel
func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope, chunkCh p2p.Channel) error {
switch msg := envelope.Message.(type) {
case *ssproto.ChunkRequest:
r.logger.Debug(
"received chunk request",
r.logger.Debug("received chunk request",
"height", msg.Height,
"format", msg.Format,
"chunk", msg.Index,
"peer", envelope.From,
)
"peer", envelope.From)
resp, err := r.conn.LoadSnapshotChunk(ctx, &abci.RequestLoadSnapshotChunk{
Height: msg.Height,
Format: msg.Format,
Chunk: msg.Index,
})
if err != nil {
r.logger.Error(
"failed to load chunk",
r.logger.Error("failed to load chunk",
"height", msg.Height,
"format", msg.Format,
"chunk", msg.Index,
"err", err,
"peer", envelope.From,
)
"peer", envelope.From)
return nil
}
r.logger.Debug(
"sending chunk",
r.logger.Debug("sending chunk",
"height", msg.Height,
"format", msg.Format,
"chunk", msg.Index,
"peer", envelope.From,
)
"peer", envelope.From)
if err := chunkCh.Send(ctx, p2p.Envelope{
To: envelope.From,
Message: &ssproto.ChunkResponse{
@@ -739,13 +733,11 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope
return nil
}
r.logger.Debug(
"received chunk; adding to sync",
r.logger.Debug("received chunk; adding to sync",
"height", msg.Height,
"format", msg.Format,
"chunk", msg.Index,
"peer", envelope.From,
)
"peer", envelope.From)
_, err := r.syncer.AddChunk(&chunk{
Height: msg.Height,
Format: msg.Format,
@@ -754,14 +746,12 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope
Sender: envelope.From,
})
if err != nil {
r.logger.Error(
"failed to add chunk",
r.logger.Error("failed to add chunk",
"height", msg.Height,
"format", msg.Format,
"chunk", msg.Index,
"err", err,
"peer", envelope.From,
)
"peer", envelope.From)
return nil
}

View File

@@ -197,6 +197,10 @@ func setup(
}
func TestReactor_Sync(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
@@ -618,6 +622,10 @@ func TestReactor_StateProviderP2P(t *testing.T) {
}
func TestReactor_Backfill(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -626,6 +634,10 @@ func TestReactor_Backfill(t *testing.T) {
for _, failureRate := range failureRates {
failureRate := failureRate
t.Run(fmt.Sprintf("failure rate: %d", failureRate), func(t *testing.T) {
if testing.Short() && failureRate > 0 {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()

View File

@@ -84,11 +84,9 @@ func (s *syncer) AddChunk(chunk *chunk) (bool, error) {
return false, err
}
if added {
s.logger.Debug("Added chunk to queue", "height", chunk.Height, "format", chunk.Format,
"chunk", chunk.Index)
s.logger.Debug("Added chunk to queue", "height", chunk.Height, "format", chunk.Format, "chunk", chunk.Index)
} else {
s.logger.Debug("Ignoring duplicate chunk in queue", "height", chunk.Height, "format", chunk.Format,
"chunk", chunk.Index)
s.logger.Debug("Ignoring duplicate chunk in queue", "height", chunk.Height, "format", chunk.Format, "chunk", chunk.Index)
}
return added, nil
}
@@ -137,12 +135,20 @@ func (s *syncer) SyncAny(
discoveryTime = minimumDiscoveryTime
}
timer := time.NewTimer(discoveryTime)
defer timer.Stop()
if discoveryTime > 0 {
if err := requestSnapshots(); err != nil {
return sm.State{}, nil, err
}
s.logger.Info(fmt.Sprintf("Discovering snapshots for %v", discoveryTime))
time.Sleep(discoveryTime)
s.logger.Info("discovering snapshots",
"interval", discoveryTime)
select {
case <-ctx.Done():
return sm.State{}, nil, ctx.Err()
case <-timer.C:
}
}
// The app may ask us to retry a snapshot restoration, in which case we need to reuse
@@ -151,8 +157,11 @@ func (s *syncer) SyncAny(
snapshot *snapshot
chunks *chunkQueue
err error
iters int
)
for {
iters++
// If not nil, we're going to retry restoration of the same snapshot.
if snapshot == nil {
snapshot = s.snapshots.Best()
@@ -162,9 +171,16 @@ func (s *syncer) SyncAny(
if discoveryTime == 0 {
return sm.State{}, nil, errNoSnapshots
}
s.logger.Info(fmt.Sprintf("Discovering snapshots for %v", discoveryTime))
time.Sleep(discoveryTime)
continue
s.logger.Info("discovering snapshots",
"iterations", iters,
"interval", discoveryTime)
timer.Reset(discoveryTime)
select {
case <-ctx.Done():
return sm.State{}, nil, ctx.Err()
case <-timer.C:
continue
}
}
if chunks == nil {
chunks, err = newChunkQueue(snapshot, s.tempDir)
@@ -494,13 +510,11 @@ func (s *syncer) requestChunk(ctx context.Context, snapshot *snapshot, chunk uin
return nil
}
s.logger.Debug(
"Requesting snapshot chunk",
s.logger.Debug("Requesting snapshot chunk",
"height", snapshot.Height,
"format", snapshot.Format,
"chunk", chunk,
"peer", peer,
)
"peer", peer)
msg := p2p.Envelope{
To: peer,

View File

@@ -22,6 +22,10 @@ import (
)
func TestSyncer_SyncAny(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -55,9 +55,7 @@ func NewDefaultLogger(format, level string) (Logger, error) {
// make the writer thread-safe
logWriter = newSyncWriter(logWriter)
return &defaultLogger{
Logger: zerolog.New(logWriter).Level(logLevel).With().Timestamp().Logger(),
}, nil
return &defaultLogger{Logger: zerolog.New(logWriter).Level(logLevel).With().Timestamp().Logger()}, nil
}
func (l defaultLogger) Info(msg string, keyVals ...interface{}) {
@@ -73,9 +71,7 @@ func (l defaultLogger) Debug(msg string, keyVals ...interface{}) {
}
func (l defaultLogger) With(keyVals ...interface{}) Logger {
return &defaultLogger{
Logger: l.Logger.With().Fields(keyVals).Logger(),
}
return &defaultLogger{Logger: l.Logger.With().Fields(keyVals).Logger()}
}
// OverrideWithNewLogger replaces an existing logger's internal with

View File

@@ -9,6 +9,7 @@ import (
"sync"
"time"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/libs/log"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/light/provider"
@@ -475,7 +476,8 @@ func (c *Client) VerifyHeader(ctx context.Context, newHeader *types.Header, now
return fmt.Errorf("existing trusted header %X does not match newHeader %X", l.Hash(), newHeader.Hash())
}
c.logger.Debug("header has already been verified",
"height", newHeader.Height, "hash", newHeader.Hash())
"height", newHeader.Height,
"hash", tmstrings.LazyBlockHash(newHeader))
return nil
}
@@ -576,7 +578,7 @@ func (c *Client) verifySequential(
// 2) Verify them
c.logger.Debug("verify adjacent newLightBlock against verifiedBlock",
"trustedHeight", verifiedBlock.Height,
"trustedHash", verifiedBlock.Hash(),
"trustedHash", tmstrings.LazyBlockHash(verifiedBlock),
"newHeight", interimBlock.Height,
"newHash", interimBlock.Hash())
@@ -663,9 +665,9 @@ func (c *Client) verifySkipping(
for {
c.logger.Debug("verify non-adjacent newHeader against verifiedBlock",
"trustedHeight", verifiedBlock.Height,
"trustedHash", verifiedBlock.Hash(),
"trustedHash", tmstrings.LazyBlockHash(verifiedBlock),
"newHeight", blockCache[depth].Height,
"newHash", blockCache[depth].Hash())
"newHash", tmstrings.LazyBlockHash(blockCache[depth]))
// Verify the untrusted header. This function is equivalent to
// ValidAndVerified in the spec
@@ -897,9 +899,9 @@ func (c *Client) backwards(
interimHeader = interimBlock.Header
c.logger.Debug("verify newHeader against verifiedHeader",
"trustedHeight", verifiedHeader.Height,
"trustedHash", verifiedHeader.Hash(),
"trustedHash", tmstrings.LazyBlockHash(verifiedHeader),
"newHeight", interimHeader.Height,
"newHash", interimHeader.Hash())
"newHash", tmstrings.LazyBlockHash(interimHeader))
if err := VerifyBackwards(interimHeader, verifiedHeader); err != nil {
// verification has failed
c.logger.Info("backwards verification failed, replacing primary...", "err", err, "primary", c.primary)

View File

@@ -39,8 +39,10 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig
lastVerifiedHeader = primaryTrace[len(primaryTrace)-1].SignedHeader
witnessesToRemove = make([]int, 0)
)
c.logger.Debug("running detector against trace", "finalizeBlockHeight", lastVerifiedHeader.Height,
"finalizeBlockHash", lastVerifiedHeader.Hash, "length", len(primaryTrace))
c.logger.Debug("running detector against trace",
"finalizeBlockHeight", lastVerifiedHeader.Height,
"finalizeBlockHash", lastVerifiedHeader.Hash,
"length", len(primaryTrace))
// launch one goroutine per witness to retrieve the light block of the target height
// and compare it with the header from the primary

View File

@@ -235,6 +235,10 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
}
func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// primary performs a lunatic attack but changes the time of the header to
// something in the future relative to the blockchain
var (

View File

@@ -17,6 +17,10 @@ import (
// Manually getting light blocks and verifying them.
func TestExampleClient(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conf, err := rpctest.CreateConfig(t, "ExampleClient_VerifyLightBlockAtHeight")

View File

@@ -23,6 +23,10 @@ import (
// Automatically getting new headers and verifying them.
func TestClientIntegration_Update(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
@@ -84,6 +88,10 @@ func TestClientIntegration_Update(t *testing.T) {
// Manually getting light blocks and verifying them.
func TestClientIntegration_VerifyLightBlockAtHeight(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -162,6 +170,10 @@ func waitForBlock(ctx context.Context, p provider.Provider, height int64) (*type
}
func TestClientStatusRPC(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conf, err := rpctest.CreateConfig(t, t.Name())

View File

@@ -33,6 +33,10 @@ func TestNewProvider(t *testing.T) {
}
func TestProvider(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cfg, err := rpctest.CreateConfig(t, t.Name())

View File

@@ -106,6 +106,10 @@ func getTestNode(ctx context.Context, t *testing.T, conf *config.Config, logger
}
func TestNodeDelayedStart(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
cfg, err := config.ResetTestRoot(t.TempDir(), "node_delayed_start_test")
require.NoError(t, err)
@@ -195,6 +199,10 @@ func TestNodeSetPrivValTCP(t *testing.T) {
// address without a protocol must result in error
func TestPrivValidatorListenAddrNoProtocol(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -441,6 +449,10 @@ func TestMaxTxsProposalBlockSize(t *testing.T) {
}
func TestMaxProposalBlockSize(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -18,6 +18,10 @@ import (
)
func TestHTTPSimple(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -68,6 +72,10 @@ func TestHTTPSimple(t *testing.T) {
}
func TestHTTPBatching(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -15,6 +15,10 @@ import (
)
func TestWaitForHeight(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -133,6 +133,10 @@ func TestClientOperations(t *testing.T) {
})
t.Run("Batching", func(t *testing.T) {
t.Run("JSONRPCCalls", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
logger := log.NewTestingLogger(t)
c := getHTTPClient(t, logger, conf)
testBatchedJSONRPCCalls(ctx, t, c)
@@ -171,6 +175,10 @@ func TestClientOperations(t *testing.T) {
require.Zero(t, batch.Clear(), "clearing an empty batch of JSON RPC requests should result in a 0 result")
})
t.Run("ConcurrentJSONRPC", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
logger := log.NewTestingLogger(t)
var wg sync.WaitGroup
@@ -291,6 +299,10 @@ func TestClientMethodCalls(t *testing.T) {
"first: %+v, doc: %s", first, string(doc))
})
t.Run("ABCIQuery", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// write something
k, v, tx := MakeTxKV()
status, err := c.Status(ctx)
@@ -309,6 +321,10 @@ func TestClientMethodCalls(t *testing.T) {
}
})
t.Run("AppCalls", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// get an offset of height to avoid racing and guessing
s, err := c.Status(ctx)
require.NoError(t, err)
@@ -409,6 +425,10 @@ func TestClientMethodCalls(t *testing.T) {
// XXX Test proof
})
t.Run("BlockchainInfo", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -439,6 +459,10 @@ func TestClientMethodCalls(t *testing.T) {
assert.Contains(t, err.Error(), "can't be greater than max")
})
t.Run("BroadcastTxCommit", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
_, _, tx := MakeTxKV()
bres, err := c.BroadcastTxCommit(ctx, tx)
require.NoError(t, err, "%d: %+v", i, err)
@@ -481,6 +505,10 @@ func TestClientMethodCalls(t *testing.T) {
// TODO: more checks...
})
t.Run("Block", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
const subscriber = "TestBlockEvents"
eventCh, err := c.Subscribe(ctx, subscriber, types.QueryForEvent(types.EventNewBlockValue).String())
@@ -515,6 +543,10 @@ func TestClientMethodCalls(t *testing.T) {
})
t.Run("Evidence", func(t *testing.T) {
t.Run("BroadcastDuplicateVote", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -722,6 +754,10 @@ func TestClientMethodCallsAdvanced(t *testing.T) {
}
})
t.Run("TxSearchWithTimeout", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
logger := log.NewTestingLogger(t)
timeoutClient := getHTTPClientWithTimeout(t, logger, conf, 10*time.Second)

View File

@@ -65,6 +65,10 @@ func (h *myTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func TestWSClientReconnectsAfterReadFailure(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
// start server
@@ -97,6 +101,10 @@ func TestWSClientReconnectsAfterReadFailure(t *testing.T) {
}
func TestWSClientReconnectsAfterWriteFailure(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
// start server
@@ -127,6 +135,10 @@ func TestWSClientReconnectsAfterWriteFailure(t *testing.T) {
}
func TestWSClientReconnectFailure(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
t.Cleanup(leaktest.Check(t))
// start server

View File

@@ -340,6 +340,10 @@ func TestRPC(t *testing.T) {
}
})
t.Run("WSClientPingPong", func(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// TestWSClientPingPong checks that a client & server exchange pings
// & pongs so connection stays alive.
t.Cleanup(leaktest.CheckTimeout(t, 4*time.Second))

View File

@@ -58,7 +58,7 @@ func DefaultConfig() *Config {
// Serve creates a http.Server and calls Serve with the given listener. It
// wraps handler to recover panics and limit the request body size.
func Serve(ctx context.Context, listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error {
logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr()))
logger.Info("Starting RPC HTTP server on", "addr", listener.Addr())
h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger)
s := &http.Server{
Handler: h,

14
spec/p2p/v0.35/README.md Normal file
View File

@@ -0,0 +1,14 @@
# Peer-to-peer communication substrate - WIP
This document details the operation of the [`p2p`][p2p-package] package of
Tendermint, refactored in the `v0.35` release.
**This is a work in progress** ([#8935][issue]). The following files represent the current (unfinished) state of this documentation. It has been decided not to finish the documents at this point in time, but to publish them here in the current form for future reference.
- [Peer manager](./peer_manager.md): determines when a node should connect to a
new peer, and which peer is preferred for establishing connections.
- [Router](./router.md): implements the actions instructed by the peer manager,
and route messages between the local reactors and the remote peers.
[issue]: https://github.com/tendermint/tendermint/issues/8935
[p2p-package]: https://github.com/tendermint/tendermint/tree/v0.35.x/internal/p2p

View File

@@ -0,0 +1,473 @@
# Peer manager - WIP
The peer manager is the component of the *new* p2p layer that implements
the connection policy for the node, based on the
configuration provided by the operators, the current state of the connections
reported by the [`Router`](./router.md), and the set of known candidate peers.
This document uses *candidate peer* to refer to the information about a node in
the network, namely its unique identifier and one or more network addresses.
This information can be manually configured by the node operator (e.g., via
`PersistentPeers` parameter) or can be obtained via the Peer-Exchange Protocol
(PEX), which feeds the peer manager with discovered peers' information.
## Connection policy
The connection policy defines:
1. When the node should establish new connections to peers, and
1. The next peer to which the router should try to establish a connection.
The first definition is made based on the concept of [connection slots](#connection-slots).
In short, the peer manager will try to fill every connection slot with a peer.
If all the connection slots are full but there is the possibility to connect to
a peer that is higher-[ranked](#peer-ranking) than one of the connecting peers,
the peer manager may attempt to [upgrade a connection slot](#slot-upgrades).
Details of these operations are provided in the following.
### Connection slots
The number of connection slots is defined by the `MaxConnected` parameter.
While there are available connection slots, the peer manager will provide
candidate peers to the router, which will try to establish
new connections to them.
When the peer manager provides a candidate peer to
the router, a connection slot becomes _virtually_ occupied by the peer, as the
router should be dialing it.
When the router establishes a connection to a peer, either
because it [accepted a connection](#accepted-transition) from a peer,
or because it [successfully dialed](#dialed-transition) a candidate peer,
the peer manager should find a slot for this connection.
If there is an available connection slot, and this is the first connection
established with that peer, the slot is filled by the new connection and
the peer becomes a [connected peer](#connected-peer).
The peer manager does not allow two slots to be filled with connections to the
same peer.
If all `MaxConnected` connection slots are full, the node should _a priori_
reject the connection established with or accepted from the peer.
However, it is possible that the new connection is with a peer whose score is
better than the score of a peer occupying one of the connection slots.
In this case, the peer manager will try to [upgrade the slot](#slot-upgrades)
to make room to the new connection, by evicting the peer currently occupying
this slot.
> Although not advisable, the `MaxConnected` parameter can be set to `0`, which
> means no limit.
>
> In this case, the node will accept all connections established by peers, and
> will try to establish connections (dial) to every candidate peer it knows.
### Outgoing connections
The peer manager distinguishes *incoming* from *outgoing* connections.
A connection is *incoming* when the router has [accepted](#accepted-transition)
it from a peer.
A connection is *outgoing* when the router has successfully
[dialed](#dialed-transition) a peer.
If the `MaxOutgoingConnections` parameter is set (i.e., it is greater than zero), it
defines the maximum number of *outgoing* connections the node should maintain.
More precisely, it determines that the node should not attempt to dial new
peers when the router already has established outgoing connections to
`MaxOutgoingConnections` peers.
This parameter cannot be set to a value larger than `MaxConnected`.
> The previous version of the `p2p` explicitly distinguished incoming and
> outgoing peers. Configuring the `MaxOutgoingConnections` parameters should
> therefore make the connection policy similar to the one adopted in the
> previous version. (TODO: check)
### Slot upgrades
The rationale behind this concept is that the node may try to establish or
accept connections even when all the connection slots are full, provided that
the peer in the other side of the new connection is better-ranked than a peer
that is occupying a connection slot.
A slot can therefore be upgraded, meaning that the lower-ranked peer
occupying the slot will be replaced by a higher-ranked peer.
The upgrading of connection slots is determined by the `MaxConnectedUpgrade`
parameter, which defines the number of connections that the peer manager can
use for upgrading connection slots.
If `MaxConnectedUpgrade` is set to zero, the upgrading of connection slots is
disabled.
This means, in particular, that `MaxConnected` is the hard limit of peers that
can be in the [connected state](#connected-peer).
If `MaxConnectedUpgrade` is larger than zero, the upgrading of connection slots
is enabled.
As a result, the hard limit for the number of peers that can be in the
[connected state](#connected-peer) becomes `MaxConnected + MaxConnectedUpgrade`.
Some of these peers, however, will not remain in this state as they should be
[evicted](#evictnext-transition) by the router.
### Peer ranking
The peer manager should rank peers based on user-provided parameters and on the
current state of the peer.
The ranking is established by ordering all known peers by its score.
This mechanism is currently very basic.
> The code contains a number of potential replacements for this ranking
> mechanism. Therefore, improving this mechanism is a work in progress.
Peers configured as `PersistentPeers` have _always_ `PeerScorePersistent`,
which is the maximum allowed peer score.
The remaining peers have a `MutableScore`, initialized to `0` when the peer is
added to the peer manager.
When the peer is reported as a `PeerStatusGood`, its score is incremented.
When the peer is reported as a `PeerStatusBad`, its score is decremented.
> The mechanisms based on the "reputation" of the peer according to reactors,
> however, appears not to be fully implemented.
> A peer is never `PeerStatusGood`, and is only reported as `PeerStatusBad` a
> reactor interacting with the peer reports an error to the router, and the
> error is not "fatal".
> If the error is fatal, the peer is reported as [errored](#errored-transition).
This score can also be _temporarily_ decremented due to connection errors.
When the router fails to dial to a peer, it increments the peer's
`DialFailures` counter.
This counter is reset when the router successfully dials the peer, establishing
a connection to it.
During this period, between dial failures and succeeding to dial the peer, the
peer score is decremented by the `DialFailures` counter.
> `DialFailures` actually refers to a peer address. A peer may have multiple
> addresses, and all associated counters are considered for decrementing the
> peer's score. Also, all counters are reset when the router succeeds dialing
> the peer.
## Peer life cycle
For implementing the connection policy, the peer manager keeps track of the
state of peers and manages their life-cycle.
The life cycle of a peer is summarized in the picture below.
The circles represent _states_ of a peer and the rectangles represent
_transitions_.
All transitions are performed by the `Router`, by invoking methods of the peer
manager with corresponding names.
Normal transitions are represented by green arrows, while red arrows represent
alternative transitions taken in case of errors.
<img src="pics/p2p-v0.35-peermanager.png" alt="peer life cycle" title="" width="600px" name="" align="center"/>
### Candidate peer
The initial state of a peer in the peer manager.
A `Candidate` peer may become an actual peer, to which the node is connected.
We do not use candidate to refer to a peer to which we are connected, nor to
a peer we are attempting to connect.
Candidate peers from which the router recently disconnected or failed to dial
are, during a certain period, not eligible for establishing connections.
This scenario is represented by the `Frozen Candidate` state.
### DialNext transition
This state transition produces candidate peers the node should dial to, which
are consumed by the [dialing routine](./router.md#dialing-peers) of the router.
The transition is performed when the [connection policy](#connection-policy)
determines that the node should try to establish a connection with a peer.
The algorithm controlling this state transition can be synthesized as follows:
1. Wait until there are peers in `Candidate` state
1. Select the best-ranked `peer` in `Candidate` state
1. If `|Candidate + Dialing| < MaxConnected`, returns the selected `peer`
1. Else if `|Candidate + Dialing| < MaxConnected + MaxConnectedUpgrade`, try to
find a connection `slot` to upgrade to give room to the selected `peer`
1. If a connection `slot` to upgrade is found, set the peer in the slot to
the `Upgrading` sub-state and returns the selected `peer`
The peer manager selects the [best-ranked](#peer-ranking) peer which is in the
[`Candidate`](#candidate-peer) state and provides it to the router.
As the router is supposed to dial the peer, the peer manager sets the peer to
the [dialing](#dialing-peer) state.
Dialing a candidate peer may have become possible because the peer manager
has found a [connection slot to upgrade](#slot-upgrades) to given room to the
selected candidate peer.
If this is the case, the peer occupying this connection slot is set to the
[upgrading state](#upgrading-peer), and will be evicted once the
connection to the candidate peer is successfully established.
### Dialing peer
A peer that has been returned to the router as the next peer to dial.
The router should be attempting to connect to this peer.
A peer in `Dialing` state is not considered as a candidate peer.
### Dialed transition
This transition is performed when the node establishes an outgoing connection
with a peer.
This means that the peer manager has provided this peer to the router as the
[next peer to dial](#dialnext-transition), and the router has dialed and
successfully established a connection with the peer.
The peer is thus expected to be in the `Dialing` state.
It may occur, however, that when this transition is invoked the peer is already
in the `Connected` state.
The most likely reason is that the router, while dialing this peer, has also
accepted an incoming connection from the same peer.
In this case, the transition fails, indicating to the router that is should
close the newly established connection.
It may also occur that the node is already connected to `MaxConnected` peers,
which means that all connection slots are full.
In this case, the peer manager tries to find a connection slot that can be
[upgraded](#slot-upgrades) to give room for the new established connection.
If no suitable connection slot is found, the transitions fails.
This logic considered in this step is synthesized by the following algorithm:
1. If `|Connected| < MaxConnected`, the transition succeeds
1. The established connection occupies one of the available connection slots
1. If a connected peer was put in the `Upgrading` sub-state to give room to this peer
1. Let `slot` be the connection slot occupied by this peer
1. If `|Connected| < MaxConnected + MaxConnectedUpgrade`
1. Let `slot` be a connection slot that can be upgraded to give room to
the established connection, if any
1. If `slot` is set to a valid connection slot, the transition succeeds
1. Set the peer occupying `slot` to the `Evict` sub-state
1. The established connection occupies one of the connection slots reserved for upgrades
1. Else the transition fails and the connection is refused
Notice that, in order to dial this peer, the peer manager may have put another
lower-ranked peer in the [upgrading sub-state](#upgrading-peer) to give room
to this connection.
In this case, as illustrated above, the slot for the established connection was
*reserved*, and this transition will not fail.
If the transition succeeds, the peer is set to the
[`Connected`](#connected-peer) state as an _outgoing_ peer.
The peer's `LastConnected` and the dialed address' `LastDialSuccess` times are
set, and dialed address' `DialFailures` counter is reset.
> If the peer is `Inactive`, it is set as active.
> This action has no effect apart from producing metrics.
If a connection slot was upgraded to give room for the established connection, the
peer on that slot transitions to the [evict sub-state](#evict-peer).
#### Errors
The transition fails if:
- the node dialed itself
- the peer is already in the `Connected` state
- the node is connected to `MaxConnected` peers, and no slot is suitable for upgrading
Errors are also returned if:
- the dialed peer was pruned from the peer store (because it had more than `MaxPeers` stored)
- the updated peer information is invalid
- there is an error when saving the peer state to the peer store
### DialFailed transition
This transition informs of a failure when establishing an outgoing connection to
a peer.
The dialed address's `LastDialFailure` time is set, and its `DialFailures`
counter is increased.
This information is used to compute the [retry delay](#retry-delay) for the
dialed address.
The peer manager then spawns a routine that after the computed retry delay
notifies the next peer to dial routine about the availability of this peer.
Until then, the peer is the `Frozen Candidate` state.
#### Retry delay
The retry delay is the minimum time, from the latest failed dialing attempt, we
should wait until dialing a peer address again.
The default delay is defined by `MinRetryTime` parameter.
If it is set to zero, we *never* retry dialing a peer address.
Upon each failed dial attempt, we increase the delay by `MinRetryTime`, plus an
optional random jitter of up to `RetryTimeJitter`.
The retry delay should not be longer than the `MaxRetryTime` parameter,
or `MaxRetryTimePersistent` parameter in the case of persistent peers.
#### Errors
Errors are also returned if:
- the updated peer information is invalid
- there is an error when saving the peer state to the peer store
### Accepted transition
This transition is performed when the node establishes an *incoming* connection
with a peer.
This means that the router has received a connection attempt from this peer and
successfully established a connection with it.
It may occur, however, that when this transition is invoked the peer is already
in the [`Connected`](#connected-peer) state.
The most likely reason is that the router was simultaneously dialing the same
peer, and has successfully [established a connection](#dialed-transition) with
it.
In this case, the transition fails, indicating to the router that it should
close the accepted connection.
It may also occur that the node is already connected to `MaxConnected` peers,
which means that all connection slots are full.
In this case, the peer manager tries to find a connection slot that can be
[upgraded](#slot-upgrades) to give room for the accepted connection.
If no suitable connection slot is found, the transitions fails.
This logic considered in this step is synthesized by the following algorithm:
1. If `|Connected| < MaxConnected`, the transition succeeds
1. The established connection occupies one of the available connection slots
1. Let `slot` be a connection slot that can be upgraded to give room to the
established connection, if any
1. If `|Connected| < MaxConnected + MaxConnectedUpgrade` and `slot` is set to a
valid connection slot, the transition succeeds
1. Set the peer occupying `slot` to the `Evict` sub-state
1. The established connection occupies one of the connection slots reserved for upgrades
1. Else the transition fails and the connection is refused
If the transition succeeds, the peer is set to the
[`Connected`](#connected-peer) state as an *incoming* peer.
The accepted peer might not be known by the peer manager.
In this case the peer is registered in the peer store, without any associated
address (as the connection remote address usually _is not_ the peer's listen address).
The peer's `LastConnected` time is set and the `DialFailures` counter is reset
for all addresses associated to the peer.
> If the peer is `Inactive`, it is set as active.
> This action has not effect apart from producing metrics.
If a connection slot was upgraded to give room for the accepted connection, the
peer on that slot transitions to the [evict sub-state](#evict-peer).
#### Errors
The transition fails if:
- the node accepted itself
- the peer is already in the `Connected` state
- the node is connected to `MaxConnected` peers, and no slot is suitable for upgrading
Errors are also returned if:
- the updated peer information is invalid
- there is an error when saving the peer state to the peer store
### Connected peer
A peer to which the node is connected.
A peer in this state is not considered a candidate peer.
The peer manager distinguishes *incoming* from *outgoing* connections.
Incoming connections are established through the [`Accepted`](#accepted-transition) transition.
Outgoing connections are established through the [`Dialed`](#dialed-transition) transition.
### Ready transition
By invoking this transition, the router notifies the peer manager that it is
ready to exchange messages with a peer.
The router invokes this transition just after successfully performing the
[`Dialed`](#dialed-transition) or [`Accepted`](#accepted-transition) transitions,
providing to the peer manager a list of channels supported by the peer.
This information is broadcast to all reactors in a `PeerUpdate` message that
informs the new state (up) of the peer.
This transition is not represented in the picture because it does not change
the state of the peer, which should be in the [`Connected`](#connected-peer) state.
### Disconnected transition
This transition is performed when the node disconnects from a peer.
It is invoked by the router when an error is returned by the routines used to
exchange messages with the peer.
The peer is expected to be in the [`Connected`](#connected-peer) state.
If the [`Ready`](#ready-transition) transition has been performed, the peer manager broadcasts a
`PeerUpdate` to all reactors notifying the new status (down) of this peer.
If the peer is still present in the peer store, its `LastDisconnected` time is
set and the peer manager spawns a routine that after `DisconnectCooldownPeriod`
notifies the next peer to dial routine about the availability of this peer.
Until then, the peer is the `Frozen Candidate` state.
### Errored transition
This transition is performed when a reactor interacting with the peer reports
an error to the router.
The peer is expected to be in the [`Connected`](#connected-peer) state.
If so, the peer transitions to the [`Evict`](#evict-peer) sub-state, which
should lead the router to disconnect from the peer, and the next peer to evict
routine is notified.
### Upgrading peer
A [connected peer](#connected-peer) which should be evicted to give room to a
higher-ranked peer the router is dialing to.
The `Upgrading` sub-state is part of the procedure to [upgrade connection slots](#slot-upgrades).
When a connection with the higher-ranked peer that should take the connection
slot from this peer is [established](#dialed-transition), the
[eviction](#evict-peer) of this peer is scheduled.
### Evict peer
A peer whose eviction was scheduled, for either of the following reasons:
1. to give room to a higher-ranked peer the router is connected to, as part of
the procedure to [upgrade connection slots](#slot-upgrades),
2. or because this peer was reported as [errored](#errored-transition) by a
reactor interacting with this peer.
This peer is a [connected peer](#connected-peer).
`Evict` is the first sub-state of the procedure that should lead the node to
[disconnect](#disconnected-transition) from a peer.
### EvictNext transition
This transition returns a peer to the router to evict.
The state transition is performed whenever the peer manager has scheduled the
eviction of a peer, i.e., whenever there is a peer on `Evict` sub-state.
The peer to evict must be a peer in the `Connected` state.
The peer to evict is randomly picked from the possible multiple peers with
eviction scheduled.
> This transition is invoked when the next to evict routine is notified by
> another routine.
> In some cases, the transition is processed when no peer should be evicted. In
> this case, if the connections slots are not full, or there are enough peers
> in the `Evicting` state so to respect the `MaxConnected` parameter, the
> transition is not taken.
> Otherwise, the peer with the lowest rank is evicted. This should not occur,
> from comments in the code, but this is something to check.
### Evicting peer
A peer whose eviction is in progress.
A peer transitions to this sub-state when it is returned to the router by the
[next peer to evict](#evictnext-transition) transition.
This peer is still a [connected peer](#connected-peer).
`Evicting` is the second and last sub-state of the procedure for
[disconnecting](#disconnected-transition) from a peer.
[peermanager.go]: https://github.com/tendermint/tendermint/blob/v0.35.x/internal/p2p/peermanager.go

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

128
spec/p2p/v0.35/router.md Normal file
View File

@@ -0,0 +1,128 @@
# Router - WIP
The router is the component of the *new* p2p layer
responsible for establishing connection with peers,
and for routing messages from reactors to peers and vice-versa.
## Dialing peers
The router maintains a persistent routine `dialPeers()` consuming
[candidate peers to dial](./peer_manager.md#dialnext-transition)
produced by the peer manager.
The consumed candidate peers (addresses) are provided for dialing routines,
retrieved from a pool with `numConcurrentDials()` threads.
The default number of threads in the pool is set to 32 times the number of
available CPUs.
> The 32 factor was introduced in [#8827](https://github.com/tendermint/tendermint/pull/8827),
> with the goal of speeding up the establishment of outbound connections.
The router thus dials a peer whenever there are: (i) a candidate peer to be
consumed and (ii) a dialing routine is available in the pool.
Given the size of the thread pool, the router is in practice is expected to
dial in parallel all candidate peers produced by the peer manager.
> There was a random-interval sleep between starting subsequent dialing
> routines. This behavior was removed by [#8839](https://github.com/tendermint/tendermint/pull/8839).
The dialing routine selected to dial to a peer runs by the `connectPeer()`
method, which:
1. Calls `dialPeer()` to establish a connection with the remote peer
1. In case of errors, invokes the `DialFailed` transition of the peer manager
1. Calls `handshakePeer()` with the established connection and the expected remote node ID
1. In case of errors, invokes the `DialFailed` transition of the peer manager
1. Reports the established outbound connection through the `Dialed` transition of the peer manager
1. In the transition fails, the established connection was refused
1. Spawns a `routePeer()` routine for the peer
> Step 3. above acquires a mutex, preventing concurrent calls from different threads.
> The reason is not clear, as all peer manager transitions are also protected by a mutex.
>
> Step 3i. above also notifies the peer manager's next peer to dial routine.
> This should trigger the peer manager to produce another candidate peer.
> TODO: check when this was introduced, as it breaks modularity.
In case of any of the above errors, the connection with the remote peer is
**closed** and the dialing routines returns.
## Accepting peers
The router maintains a persistent routine `acceptPeers()` consuming connections
accepted by each of the configured transports.
Each accepted connection is handled by a different `openConnection()` routine,
spawned for this purpose, that operate as follows.
There is no limit for the number of concurrent routines accepting peer's connections.
1. Calls `filterPeersIP()` with the peer address
1. If the peer IP is rejected, the method returns
1. Calls `handshakePeer()` with the accepted connection to retrieve the remote node ID
1. If the handshake fails, the method returns
1. Calls `filterPeersID()` with the peer ID (learned from the handshake)
1. If the peer ID is rejected, the method returns
1. Reports the established incoming connection through the `Accepted` transition of the peer manager
1. In the transition fails, the accepted connection was refused and the method returns
1. Switches to the `routePeer()` routine for the accepted peer
> Step 4. above acquires a mutex, preventing concurrent calls from different threads.
> The reason is not clear, as all peer manager transitions are also protected by a mutex.
In case of any of the above errors, the connection with the remote peer is
**closed**.
> TODO: Step 2. above has a limitation, commented in the source code, referring
> to absence of an ack/nack in the handshake, which may case further
> connections to be rejected.
> TODO: there is a `connTracker` in the router that rate limits addresses that
> try to establish connections to often. This procedure should be documented.
## Evicting peers
The router maintains a persistent routine `evictPeers()` consuming
[peers to evict](./peer_manager.md#evictnext-transition)
produced by the peer manager.
The eviction of a peer is performed by closing the send queue associated to the peer.
This queue maintains outbound messages destined to the peer, consumed by the
peer's send routine.
When the peer's send queue is closed, the peer's send routine is interrupted
with no errors.
When the [routing messages](#routing-messages) routine notices that the peer's
send routine was interrupted, it forces the interruption of peer's receive routine.
When both send and receive routines are interrupted, the router considers the
peer as disconnected, and its eviction has been done.
## Routing messages
When the router successfully establishes a connection with a peer, because it
dialed the peer or accepted a connection from the peer, it starts routing
messages from and to the peer.
This role is implemented by the `routePeer()` routine.
Initially, the router notifies the peer manager that the peer is
[`Ready`](./peer_manager.md#ready-transition).
This notification includes the list of channels IDs supported by the peer,
information obtained during the handshake process.
Then, the peer's send and receive routines are spawned.
The send routine receives the peer ID, the established connection, and a new
send queue associated with the peer.
The peer's send queue is fed with messages produced by reactors and destined to
the peer, which are sent to the peer through the established connection.
The receive routine receives the peer ID and the established connection.
Messages received through the established connections are forwarded to the
appropriate reactors, using message queues associated to each channel ID.
From this point, the routing routine will monitor the peer's send and receive routines.
When one of them returns, due to errors or because it was interrupted, the
router forces the interruption of the other.
To force the interruption of the send routine, the router closes the peer's
send queue. To force the interruption of the receive routine, the router closes
the connection established with the peer.
Finally, when both peer's send and receive routine return, the router notifies
the peer manager that the peer is [`Disconnected`](./peer_manager.md#disconnected-transition).

View File

@@ -18,6 +18,7 @@ import (
"github.com/tendermint/tendermint/abci/example/code"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
tmstrings "github.com/tendermint/tendermint/internal/libs/strings"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/version"
@@ -484,7 +485,10 @@ func (app *Application) ExtendVote(_ context.Context, req *abci.RequestExtendVot
time.Sleep(time.Duration(app.cfg.VoteExtensionDelayMS) * time.Millisecond)
}
app.logger.Info("generated vote extension", "num", num, "ext", fmt.Sprintf("%x", ext[:extLen]), "state.Height", app.state.Height)
app.logger.Info("generated vote extension",
"num", num,
"ext", tmstrings.LazySprintf("%x", ext[:extLen]),
"state.Height", app.state.Height)
return &abci.ResponseExtendVote{
VoteExtension: ext[:extLen],
}, nil

View File

@@ -3,7 +3,6 @@ package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/tendermint/tendermint/libs/log"
@@ -27,6 +26,6 @@ func Cleanup(ctx context.Context, logger log.Logger, testnetDir string, ti infra
return err
}
logger.Info(fmt.Sprintf("Removing testnet directory %q", testnetDir))
logger.Info("Removing testnet", "directory", testnetDir)
return os.RemoveAll(testnetDir)
}

View File

@@ -16,6 +16,10 @@ const (
)
func TestBasicPartSet(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// Construct random data of size partSize * 100
nParts := 100
data := tmrand.Bytes(testPartSize * nParts)
@@ -64,6 +68,10 @@ func TestBasicPartSet(t *testing.T) {
}
func TestWrongProof(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
// Construct random data of size partSize * 100
data := tmrand.Bytes(testPartSize * 100)
partSet := NewPartSetFromData(data, testPartSize)
@@ -89,6 +97,10 @@ func TestWrongProof(t *testing.T) {
}
func TestPartSetHeaderValidateBasic(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
testCases := []struct {
testName string
malleatePartSetHeader func(*PartSetHeader)
@@ -110,6 +122,10 @@ func TestPartSetHeaderValidateBasic(t *testing.T) {
}
func TestPartValidateBasic(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
testCases := []struct {
testName string
malleatePart func(*Part)

View File

@@ -1207,6 +1207,10 @@ func applyChangesToValSet(t *testing.T, expErr error, valSet *ValidatorSet, vals
}
func TestValSetUpdatePriorityOrderTests(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
const nMaxElections int32 = 5000
testCases := []testVSetCfg{