Compare commits

..

40 Commits

Author SHA1 Message Date
Erik Grinaker
1b1fe4d7cc prepare 0.33.7 release (#5202) 2020-08-04 13:21:05 +02:00
Anton Kaliaev
8026fd3cc9 privval: if remote signer errors, don't retry (#5140)
Closes #5112
2020-08-04 13:21:05 +02:00
Tess Rinearson
606d0a89cc changelog: tweak 0.33.6 entry 2020-07-02 15:41:49 +02:00
Anton Kaliaev
cefeab0fbb update changelog and bump version 2020-07-02 15:41:49 +02:00
Anton Kaliaev
8ccfdb96d0 consensus: Do not allow signatures for a wrong block in commits
Closes #4926

The dump consensus state had this:

      "last_commit": {
        "votes": [
          "Vote{0:04CBBF43CA3E 385085/00/2(Precommit) 1B73DA9FC4C8 42C97B86D89D @ 2020-05-27T06:46:51.042392895Z}",
          "Vote{1:055799E028FA 385085/00/2(Precommit) 652B08AD61EA 0D507D7FA3AB @ 2020-06-28T04:57:29.20793209Z}",
          "Vote{2:056024CFA910 385085/00/2(Precommit) 652B08AD61EA C8E95532A4C3 @ 2020-06-28T04:57:29.452696998Z}",
          "Vote{3:0741C95814DA 385085/00/2(Precommit) 652B08AD61EA 36D567615F7C @ 2020-06-28T04:57:29.279788593Z}",

Note there's a precommit in there from the first val from May (2020-05-27) while the rest are from today (2020-06-28). It suggests there's a validator from an old instance of the network at this height (they're using the same chain-id!). Obviously a single bad validator shouldn't be an issue. But the Commit refactor work introduced a bug.

When we propose a block, we get the block.LastCommit by calling MakeCommit on the set of precommits we saw for the last height. This set may include precommits for a different block, and hence the block.LastCommit we propose may include precommits that aren't actually for the last block (but of course +2/3 will be). Before v0.33, we just skipped over these precommits during verification. But in v0.33, we expect all signatures for a blockID to be for the same block ID! Thus we end up proposing a block that we can't verify.
2020-07-02 15:41:49 +02:00
Anton Kaliaev
5e52a6ec55 types: verify commit fully
Since the light client work introduced in v0.33 it appears full nodes
are no longer fully verifying commit signatures during block execution -
they stop after +2/3. See in VerifyCommit:
0c7fd316eb/types/validator_set.go (L700-L703)

This means proposers can propose blocks that contain valid +2/3
signatures and then the rest of the signatures can be whatever they
want. They can claim that all the other validators signed just by
including a CommitSig with arbitrary signature data. While this doesn't
seem to impact safety of Tendermint per se, it means that Commits may
contain a lot of invalid data. This is already true of blocks, since
they can include invalid txs filled with garbage, but in that case the
application knows they they are invalid and can punish the proposer. But
since applications dont verify commit signatures directly (they trust
tendermint to do that), they won't be able to detect it.

This can impact incentivization logic in the application that depends on
the LastCommitInfo sent in BeginBlock, which includes which validators
signed. For instance, Gaia incentivizes proposers with a bonus for
including more than +2/3 of the signatures. But a proposer can now claim
that bonus just by including arbitrary data for the final -1/3 of
validators without actually waiting for their signatures. There may be
other tricks that can be played because of this.

In general, the full node should be a fully verifying machine. While
it's true that the light client can avoid verifying all signatures by
stopping after +2/3, the full node can not. Thus the light client and
full node should use distinct VerifyCommit functions if one is going to
stop after +2/3 or otherwise perform less validation (for instance light
clients can also skip verifying votes for nil while full nodes can not).

See a commit with a bad signature that verifies here: 56367fd. From what
I can tell, Tendermint will go on to think this commit is valid and
forward this data to the app, so the app will think the second validator
actually signed when it clearly did not.
2020-07-02 15:41:49 +02:00
Tess Rinearson
e22acc0f45 prepare v0.33.5 RC2 2020-05-28 12:41:32 +02:00
Marko
d9c2f01bb5 proto: add proto files for ibc unblock (#4853) (#4906)
these proto files are meant to help unblock ibc in their quest of migrating the ibc module to proto.
2020-05-28 12:41:32 +02:00
Callum Waters
64c7771966 lite2: fix pivot height during bisection
fix bug with assigning the new pivot height during bisection. PR: #4850
2020-05-28 12:41:32 +02:00
Anton Kaliaev
e40602502f p2p: return masked IP (not the actual IP) in addrbook#groupKey
Closes #4846 
Spec https://github.com/tendermint/spec/pull/96
2020-05-28 12:41:32 +02:00
Anton Kaliaev
69b6e0eccb rpc/core: do not lock ConsensusState mutex
in /validators, /consensus_params and /status

Closes #3161
2020-05-28 12:41:32 +02:00
Anton Kaliaev
e8b4226b17 rpc: refactor lib folder (#4836)
Closes https://github.com/tendermint/tendermint/issues/3857

Moves `lib/` folder to `jsonrpc/`.

Renames:

**packages**

`rpc` package -> `jsonrpc` package
`rpcclient` package -> `client` package
`rpcserver` package -> `server` package

**structs and interfaces**

```
JSONRPCClient to Client
JSONRPCRequestBatch to RequestBatch
JSONRPCCaller to Caller
```

**functions**

```
StartHTTPServer to Serve
StartHTTPAndTLSServer to ServeTLS

rpc/jsonrpc/client: rename NewURIClient to NewURI

NewJSONRPCClient to New
NewJSONRPCClientWithHTTPClient to NewWithHTTPClient
NewWSClient to NewWS
```

**misc**

- unexpose `ResponseWriterWrapper`
- remove unused http_params.go
2020-05-28 12:41:32 +02:00
Anton Kaliaev
7772cdc4a4 rpc: use a struct to wrap all the global objects
Closes #3433
2020-05-28 12:41:32 +02:00
Anton Kaliaev
cba24382ac privval: retry GetPubKey/SignVote/SignProposal N times before
returning an error

Closes #4707
2020-05-28 12:41:32 +02:00
Anton Kaliaev
e4e4bf6d6d pex: use highwayhash for pex bucket
Closes #2998

Spec PR: https://github.com/tendermint/spec/pull/94
2020-05-28 12:41:32 +02:00
Anton Kaliaev
62018d90df mempool: allow ReapX and CheckTx functions to run in parallel
allow ReapX and CheckTx functions to run in parallel, making it not possible to block certain proposers from creating a new block.

Closes: #2972
2020-05-28 12:41:32 +02:00
Erik Grinaker
29035985c6 rpc: handle panics during panic handling (#4888)
Fixes #4802. The Go HTTP server has a global panic handler for requests, so it was not as severe as first thought.

This fix can still panic, since we try to send a `500` response - if that happens, the Go HTTP server will terminate the connection. Otherwise, the client will get a 200 response, which we should avoid. I'm sort of torn on whether it's even necessary to include this fix, instead of just letting the HTTP server deal with it.
2020-05-28 12:41:32 +02:00
Anton Kaliaev
e1b3903a7f types: return an error if voting power overflows
in VerifyCommitTrusting

Closes #4755
2020-05-28 12:41:32 +02:00
Erik Grinaker
901ce19cc6 blockchain/v2: backport fixes (#4887)
* blockchain/v2: fix excessive CPU usage due to spinning on closed channels (#4761)

The event loop uses a `select` on multiple channels. However, reading from a closed channel in Go always yields the channel's zero value. The processor and scheduler close their channels when done, and since these channels are always ready to receive, the event loop keeps spinning on them.

This changes `routine.terminate()` to not close the channel, and also removes `stopDemux` and instead uses `events` channel closure to signal event loop termination.

Fixes #4687.

* blockchain/v2: respect fast_sync option (#4772)

Not thoroughly tested, but seems to work. Will do further testing as this is integrated with state sync.

Fixes #4688.
2020-05-28 12:41:32 +02:00
Anton Kaliaev
e080280d34 privval: remove deprecated OldFilePV (#4889)
* privval: remove deprecated `OldFilePV`

The old format was deprecated in v0.28. It's time we remove it.

* fix changelog
2020-05-28 12:41:32 +02:00
Erik Grinaker
642dcd4a12 lite: fix HTTP provider error handling (#4882)
* lite: fix HTTP provider error handling

Fixes #4739, kind of. See #4740 for the proper fix.

---

For contributor use:

- [x] Wrote tests
- [x] Updated CHANGELOG_PENDING.md
- [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [x] Updated relevant documentation (`docs/`) and code comments
- [x] Re-reviewed `Files changed` in the Github PR explorer
- [x] Applied Appropriate Labels

* adapt tests to missing pull request

Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
2020-05-28 12:41:32 +02:00
Erik Grinaker
24c39aa3c3 Makefile: parse TENDERMINT_BUILD_OPTIONS (#4738) (#4883)
Reduce the number of targets and make the buildsystem more
flexible by parsing the TENDERMINT_BUILD_OPTIONS command
line variable (a-la Debian, inspired by dpkg-buildpackage's
DEB_BUILD_OPTIONS), e.g:

 $ make install TENDERMINT_BUILD_OPTIONS='cleveldb'

replaces the old:

 $ make install_c

Options can be mix&match'd, e.g.:

 $ make install TENDERMINT_BUILD_OPTIONS='cleveldb race nostrip'

Three options are available:
- nostrip: don't strip debugging symbols nor DWARF tables.
- cleveldb: use cleveldb as db backend instead of goleveldb;
  it switches on the CGO_ENABLED Go environment variale.
- race: pass -race to go build and enable data race detection.

This changeset is a port of gaia pull request: cosmos/gaia#363.

Co-authored-by: Alessio Treglia <alessio@tendermint.com>
2020-05-28 12:41:32 +02:00
Anton Kaliaev
9550e8a54d lite2: fix TestVerifyAdjacentHeaders (#4884) 2020-05-28 12:41:32 +02:00
Alexander Bezobchuk
4ddf549e36 types: implement Header#ValidateBasic (#4638)
- Move core stateless validation of the Header type to a ValidateBasic method.
- Call header.ValidateBasic during a SignedHeader validation.
- Call header.ValidateBasic during a PhantomValidatorEvidence validation.
- Call header.ValidateBasic during a LunaticValidatorEvidence validation.

lite tests are skipped since the package is deprecated, no need to waste time on it

closes: #4572

Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
2020-05-28 12:41:32 +02:00
Erik Grinaker
ea2d3f4889 test: fix p2p test build breakage caused by Debian testing (#4880) 2020-05-28 12:41:32 +02:00
Anton Kaliaev
8f4080e0cc abci/server: print panic & stack trace to STDERR if logger is not set
Closes #4382
2020-05-28 12:41:32 +02:00
Tess Rinearson
70a4b5ea08 Merge pull request #4714 from tendermint/release/v0.33.4
release: v0.33.4
2020-04-21 15:47:36 +02:00
Tess Rinearson
6018f2d1a5 kick CI 2020-04-21 15:00:22 +02:00
Tess Rinearson
31a0f48c4a Merge branch 'v0.33' into release/v0.33.4 2020-04-21 14:44:33 +02:00
Alessio Treglia
13eff7f7ed Merge pull request from GHSA-v24h-pjjv-mcp6
v0.33.3: advisory fix
2020-04-09 15:48:13 +02:00
Anton Kaliaev
62c1a1399c update changelog and version 2020-04-03 17:06:27 +04:00
Anton Kaliaev
9821af83e7 p2p: limit the number of incoming connections
to p2p.max_num_inbound_peers + len(p2p.unconditional_peer_ids)
2020-04-03 17:06:11 +04:00
Tess Rinearson
0c0cb9f200 mempool: reserve IDs in InitPeer instead of AddPeer 2020-04-03 17:05:43 +04:00
Anton Kaliaev
2544a5cf3a noop commit (#4553)
should force CircleCI to update the code cache

Found a cache from build 100135 at go-src-v1-b215e07ecdc6ad5c363c3b420580aa6b52913feb
Size: 65 MiB
Cached paths:
* /go/src/github.com/tendermint/tendermint/.git

because we have v0.33.1-dev3 tag, bump-semver.py script is thinking the
next tag should be v0.33.1, not v0.33.2:

if re.match('[0-9]+$',patch) is None:
  patchfound = re.match('([0-9]+)',patch)
patch = int(patchfound.group(1))
  else:
  patch = int(patch) + 1

  The last tag it finds is v0.33.1-dev3. So it hits the first branch. But
  the first branch only fetches the number, and doesn't increment it, so
  the result is v0.33.1.

  if there exists a patch tag like 1-dev3, then the next version should be
  0.33.1. But in this case, 0.33.1 has already been released.
2020-03-11 13:55:00 +01:00
Anton Kaliaev
b215e07ecd Merge pull request #4550 from tendermint/release/v0.33.2
Release/v0.33.2
2020-03-11 15:22:48 +04:00
Anton Kaliaev
ec9424c6ce Merge pull request #4402 from tendermint/release/v0.33.1
Release/v0.33.1
2020-02-14 12:56:26 +01:00
Alessio Treglia
30e5619ac0 Merge branch 'v0.33' into release/v0.33.1 2020-02-14 11:39:01 +00:00
Alessio Treglia
e62b21c9bd Merge pull request #4404 from tendermint/rc2/0.33.1
upgrade: update upgrade.md for protobuf changes
2020-02-13 16:42:25 +00:00
Marko Baricevic
42d8bc5124 upgrade: update upgrade.md for protobuf changes
Signed-off-by: Marko Baricevic <marbar3778@yahoo.com>
2020-02-13 17:13:48 +01:00
Marko Baricevic
af99236105 release: 0.33
Signed-off-by: Marko Baricevic <marbar3778@yahoo.com>
2020-01-15 12:45:10 +01:00
1743 changed files with 144727 additions and 254346 deletions

1550
.circleci/codecov.sh Normal file

File diff suppressed because it is too large Load Diff

421
.circleci/config.yml Normal file
View File

@@ -0,0 +1,421 @@
version: 2.1
executors:
golang:
docker:
- image: tendermintdev/docker-tendermint-build
working_directory: /go/src/github.com/tendermint/tendermint
environment:
GOBIN: /tmp/bin
release:
machine: true
docs:
docker:
- image: tendermintdev/docker-website-deployment
environment:
AWS_REGION: us-east-1
commands:
run_test:
parameters:
script_path:
type: string
steps:
- attach_workspace:
at: /tmp/bin
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- restore_cache:
name: "Restore go modules cache"
keys:
- go-mod-v1-{{ checksum "go.sum" }}
- run:
name: "Running test"
command: |
bash << parameters.script_path >>
jobs:
setup_dependencies:
executor: golang
steps:
- checkout
- restore_cache:
name: "Restore go modules cache"
keys:
- go-mod-v1-{{ checksum "go.sum" }}
- run:
command: |
mkdir -p /tmp/bin
- run:
name: Cache go modules
command: make go-mod-cache
- run:
name: tools
command: make tools
- run:
name: "Build binaries"
command: make install install_abci
- save_cache:
name: "Save go modules cache"
key: go-mod-v1-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
- save_cache:
name: "Save source code cache"
key: go-src-v1-{{ .Revision }}
paths:
- ".git"
- persist_to_workspace:
root: "/tmp/bin"
paths:
- "."
test_persistence:
executor: golang
steps:
- run_test:
script_path: test/persist/test_failure_indices.sh
test_cover:
executor: golang
parallelism: 4
steps:
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- restore_cache:
name: "Restore go module cache"
keys:
- go-mod-v2-{{ checksum "go.sum" }}
- run:
name: "Run tests"
command: |
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
export GO111MODULE=on
mkdir -p /tmp/logs /tmp/workspace/profiles
for pkg in $(go list github.com/tendermint/tendermint/... | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
go test -v -timeout 5m -mod=readonly -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done
- persist_to_workspace:
root: /tmp/workspace
paths:
- "profiles/*"
- store_artifacts:
path: /tmp/logs
localnet:
working_directory: /home/circleci/.go_workspace/src/github.com/tendermint/tendermint
machine:
image: circleci/classic:latest
environment:
GOBIN: /home/circleci/.go_workspace/bin
GOPATH: /home/circleci/.go_workspace/
GOOS: linux
GOARCH: amd64
parallelism: 1
steps:
- checkout
- run:
name: run localnet and exit on failure
command: |
set -x
docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux
make localnet-start &
./scripts/localnet-blocks-test.sh 40 5 10 localhost
test_p2p:
environment:
GOBIN: /home/circleci/.go_workspace/bin
GOPATH: /home/circleci/.go_workspace
machine:
image: circleci/classic:latest
parameters:
ipv:
type: integer
default: 4
steps:
- checkout
- run: mkdir -p $GOPATH/src/github.com/tendermint
- run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint
- run: bash test/p2p/circleci.sh << parameters.ipv >>
- store_artifacts:
path: /home/circleci/project/test/p2p/logs
upload_coverage:
executor: golang
steps:
- attach_workspace:
at: /tmp/workspace
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- restore_cache:
name: "Restore go module cache"
keys:
- go-mod-v2-{{ checksum "go.sum" }}
- run:
name: gather
command: |
echo "mode: atomic" > coverage.txt
for prof in $(ls /tmp/workspace/profiles/); do
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
done
- run:
name: upload
command: bash .circleci/codecov.sh -f coverage.txt
deploy_docs:
executor: docs
steps:
- checkout
- run:
name: "Build docs"
command: make build-docs
- run:
name: "Sync to S3"
command: make sync-docs
prepare_build:
executor: golang
steps:
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- run:
name: Get next release number
command: |
export LAST_TAG="`git describe --tags --abbrev=0 --match "${CIRCLE_BRANCH}.*"`"
echo "Last tag: ${LAST_TAG}"
if [ -z "${LAST_TAG}" ]; then
export LAST_TAG="${CIRCLE_BRANCH}"
echo "Last tag not found. Possibly fresh branch or feature branch. Setting ${LAST_TAG} as tag."
fi
export NEXT_TAG="`python -u scripts/release_management/bump-semver.py --version "${LAST_TAG}"`"
echo "Next tag: ${NEXT_TAG}"
echo "export CIRCLE_TAG=\"${NEXT_TAG}\"" > release-version.source
- run:
name: Build dependencies
command: make tools
- persist_to_workspace:
root: .
paths:
- "release-version.source"
- save_cache:
key: v2-release-deps-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
build_artifacts:
executor: golang
parallelism: 4
steps:
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- restore_cache:
name: "Restore release dependencies cache"
keys:
- v2-release-deps-{{ checksum "go.sum" }}
- attach_workspace:
at: /tmp/workspace
- run:
name: Build artifact
command: |
# Setting CIRCLE_TAG because we do not tag the release ourselves.
source /tmp/workspace/release-version.source
if test ${CIRCLE_NODE_INDEX:-0} == 0 ;then export GOOS=linux GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi
if test ${CIRCLE_NODE_INDEX:-0} == 1 ;then export GOOS=darwin GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi
if test ${CIRCLE_NODE_INDEX:-0} == 2 ;then export GOOS=windows GOARCH=amd64 && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi
if test ${CIRCLE_NODE_INDEX:-0} == 3 ;then export GOOS=linux GOARCH=arm && export OUTPUT=build/tendermint_${GOOS}_${GOARCH} && make build && python -u scripts/release_management/zip-file.py ;fi
- persist_to_workspace:
root: build
paths:
- "*.zip"
- "tendermint_linux_amd64"
release_artifacts:
executor: golang
steps:
- restore_cache:
name: "Restore source code cache"
keys:
- go-src-v1-{{ .Revision }}
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
name: "Deploy to GitHub"
command: |
# Setting CIRCLE_TAG because we do not tag the release ourselves.
source /tmp/workspace/release-version.source
echo "---"
ls -la /tmp/workspace/*.zip
echo "---"
python -u scripts/release_management/sha-files.py
echo "---"
cat /tmp/workspace/SHA256SUMS
echo "---"
export RELEASE_ID="`python -u scripts/release_management/github-draft.py`"
echo "Release ID: ${RELEASE_ID}"
#Todo: Parallelize uploads
export GOOS=linux GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}"
export GOOS=darwin GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}"
export GOOS=windows GOARCH=amd64 && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}"
export GOOS=linux GOARCH=arm && python -u scripts/release_management/github-upload.py --id "${RELEASE_ID}"
python -u scripts/release_management/github-upload.py --file "/tmp/workspace/SHA256SUMS" --id "${RELEASE_ID}"
python -u scripts/release_management/github-publish.py --id "${RELEASE_ID}"
release_docker:
machine:
image: ubuntu-1604:201903-01
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
name: "Deploy to Docker Hub"
command: |
# Setting CIRCLE_TAG because we do not tag the release ourselves.
source /tmp/workspace/release-version.source
cp /tmp/workspace/tendermint_linux_amd64 DOCKER/tendermint
docker build --label="tendermint" --tag="tendermint/tendermint:${CIRCLE_TAG}" --tag="tendermint/tendermint:latest" "DOCKER"
docker login -u "${DOCKERHUB_USER}" --password-stdin \<<< "${DOCKERHUB_PASS}"
docker push "tendermint/tendermint"
docker logout
reproducible_builds:
executor: golang
steps:
- attach_workspace:
at: /tmp/workspace
- checkout
- setup_remote_docker:
docker_layer_caching: true
- run:
name: Build tendermint
no_output_timeout: 20m
command: |
sudo apt-get update
sudo apt-get install -y ruby
bash -x ./scripts/gitian-build.sh all
for os in darwin linux windows; do
cp gitian-build-${os}/result/tendermint-${os}-res.yml .
cp gitian-build-${os}/build/out/tendermint-*.tar.gz .
rm -rf gitian-build-${os}/
done
- store_artifacts:
path: /go/src/github.com/tendermint/tendermint/tendermint-darwin-res.yml
- store_artifacts:
path: /go/src/github.com/tendermint/tendermint/tendermint-linux-res.yml
- store_artifacts:
path: /go/src/github.com/tendermint/tendermint/tendermint-windows-res.yml
- store_artifacts:
path: /go/src/github.com/tendermint/tendermint/tendermint-*.tar.gz
# # Test RPC implementation against the swagger documented specs
# contract_tests:
# working_directory: /home/circleci/.go_workspace/src/github.com/tendermint/tendermint
# machine:
# image: circleci/classic:latest
# environment:
# GOBIN: /home/circleci/.go_workspace/bin
# GOPATH: /home/circleci/.go_workspace/
# GOOS: linux
# GOARCH: amd64
# parallelism: 1
# steps:
# - checkout
# - run:
# name: Test RPC endpoints against swagger documentation
# command: |
# set -x
# export PATH=~/.local/bin:$PATH
# # install node and dredd
# ./scripts/get_nodejs.sh
# # build the binaries with a proper version of Go
# docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux build-contract-tests-hooks
# # This docker image works with go 1.7, we can install here the hook handler that contract-tests is going to use
# go get github.com/snikch/goodman/cmd/goodman
# make contract-tests
workflows:
version: 2
test-suite:
jobs:
- deploy_docs:
context: tendermint-docs
filters:
branches:
only:
- master
tags:
only:
- /^v.*/
- deploy_docs:
context: tendermint-docs-staging
filters:
branches:
only:
- docs-staging
- setup_dependencies
- test_cover:
requires:
- setup_dependencies
- test_persistence:
requires:
- setup_dependencies
- localnet:
requires:
- setup_dependencies
- test_p2p
- test_p2p:
name: test_p2p_ipv6
ipv: 6
- upload_coverage:
requires:
- test_cover
- reproducible_builds:
filters:
branches:
only:
- master
- /v[0-9]+\.[0-9]+/
# - contract_tests:
# requires:
# - setup_dependencies
release:
jobs:
- prepare_build
- build_artifacts:
requires:
- prepare_build
- release_artifacts:
requires:
- prepare_build
- build_artifacts
filters:
branches:
only:
- /v[0-9]+\.[0-9]+/
- release_docker:
requires:
- prepare_build
- build_artifacts
filters:
branches:
only:
- /v[0-9]+\.[0-9]+/
- master

View File

@@ -1,11 +0,0 @@
---
Language: Proto
BasedOnStyle: Google
IndentWidth: 2
ColumnLimit: 0
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
SpacesInSquareBrackets: true
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true

View File

@@ -1,5 +0,0 @@
build
test/e2e/build
test/e2e/networks
test/logs
test/p2p/data

16
.github/CODEOWNERS vendored
View File

@@ -1,13 +1,9 @@
# CODEOWNERS: https://help.github.com/articles/about-codeowners/
# Everything goes through the following "global owners" by default.
# Unless a later match takes precedence, these three will be
# requested for review when someone opens a PR.
# Note that the last matching pattern takes precedence, so
# global owners are only requested if there isn't a more specific
# codeowner specified below. For this reason, the global codeowners
# are often repeated in package-level definitions.
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair @sergio-mena @jmalicevic @thanethomson @ancazamfir @samricotta
# Everything goes through Bucky, Anton, Tess. For now.
* @ebuchman @melekes @tessr
# Spec related changes can be approved by the protocol design team
/spec @josef-widder @milosevic @cason @sergio-mena @jmalicevic
# Precious documentation
/docs/README.md @zramsay
/docs/DOCS_README.md @zramsay
/docs/.vuepress/ @zramsay

View File

@@ -37,6 +37,6 @@ manner. We might ask you to provide additional logs and data (tendermint & app).
**node command runtime flags**:
**Please provide the output from the `http://<ip>:<port>/dump_consensus_state` RPC endpoint for consensus bugs**
**`/dump_consensus_state` output for consensus bugs**
**Anything else we need to know**:

View File

@@ -1,37 +0,0 @@
---
name: Protocol Change Proposal
about: Create a proposal to request a change to the protocol
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for opening an issue! ✰
v Before smashing the submit button please review the template.
v Word of caution: Under-specified proposals may be rejected summarily
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
# Protocol Change Proposal
## Summary
<!-- Short, concise description of the proposed change -->
## Problem Definition
<!-- Why do we need this change?
What problems may be addressed by introducing this change?
What benefits does Tendermint stand to gain by including this change?
Are there any disadvantages of including this change? -->
## Proposal
<!-- Detailed description of requirements of implementation -->
____
#### For Admin Use
- [ ] Not duplicate issue
- [ ] Appropriate labels applied
- [ ] Appropriate contributors tagged
- [ ] Contributor assigned/self-assigned

View File

@@ -1,7 +1,24 @@
Please add a description of the changes that this PR introduces and the files that
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for creating a PR! ✰
v Before smashing the submit button please review the checkboxes.
v If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
Closes: #XXX
## Description
<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review.
If this PR fixes an open Issue, please include "Closes #XXX" (where "XXX" is the Issue number)
so that GitHub will automatically close the Issue when this PR is merged.
-->
______
For contributor use:
- [ ] Wrote tests
- [ ] Updated CHANGELOG_PENDING.md
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Updated relevant documentation (`docs/`) and code comments
- [ ] Re-reviewed `Files changed` in the Github PR explorer

View File

@@ -1,16 +0,0 @@
pullRequestOpened: |
:wave: Thanks for creating a PR!
Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.
- [ ] Wrote tests
- [ ] Updated CHANGELOG_PENDING.md
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Updated relevant documentation (`docs/`) and code comments
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Applied Appropriate Labels
Thank you for your contribution to Tendermint! :rocket:

View File

@@ -1,91 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "master"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "v0.34.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "v0.35.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "v0.36.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: npm
directory: "/docs"
schedule:
interval: weekly
open-pull-requests-limit: 10
###################################
##
## Update All Go Dependencies
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
target-branch: "master"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
target-branch: "v0.34.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
target-branch: "v0.35.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
target-branch: "v0.36.x"
open-pull-requests-limit: 10
labels:
- T:dependencies
- S:automerge

View File

@@ -1,8 +0,0 @@
default: true,
MD007: {"indent": 4}
MD013: false
MD024: {siblings_only: true}
MD025: false
MD033: {no-inline-html: false}
no-hard-tabs: false
whitespace: false

View File

@@ -1,9 +0,0 @@
---
# Default rules for YAML linting from super-linter.
# See: See https://yamllint.readthedocs.io/en/stable/rules.html
extends: default
rules:
document-end: disable
document-start: disable
line-length: disable
truthy: disable

43
.github/mergify.yml vendored
View File

@@ -1,43 +0,0 @@
queue_rules:
- name: default
conditions:
- base=master
- label=S:automerge
pull_request_rules:
- name: Automerge to master
conditions:
- base=master
- label=S:automerge
actions:
queue:
method: squash
name: default
commit_message_template: |
{{ title }} (#{{ number }})
{{ body }}
- name: backport patches to v0.34.x branch
conditions:
- base=master
- label=S:backport-to-v0.34.x
actions:
backport:
branches:
- v0.34.x
- name: backport patches to v0.35.x branch
conditions:
- base=master
- label=S:backport-to-v0.35.x
actions:
backport:
branches:
- v0.35.x
- name: backport patches to v0.36.x branch
conditions:
- base=master
- label=S:backport-to-v0.36.x
actions:
backport:
branches:
- v0.36.x

47
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 9
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- major-release
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
Limit to only `issues` or `pulls`
only: pulls
Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
pulls:
daysUntilStale: 30
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.

View File

@@ -1,82 +0,0 @@
name: Build
# Tests runs different tests (test_abci_apps, test_abci_cli, test_apps)
# This workflow runs on every push to master or release branch and every pull requests
# All jobs will pass without running if no *{.go, .mod, .sum} files have been modified
on:
pull_request:
push:
branches:
- master
- release/**
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
goarch: ["arm", "amd64"]
goos: ["linux"]
timeout-minutes: 5
steps:
- uses: actions/setup-go@v3
with:
go-version: "1.18"
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
"!test/"
go.mod
go.sum
Makefile
- name: install
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} make build
if: "env.GIT_DIFF != ''"
test_abci_cli:
runs-on: ubuntu-latest
needs: build
timeout-minutes: 5
steps:
- uses: actions/setup-go@v3
with:
go-version: "1.18"
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- name: install
run: make install_abci
if: "env.GIT_DIFF != ''"
- run: abci/tests/test_cli/test.sh
shell: bash
if: "env.GIT_DIFF != ''"
test_apps:
runs-on: ubuntu-latest
needs: build
timeout-minutes: 5
steps:
- uses: actions/setup-go@v3
with:
go-version: "1.18"
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- name: install
run: make install install_abci
if: "env.GIT_DIFF != ''"
- name: test_apps
run: test/app/test.sh
shell: bash
if: "env.GIT_DIFF != ''"

View File

@@ -1,75 +0,0 @@
# Verify that generated code is up-to-date.
#
# Note that we run these checks regardless whether the input files have
# changed, because generated code can change in response to toolchain updates
# even if no files in the repository are modified.
name: Check generated code
on:
pull_request:
branches:
- master
permissions:
contents: read
jobs:
check-mocks:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
- name: "Check generated mocks"
run: |
set -euo pipefail
readonly MOCKERY=2.12.3 # N.B. no leading "v"
curl -sL "https://github.com/vektra/mockery/releases/download/v${MOCKERY}/mockery_${MOCKERY}_Linux_x86_64.tar.gz" | tar -C /usr/local/bin -xzf -
make mockery 2>/dev/null
if ! git diff --stat --exit-code ; then
echo ">> ERROR:"
echo ">>"
echo ">> Generated mocks require update (either Mockery or source files may have changed)."
echo ">> Ensure your tools are up-to-date, re-run 'make mockery' and update this PR."
echo ">>"
exit 1
fi
check-proto:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
with:
fetch-depth: 1 # we need a .git directory to run git diff
- name: "Check protobuf generated code"
run: |
set -euo pipefail
# Install buf and gogo tools, so that differences that arise from
# toolchain differences are also caught.
readonly tools="$(mktemp -d)"
export PATH="${PATH}:${tools}/bin"
export GOBIN="${tools}/bin"
go install github.com/bufbuild/buf/cmd/buf
go install github.com/gogo/protobuf/protoc-gen-gogofaster@latest
make proto-gen
if ! git diff --stat --exit-code ; then
echo ">> ERROR:"
echo ">>"
echo ">> Protobuf generated code requires update (either tools or .proto files may have changed)."
echo ">> Ensure your tools are up-to-date, re-run 'make proto-gen' and update this PR."
echo ">>"
exit 1
fi

View File

@@ -1,58 +0,0 @@
name: Docker
# Build & Push rebuilds the tendermint docker image on every push to master and creation of tags
# and pushes the image to https://hub.docker.com/r/interchainio/simapp/tags
on:
push:
branches:
- master
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10
- "v[0-9]+.[0-9]+.[0-9]+-rc*" # Push events to matching v*, i.e. v1.0-rc1, v20.15.10-rc5
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=tendermint/tendermint
VERSION=noop
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
elif [[ $GITHUB_REF == refs/heads/* ]]; then
VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
if [ "${{ github.event.repository.default_branch }}" = "$VERSION" ]; then
VERSION=latest
fi
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:${VERSION}"
fi
echo ::set-output name=tags::${TAGS}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Build
uses: docker/setup-buildx-action@v2.0.0
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v2.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish to Docker Hub
uses: docker/build-push-action@v3.1.0
with:
context: .
file: ./DOCKER/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }}

View File

@@ -1,62 +0,0 @@
# Build and deploy the docs.tendermint.com website content.
# The static content is published to GitHub Pages.
#
# For documentation build info, see docs/DOCS_README.md.
name: Build static documentation site
on:
workflow_dispatch: # allow manual updates
push:
branches:
- master
paths:
- docs/**
- spec/**
jobs:
# This is split into two jobs so that the build, which runs npm, does not
# have write access to anything. The deploy requires write access to publish
# to the branch used by GitHub Pages, however, so we can't just make the
# whole workflow read-only.
build:
name: VuePress build
runs-on: ubuntu-latest
container:
image: alpine:latest
permissions:
contents: read
steps:
- name: Install generator dependencies
run: |
apk add --no-cache make bash git npm
- uses: actions/checkout@v4
with:
# We need to fetch full history so the backport branches for previous
# versions will be available for the build.
fetch-depth: 0
- name: Build documentation
run: |
git config --global --add safe.directory "$PWD"
make build-docs
- uses: actions/upload-artifact@v4
with:
name: build-output
path: ~/output/
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-output
path: ~/output
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: 'docs-tendermint-com'
folder: ~/output
single-commit: true

View File

@@ -1,20 +0,0 @@
# Verify that important design docs have ToC entries.
name: Check documentation ToC
on:
pull_request:
push:
branches:
- master
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
docs/architecture/**
docs/rfc/**
- run: ./docs/presubmit.sh
if: env.GIT_DIFF

View File

@@ -1,36 +0,0 @@
# Runs randomly generated E2E testnets nightly on master
# manually run e2e tests
name: e2e-manual
on:
workflow_dispatch:
jobs:
e2e-nightly-test:
# Run parallel jobs for the listed testnet groups (must match the
# ./build/generator -g flag)
strategy:
fail-fast: false
matrix:
group: ['00', '01', '02', '03', '04']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
- name: Build
working-directory: test/e2e
# Run make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker generator runner tests
- name: Generate testnets
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 5 -d networks/nightly/
- name: Run ${{ matrix.p2p }} p2p testnets
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml

View File

@@ -1,58 +0,0 @@
# Runs randomly generated E2E testnets nightly on the 0.34.x branch.
# !! This file should be kept in sync with the e2e-nightly-master.yml file,
# modulo changes to the version labels.
name: e2e-nightly-34x
on:
schedule:
- cron: '0 2 * * *'
jobs:
e2e-nightly-test:
# Run parallel jobs for the listed testnet groups (must match the
# ./build/generator -g flag)
strategy:
fail-fast: false
matrix:
group: ['00', '01']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
with:
ref: 'v0.34.x'
- name: Build
working-directory: test/e2e
# Run make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker generator runner
- name: Generate testnets
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 2 -d networks/nightly
- name: Run testnets in group ${{ matrix.group }}
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml
e2e-nightly-fail:
needs: e2e-nightly-test
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on failure
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':skull:'
SLACK_COLOR: danger
SLACK_MESSAGE: Nightly E2E tests failed on v0.34.x
SLACK_FOOTER: ''

View File

@@ -1,75 +0,0 @@
# Runs randomly generated E2E testnets nightly on the v0.35.x branch.
# !! This file should be kept in sync with the e2e-nightly-master.yml file,
# modulo changes to the version labels.
name: e2e-nightly-35x
on:
schedule:
- cron: '0 2 * * *'
jobs:
e2e-nightly-test:
# Run parallel jobs for the listed testnet groups (must match the
# ./build/generator -g flag)
strategy:
fail-fast: false
matrix:
p2p: ['legacy', 'new', 'hybrid']
group: ['00', '01', '02', '03']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
with:
ref: 'v0.35.x'
- name: Build
working-directory: test/e2e
# Run make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker generator runner tests
- name: Generate testnets
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 4 -d networks/nightly/${{ matrix.p2p }} -p ${{ matrix.p2p }}
- name: Run ${{ matrix.p2p }} p2p testnets in group ${{ matrix.group }}
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/${{ matrix.p2p }}/*-group${{ matrix.group }}-*.toml
e2e-nightly-fail-2:
needs: e2e-nightly-test
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on failure
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':skull:'
SLACK_COLOR: danger
SLACK_MESSAGE: Nightly E2E tests failed on v0.35.x
SLACK_FOOTER: ''
e2e-nightly-success: # may turn this off once they seem to pass consistently
needs: e2e-nightly-test
if: ${{ success() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on success
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':white_check_mark:'
SLACK_COLOR: good
SLACK_MESSAGE: Nightly E2E tests passed on v0.35.x
SLACK_FOOTER: ''

View File

@@ -1,74 +0,0 @@
# Runs randomly generated E2E testnets nightly on the v0.36.x branch.
# !! This file should be kept in sync with the e2e-nightly-master.yml file,
# modulo changes to the version labels.
name: e2e-nightly-36x
on:
schedule:
- cron: '0 2 * * *'
jobs:
e2e-nightly-test:
# Run parallel jobs for the listed testnet groups (must match the
# ./build/generator -g flag)
strategy:
fail-fast: false
matrix:
group: ['00', '01', '02', '03', '04']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
with:
ref: 'v0.36.x'
- name: Build
working-directory: test/e2e
# Run make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker generator runner tests
- name: Generate testnets
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 5 -d networks/nightly
- name: Run testnets in group ${{ matrix.group }}
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml
e2e-nightly-fail-2:
needs: e2e-nightly-test
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on failure
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':skull:'
SLACK_COLOR: danger
SLACK_MESSAGE: Nightly E2E tests failed on v0.36.x
SLACK_FOOTER: ''
e2e-nightly-success: # may turn this off once they seem to pass consistently
needs: e2e-nightly-test
if: ${{ success() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on success
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':white_check_mark:'
SLACK_COLOR: good
SLACK_MESSAGE: Nightly E2E tests passed on v0.36.x
SLACK_FOOTER: ''

View File

@@ -1,73 +0,0 @@
# Runs randomly generated E2E testnets nightly on master
# !! Relevant changes to this file should be propagated to the e2e-nightly-<V>x
# files for the supported backport branches, when appropriate, modulo version
# markers.
name: e2e-nightly-master
on:
schedule:
- cron: '0 2 * * *'
jobs:
e2e-nightly-test:
# Run parallel jobs for the listed testnet groups (must match the
# ./build/generator -g flag)
strategy:
fail-fast: false
matrix:
group: ['00', '01', '02', '03', "04"]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
- name: Build
working-directory: test/e2e
# Run make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker generator runner tests
- name: Generate testnets
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 5 -d networks/nightly/
- name: Run ${{ matrix.p2p }} p2p testnets
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml
e2e-nightly-fail-2:
needs: e2e-nightly-test
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on failure
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':skull:'
SLACK_COLOR: danger
SLACK_MESSAGE: Nightly E2E tests failed on master
SLACK_FOOTER: ''
e2e-nightly-success: # may turn this off once they seem to pass consistently
needs: e2e-nightly-test
if: ${{ success() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack on success
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly E2E Tests
SLACK_ICON_EMOJI: ':white_check_mark:'
SLACK_COLOR: good
SLACK_MESSAGE: Nightly E2E tests passed on master
SLACK_FOOTER: ''

View File

@@ -1,37 +0,0 @@
name: e2e
# Runs the CI end-to-end test network on all pushes to master or release branches
# and every pull request, but only if any Go files have been changed.
on:
workflow_dispatch: # allow running workflow manually
pull_request:
push:
branches:
- master
- release/**
jobs:
e2e-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- name: Build
working-directory: test/e2e
# Run two make jobs in parallel, since we can't run steps in parallel.
run: make -j2 docker runner tests
if: "env.GIT_DIFF != ''"
- name: Run CI testnet
working-directory: test/e2e
run: ./run-multiple.sh networks/ci.toml
if: "env.GIT_DIFF != ''"

View File

@@ -1,77 +0,0 @@
# Runs fuzzing nightly.
name: Fuzz Tests
on:
workflow_dispatch: # allow running workflow manually
schedule:
- cron: '0 3 * * *'
pull_request:
branches: [master]
paths:
- "test/fuzz/**/*.go"
jobs:
fuzz-nightly-test:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: actions/checkout@v3
- name: Install go-fuzz
working-directory: test/fuzz
run: go install github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest
- name: Fuzz mempool
working-directory: test/fuzz
run: timeout -s SIGINT --preserve-status 10m make fuzz-mempool
continue-on-error: true
- name: Fuzz p2p-sc
working-directory: test/fuzz
run: timeout -s SIGINT --preserve-status 10m make fuzz-p2p-sc
continue-on-error: true
- name: Fuzz p2p-rpc-server
working-directory: test/fuzz
run: timeout -s SIGINT --preserve-status 10m make fuzz-rpc-server
continue-on-error: true
- name: Archive crashers
uses: actions/upload-artifact@v3
with:
name: crashers
path: test/fuzz/**/crashers
retention-days: 3
- name: Archive suppressions
uses: actions/upload-artifact@v3
with:
name: suppressions
path: test/fuzz/**/suppressions
retention-days: 3
- name: Set crashers count
working-directory: test/fuzz
run: echo "::set-output name=count::$(find . -type d -name 'crashers' | xargs -I % sh -c 'ls % | wc -l' | awk '{total += $1} END {print total}')"
id: set-crashers-count
outputs:
crashers-count: ${{ steps.set-crashers-count.outputs.count }}
fuzz-nightly-fail:
needs: fuzz-nightly-test
if: ${{ needs.fuzz-nightly-test.outputs.crashers-count != 0 }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack if any crashers
uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: tendermint-internal
SLACK_USERNAME: Nightly Fuzz Tests
SLACK_ICON_EMOJI: ':firecracker:'
SLACK_COLOR: danger
SLACK_MESSAGE: Crashers found in Nightly Fuzz tests
SLACK_FOOTER: ''

View File

@@ -1,16 +0,0 @@
name: Janitor
# Janitor cleans up previous runs of various workflows
# To add more workflows to cancel visit https://api.github.com/repos/tendermint/tendermint/actions/workflows and find the actions name
on:
pull_request:
jobs:
cancel:
name: "Cancel Previous Runs"
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: styfle/cancel-workflow-action@0.10.0
with:
workflow_id: 1041851,1401230,2837803
access_token: ${{ github.token }}

View File

@@ -1,65 +0,0 @@
# Runs a Jepsen test - cas-register (no nemesis) by default.
# See inputs for various options.
# Repo: https://github.com/tendermint/jepsen
#
# If you want to test a new breaking version of Tendermint, you'll need to
# update the Merkleeyes ABCI app and 'merkleeyesUrl' input accordingly. You can
# upload a new tarball to
# https://github.com/tendermint/jepsen/releases/tag/0.2.1.
#
# Manually triggered.
name: jepsen
on:
workflow_dispatch:
inputs:
workload:
description: 'Test workload to run: (cas-register | set)'
required: true
default: 'cas-register'
nemesis:
description: 'Nemesis to use: (none | clocks | single-partitions | half-partitions | ring-partitions | split-dup-validators | peekaboo-dup-validators | changing-validators | crash | truncate-tendermint | truncate-merkleeyes)'
required: true
default: 'none'
dupOrSuperByzValidators:
description: '"--dup-validators" (multiple validators share the same key) and(or) "--super-byzantine-validators" (byzantine validators have just shy of 2/3 the voting weight)'
required: false
default: ''
concurrency:
description: 'How many workers should we run? Must be an integer and >= 10, optionally followed by n (e.g. 3n) to multiply by the number of nodes.'
required: true
default: 10
timeLimit:
description: 'Excluding setup and teardown, how long should a test run for, in seconds?'
required: true
default: 60
tendermintUrl:
description: 'Where to grab the Tendermint tarball (w/ linux/amd64 binary)'
required: true
default: 'https://github.com/melekes/katas/releases/download/0.2.0/tendermint.tar.gz'
merkleeyesUrl:
description: 'Where to grab the Merkleeyes tarball (w/ linux/amd64 binary)'
required: true
default: 'https://github.com/tendermint/jepsen/releases/download/0.2.1/merkleeyes_0.1.7.tar.gz'
jobs:
jepsen-test:
runs-on: ubuntu-latest
steps:
- name: Checkout the Jepsen repository
uses: actions/checkout@v3
with:
repository: 'tendermint/jepsen'
- name: Start a Jepsen cluster in background
working-directory: docker
run: ./bin/up --daemon
- name: Run the test
run: docker exec -i jepsen-control bash -c 'source /root/.bashrc; cd /jepsen/tendermint; lein run test --nemesis ${{ github.event.inputs.nemesis }} --workload ${{ github.event.inputs.workload }} --concurrency ${{ github.event.inputs.concurrency }} --tendermint-url ${{ github.event.inputs.tendermintUrl }} --merkleeyes-url ${{ github.event.inputs.merkleeyesUrl }} --time-limit ${{ github.event.inputs.timeLimit }} ${{ github.event.inputs.dupOrSuperByzValidators }}'
- name: Archive results
uses: actions/upload-artifact@v3
with:
name: results
path: tendermint/store/latest
retention-days: 3

12
.github/workflows/linkchecker.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Check Markdown links
on:
schedule:
- cron: '* */24 * * *'
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@0.6.0
with:
folder-path: "docs"

12
.github/workflows/lint.yaml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Lint
on: [pull_request]
jobs:
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: golangci-lint
uses: reviewdog/action-golangci-lint@v1
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-review

View File

@@ -1,38 +0,0 @@
name: Golang Linter
# Lint runs golangci-lint over the entire Tendermint repository.
#
# This workflow is run on every pull request and push to master.
#
# The `golangci` job will pass without running if no *.{go, mod, sum}
# files have been modified.
on:
pull_request:
push:
branches:
- master
jobs:
golangci:
name: golangci-lint
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- uses: golangci/golangci-lint-action@v3
with:
# Required: the version of golangci-lint is required and
# must be specified without patch version: we always use the
# latest patch version.
version: v1.45
args: --timeout 10m
github-token: ${{ secrets.github_token }}
if: env.GIT_DIFF

View File

@@ -1,32 +0,0 @@
name: Markdown Linter
on:
push:
branches:
- master
paths:
- "**.md"
- "**.yml"
- "**.yaml"
pull_request:
branches: [master]
paths:
- "**.md"
- "**.yml"
jobs:
build:
name: Super linter
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Lint Code Base
uses: docker://github/super-linter:v4
env:
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_MD: true
VALIDATE_OPENAPI: true
VALIDATE_YAML: true
YAML_CONFIG_FILE: yaml-lint.yml

View File

@@ -1,23 +0,0 @@
name: Check Markdown links
on:
push:
branches:
- master
pull_request:
branches: [master]
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.md
- uses: creachadair/github-action-markdown-link-check@master
with:
check-modified-files-only: 'yes'
config-file: '.md-link-check.json'
if: env.GIT_DIFF

View File

@@ -1,21 +0,0 @@
name: Protobuf Lint
on:
pull_request:
paths:
- 'proto/**'
push:
branches:
- master
paths:
- 'proto/**'
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: bufbuild/buf-setup-action@v1.6.0
- uses: bufbuild/buf-lint-action@v1
with:
input: 'proto'

12
.github/workflows/proto.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Proto check
on: [pull_request]
jobs:
proto-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: docker-practice/actions-setup-docker@master
- name: lint
run: make proto-lint
- name: check-breakage
run: make proto-check-breaking-ci

View File

@@ -1,39 +0,0 @@
name: "Release"
on:
push:
branches:
- "RC[0-9]/**"
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-go@v3
with:
go-version: '1.18'
- name: Build
uses: goreleaser/goreleaser-action@v3
if: ${{ github.event_name == 'pull_request' }}
with:
version: latest
args: build --skip-validate # skip validate skips initial sanity checks in order to be able to fully run
- run: echo https://github.com/tendermint/tendermint/blob/${GITHUB_REF#refs/tags/}/CHANGELOG.md#${GITHUB_REF#refs/tags/} > ../release_notes.md
- name: Release
uses: goreleaser/goreleaser-action@v3
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest
args: release --rm-dist --release-notes=../release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,20 +0,0 @@
name: "Close stale pull requests"
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-pr-message: "This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions."
days-before-stale: -1
days-before-close: -1
days-before-pr-stale: 10
days-before-pr-close: 4
exempt-pr-labels: "S:wip"

View File

@@ -1,34 +1,82 @@
name: Test
name: Tests
on:
pull_request:
push:
paths:
- "**.go"
branches:
- master
- release/**
jobs:
tests:
cleanup-runs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
part: ["00", "01", "02", "03", "04", "05"]
steps:
- uses: actions/setup-go@v3
with:
go-version: "1.18"
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
"!test/"
go.mod
go.sum
Makefile
- name: Run Go Tests
- uses: rokroskar/workflow-run-cleanup-action@master
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/master'"
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2-beta
- name: Set GOBIN
run: |
make test-group-${{ matrix.part }} NUM_SPLIT=6
if: env.GIT_DIFF
echo "::add-path::$(go env GOPATH)/bin"
- uses: actions/checkout@v2
- name: install
run: make install install_abci
# Cache bin
- uses: actions/cache@v1
with:
path: ~/go/bin
key: ${{ runner.os }}-go-tm-binary
test_abci_apps:
runs-on: ubuntu-latest
needs: Build
steps:
- uses: actions/setup-go@v2-beta
- name: Set GOBIN
run: |
echo "::add-path::$(go env GOPATH)/bin"
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/go/bin
key: ${{ runner.os }}-go-tm-binary
- name: test_abci_apps
run: abci/tests/test_app/test.sh
shell: bash
test_abci_cli:
runs-on: ubuntu-latest
needs: Build
steps:
- uses: actions/setup-go@v2-beta
- name: Set GOBIN
run: |
echo "::add-path::$(go env GOPATH)/bin"
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/go/bin
key: ${{ runner.os }}-go-tm-binary
- run: abci/tests/test_cli/test.sh
shell: bash
test_apps:
runs-on: ubuntu-latest
needs: Build
steps:
- uses: actions/setup-go@v2-beta
- name: Set GOBIN
run: |
echo "::add-path::$(go env GOPATH)/bin"
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/go/bin
key: ${{ runner.os }}-go-tm-binary
- name: test_apps
run: test/app/test.sh
shell: bash

79
.gitignore vendored
View File

@@ -1,57 +1,48 @@
*.bak
*.iml
*.log
*.swo
*.swp
*/.glide
*/vendor
.DS_Store
*.swo
.bak
.idea/
.revision
.tendermint
.tendermint-lite
.terraform
.vagrant
.vendor-new/
.vscode/
abci/abci-cli
addrbook.json
artifacts/*
*.bak
.DS_Store
build/*
rpc/test/.tendermint
.tendermint
remote_dump
.revision
vendor
.vagrant
test/p2p/data/
test/logs
coverage.txt
docs/.vuepress/dist
docs/_build
docs/dist
docs/.vuepress/dist
*.log
abci-cli
docs/node_modules/
docs/spec
docs/.vuepress/public/rpc
index.html.md
libs/pubsub/query/fuzz_test/output
profile\.out
remote_dump
rpc/test/.tendermint
scripts/cutWALUntil/cutWALUntil
scripts/wal2json/wal2json
scripts/cutWALUntil/cutWALUntil
.idea/
*.iml
.vscode/
libs/pubsub/query/fuzz_test/output
shunit2
.tendermint-lite
addrbook.json
*/vendor
.vendor-new/
*/.glide
.terraform
terraform.tfstate
terraform.tfstate.backup
terraform.tfstate.d
test/app/grpc_client
test/e2e/build
test/e2e/networks/*/
test/logs
test/p2p/data/
vendor
test/fuzz/**/corpus
test/fuzz/**/crashers
test/fuzz/**/suppressions
test/fuzz/**/*.zip
proto/spec/**/*.pb.go
*.aux
*.bbl
*.blg
*.log
*.pdf
*.gz
*.dvi
.vscode
profile\.out

View File

@@ -1,35 +1,32 @@
linters:
enable:
- asciicheck
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exportloopref
# - errcheck
# - funlen
# - gochecknoglobals
# - gochecknoinits
# - gocognit
- goconst
# - gocritic
- gocritic
# - gocyclo
# - godox
- gofmt
- goimports
- revive
- golint
- gosec
- gosimple
- govet
- ineffassign
# - interfacer
# - lll
# - maligned
- interfacer
- lll
- misspell
# - maligned
- nakedret
- nolintlint
- prealloc
- scopelint
- staticcheck
- structcheck
- stylecheck
@@ -40,20 +37,34 @@ linters:
- varcheck
# - whitespace
# - wsl
# - gocognit
disable:
- errcheck
issues:
exclude-rules:
- path: _test\.go
linters:
- gosec
max-same-issues: 50
- linters:
- lll
source: "https://"
linters-settings:
dogsled:
max-blank-identifiers: 3
golint:
min-confidence: 0
maligned:
suggest-new: true
misspell:
locale: US
# govet:
# check-shadowing: true
golint:
min-confidence: 0
# gocyclo:
# min-complexity: 10
# misspell:
# locale: US
# gocritic:
# enabled-tags:
# - performance
# - style
# - experimental
# disabled-checks:
# - wrapperFunc
# - commentFormatting # https://github.com/go-critic/go-critic/issues/755

View File

@@ -1,36 +0,0 @@
project_name: tendermint
env:
# Require use of Go modules.
- GO111MODULE=on
builds:
- id: "tendermint"
main: ./cmd/tendermint/main.go
ldflags:
- -s -w -X github.com/tendermint/tendermint/version.TMCoreSemVer={{ .Version }}
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm
- arm64
checksum:
name_template: SHA256SUMS-{{.Version}}.txt
algorithm: sha256
release:
name_template: "{{.Version}} (WARNING: BETA SOFTWARE)"
archives:
- files:
- LICENSE
- README.md
- UPGRADING.md
- SECURITY.md
- CHANGELOG.md

View File

@@ -1,11 +0,0 @@
default: true
MD001: false
MD007: {indent: 4}
MD013: false
MD024: {siblings_only: true}
MD025: false
MD033: false
MD036: false
MD010: false
MD012: false
MD028: false

View File

@@ -1,6 +0,0 @@
docs/node_modules
CHANGELOG.md
docs/architecture/*
crypto/secp256k1/**
scripts/*
.github

View File

@@ -1,6 +0,0 @@
{
"retryOn429": true,
"retryCount": 5,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [200, 206, 503]
}

10
.mergify.yml Normal file
View File

@@ -0,0 +1,10 @@
pull_request_rules:
- name: Automerge to master
conditions:
- base=master
- label=S:automerge
actions:
merge:
method: squash
strict: true
commit_message: title+body

File diff suppressed because it is too large Load Diff

View File

@@ -1,104 +1,29 @@
# Unreleased Changes
## v0.33.8
Friendly reminder: We have a [bug bounty program](https://hackerone.com/cosmos).
## vX.X
Month, DD, YYYY
\*\*
Special thanks to external contributors on this release:
### BREAKING CHANGES
Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint).
### BREAKING CHANGES:
- CLI/RPC/Config
- [rpc] \#7121 Remove the deprecated gRPC interface to the RPC service. (@creachadair)
- [blocksync] \#7159 Remove support for disabling blocksync in any circumstance. (@tychoish)
- [mempool] \#7171 Remove legacy mempool implementation. (@tychoish)
- [rpc] \#7575 Rework how RPC responses are written back via HTTP. (@creachadair)
- [rpc] \#7713 Remove unused options for websocket clients. (@creachadair)
- [config] \#7930 Add new event subscription options and defaults. (@creachadair)
- [rpc] \#7982 Add new Events interface and deprecate Subscribe. (@creachadair)
- [cli] \#8081 make the reset command safe to use by intoducing `reset-state` command. Fixed by \#8259. (@marbar3778, @cmwaters)
- [config] \#8222 default indexer configuration to null. (@creachadair)
- [rpc] \#8570 rework timeouts to be per-method instead of global. (@creachadair)
- [rpc] \#8624 deprecate `broadcast_tx_commit` and `braodcast_tx_sync` and `broadcast_tx_async` in favor of `braodcast_tx`. (@tychoish)
- [config] \#8654 remove deprecated `seeds` field from config. Users should switch to `bootstrap-peers` instead. (@cmwaters)
- Apps
- [tendermint/spec] \#7804 Migrate spec from [spec repo](https://github.com/tendermint/spec).
- [abci] \#7984 Remove the locks preventing concurrent use of ABCI applications by Tendermint. (@tychoish)
- [abci] \#8605 Remove info, log, events, gasUsed and mempoolError fields from ResponseCheckTx as they are not used by Tendermint. (@jmalicevic)
- [abci] \#8664 Move `app_hash` parameter from `Commit` to `FinalizeBlock`. (@sergio-mena)
- [abci] \#8656 Added cli command for `PrepareProposal`. (@jmalicevic)
- [sink/psql] \#8637 tx_results emitted from psql sink are now json encoded, previously they were protobuf encoded
- [abci] \#8901 Added cli command for `ProcessProposal`. (@hvanz)
- P2P Protocol
- [p2p] \#7035 Remove legacy P2P routing implementation and associated configuration options. (@tychoish)
- [p2p] \#7265 Peer manager reduces peer score for each failed dial attempts for peers that have not successfully dialed. (@tychoish)
- [p2p] [\#7594](https://github.com/tendermint/tendermint/pull/7594) always advertise self, to enable mutual address discovery. (@altergui)
- [p2p] \#8737 Introduce "inactive" peer label to avoid re-dialing incompatible peers. (@tychoish)
- [p2p] \#8737 Increase frequency of dialing attempts to reduce latency for peer acquisition. (@tychoish)
- [p2p] \#8737 Improvements to peer scoring and sorting to gossip a greater variety of peers during PEX. (@tychoish)
- [p2p] \#8737 Track incoming and outgoing peers separately to ensure more peer slots open for incoming connections. (@tychoish)
- Go API
- [rpc] \#7474 Remove the "URI" RPC client. (@creachadair)
- [libs/pubsub] \#7451 Internalize the pubsub packages. (@creachadair)
- [libs/sync] \#7450 Internalize and remove the library. (@creachadair)
- [libs/async] \#7449 Move library to internal. (@creachadair)
- [pubsub] \#7231 Remove unbuffered subscriptions and rework the Subscription interface. (@creachadair)
- [eventbus] \#7231 Move the EventBus type to the internal/eventbus package. (@creachadair)
- [blocksync] \#7046 Remove v2 implementation of the blocksync service and recactor, which was disabled in the previous release. (@tychoish)
- [p2p] \#7064 Remove WDRR queue implementation. (@tychoish)
- [config] \#7169 `WriteConfigFile` now returns an error. (@tychoish)
- [libs/service] \#7288 Remove SetLogger method on `service.Service` interface. (@tychoish)
- [abci/client] \#7607 Simplify client interface (removes most "async" methods). (@creachadair)
- [libs/json] \#7673 Remove the libs/json (tmjson) library. (@creachadair)
- [crypto] \#8412 \#8432 Remove `crypto/tmhash` package in favor of small functions in `crypto` package and cleanup of unused functions. (@tychoish)
- Blockchain Protocol
### FEATURES
### FEATURES:
- [rpc] [\#7270](https://github.com/tendermint/tendermint/pull/7270) Add `header` and `header_by_hash` RPC Client queries. (@fedekunze)
- [rpc] [\#7701] Add `ApplicationInfo` to `status` rpc call which contains the application version. (@jonasbostoen)
- [cli] [#7033](https://github.com/tendermint/tendermint/pull/7033) Add a `rollback` command to rollback to the previous tendermint state in the event of non-determinstic app hash or reverting an upgrade.
- [mempool, rpc] \#7041 Add removeTx operation to the RPC layer. (@tychoish)
- [consensus] \#7354 add a new `synchrony` field to the `ConsensusParams` struct for controlling the parameters of the proposer-based timestamp algorithm. (@williambanfield)
- [consensus] \#7376 Update the proposal logic per the Propose-based timestamps specification so that the proposer will wait for the previous block time to occur before proposing the next block. (@williambanfield)
- [consensus] \#7391 Use the proposed block timestamp as the proposal timestamp. Update the block validation logic to ensure that the proposed block's timestamp matches the timestamp in the proposal message. (@williambanfield)
- [consensus] \#7415 Update proposal validation logic to Prevote nil if a proposal does not meet the conditions for Timelyness per the proposer-based timestamp specification. (@anca)
- [consensus] \#7382 Update block validation to no longer require the block timestamp to be the median of the timestamps of the previous commit. (@anca)
- [consensus] \#7711 Use the proposer timestamp for the first height instead of the genesis time. Chains will still start consensus at the genesis time. (@anca)
- [cli] \#8281 Add a tool to update old config files to the latest version. (@creachadair)
- [consenus] \#8514 move `RecheckTx` from the local node mempool config to a global `ConsensusParams` field in `BlockParams` (@cmwaters)
- [abci] ABCI++ [specified](https://github.com/tendermint/tendermint/tree/master/spec/abci%2B%2B). (@sergio-mena, @cmwaters, @josef-widder)
- [abci] ABCI++ [implemented](https://github.com/orgs/tendermint/projects/9). (@williambanfield, @thanethomson, @sergio-mena)
### IMPROVEMENTS
- [internal/protoio] \#7325 Optimized `MarshalDelimited` by inlining the common case and using a `sync.Pool` in the worst case. (@odeke-em)
- [consensus] \#6969 remove logic to 'unlock' a locked block.
- [evidence] \#7700 Evidence messages contain single Evidence instead of EvidenceList (@jmalicevic)
- [evidence] \#7802 Evidence pool emits events when evidence is validated and updates a metric when the number of evidence in the evidence pool changes. (@jmalicevic)
- [pubsub] \#7319 Performance improvements for the event query API (@creachadair)
- [node] \#7521 Define concrete type for seed node implementation (@spacech1mp)
- [rpc] \#7612 paginate mempool /unconfirmed_txs rpc endpoint (@spacech1mp)
- [light] [\#7536](https://github.com/tendermint/tendermint/pull/7536) rpc /status call returns info about the light client (@jmalicevic)
- [types] \#7765 Replace EvidenceData with EvidenceList to avoid unnecessary nesting of evidence fields within a block. (@jmalicevic)
### IMPROVEMENTS:
### BUG FIXES
- fix: assignment copies lock value in `BitArray.UnmarshalJSON()` (@lklimek)
- [light] \#7640 Light Client: fix absence proof verification (@ashcherbakov)
- [light] \#7641 Light Client: fix querying against the latest height (@ashcherbakov)
- [cli] [#7837](https://github.com/tendermint/tendermint/pull/7837) fix app hash in state rollback. (@yihuang)
- [cli] \#8276 scmigrate: ensure target key is correctly renamed. (@creachadair)
- [cli] \#8294 keymigrate: ensure block hash keys are correctly translated. (@creachadair)
- [cli] \#8352 keymigrate: ensure transaction hash keys are correctly translated. (@creachadair)
- (indexer) \#8625 Fix overriding tx index of duplicated txs.
### BUG FIXES:

View File

@@ -1,5 +1,4 @@
# The Tendermint Code of Conduct
This code of conduct applies to all projects run by the Tendermint/COSMOS team and hence to tendermint.
@@ -7,7 +6,6 @@ This code of conduct applies to all projects run by the Tendermint/COSMOS team a
# Conduct
## Contact: conduct@tendermint.com
* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
@@ -20,7 +18,7 @@ This code of conduct applies to all projects run by the Tendermint/COSMOS team a
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we dont tolerate behavior that excludes people in socially marginalized groups.
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we dont tolerate behavior that excludes people in socially marginalized groups.
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel admins or the person mentioned above immediately. Whether youre a regular contributor or a newcomer, we care about making this community a safe place for you and weve got your back.
@@ -31,7 +29,6 @@ This code of conduct applies to all projects run by the Tendermint/COSMOS team a
# Moderation
These are the policies for upholding our communitys standards of conduct. If you feel that a thread needs moderation, please contact the above mentioned person.
1. Remarks that violate the Tendermint/COSMOS standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)

View File

@@ -5,7 +5,7 @@ contributing, it may be helpful to understand the goal of the project. The goal
of Tendermint is to develop a BFT consensus engine robust enough to
support permissionless value-carrying networks. While all contributions are
welcome, contributors should bear this goal in mind in deciding if they should
target the main Tendermint project or a potential fork. When targeting the
target the main tendermint project or a potential fork. When targeting the
main Tendermint project, the following process leads to the best chance of
landing changes in master.
@@ -26,22 +26,18 @@ will indicate their support with a heartfelt emoji.
If the issue would benefit from thorough discussion, maintainers may
request that you create a [Request For
Comment](https://github.com/tendermint/spec/tree/master/rfc)
in the Tendermint spec repo. Discussion
Comment](https://github.com/tendermint/spec/tree/master/rfc). Discussion
at the RFC stage will build collective understanding of the dimensions
of the problems and help structure conversations around trade-offs.
When the problem is well understood but the solution leads to large structural
changes to the code base, these changes should be proposed in the form of an
[Architectural Decision Record (ADR)](./docs/architecture/). The ADR will help
build consensus on an overall strategy to ensure the code base maintains
coherence in the larger context. If you are not comfortable with writing an
ADR, you can open a less-formal issue and the maintainers will help you turn it
into an ADR.
> How to pick a number for the ADR?
Find the largest existing ADR number and bump it by 1.
When the problem is well understood but the solution leads to large
structural changes to the code base, these changes should be proposed in
the form of an [Architectural Decision Record
(ADR)](./docs/architecture/). The ADR will help build consensus on an
overall strategy to ensure the code base maintains coherence
in the larger context. If you are not comfortable with writing an ADR,
you can open a less-formal issue and the maintainers will help you
turn it into an ADR. ADR numbers can be registered [here](https://github.com/tendermint/tendermint/issues/2313).
When the problem as well as proposed solution are well understood,
changes should start with a [draft
@@ -67,12 +63,12 @@ Instead, we use `git remote` to add the fork as a new remote for the original re
For instance, to create a fork and work on a branch of it, I would:
- Create the fork on GitHub, using the fork button.
- Create the fork on github, using the fork button.
- Go to the original repo checked out locally (i.e. `$GOPATH/src/github.com/tendermint/tendermint`)
- `git remote rename origin upstream`
- `git remote add origin git@github.com:ebuchman/basecoin.git`
Now `origin` refers to my fork and `upstream` refers to the Tendermint version.
Now `origin` refers to my fork and `upstream` refers to the tendermint version.
So I can `git push -u origin master` to update my fork, and make pull requests to tendermint from there.
Of course, replace `ebuchman` with your git handle.
@@ -105,47 +101,41 @@ specify exactly the dependency you want to update, eg.
## Protobuf
We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along
with [`gogoproto`](https://github.com/gogo/protobuf) to generate code for use
across Tendermint Core.
We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along with [gogoproto](https://github.com/gogo/protobuf) to generate code for use across Tendermint Core.
To generate proto stubs, lint, and check protos for breaking changes, you will
need to install [buf](https://buf.build/) and `gogoproto`. Then, from the root
of the repository, run:
For linting and checking breaking changes, we use [buf](https://buf.build/). If you would like to run linting and check if the changes you have made are breaking then you will need to have docker running locally. Then the linting cmd will be `make proto-lint` and the breaking changes check will be `make proto-check-breaking`.
```bash
# Lint all of the .proto files in proto/tendermint
make proto-lint
There are two ways to generate your proto stubs.
# Check if any of your local changes (prior to committing to the Git repository)
# are breaking
make proto-check-breaking
1. Use Docker, pull an image that will generate your proto stubs with no need to install anything. `make proto-gen-docker`
2. Run `make proto-gen` after installing `protoc` and gogoproto.
# Generate Go code from the .proto files in proto/tendermint
make proto-gen
### Installation Instructions
To install `protoc`, download an appropriate release (https://github.com/protocolbuffers/protobuf) and then move the provided binaries into your PATH (follow instructions in README included with the download).
To install `gogoproto`, do the following:
```sh
$ go get github.com/gogo/protobuf/gogoproto
$ cd $GOPATH/pkg/mod/github.com/gogo/protobuf@v1.3.1 # or wherever go get installs things
$ make install
```
To automatically format `.proto` files, you will need
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) installed. Once
installed, you can run:
You should now be able to run `make proto-gen` from inside the root Tendermint directory to generate new files from proto files.
## Vagrant
If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started
hacking Tendermint with the commands below.
NOTE: In case you installed Vagrant in 2017, you might need to run
`vagrant box update` to upgrade to the latest `ubuntu/xenial64`.
```bash
make proto-format
```
### Visual Studio Code
If you are a VS Code user, you may want to add the following to your `.vscode/settings.json`:
```json
{
"protoc": {
"options": [
"--proto_path=${workspaceRoot}/proto",
"--proto_path=${workspaceRoot}/third_party/proto"
]
}
}
vagrant up
vagrant ssh
make test
```
## Changelog
@@ -155,7 +145,7 @@ pull-request that includes an update to the `CHANGELOG_PENDING.md` file.
Changelog entries should be formatted as follows:
```md
```
- [module] \#xxx Some description about the change (@contributor)
```
@@ -163,7 +153,7 @@ Here, `module` is the part of the code that changed (typically a
top-level Go package), `xxx` is the pull-request number, and `contributor`
is the author/s of the change.
It's also acceptable for `xxx` to refer to the relevant issue number, but pull-request
It's also acceptable for `xxx` to refer to the relevent issue number, but pull-request
numbers are preferred.
Note this means pull-requests should be opened first so the changelog can then
be updated with the pull-request's number.
@@ -180,7 +170,7 @@ Breaking changes are further subdivided according to the APIs/users they impact.
Any change that effects multiple APIs/users should be recorded multiply - for
instance, a change to the `Blockchain Protocol` that removes a field from the
header should also be recorded under `CLI/RPC/Config` since the field will be
removed from the header in RPC responses as well.
removed from the header in rpc responses as well.
## Branching Model and Release
@@ -188,147 +178,122 @@ The main development branch is master.
Every release is maintained in a release branch named `vX.Y.Z`.
Pending minor releases have long-lived release candidate ("RC") branches. Minor release changes should be merged to these long-lived RC branches at the same time that the changes are merged to master.
Note all pull requests should be squash merged except for merging to a release branch (named `vX.Y`). This keeps the commit history clean and makes it
easy to reference the pull request where a change was introduced.
### Development Procedure
The latest state of development is on `master`, which must never fail `make test`. _Never_ force push `master`, unless fixing broken git history (which we rarely do anyways).
- the latest state of development is on `master`
- `master` must never fail `make test`
- never --force onto `master` (except when reverting a broken commit, which should seldom happen)
- create a development branch either on github.com/tendermint/tendermint, or your fork (using `git remote add origin`)
- make changes and update the `CHANGELOG_PENDING.md` to record your change
- before submitting a pull request, run `git rebase` on top of the latest `master`
To begin contributing, create a development branch either on `github.com/tendermint/tendermint`, or your fork (using `git remote add origin`).
When you have submitted a pull request label the pull request with either `R:minor`, if the change can be accepted in a minor release, or `R:major`, if the change is meant for a major release.
Make changes, and before submitting a pull request, update the `CHANGELOG_PENDING.md` to record your change. Also, run either `git rebase` or `git merge` on top of the latest `master`. (Since pull requests are squash-merged, either is fine!)
### Pull Merge Procedure
Update the `UPGRADING.md` if the change you've made is breaking and the
instructions should be in place for a user on how he/she can upgrade it's
software (ABCI application, Tendermint-based blockchain, light client, wallet).
Once you have submitted a pull request label the pull request with either `R:minor`, if the change should be included in the next minor release, or `R:major`, if the change is meant for a major release.
Sometimes (often!) pull requests get out-of-date with master, as other people merge different pull requests to master. It is our convention that pull request authors are responsible for updating their branches with master. (This also means that you shouldn't update someone else's branch for them; even if it seems like you're doing them a favor, you may be interfering with their git flow in some way!)
#### Merging Pull Requests
It is also our convention that authors merge their own pull requests, when possible. External contributors may not have the necessary permissions to do this, in which case, a member of the core team will merge the pull request once it's been approved.
Before merging a pull request:
- Ensure pull branch is up-to-date with a recent `master` (GitHub won't let you merge without this!)
- Run `make test` to ensure that all tests pass
- [Squash](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git) merge pull request
#### Pull Requests for Minor Releases
If your change should be included in a minor release, please also open a PR against the long-lived minor release candidate branch (e.g., `rc1/v0.33.5`) _immediately after your change has been merged to master_.
You can do this by cherry-picking your commit off master:
```sh
$ git checkout rc1/v0.33.5
$ git checkout -b {new branch name}
$ git cherry-pick {commit SHA from master}
# may need to fix conflicts, and then use git add and git cherry-pick --continue
$ git push origin {new branch name}
```
After this, you can open a PR. Please note in the PR body if there were merge conflicts so that reviewers can be sure to take a thorough look.
- ensure pull branch is based on a recent `master`
- run `make test` to ensure that all tests pass
- [squash](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git) merge pull request
- the `unstable` branch may be used to aggregate pull merges before fixing tests
### Git Commit Style
We follow the [Go style guide on commit messages](https://tip.golang.org/doc/contribute.html#commit_messages). Write concise commits that start with the package name and have a description that finishes the sentence "This change modifies Tendermint to...". For example,
```sh
\```
cmd/debug: execute p.Signal only when p is not nil
[potentially longer description in the body]
Fixes #nnnn
```
\```
Each PR should have one commit once it lands on `master`; this can be accomplished by using the "squash and merge" button on Github. Be sure to edit your commit message, though!
### Release Procedure
#### Major Release
1. start on `master`
2. run integration tests (see `test_integrations` in Makefile)
3. prepare release in a pull request against `master` (to be squash merged):
- copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md`
- run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for
all issues
- run `bash ./scripts/authors.sh` to get a list of authors since the latest
release, and add the github aliases of external contributors to the top of
the changelog. To lookup an alias from an email, try `bash ./scripts/authors.sh <email>`
- reset the `CHANGELOG_PENDING.md`
- bump the appropriate versions in `version.go`
4. push your changes with prepared release details to `vX.X` (this will trigger the release `vX.X.0`)
5. merge back to master (don't squash merge!)
#### Minor Release
Minor releases are done differently from major releases. Minor release pull requests should be labeled with `R:minor` if they are to be included.
1. Checkout the last major release, `vX.X`.
- `git checkout vX.X`
2. Create a release candidate branch off the most recent major release with your upcoming version specified, `rc1/vX.X.x`, and push the branch.
- `git checkout -b rc1/vX.X.x`
- `git push -u origin rc1/vX.X.x`
3. Create a cherry-picking branch, and make a pull request into the release candidate.
- `git checkout -b cherry-picks/rc1/vX.X.x`
- This is for devs to approve the commits that are entering the release candidate.
- There may be merge conflicts.
4. Begin cherry-picking.
- `git cherry-pick {PR commit from master you wish to cherry pick}`
- Fix conflicts
- `git cherry-pick --continue`
- `git push cherry-picks/rc1/vX.X.x`
> Once all commits are included and CI/tests have passed, then it is ready for a release.
5. Create a release branch `release/vX.X.x` off the release candidate branch.
- `git checkout -b release/vX.X.x`
- `git push -u origin release/vX.X.x`
> Note this Branch is protected once pushed, you will need admin help to make any change merges into the branch.
6. Merge Commit the release branch into the latest major release branch `vX.X`, this will start the release process.
7. 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.
> Note: Do not merge the release branch into master.
#### Backport Release
1. start from the existing release branch you want to backport changes to (e.g. v0.30)
Branch to a release/vX.X.X branch locally (e.g. release/v0.30.7)
2. cherry pick the commit(s) that contain the changes you want to backport (usually these commits are from squash-merged PRs which were already reviewed)
3. steps 2 and 3 from [Major Release](#major-release)
4. push changes to release/vX.X.X branch
5. open a PR against the existing vX.X branch
## Testing
### Unit tests
All repos should be hooked up to [CircleCI](https://circleci.com/).
Unit tests are located in `_test.go` files as directed by [the Go testing
package](https://golang.org/pkg/testing/). If you're adding or removing a
function, please check there's a `TestType_Method` test for it.
Run: `make test`
### Integration tests
Integration tests are also located in `_test.go` files. What differentiates
them is a more complicated setup, which usually involves setting up two or more
components.
Run: `make test_integrations`
### End-to-end tests
End-to-end tests are used to verify a fully integrated Tendermint network.
See [README](./test/e2e/README.md) for details.
Run:
```sh
cd test/e2e && \
make && \
./build/runner -f networks/ci.toml
```
### Model-based tests (ADVANCED)
*NOTE: if you're just submitting your first PR, you won't need to touch these
most probably (99.9%)*.
For components, that have been [formally
verified](https://en.wikipedia.org/wiki/Formal_verification) using
[TLA+](https://en.wikipedia.org/wiki/TLA%2B), it may be possible to generate
tests using a combination of the [Apalache Model
Checker](https://apalache.informal.systems/) and [tendermint-rs testgen
util](https://github.com/informalsystems/tendermint-rs/tree/master/testgen).
Now, I know there's a lot to take in. If you want to learn more, check out [
this video](https://www.youtube.com/watch?v=aveoIMphzW8) by Andrey Kupriyanov
& Igor Konnov.
At the moment, we have model-based tests for the light client, located in the
`./light/mbt` directory.
Run: `cd light/mbt && go test`
### Fuzz tests (ADVANCED)
*NOTE: if you're just submitting your first PR, you won't need to touch these
most probably (99.9%)*.
[Fuzz tests](https://en.wikipedia.org/wiki/Fuzzing) can be found inside the
`./test/fuzz` directory. See [README.md](./test/fuzz/README.md) for details.
Run: `cd test/fuzz && make fuzz-{PACKAGE-COMPONENT}`
### Jepsen tests (ADVANCED)
*NOTE: if you're just submitting your first PR, you won't need to touch these
most probably (99.9%)*.
[Jepsen](http://jepsen.io/) tests are used to verify the
[linearizability](https://jepsen.io/consistency/models/linearizable) property
of the Tendermint consensus. They are located in a separate repository
-> <https://github.com/tendermint/jepsen>. Please refer to its README for more
information.
If they have `.go` files in the root directory, they will be automatically
tested by circle using `go test -v -race ./...`. If not, they will need a
`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and
includes its continuous integration status using a badge in the `README.md`.
### RPC Testing
**If you contribute to the RPC endpoints it's important to document your
changes in the [Openapi file](./rpc/openapi/openapi.yaml)**.
To test your changes you must install `nodejs` and run:
If you contribute to the RPC endpoints it's important to document your changes in the [Swagger file](./rpc/swagger/swagger.yaml)
To test your changes you should install `nodejs` and run:
```bash
npm i -g dredd
@@ -336,8 +301,4 @@ make build-linux build-contract-tests-hooks
make contract-tests
```
**WARNING: these are currently broken due to <https://github.com/apiaryio/dredd>
not supporting complete OpenAPI 3**.
This command will popup a network and check every endpoint against what has
been documented.
This command will popup a network and check every endpoint against what has been documented

1
DOCKER/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
tendermint

View File

@@ -1,14 +1,4 @@
# stage 1 Generate Tendermint Binary
FROM golang:1.17-alpine as builder
RUN apk update && \
apk upgrade && \
apk --no-cache add make git
COPY / /tendermint
WORKDIR /tendermint
RUN make build-linux
# stage 2
FROM golang:1.17-alpine
FROM alpine:3.9
LABEL maintainer="hello@tendermint.com"
# Tendermint will be looking for the genesis file in /tendermint/config/genesis.json
@@ -37,19 +27,28 @@ WORKDIR $TMHOME
# p2p, rpc and prometheus port
EXPOSE 26656 26657 26660
ENTRYPOINT ["/usr/bin/tendermint"]
CMD ["node"]
STOPSIGNAL SIGTERM
COPY --from=builder /tendermint/build/tendermint /usr/bin/tendermint
ARG BINARY=tendermint
COPY $BINARY /usr/bin/tendermint
# You can overwrite these before the first run to influence
# config.json and genesis.json. Additionally, you can override
# CMD to add parameters to `tendermint node`.
ENV PROXY_APP=kvstore MONIKER=dockernode CHAIN_ID=dockerchain
COPY ./DOCKER/docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["start"]
# Create default configuration for docker run.
RUN /usr/bin/tendermint init && \
sed -i \
-e 's/^proxy_app\s*=.*/proxy_app = "kvstore"/' \
-e 's/^moniker\s*=.*/moniker = "dockernode"/' \
-e 's/^addr_book_strict\s*=.*/addr_book_strict = false/' \
-e 's/^timeout_commit\s*=.*/timeout_commit = "500ms"/' \
-e 's/^index_all_tags\s*=.*/index_all_tags = true/' \
-e 's,^laddr = "tcp://127.0.0.1:26657",laddr = "tcp://0.0.0.0:26657",' \
-e 's/^prometheus\s*=.*/prometheus = true/' \
$TMHOME/config/config.toml && \
sed -i \
-e 's/^\s*"chain_id":.*/ "chain_id": "dockerchain",/' \
$TMHOME/config/genesis.json
# Expose the data directory as a volume since there's mutable state in there
VOLUME [ "$TMHOME" ]
VOLUME [ $TMHOME ]

View File

@@ -0,0 +1,28 @@
FROM amazonlinux:2
RUN yum -y update && \
yum -y install wget
RUN wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \
rpm -ivh epel-release-latest-7.noarch.rpm
RUN yum -y groupinstall "Development Tools"
RUN yum -y install leveldb-devel which
ENV GOVERSION=1.12.9
RUN cd /tmp && \
wget https://dl.google.com/go/go${GOVERSION}.linux-amd64.tar.gz && \
tar -C /usr/local -xf go${GOVERSION}.linux-amd64.tar.gz && \
mkdir -p /go/src && \
mkdir -p /go/bin
ENV PATH=$PATH:/usr/local/go/bin:/go/bin
ENV GOBIN=/go/bin
ENV GOPATH=/go/src
RUN mkdir -p /tendermint
WORKDIR /tendermint
CMD ["/usr/bin/make", "build", "TENDERMINT_BUILD_OPTIONS=cleveldb"]

16
DOCKER/Dockerfile.testing Normal file
View File

@@ -0,0 +1,16 @@
FROM golang:latest
# Grab deps (jq, hexdump, xxd, killall)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
jq bsdmainutils vim-common psmisc netcat
# Add testing deps for curl
RUN echo 'deb http://httpredir.debian.org/debian testing main non-free contrib' >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y --no-install-recommends curl
VOLUME /go
EXPOSE 26656
EXPOSE 26657

13
DOCKER/Makefile Normal file
View File

@@ -0,0 +1,13 @@
build:
@sh -c "'$(CURDIR)/build.sh'"
push:
@sh -c "'$(CURDIR)/push.sh'"
build_testing:
docker build --tag tendermint/testing -f ./Dockerfile.testing .
build_amazonlinux_buildimage:
docker build -t "tendermint/tendermint:build_c-amazonlinux" -f Dockerfile.build_c-amazonlinux .
.PHONY: build push build_testing build_amazonlinux_buildimage

View File

@@ -8,12 +8,12 @@ Official releases can be found [here](https://github.com/tendermint/tendermint/r
The Dockerfile for tendermint is not expected to change in the near future. The master file used for all builds can be found [here](https://raw.githubusercontent.com/tendermint/tendermint/master/DOCKER/Dockerfile).
Respective versioned files can be found at `https://raw.githubusercontent.com/tendermint/tendermint/vX.XX.XX/DOCKER/Dockerfile` (replace the Xs with the version number).
Respective versioned files can be found https://raw.githubusercontent.com/tendermint/tendermint/vX.XX.XX/DOCKER/Dockerfile (replace the Xs with the version number).
## Quick reference
- **Where to get help:** <https://tendermint.com/>
- **Where to file issues:** <https://github.com/tendermint/tendermint/issues>
- **Where to get help:** https://tendermint.com/
- **Where to file issues:** https://github.com/tendermint/tendermint/issues
- **Supported Docker versions:** [the latest release](https://github.com/moby/moby/releases) (down to 1.6 on a best-effort basis)
## Tendermint
@@ -30,16 +30,16 @@ To get started developing applications, see the [application developers guide](h
A quick example of a built-in app and Tendermint core in one container.
```sh
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init validator
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint start --proxy-app=kvstore
```
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint init
docker run -it --rm -v "/tmp:/tendermint" tendermint/tendermint node --proxy_app=kvstore
```
## Local cluster
To run a 4-node network, see the `Makefile` in the root of [the repo](https://github.com/tendermint/tendermint/blob/master/Makefile) and run:
```sh
```
make build-linux
make build-docker-localnode
make localnet-start

20
DOCKER/build.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
# Get the tag from the version, or try to figure it out.
if [ -z "$TAG" ]; then
TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go)
fi
if [ -z "$TAG" ]; then
echo "Please specify a tag."
exit 1
fi
TAG_NO_PATCH=${TAG%.*}
read -p "==> Build 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
docker build -t "tendermint/tendermint" -t "tendermint/tendermint:$TAG" -t "tendermint/tendermint:$TAG_NO_PATCH" .
fi

View File

@@ -1,23 +0,0 @@
#!/bin/bash
set -e
if [ ! -d "$TMHOME/config" ]; then
echo "Running tendermint init to create (default) configuration for docker run."
tendermint init validator
sed -i \
-e "s/^proxy-app\s*=.*/proxy-app = \"$PROXY_APP\"/" \
-e "s/^moniker\s*=.*/moniker = \"$MONIKER\"/" \
-e 's/^addr-book-strict\s*=.*/addr-book-strict = false/' \
-e 's/^timeout-commit\s*=.*/timeout-commit = "500ms"/' \
-e 's/^index-all-tags\s*=.*/index-all-tags = true/' \
-e 's,^laddr = "tcp://127.0.0.1:26657",laddr = "tcp://0.0.0.0:26657",' \
-e 's/^prometheus\s*=.*/prometheus = true/' \
"$TMHOME/config/config.toml"
jq ".chain_id = \"$CHAIN_ID\" | .consensus_params.block.time_iota_ms = \"500\"" \
"$TMHOME/config/genesis.json" > "$TMHOME/config/genesis.json.new"
mv "$TMHOME/config/genesis.json.new" "$TMHOME/config/genesis.json"
fi
exec tendermint "$@"

22
DOCKER/push.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -e
# Get the tag from the version, or try to figure it out.
if [ -z "$TAG" ]; then
TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go)
fi
if [ -z "$TAG" ]; then
echo "Please specify a tag."
exit 1
fi
TAG_NO_PATCH=${TAG%.*}
read -p "==> Push 3 docker images with the following tags (latest, $TAG, $TAG_NO_PATCH)? y/n" -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
docker push "tendermint/tendermint:latest"
docker push "tendermint/tendermint:$TAG"
docker push "tendermint/tendermint:$TAG_NO_PATCH"
fi

217
Makefile
View File

@@ -1,18 +1,11 @@
#!/usr/bin/make -f
BUILDDIR ?= $(CURDIR)/build
PACKAGES=$(shell go list ./...)
OUTPUT?=build/tendermint
BUILD_TAGS?=tendermint
# If building a release, please checkout the version tag to get the correct version setting
ifneq ($(shell git symbolic-ref -q --short HEAD),)
VERSION := unreleased-$(shell git symbolic-ref -q --short HEAD)-$(shell git rev-parse HEAD)
else
VERSION := $(shell git describe)
endif
LD_FLAGS = -X github.com/tendermint/tendermint/version.TMVersion=$(VERSION)
LD_FLAGS = -X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`
BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)"
HTTPS_GIT := https://github.com/tendermint/tendermint.git
DOCKER_BUF := docker run -v $(shell pwd):/workspace --workdir /workspace bufbuild/buf
CGO_ENABLED ?= 0
# handle nostrip
@@ -33,87 +26,61 @@ ifeq (cleveldb,$(findstring cleveldb,$(TENDERMINT_BUILD_OPTIONS)))
BUILD_TAGS += cleveldb
endif
# handle badgerdb
ifeq (badgerdb,$(findstring badgerdb,$(TENDERMINT_BUILD_OPTIONS)))
BUILD_TAGS += badgerdb
endif
# handle rocksdb
ifeq (rocksdb,$(findstring rocksdb,$(TENDERMINT_BUILD_OPTIONS)))
CGO_ENABLED=1
BUILD_TAGS += rocksdb
endif
# handle boltdb
ifeq (boltdb,$(findstring boltdb,$(TENDERMINT_BUILD_OPTIONS)))
BUILD_TAGS += boltdb
endif
# allow users to pass additional flags via the conventional LDFLAGS variable
LD_FLAGS += $(LDFLAGS)
all: check build test install
.PHONY: all
include test/Makefile
# The below include contains the tools.
include tools.mk
include tests.mk
###############################################################################
### Build Tendermint ###
### Build Tendermint ###
###############################################################################
build: $(BUILDDIR)/
CGO_ENABLED=$(CGO_ENABLED) go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(BUILDDIR)/ ./cmd/tendermint/
build:
CGO_ENABLED=$(CGO_ENABLED) go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT) ./cmd/tendermint/
.PHONY: build
install:
CGO_ENABLED=$(CGO_ENABLED) go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/tendermint
.PHONY: install
$(BUILDDIR)/:
mkdir -p $@
###############################################################################
### Protobuf ###
###############################################################################
check-proto-deps:
ifeq (,$(shell which protoc-gen-gogofaster))
$(error "gogofaster plugin for protoc is required. Run 'go install github.com/gogo/protobuf/protoc-gen-gogofaster@latest' to install")
endif
.PHONY: check-proto-deps
proto-all: proto-gen proto-lint proto-check-breaking
.PHONY: proto-all
check-proto-format-deps:
ifeq (,$(shell which clang-format))
$(error "clang-format is required for Protobuf formatting. See instructions for your platform on how to install it.")
endif
.PHONY: check-proto-format-deps
proto-gen: check-proto-deps
@echo "Generating Protobuf files"
@go run github.com/bufbuild/buf/cmd/buf generate
@mv ./proto/tendermint/abci/types.pb.go ./abci/types/
proto-gen:
## If you get the following error,
## "error while loading shared libraries: libprotobuf.so.14: cannot open shared object file: No such file or directory"
## See https://stackoverflow.com/a/25518702
## Note the $< here is substituted for the %.proto
## Note the $@ here is substituted for the %.pb.go
@sh scripts/protocgen.sh
.PHONY: proto-gen
# These targets are provided for convenience and are intended for local
# execution only.
proto-lint: check-proto-deps
@echo "Linting Protobuf files"
@go run github.com/bufbuild/buf/cmd/buf lint
proto-gen-docker:
@echo "Generating Protobuf files"
@docker run -v $(shell pwd):/workspace --workdir /workspace tendermintdev/docker-build-proto sh ./scripts/protocgen.sh
.PHONY: proto-gen-docker
proto-lint:
@$(DOCKER_BUF) check lint --error-format=json
.PHONY: proto-lint
proto-format: check-proto-format-deps
@echo "Formatting Protobuf files"
@find . -name '*.proto' -path "./proto/*" -exec clang-format -i {} \;
.PHONY: proto-format
proto-check-breaking: check-proto-deps
@echo "Checking for breaking changes in Protobuf files against local branch"
@echo "Note: This is only useful if your changes have not yet been committed."
@echo " Otherwise read up on buf's \"breaking\" command usage:"
@echo " https://docs.buf.build/breaking/usage"
@go run github.com/bufbuild/buf/cmd/buf breaking --against ".git"
proto-check-breaking:
@$(DOCKER_BUF) check breaking --against-input .git#branch=master
.PHONY: proto-check-breaking
proto-check-breaking-ci:
@$(DOCKER_BUF) check breaking --against-input $(HTTPS_GIT)#branch=master
.PHONY: proto-check-breaking-ci
###############################################################################
### Build ABCI ###
###############################################################################
@@ -126,27 +93,6 @@ install_abci:
@go install -mod=readonly ./abci/cmd/...
.PHONY: install_abci
###############################################################################
### Privval Server ###
###############################################################################
build_privval_server:
@go build -mod=readonly -o $(BUILDDIR)/ -i ./cmd/priv_val_server/...
.PHONY: build_privval_server
generate_test_cert:
# generate self signing ceritificate authority
@certstrap init --common-name "root CA" --expires "20 years"
# generate server cerificate
@certstrap request-cert -cn server -ip 127.0.0.1
# self-sign server cerificate with rootCA
@certstrap sign server --CA "root CA"
# generate client cerificate
@certstrap request-cert -cn client -ip 127.0.0.1
# self-sign client cerificate with rootCA
@certstrap sign client --CA "root CA"
.PHONY: generate_test_cert
###############################################################################
### Distribution ###
###############################################################################
@@ -169,13 +115,13 @@ go.sum: go.mod
draw_deps:
@# requires brew install graphviz or apt-get install graphviz
go install github.com/RobotsAndPencils/goviz@latest
go get github.com/RobotsAndPencils/goviz
@goviz -i github.com/tendermint/tendermint/cmd/tendermint -d 3 | dot -Tpng -o dependency-graph.png
.PHONY: draw_deps
get_deps_bin_size:
@# Copy of build recipe with additional flags to perform binary size analysis
$(eval $(shell go build -work -a $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(BUILDDIR)/ ./cmd/tendermint/ 2>&1))
$(eval $(shell go build -work -a $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(OUTPUT) ./cmd/tendermint/ 2>&1))
@find $(WORK) -type f -name "*.a" | xargs -I{} du -hxs "{}" | sort -rh | sed -e s:${WORK}/::g > deps_bin_size.log
@echo "Results can be found here: $(CURDIR)/deps_bin_size.log"
.PHONY: get_deps_bin_size
@@ -211,7 +157,7 @@ format:
lint:
@echo "--> Running linter"
go run github.com/golangci/golangci-lint/cmd/golangci-lint run
@golangci-lint run
.PHONY: lint
DESTINATION = ./index.html.md
@@ -219,56 +165,41 @@ DESTINATION = ./index.html.md
###############################################################################
### Documentation ###
###############################################################################
# todo remove once tendermint.com DNS is solved
build-docs:
@cd docs && \
while read -r branch path_prefix; do \
( git checkout $${branch} && npm ci --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 ; \
cd docs && \
while read p; do \
(git checkout $${p} && npm install && VUEPRESS_BASE="/$${p}/" npm run build) ; \
mkdir -p ~/output/$${p} ; \
cp -r .vuepress/dist/* ~/output/$${p}/ ; \
cp ~/output/$${p}/index.html ~/output ; \
done < versions ;
.PHONY: build-docs
sync-docs:
cd ~/output && \
echo "role_arn = ${DEPLOYMENT_ROLE_ARN}" >> /root/.aws/config ; \
echo "CI job = ${CIRCLE_BUILD_URL}" >> version.html ; \
aws s3 sync . s3://${WEBSITE_BUCKET} --profile terraform --delete ; \
aws cloudfront create-invalidation --distribution-id ${CF_DISTRIBUTION_ID} --profile terraform --path "/*" ;
.PHONY: sync-docs
###############################################################################
### Docker image ###
###############################################################################
build-docker:
docker build --label=tendermint --tag="tendermint/tendermint" -f DOCKER/Dockerfile .
cp $(OUTPUT) DOCKER/tendermint
docker build --label=tendermint --tag="tendermint/tendermint" DOCKER
rm -rf DOCKER/tendermint
.PHONY: build-docker
###############################################################################
### Mocks ###
###############################################################################
mockery:
go generate -run="./scripts/mockery_generate.sh" ./...
.PHONY: mockery
###############################################################################
### Metrics ###
###############################################################################
metrics: testdata-metrics
go generate -run="scripts/metricsgen" ./...
.PHONY: metrics
# By convention, the go tool ignores subdirectories of directories named
# 'testdata'. This command invokes the generate command on the folder directly
# to avoid this.
testdata-metrics:
ls ./scripts/metricsgen/testdata | xargs -I{} go generate -run="scripts/metricsgen" ./scripts/metricsgen/testdata/{}
.PHONY: testdata-metrics
###############################################################################
### Local testnet using docker ###
###############################################################################
# Build linux binary on other platforms
build-linux:
build-linux: tools
GOOS=linux GOARCH=amd64 $(MAKE) build
.PHONY: build-linux
@@ -286,7 +217,7 @@ build_c-amazonlinux:
# Run a 4-node testnet locally
localnet-start: localnet-stop build-docker-localnode
@if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --config /etc/tendermint/config-template.toml --o . --starting-ip-address 192.167.10.2; fi
@if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --config /etc/tendermint/config-template.toml --v 4 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2; fi
docker-compose up
.PHONY: localnet-start
@@ -312,39 +243,3 @@ endif
contract-tests:
dredd
.PHONY: contract-tests
clean:
rm -rf $(CURDIR)/artifacts/ $(BUILDDIR)/
build-reproducible:
docker rm latest-build || true
docker run --volume=$(CURDIR):/sources:ro \
--env TARGET_PLATFORMS='linux/amd64 linux/arm64 darwin/amd64 windows/amd64' \
--env APP=tendermint \
--env COMMIT=$(shell git rev-parse --short=8 HEAD) \
--env VERSION=$(shell git describe --tags) \
--name latest-build cosmossdk/rbuilder:latest
docker cp -a latest-build:/home/builder/artifacts/ $(CURDIR)/
.PHONY: build-reproducible
# Implements test splitting and running. This is pulled directly from
# the github action workflows for better local reproducibility.
GO_TEST_FILES != find $(CURDIR) -name "*_test.go"
# default to four splits by default
NUM_SPLIT ?= 4
$(BUILDDIR):
mkdir -p $@
# The format statement filters out all packages that don't have tests.
# Note we need to check for both in-package tests (.TestGoFiles) and
# out-of-package tests (.XTestGoFiles).
$(BUILDDIR)/packages.txt:$(GO_TEST_FILES) $(BUILDDIR)
go list -f "{{ if (or .TestGoFiles .XTestGoFiles) }}{{ .ImportPath }}{{ end }}" ./... | sort > $@
split-test-packages:$(BUILDDIR)/packages.txt
split -d -n l/$(NUM_SPLIT) $< $<.
test-group-%:split-test-packages
cat $(BUILDDIR)/packages.txt.$* | xargs go test -mod=readonly -timeout=5m -race -coverprofile=$(BUILDDIR)/$*.profile.out

158
PHILOSOPHY.md Normal file
View File

@@ -0,0 +1,158 @@
## Design goals
The design goals for Tendermint (and the SDK and related libraries) are:
* Simplicity and Legibility
* Parallel performance, namely ability to utilize multicore architecture
* Ability to evolve the codebase bug-free
* Debuggability
* Complete correctness that considers all edge cases, esp in concurrency
* Future-proof modular architecture, message protocol, APIs, and encapsulation
### Justification
Legibility is key to maintaining bug-free software as it evolves toward more
optimizations, more ease of debugging, and additional features.
It is too easy to introduce bugs over time by replacing lines of code with
those that may panic, which means ideally locks are unlocked by defer
statements.
For example,
```go
func (obj *MyObj) something() {
mtx.Lock()
obj.something = other
mtx.Unlock()
}
```
It is too easy to refactor the codebase in the future to replace `other` with
`other.String()` for example, and this may introduce a bug that causes a
deadlock. So as much as reasonably possible, we need to be using defer
statements, even though it introduces additional overhead.
If it is necessary to optimize the unlocking of mutex locks, the solution is
more modularity via smaller functions, so that defer'd unlocks are scoped
within a smaller function.
Similarly, idiomatic for-loops should always be preferred over those that use
custom counters, because it is too easy to evolve the body of a for-loop to
become more complicated over time, and it becomes more and more difficult to
assess the correctness of such a for-loop by visual inspection.
### On performance
It doesn't matter whether there are alternative implementations that are 2x or
3x more performant, when the software doesn't work, deadlocks, or if bugs
cannot be debugged. By taking advantage of multicore concurrency, the
Tendermint implementation will at least be an order of magnitude within the
range of what is theoretically possible. The design philosophy of Tendermint,
and the choice of Go as implementation language, is designed to make Tendermint
implementation the standard specification for concurrent BFT software.
By focusing on the message protocols (e.g. ABCI, p2p messages), and
encapsulation e.g. IAVL module, (relatively) independent reactors, we are both
implementing a standard implementation to be used as the specification for
future implementations in more optimizable languages like Rust, Java, and C++;
as well as creating sufficiently performant software. Tendermint Core will
never be as fast as future implementations of the Tendermint Spec, because Go
isn't designed to be as fast as possible. The advantage of using Go is that we
can develop the whole stack of modular components **faster** than in other
languages.
Furthermore, the real bottleneck is in the application layer, and it isn't
necessary to support more than a sufficiently decentralized set of validators
(e.g. 100 ~ 300 validators is sufficient, with delegated bonded PoS).
Instead of optimizing Tendermint performance down to the metal, lets focus on
optimizing on other matters, namely ability to push feature complete software
that works well enough, can be debugged and maintained, and can serve as a spec
for future implementations.
### On encapsulation
In order to create maintainable, forward-optimizable software, it is critical
to develop well-encapsulated objects that have well understood properties, and
to re-use these easy-to-use-correctly components as building blocks for further
encapsulated meta-objects.
For example, mutexes are cheap enough for Tendermint's design goals when there
isn't goroutine contention, so it is encouraged to create concurrency safe
structures with struct-level mutexes. If they are used in the context of
non-concurrent logic, then the performance is good enough. If they are used in
the context of concurrent logic, then it will still perform correctly.
Examples of this design principle can be seen in the types.ValidatorSet struct,
and the rand.Rand struct. It's one single struct declaration that can be used
in both concurrent and non-concurrent logic, and due to its well encapsulation,
it's easy to get the usage of the mutex right.
#### example: rand.Rand:
`The default Source is safe for concurrent use by multiple goroutines, but
Sources created by NewSource are not`. The reason why the default
package-level source is safe for concurrent use is because it is protected (see
`lockedSource` in https://golang.org/src/math/rand/rand.go).
But we shouldn't rely on the global source, we should be creating our own
Rand/Source instances and using them, especially for determinism in testing.
So it is reasonable to have rand.Rand be protected by a mutex. Whether we want
our own implementation of Rand is another question, but the answer there is
also in the affirmative. Sometimes you want to know where Rand is being used
in your code, so it becomes a simple matter of dropping in a log statement to
inject inspectability into Rand usage. Also, it is nice to be able to extend
the functionality of Rand with custom methods. For these reasons, and for the
reasons which is outlined in this design philosophy document, we should
continue to use the rand.Rand object, with mutex protection.
Another key aspect of good encapsulation is the choice of exposed vs unexposed
methods. It should be clear to the reader of the code, which methods are
intended to be used in what context, and what safe usage is. Part of this is
solved by hiding methods via unexported methods. Another part of this is
naming conventions on the methods (e.g. underscores) with good documentation,
and code organization. If there are too many exposed methods and it isn't
clear what methods have what side effects, then there is something wrong about
the design of abstractions that should be revisited.
### On concurrency
In order for Tendermint to remain relevant in the years to come, it is vital
for Tendermint to take advantage of multicore architectures. Due to the nature
of the problem, namely consensus across a concurrent p2p gossip network, and to
handle RPC requests for a large number of consuming subscribers, it is
unavoidable for Tendermint development to require expertise in concurrency
design, especially when it comes to the reactor design, and also for RPC
request handling.
## Guidelines
Here are some guidelines for designing for (sufficient) performance and concurrency:
* Mutex locks are cheap enough when there isn't contention.
* Do not optimize code without analytical or observed proof that it is in a hot path.
* Don't over-use channels when mutex locks w/ encapsulation are sufficient.
* The need to drain channels are often a hint of unconsidered edge cases.
* The creation of O(N) one-off goroutines is generally technical debt that
needs to get addressed sooner than later. Avoid creating too many
goroutines as a patch around incomplete concurrency design, or at least be
aware of the debt and do not invest in the debt. On the other hand, Tendermint
is designed to have a limited number of peers (e.g. 10 or 20), so the creation
of O(C) goroutines per O(P) peers is still O(C\*P=constant).
* Use defer statements to unlock as much as possible. If you want to unlock sooner,
try to create more modular functions that do make use of defer statements.
## Matras
* Premature optimization kills
* Readability is paramount
* Beautiful is better than fast.
* In the face of ambiguity, refuse the temptation to guess.
* In the face of bugs, refuse the temptation to cover the bug.
* There should be one-- and preferably only one --obvious way to do it.

159
README.md
View File

@@ -3,75 +3,81 @@
![banner](docs/tendermint-core-image.jpg)
[Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance)
[State Machine Replication](https://en.wikipedia.org/wiki/State_machine_replication).
[State Machines](https://en.wikipedia.org/wiki/State_machine_replication).
Or [Blockchain](<https://en.wikipedia.org/wiki/Blockchain_(database)>), for short.
[![version](https://img.shields.io/github/tag/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/releases/latest)
[![API Reference](https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667)](https://pkg.go.dev/github.com/tendermint/tendermint)
[![Go version](https://img.shields.io/badge/go-1.16-blue.svg)](https://github.com/moovweb/gvm)
[![Discord chat](https://img.shields.io/discord/669268347736686612.svg)](https://discord.gg/cosmosnetwork)
[![API Reference](https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667)](https://godoc.org/github.com/tendermint/tendermint)
[![Go version](https://img.shields.io/badge/go-1.13-blue.svg)](https://github.com/moovweb/gvm)
[![Discord](https://img.shields.io/discord/669268347736686612.svg)](https://discord.gg/AzefAFd)
[![license](https://img.shields.io/github/license/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/blob/master/LICENSE)
[![tendermint/tendermint](https://tokei.rs/b1/github/tendermint/tendermint?category=lines)](https://github.com/tendermint/tendermint)
[![Sourcegraph](https://sourcegraph.com/github.com/tendermint/tendermint/-/badge.svg)](https://sourcegraph.com/github.com/tendermint/tendermint?badge)
[![](https://tokei.rs/b1/github/tendermint/tendermint?category=lines)](https://github.com/tendermint/tendermint)
| Branch | Tests | Coverage | Linting |
|--------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
| master | ![Tests](https://github.com/tendermint/tendermint/workflows/Tests/badge.svg?branch=master) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/master/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) | ![Lint](https://github.com/tendermint/tendermint/workflows/Lint/badge.svg) |
| Branch | Tests | Coverage |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| master | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/master.svg?style=shield)](https://circleci.com/gh/tendermint/tendermint/tree/master) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/master/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) |
Tendermint Core is a Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language - and securely replicates it on many machines.
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language -
and securely replicates it on many machines.
For protocol details, refer to the [Tendermint Specification](./spec/README.md).
For protocol details, see [the specification](https://github.com/tendermint/spec).
For detailed analysis of the consensus protocol, including safety and liveness proofs,
read our paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
## Documentation
Complete documentation can be found on the [website](https://docs.tendermint.com/).
see our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
## Releases
Please do not depend on master as your production branch. Use [releases](https://github.com/tendermint/tendermint/releases) instead.
NOTE: The master branch is now an active development branch (starting with `v0.32`). Please, do not depend on it and
use [releases](https://github.com/tendermint/tendermint/releases) instead.
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.
Tendermint is being used in production in both private and public environments,
most notably the blockchains of the [Cosmos Network](https://cosmos.network/).
However, we are still making breaking changes to the protocol and the APIs and have not yet released v1.0.
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@newtendermint.org) or [join the
chat](https://discord.gg/gnoland).
More on how releases are conducted can be found [here](./RELEASES.md).
In any case, if you intend to run Tendermint in production,
please [contact us](mailto:partners@tendermint.com) and [join the chat](https://riot.im/app/#/room/#tendermint:matrix.org).
## Security
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).
To report a security vulnerability, see our [bug bounty
program](https://hackerone.com/tendermint)
For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY.md)
## Minimum requirements
| Requirement | Notes |
|-------------|------------------|
| Go version | Go1.17 or higher |
| ----------- | ---------------- |
| Go version | Go1.13 or higher |
## Documentation
Complete documentation can be found on the [website](https://docs.tendermint.com/master/).
### Install
See the [install instructions](./docs/introduction/install.md).
See the [install instructions](/docs/introduction/install.md)
### Quick Start
- [Single node](./docs/introduction/quick-start.md)
- [Local cluster using docker-compose](./docs/tools/docker-compose.md)
- [Remote cluster using Terraform and Ansible](./docs/tools/terraform-and-ansible.md)
- [Single node](/docs/introduction/quick-start.md)
- [Local cluster using docker-compose](/docs/networks/docker-compose.md)
- [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md)
- [Join the Cosmos testnet](https://cosmos.network/testnet)
## Contributing
Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions.
Before contributing to the project, please take a look at the [contributing guidelines](CONTRIBUTING.md)
and the [style guide](STYLE_GUIDE.md). You may also find it helpful to read the
[specifications](./spec/README.md),
and familiarize yourself with our
[Architectural Decision Records (ADRs)](./docs/architecture/README.md) and [Request For Comments (RFCs)](./docs/rfc/README.md).
and the [style guide](STYLE_GUIDE.md).
To get more active, Join the wider community at [Discord](https://discord.gg/AzefAFd) or jump onto the [Forum](https://forum.cosmos.network/).
Learn more by reading the code and the
[specifications](https://github.com/tendermint/spec) or watch the [Developer Sessions](/docs/DEV_SESSIONS.md) and read up on the
[Architectural Decision Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture).
## Versioning
@@ -80,65 +86,82 @@ and familiarize yourself with our
Tendermint uses [Semantic Versioning](http://semver.org/) to determine when and how the version changes.
According to SemVer, anything in the public API can change at any time before version 1.0.0
To provide some stability to users of 0.X.X versions of Tendermint, the MINOR version is used
to signal breaking changes across Tendermint's API. This API includes all
publicly exposed types, functions, and methods in non-internal Go packages as well as
the types and methods accessible via the Tendermint RPC interface.
To provide some stability to Tendermint users in these 0.X.X days, the MINOR version is used
to signal breaking changes across a subset of the total public API. This subset includes all
interfaces exposed to other processes (cli, rpc, p2p, etc.), but does not
include the in-process Go APIs.
Breaking changes to these public APIs will be documented in the CHANGELOG.
That said, breaking changes in the following packages will be documented in the
CHANGELOG even if they don't lead to MINOR version bumps:
- crypto
- types
- rpc/client
- config
- node
- libs
- bech32
- common
- db
- errors
- log
Exported objects in these packages that are not covered by the versioning scheme
are explicitly marked by `// UNSTABLE` in their go doc comment and may change at any
time without notice. Functions, types, and values in any other package may also change at any time.
### Upgrades
In an effort to avoid accumulating technical debt prior to 1.0.0,
we do not guarantee that breaking changes (ie. bumps in the MINOR version)
will work with existing Tendermint blockchains. In these cases you will
will work with existing tendermint blockchains. In these cases you will
have to start a new blockchain, or write something custom to get the old
data into the new chain. However, any bump in the PATCH version should be
compatible with existing blockchain histories.
data into the new chain.
However, any bump in the PATCH version should be compatible with existing histories
(if not please open an [issue](https://github.com/tendermint/tendermint/issues)).
For more information on upgrading, see [UPGRADING.md](./UPGRADING.md).
### Supported Versions
Because we are a small core team, we only ship patch updates, including security updates,
to the most recent minor release and the second-most recent minor release. Consequently,
we strongly recommend keeping Tendermint up-to-date. Upgrading instructions can be found
Because we are a small core team, we only ship patch updates, including security updates,
to the most recent minor release and the second-most recent minor release. Consequently,
we strongly recommend keeping Tendermint up-to-date. Upgrading instructions can be found
in [UPGRADING.md](./UPGRADING.md).
## Resources
### Roadmap
### Tendermint Core
We keep a public up-to-date version of our roadmap [here](./docs/roadmap/roadmap.md)
For details about the blockchain data structures and the p2p protocols, see the
[Tendermint specification](https://docs.tendermint.com/master/spec/).
### Libraries
For details on using the software, see the [documentation](/docs/) which is also
hosted at: https://docs.tendermint.com/master/
- [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); A framework for building applications in Golang
- [Tendermint in Rust](https://github.com/informalsystems/tendermint-rs)
- [ABCI Tower](https://github.com/penumbra-zone/tower-abci)
### Tools
Benchmarking is provided by `tm-load-test`.
The code for `tm-load-test` can be found [here](https://github.com/informalsystems/tm-load-test) this binary needs to be built separately.
Additional documentation is found [here](/docs/tools).
### Sub-projects
- [Amino](http://github.com/tendermint/go-amino), reflection-based proto3, with
interfaces
- [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation
- [Tm-db](http://github.com/tendermint/tm-db), Data Base abstractions to be used in applications.
### Applications
- [Cosmos Hub](https://hub.cosmos.network/)
- [Terra](https://www.terra.money/)
- [Celestia](https://celestia.org/)
- [Anoma](https://anoma.network/)
- [Vocdoni](https://docs.vocdoni.io/)
- [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
- [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint
- [Many more](https://tendermint.com/ecosystem)
### Research
- [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)
- [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769)
- [Original Whitepaper: "Tendermint: Consensus Without Mining"](https://tendermint.com/static/docs/tendermint.pdf)
- [Tendermint Core Blog](https://medium.com/tendermint/tagged/tendermint-core)
- [Cosmos Blog](https://blog.cosmos.network/tendermint/home)
## Join us!
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)!
- [Blog](https://blog.cosmos.network/tendermint/home)

View File

@@ -1,343 +0,0 @@
# Releases
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
that are cut from `master` when the release process begins.
Each release "line" (e.g. 0.34 or 0.33) has its own long-lived backport branch,
and the backport branches have names like `v0.34.x` or `v0.33.x`
(literally, `x`; it is not a placeholder in this case). Tendermint only
maintains the last two releases at a time (the oldest release is predominantly
just security patches).
## Backporting
As non-breaking changes land on `master`, they should also be backported
to these backport branches.
We use Mergify's [backport feature](https://mergify.io/features/backports) to automatically backport
to the needed branch. There should be a label for any backport branch that you'll be targeting.
To notify the bot to backport a pull request, mark the pull request with the label corresponding
to the correct backport branch. For example, to backport to v0.35.x, add the label `S:backport-to-v0.35.x`.
Once the original pull request is merged, the bot will try to cherry-pick the pull request
to the backport branch. If the bot fails to backport, it will open a pull request.
The author of the original pull request is responsible for solving the conflicts and
merging the pull request.
### Creating a backport branch
If this is the first release candidate for a 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
should tag `master` with a "dev" tag that is "greater than" the backport
branches tags. See [#6072](https://github.com/tendermint/tendermint/pull/6072)
for more context.
In the following example, we'll assume that we're making a backport branch for
the 0.35.x line.
1. Start on `master`
2. Create and push the backport branch:
```sh
git checkout -b v0.35.x
git push origin v0.35.x
```
3. Create a PR to update the documentation directory for the backport branch.
We only maintain RFC and ADR documents on master, to avoid confusion.
In addition, we rewrite Markdown URLs pointing to master to point to the
backport branch, so that generated documentation will link to the correct
versions of files elsewhere in the repository. For context on the latter,
see https://github.com/tendermint/tendermint/issues/7675.
To prepare the PR:
```sh
# Remove the RFC and ADR documents from the backport.
# We only maintain these on master to avoid confusion.
git rm -r docs/rfc docs/architecture
# Update absolute links to point to the backport.
go run ./scripts/linkpatch -recur -target v0.35.x -skip-path docs/DOCS_README.md,docs/README.md docs
# Create and push the PR.
git checkout -b update-docs-v035x
git commit -m "Update docs for v0.35.x backport branch." docs
git push -u origin update-docs-v035x
```
Be sure to merge this PR before making other changes on the newly-created
backport branch.
After doing these steps, go back to `master` and do the following:
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."
git push origin v0.36.0-dev
```
2. Create a new workflow to run e2e nightlies for the new backport branch.
(See [e2e-nightly-master.yml][e2e] for an example.)
3. Add a new section to the Mergify config (`.github/mergify.yml`) to enable the
backport bot to work on this branch, and add a corresponding `S:backport-to-v0.35.x`
[label](https://github.com/tendermint/tendermint/labels) so the bot can be triggered.
4. Add a new section to the Dependabot config (`.github/dependabot.yml`) to
enable automatic update of Go dependencies on this branch. Copy and edit one
of the existing branch configurations to set the correct `target-branch`.
[e2e]: https://github.com/tendermint/tendermint/blob/master/.github/workflows/e2e-nightly-master.yml
## Release candidates
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.
Tags for RCs should follow the "standard" release naming conventions, with `-rcX` at the end
(for example, `v0.35.0-rc0`).
(Note that branches and tags _cannot_ have the same names, so it's important that these branches
have distinct names from the tags/release names.)
If this is the first RC for a 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`).
2. Run the integration tests and the e2e nightlies
(which can be triggered from the Github UI;
e.g., https://github.com/tendermint/tendermint/actions/workflows/e2e-nightly-34x.yml).
3. Prepare the changelog:
- Move the changes included in `CHANGELOG_PENDING.md` into `CHANGELOG.md`. Each RC should have
it's own changelog section. These will be squashed when the final candidate is released.
- Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for
all PRs
- Ensure that `UPGRADING.md` is up-to-date and includes notes on any breaking changes
or other upgrading flows.
- Bump TMVersionDefault version in `version.go`
- Bump P2P and block protocol versions in `version.go`, if necessary.
Check the changelog for breaking changes in these components.
- Bump ABCI protocol version in `version.go`, if necessary
4. Open a PR with these changes against the backport branch.
5. Once these changes have landed on the backport branch, be sure to pull them back down locally.
6. Once you have the changes locally, create the new tag, specifying a name and a tag "message":
`git tag -a v0.35.0-rc0 -m "Release Candidate v0.35.0-rc0`
7. Push the tag back up to origin:
`git push origin v0.35.0-rc0`
Now the tag should be available on the repo's releases page.
8. Future RCs will continue to be built off of this branch.
Note that this process should only be used for "true" RCs--
release candidates that, if successful, will be the next release.
For more experimental "RCs," create a new, short-lived branch and tag that instead.
## Minor release
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:
- "Squash" changes from the changelog entries for the RCs into a single entry,
and add all changes included in `CHANGELOG_PENDING.md`.
(Squashing includes both combining all entries, as well as removing or simplifying
any intra-RC changes. It may also help to alphabetize the entries by package name.)
- Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for
all PRs
- Ensure that `UPGRADING.md` is up-to-date and includes notes on any breaking changes
or other upgrading flows.
- Bump TMVersionDefault version in `version.go`
- Bump P2P and block protocol versions in `version.go`, if necessary
- Bump ABCI protocol version in `version.go`, if necessary
4. Open a PR with these changes against the backport branch.
5. Once these changes are on the backport branch, push a tag with prepared release details.
This will trigger the actual release `v0.35.0`.
- `git tag -a v0.35.0 -m 'Release v0.35.0'`
- `git push origin v0.35.0`
6. Make sure that `master` is updated with the latest `CHANGELOG.md`, `CHANGELOG_PENDING.md`, and `UPGRADING.md`.
7. Add the release to the documentation site generator config (see
[DOCS_README.md](./docs/DOCS_README.md) for more details). In summary:
- Start on branch `master`.
- Add a new line at the bottom of [`docs/versions`](./docs/versions) to
ensure the newest release is the default for the landing page.
- Add a new entry to `themeConfig.versions` in
[`docs/.vuepress/config.js`](./docs/.vuepress/config.js) to include the
release in the dropdown versions menu.
- Commit these changes to `master` and backport them into the backport
branch for this release.
## Patch release
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.
Patch releases don't have release candidates by default, although any tricky
changes may merit a release candidate.
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.
3. Check out a new branch and prepare the release:
- Copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md`
- Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues
- Run `bash ./scripts/authors.sh` to get a list of authors since the latest release, and add the GitHub aliases of external contributors to the top of the CHANGELOG. To lookup an alias from an email, try `bash ./scripts/authors.sh <email>`
- Reset the `CHANGELOG_PENDING.md`
- 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 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: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

@@ -1,158 +1,72 @@
# Security
## Reporting a Bug
As part of our [Coordinated Vulnerability Disclosure
Policy](https://tendermint.com/security), we operate a [bug
bounty](https://hackerone.com/cosmos).
See the policy for more details on submissions and rewards, and see "Example Vulnerabilities" (below) for examples of the kinds of bugs we're most interested in.
bounty](https://hackerone.com/tendermint).
See the policy for more details on submissions and rewards.
### Guidelines
Here is a list of examples of the kinds of bugs we're most interested in:
We require that all researchers:
## Specification
* Use the bug bounty to disclose all vulnerabilities, and avoid posting vulnerability information in public places, including Github Issues, Discord channels, and Telegram groups
* Make every effort to avoid privacy violations, degradation of user experience, disruption to production systems (including but not limited to the Cosmos Hub), and destruction of data
* Keep any information about vulnerabilities that youve discovered confidential between yourself and the Tendermint Core engineering team until the issue has been resolved and disclosed
* Avoid posting personally identifiable information, privately or publicly
- Conceptual flaws
- Ambiguities, inconsistencies, or incorrect statements
- Mis-match between specification and implementation of any component
If you follow these guidelines when reporting an issue to us, we commit to:
* Not pursue or support any legal action related to your research on this vulnerability
* Work with you to understand, resolve and ultimately disclose the issue in a timely fashion
## Disclosure Process
Tendermint Core uses the following disclosure process:
1. Once a security report is received, the Tendermint Core team works to verify the issue and confirm its severity level using CVSS.
2. The Tendermint Core team collaborates with the Gaia team to determine the vulnerabilitys potential impact on the Cosmos Hub.
3. Patches are prepared for eligible releases of Tendermint in private repositories. See “Supported Releases” below for more information on which releases are considered eligible.
4. If it is determined that a CVE-ID is required, we request a CVE through a CVE Numbering Authority.
5. We notify the community that a security release is coming, to give users time to prepare their systems for the update. Notifications can include forum posts, tweets, and emails to partners and validators, including emails sent to the [Tendermint Security Mailing List](https://berlin.us4.list-manage.com/subscribe?u=431b35421ff7edcc77df5df10&id=3fe93307bc).
6. 24 hours following this notification, the fixes are applied publicly and new releases are issued.
7. Cosmos SDK and Gaia update their Tendermint Core dependencies to use these releases, and then themselves issue new releases.
8. Once releases are available for Tendermint Core, Cosmos SDK and Gaia, we notify the community, again, through the same channels as above. We also publish a Security Advisory on Github and publish the CVE, as long as neither the Security Advisory nor the CVE include any information on how to exploit these vulnerabilities beyond what information is already available in the patch itself.
9. Once the community is notified, we will pay out any relevant bug bounties to submitters.
10. One week after the releases go out, we will publish a post with further details on the vulnerability as well as our response to it.
This process can take some time. Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow the process described above to ensure that disclosures are handled consistently and to keep Tendermint Core and its downstream dependent projects--including but not limited to Gaia and the Cosmos Hub--as secure as possible.
### Example Timeline
The following is an example timeline for the triage and response. The required roles and team members are described in parentheses after each task; however, multiple people can play each role and each person may play multiple roles.
#### 24+ Hours Before Release Time
1. Request CVE number (ADMIN)
2. Gather emails and other contact info for validators (COMMS LEAD)
3. Create patches in a private security repo, and ensure that PRs are open targeting all relevant release branches (TENDERMINT ENG, TENDERMINT LEAD)
4. Test fixes on a testnet (TENDERMINT ENG, COSMOS SDK ENG)
5. Write “Security Advisory” for forum (TENDERMINT LEAD)
#### 24 Hours Before Release Time
1. Post “Security Advisory” pre-notification on forum (TENDERMINT LEAD)
2. Post Tweet linking to forum post (COMMS LEAD)
3. Announce security advisory/link to post in various other social channels (Telegram, Discord) (COMMS LEAD)
4. Send emails to validators or other users (PARTNERSHIPS LEAD)
#### Release Time
1. Cut Tendermint releases for eligible versions (TENDERMINT ENG, TENDERMINT LEAD)
2. Cut Cosmos SDK release for eligible versions (COSMOS ENG)
3. Cut Gaia release for eligible versions (GAIA ENG)
4. Post “Security releases” on forum (TENDERMINT LEAD)
5. Post new Tweet linking to forum post (COMMS LEAD)
6. Remind everyone via social channels (Telegram, Discord) that the release is out (COMMS LEAD)
7. Send emails to validators or other users (COMMS LEAD)
8. Publish Security Advisory and CVE, if CVE has no sensitive information (ADMIN)
#### After Release Time
1. Write forum post with exploit details (TENDERMINT LEAD)
2. Approve pay-out on HackerOne for submitter (ADMIN)
#### 7 Days After Release Time
1. Publish CVE if it has not yet been published (ADMIN)
2. Publish forum post with exploit details (TENDERMINT ENG, TENDERMINT LEAD)
## Supported Releases
The Tendermint Core team commits to releasing security patch releases for both the latest minor release as well for the major/minor release that the Cosmos Hub is running.
If you are running older versions of Tendermint Core, we encourage you to upgrade at your earliest opportunity so that you can receive security patches directly from the Tendermint repo. While you are welcome to backport security patches to older versions for your own use, we will not publish or promote these backports.
## Scope
The full scope of our bug bounty program is outlined on our [Hacker One program page](https://hackerone.com/cosmos). Please also note that, in the interest of the safety of our users and staff, a few things are explicitly excluded from scope:
* Any third-party services
* Findings from physical testing, such as office access
* Findings derived from social engineering (e.g., phishing)
## Example Vulnerabilities
The following is a list of examples of the kinds of vulnerabilities that were most interested in. It is not exhaustive: there are other kinds of issues we may also be interested in!
### Specification
* Conceptual flaws
* Ambiguities, inconsistencies, or incorrect statements
* Mis-match between specification and implementation of any component
### Consensus
## Consensus
Assuming less than 1/3 of the voting power is Byzantine (malicious):
* Validation of blockchain data structures, including blocks, block parts, votes, and so on
* Execution of blocks
* Validator set changes
* Proposer round robin
* Two nodes committing conflicting blocks for the same height (safety failure)
* A correct node signing conflicting votes
* A node halting (liveness failure)
* Syncing new and old nodes
- Validation of blockchain data structures, including blocks, block parts,
votes, and so on
- Execution of blocks
- Validator set changes
- Proposer round robin
- Two nodes committing conflicting blocks for the same height (safety failure)
- A correct node signing conflicting votes
- A node halting (liveness failure)
- Syncing new and old nodes
Assuming more than 1/3 the voting power is Byzantine:
## Networking
* Attacks that go unpunished (unhandled evidence)
- Authenticated encryption (MITM, information leakage)
- Eclipse attacks
- Sybil attacks
- Long-range attacks
- Denial-of-Service
### Networking
## RPC
* Authenticated encryption (MITM, information leakage)
* Eclipse attacks
* Sybil attacks
* Long-range attacks
* Denial-of-Service
- Write-access to anything besides sending transactions
- Denial-of-Service
- Leakage of secrets
### RPC
## Denial-of-Service
* Write-access to anything besides sending transactions
* Denial-of-Service
* Leakage of secrets
Attacks may come through the P2P network or the RPC:
### Denial-of-Service
- Amplification attacks
- Resource abuse
- Deadlocks and race conditions
- Panics and unhandled errors
Attacks may come through the P2P network or the RPC layer:
## Libraries
* Amplification attacks
* Resource abuse
* Deadlocks and race conditions
- Serialization (Amino)
- Reading/Writing files and databases
- Logging and monitoring
### Libraries
## Cryptography
* Serialization
* Reading/Writing files and databases
- Elliptic curves for validator signatures
- Hash algorithms and Merkle trees for block validation
- Authenticated encryption for P2P connections
### Cryptography
## Light Client
* Elliptic curves for validator signatures
* Hash algorithms and Merkle trees for block validation
* Authenticated encryption for P2P connections
- Validation of blockchain data structures
- Correctly validating an incorrect proof
- Incorrectly validating a correct proof
- Syncing validator set changes
### Light Client
* Core verification
* Bisection/sequential algorithms

View File

@@ -15,7 +15,6 @@ We expect all contributors to be familiar with [Effective Go](https://golang.org
Perhaps more key for code readability than good commenting is having the right structure. As a rule of thumb, try to write
in a logical order of importance, taking a little time to think how to order and divide the code such that someone could
scroll down and understand the functionality of it just as well as you do. A loose example of such order would be:
* Constants, global and package-level variables
* Main Struct
* Options (only if they are seen as critical to the struct else they should be placed in another file)
@@ -27,115 +26,113 @@ scroll down and understand the functionality of it just as well as you do. A loo
## General
* Use `gofmt` (or `goimport`) to format all code upon saving it. (If you use VIM, check out vim-go).
* Use a linter (see below) and generally try to keep the linter happy (where it makes sense).
* Think about documentation, and try to leave godoc comments, when it will help new developers.
* Every package should have a high level doc.go file to describe the purpose of that package, its main functions, and any other relevant information.
* `TODO` should not be used. If important enough should be recorded as an issue.
* `BUG` / `FIXME` should be used sparingly to guide future developers on some of the vulnerabilities of the code.
* `XXX` can be used in work-in-progress (prefixed with "WIP:" on github) branches but they must be removed before approving a PR.
* Applications (e.g. clis/servers) *should* panic on unexpected unrecoverable errors and print a stack trace.
* Use `gofmt` (or `goimport`) to format all code upon saving it. (If you use VIM, check out vim-go).
* Use a linter (see below) and generally try to keep the linter happy (where it makes sense).
* Think about documentation, and try to leave godoc comments, when it will help new developers.
* Every package should have a high level doc.go file to describe the purpose of that package, its main functions, and any other relevant information.
* `TODO` should not be used. If important enough should be recorded as an issue.
* `BUG` / `FIXME` should be used sparingly to guide future developers on some of the vulnerabilities of the code.
* `XXX` can be used in work-in-progress (prefixed with "WIP:" on github) branches but they must be removed before approving a PR.
* Applications (e.g. clis/servers) *should* panic on unexpected unrecoverable errors and print a stack trace.
## Comments
* Use a space after comment deliminter (ex. `// your comment`).
* Many comments are not sentences. These should begin with a lower case letter and end without a period.
* Conversely, sentences in comments should be sentenced-cased and end with a period.
* Use a space after comment deliminter (ex. `// your comment`).
* Many comments are not sentences. These should begin with a lower case letter and end without a period.
* Conversely, sentences in comments should be sentenced-cased and end with a period.
## Linters
These must be applied to all (Go) repos.
* [shellcheck](https://github.com/koalaman/shellcheck)
* [golangci-lint](https://github.com/golangci/golangci-lint) (covers all important linters)
* See the `.golangci.yml` file in each repo for linter configuration.
* [shellcheck](https://github.com/koalaman/shellcheck)
* [golangci-lint](https://github.com/golangci/golangci-lint) (covers all important linters)
- See the `.golangci.yml` file in each repo for linter configuration.
## Various
* Reserve "Save" and "Load" for long-running persistence operations. When parsing bytes, use "Encode" or "Decode".
* Maintain consistency across the codebase.
* Functions that return functions should have the suffix `Fn`
* Names should not [stutter](https://blog.golang.org/package-names). For example, a struct generally shouldnt have
* Reserve "Save" and "Load" for long-running persistence operations. When parsing bytes, use "Encode" or "Decode".
* Maintain consistency across the codebase.
* Functions that return functions should have the suffix `Fn`
* Names should not [stutter](https://blog.golang.org/package-names). For example, a struct generally shouldnt have
a field named after itself; e.g., this shouldn't occur:
``` golang
type middleware struct {
middleware Middleware
}
```
* In comments, use "iff" to mean, "if and only if".
* Product names are capitalized, like "Tendermint", "Basecoin", "Protobuf", etc except in command lines: `tendermint --help`
* Acronyms are all capitalized, like "RPC", "gRPC", "API". "MyID", rather than "MyId".
* Prefer errors.New() instead of fmt.Errorf() unless you're actually using the format feature with arguments.
* In comments, use "iff" to mean, "if and only if".
* Product names are capitalized, like "Tendermint", "Basecoin", "Protobuf", etc except in command lines: `tendermint --help`
* Acronyms are all capitalized, like "RPC", "gRPC", "API". "MyID", rather than "MyId".
* Prefer errors.New() instead of fmt.Errorf() unless you're actually using the format feature with arguments.
## Importing Libraries
Sometimes it's necessary to rename libraries to avoid naming collisions or ambiguity.
* Use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)
* Separate imports into blocks - one for the standard lib, one for external libs and one for application libs.
* Here are some common library labels for consistency:
* dbm "github.com/tendermint/tm-db"
* tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
* tmcfg "github.com/tendermint/tendermint/config/tendermint"
* tmtypes "github.com/tendermint/tendermint/types"
* Never use anonymous imports (the `.`), for example, `tmlibs/common` or anything else.
* When importing a pkg from the `tendermint/libs` directory, prefix the pkg alias with tm.
* tmbits "github.com/tendermint/tendermint/libs/bits"
* tip: Use the `_` library import to import a library for initialization effects (side effects)
* Use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)
* Separate imports into blocks - one for the standard lib, one for external libs and one for application libs.
* Here are some common library labels for consistency:
- dbm "github.com/tendermint/tm-db"
- tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
- tmcfg "github.com/tendermint/tendermint/config/tendermint"
- tmtypes "github.com/tendermint/tendermint/types"
* Never use anonymous imports (the `.`), for example, `tmlibs/common` or anything else.
* When importing a pkg from the `tendermint/libs` directory, prefix the pkg alias with tm.
- tmbits "github.com/tendermint/tendermint/libs/bits"
* tip: Use the `_` library import to import a library for initialization effects (side effects)
## Dependencies
* Dependencies should be pinned by a release tag, or specific commit, to avoid breaking `go get` when external dependencies are updated.
* Refer to the [contributing](CONTRIBUTING.md) document for more details
* Dependencies should be pinned by a release tag, or specific commit, to avoid breaking `go get` when external dependencies are updated.
* Refer to the [contributing](CONTRIBUTING.md) document for more details
## Testing
* The first rule of testing is: we add tests to our code
* The second rule of testing is: we add tests to our code
* For Golang testing:
* Make use of table driven testing where possible and not-cumbersome
* [Inspiration](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go)
* Make use of [assert](https://godoc.org/github.com/stretchr/testify/assert) and [require](https://godoc.org/github.com/stretchr/testify/require)
* When using mocks, it is recommended to use Testify [mock] (<https://pkg.go.dev/github.com/stretchr/testify/mock>
* The first rule of testing is: we add tests to our code
* The second rule of testing is: we add tests to our code
* For Golang testing:
* Make use of table driven testing where possible and not-cumbersome
- [Inspiration](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go)
* Make use of [assert](https://godoc.org/github.com/stretchr/testify/assert) and [require](https://godoc.org/github.com/stretchr/testify/require)
* When using mocks, it is recommended to use Testify [mock] (https://pkg.go.dev/github.com/stretchr/testify/mock
) along with [Mockery](https://github.com/vektra/mockery) for autogeneration
## Errors
* Ensure that errors are concise, clear and traceable.
* Use stdlib errors package.
* For wrapping errors, use `fmt.Errorf()` with `%w`.
* Panic is appropriate when an internal invariant of a system is broken, while all other cases (in particular,
* Ensure that errors are concise, clear and traceable.
* Use stdlib errors package.
* For wrapping errors, use `fmt.Errorf()` with `%w`.
* Panic is appropriate when an internal invariant of a system is broken, while all other cases (in particular,
incorrect or invalid usage) should return errors.
## Config
* Currently the TOML filetype is being used for config files
* A good practice is to store per-user config files under `~/.[yourAppName]/config.toml`
* Currently the TOML filetype is being used for config files
* A good practice is to store per-user config files under `~/.[yourAppName]/config.toml`
## CLI
* When implementing a CLI use [Cobra](https://github.com/spf13/cobra) and [Viper](https://github.com/spf13/viper).
* Helper messages for commands and flags must be all lowercase.
* Instead of using pointer flags (eg. `FlagSet().StringVar`) use Viper to retrieve flag values (eg. `viper.GetString`)
* The flag key used when setting and getting the flag should always be stored in a
* When implementing a CLI use [Cobra](https://github.com/spf13/cobra) and [Viper](https://github.com/spf13/viper).
* Helper messages for commands and flags must be all lowercase.
* Instead of using pointer flags (eg. `FlagSet().StringVar`) use Viper to retrieve flag values (eg. `viper.GetString`)
- The flag key used when setting and getting the flag should always be stored in a
variable taking the form `FlagXxx` or `flagXxx`.
* Flag short variable descriptions should always start with a lower case character as to remain consistent with
- Flag short variable descriptions should always start with a lower case character as to remain consistent with
the description provided in the default `--help` flag.
## Version
* Every repo should have a version/version.go file that mimics the Tendermint Core repo
* We read the value of the constant version in our build scripts and hence it has to be a string
* Every repo should have a version/version.go file that mimics the Tendermint Core repo
* We read the value of the constant version in our build scripts and hence it has to be a string
## Non-Go Code
* All non-Go code (`*.proto`, `Makefile`, `*.sh`), where there is no common
* All non-Go code (`*.proto`, `Makefile`, `*.sh`), where there is no common
agreement on style, should be formatted according to
[EditorConfig](http://editorconfig.org/) config:
```toml
```
# top-most EditorConfig file
root = true

View File

@@ -1,723 +1,62 @@
# Upgrading Tendermint Core
This guide provides instructions for upgrading to specific versions of Tendermint Core.
This guide provides steps to be followed when you upgrade your applications to
a newer version of Tendermint Core.
## v0.36
## Unreleased
### ABCI Changes
<Overview>
### ResponseCheckTx Parameter Change
`ResponseCheckTx` had fields that are not used by Tendermint, they are now removed.
In 0.36, we removed the following fields, from `ResponseCheckTx`: `Log`, `Info`, `Events`,
`GasUsed` and `MempoolError`.
`MempoolError` was used to signal to operators that a transaction was rejected from the mempool
by Tendermint itself. Right now, we return a regular error when this happens.
#### ABCI++
For information on how ABCI++ works, see the
[Specification](spec/abci%2B%2B/README.md).
In particular, the simplest way to upgrade your application is described
[here](spec/abci%2B%2B/abci++_tmint_expected_behavior.md#adapting-existing-applications-that-use-abci).
#### Moving the `app_hash` parameter
The Application's hash (or any data representing the Application's current
state) is known by the time `FinalizeBlock` finishes its execution.
Accordingly, the `app_hash` parameter has been moved from `ResponseCommit` to
`ResponseFinalizeBlock`, since it makes sense for the Application to return
this value as soon as is it known.
#### ABCI Mutex
In previous versions of ABCI, Tendermint was prevented from making
concurrent calls to ABCI implementations by virtue of mutexes in the
implementation of Tendermint's ABCI infrastructure. These mutexes have
been removed from the current implementation and applications will now
be responsible for managing their own concurrency control.
To replicate the prior semantics, ensure that ABCI applications have a
single mutex that protects all ABCI method calls from concurrent
access. You can relax these requirements if your application can
provide safe concurrent access via other means. This safety is an
application concern so be very sure to test the application thoroughly
using realistic workloads and the race detector to ensure your
applications remains correct.
### Config Changes
- We have added a new, experimental tool to help operators migrate
configuration files created by previous versions of Tendermint.
To try this tool, run:
```shell
# Install the tool.
go install github.com/tendermint/tendermint/scripts/confix@latest
# Run the tool with the old configuration file as input.
# Replace the -config argument with your path.
confix -config ~/.tendermint/config/config.toml -out updated.toml
```
This tool should be able to update configurations from v0.34 and v0.35. We
plan to extend it to handle older configuration files in the future. For now,
it will report an error (without making any changes) if it does not recognize
the version that created the file.
- The default configuration for a newly-created node now disables indexing for
ABCI event metadata. Existing node configurations that already have indexing
turned on are not affected. Operators who wish to enable indexing for a new
node, however, must now edit the `config.toml` explicitly.
- The function of seed nodes was modified in the past release. Now, seed nodes
are treated identically to any other peer, however they only run the PEX
reactor. Because of this `seeds` has been removed from the config. Users
should add any seed nodes in the list of `bootstrap-peers`.
### RPC Changes
Tendermint v0.36 adds a new RPC event subscription API. The existing event
subscription API based on websockets is now deprecated. It will continue to
work throughout the v0.36 release, but the `subscribe`, `unsubscribe`, and
`unsubscribe_all` methods, along with websocket support, will be removed in
Tendermint v0.37. Callers currently using these features should migrate as
soon as is practical to the new API.
To enable the new API, node operators set a new `event-log-window-size`
parameter in the `[rpc]` section of the `config.toml` file. This defines a
duration of time during which the node will log all events published to the
event bus for use by RPC consumers.
Consumers use the new `events` JSON-RPC method to poll for events matching
their query in the log. Unlike the streaming API, events are not discarded if
the caller is slow, loses its connection, or crashes. As long as the client
recovers before its events expire from the log window, it will be able to
replay and catch up after recovering. Also unlike the streaming API, the client
can tell if it has truly missed events because they have expired from the log.
The `events` method is a normal JSON-RPC method, and does not require any
non-standard response processing (in contrast with the old `subscribe`).
Clients can modify their query at any time, and no longer need to coordinate
subscribe and unsubscribe calls to handle multiple queries.
The Go client implementations in the Tendermint Core repository have all been
updated to add a new `Events` method, including the light client proxy.
A new `rpc/client/eventstream` package has also been added to make it easier
for users to update existing use of the streaming API to use the polling API
The `eventstream` package handles polling and delivers matching events to a
callback.
For more detailed information, see [ADR 075](https://tinyurl.com/adr075) which
defines and describes the new API in detail.
#### BroadcastTx Methods
All callers should use the new `broadcast_tx` method, which has the
same semantics as the legacy `broadcast_tx_sync` method. The
`broadcast_tx_sync` and `broadcast_tx_async` methods are now
deprecated and will be removed in 0.37.
Additionally the `broadcast_tx_commit` method is *also* deprecated,
and will be removed in 0.37. Client code that submits a transaction
and needs to wait for it to be committed to the chain, should poll
the tendermint to observe the transaction in the committed state.
### Timeout Parameter Changes
Tendermint v0.36 updates how the Tendermint consensus timing parameters are
configured. These parameters, `timeout-propose`, `timeout-propose-delta`,
`timeout-prevote`, `timeout-prevote-delta`, `timeout-precommit`,
`timeout-precommit-delta`, `timeout-commit`, and `skip-timeout-commit`, were
previously configured in `config.toml`. These timing parameters have moved and
are no longer configured in the `config.toml` file. These parameters have been
migrated into the `ConsensusParameters`. Nodes with these parameters set in the
local configuration file will see a warning logged on startup indicating that
these parameters are no longer used.
These parameters have also been pared-down. There are no longer separate
parameters for both the `prevote` and `precommit` phases of Tendermint. The
separate `timeout-prevote` and `timeout-precommit` parameters have been merged
into a single `timeout-vote` parameter that configures both of these similar
phases of the consensus protocol.
A set of reasonable defaults have been put in place for these new parameters
that will take effect when the node starts up in version v0.36. New chains
created using v0.36 and beyond will be able to configure these parameters in the
chain's `genesis.json` file. Chains that upgrade to v0.36 from a previous
compatible version of Tendermint will begin running with the default values.
Upgrading applications that wish to use different values from the defaults for
these parameters may do so by setting the `ConsensusParams.Timeout` field of the
`FinalizeBlock` `ABCI` response.
As a safety measure in case of unusual timing issues during the upgrade to
v0.36, an operator may override the consensus timeout values for a single node.
Note, however, that these overrides will be removed in Tendermint v0.37. See
[configuration](https://github.com/tendermint/tendermint/blob/master/docs/nodes/configuration.md)
for more information about these overrides.
For more discussion of this, see [ADR 074](https://tinyurl.com/adr074), which
lays out the reasoning for the changes as well as [RFC
009](https://tinyurl.com/rfc009) for a discussion of the complexities of
upgrading consensus parameters.
### RecheckTx Parameter Change
`RecheckTx` was previously enabled by the `recheck` parameter in the mempool
section of the `config.toml`.
Setting it to true made Tendermint invoke another `CheckTx` ABCI call on
each transaction remaining in the mempool following the execution of a block.
Similar to the timeout parameter changes, this parameter makes more sense as a
network-wide coordinated variable so that applications can be written knowing
either all nodes agree on whether to run `RecheckTx`.
Applications can turn on `RecheckTx` by altering the `ConsensusParams` in the
`FinalizeBlock` ABCI response.
### CLI Changes
The functionality around resetting a node has been extended to make it safer. The
`unsafe-reset-all` command has been replaced by a `reset` command with four
subcommands: `blockchain`, `peers`, `unsafe-signer` and `unsafe-all`.
- `tendermint reset blockchain`: Clears a node of all blocks, consensus state, evidence,
and indexed transactions. NOTE: This command does not reset application state.
If you need to rollback the last application state (to recover from application
nondeterminism), see instead the `tendermint rollback` command.
- `tendermint reset peers`: Clears the peer store, which persists information on peers used
by the networking layer. This can be used to get rid of stale addresses or to switch
to a predefined set of static peers.
- `tendermint reset unsafe-signer`: Resets the watermark level of the PrivVal File signer
allowing it to sign votes from the genesis height. This should only be used in testing as
it can lead to the node double signing.
- `tendermint reset unsafe-all`: A summation of the other three commands. This will delete
the entire `data` directory which may include application data as well.
### Go API Changes
#### `crypto` Package Cleanup
The `github.com/tendermint/tendermint/crypto/tmhash` package was removed
to improve clarity. Users are encouraged to use the standard library
`crypto/sha256` package directly. However, as a convenience, some constants
and one function have moved to the Tendermint `crypto` package:
- The `crypto.Checksum` function returns the sha256 checksum of a
byteslice. This is a wrapper around `sha256.Sum265` from the
standard libary, but provided as a function to ease type
requirements (the library function returns a `[32]byte` rather than
a `[]byte`).
- `tmhash.TruncatedSize` is now `crypto.AddressSize` which was
previously an alias for the same value.
- `tmhash.Size` and `tmhash.BlockSize` are now `crypto.HashSize` and
`crypto.HashSize`.
- `tmhash.SumTruncated` is now available via `crypto.AddressHash` or by
`crypto.Checksum(<...>)[:crypto.AddressSize]`
## v0.35
### ABCI Changes
* Added `AbciVersion` to `RequestInfo`. Applications should check that the ABCI version they expect is being used in order to avoid unimplemented changes errors.
* The method `SetOption` has been removed from the ABCI.Client interface. This feature was used in the early ABCI implementation's.
* Messages are written to a byte stream using uin64 length delimiters instead of int64.
* When mempool `v1` is enabled, transactions broadcasted via `sync` mode may return a successful
response with a transaction hash indicating that the transaction was successfully inserted into
the mempool. While this is true for `v0`, the `v1` mempool reactor may at a later point in time
evict or even drop this transaction after a hash has been returned. Thus, the user or client must
query for that transaction to check if it is still in the mempool.
### Config Changes
* The configuration file field `[fastsync]` has been renamed to `[blocksync]`.
* The top level configuration file field `fast-sync` has moved under the new `[blocksync]`
field as `blocksync.enable`.
* `blocksync.version = "v1"` and `blocksync.version = "v2"` (previously `fastsync`)
are no longer supported. Please use `v0` instead. During the v0.35 release cycle, `v0` was
determined to suit the existing needs and the cost of maintaining the `v1` and `v2` modules
was determined to be greater than necessary.
* All config parameters are now hyphen-case (also known as kebab-case) instead of snake_case. Before restarting the node make sure
you have updated all the variables in your `config.toml` file.
* Added `--mode` flag and `mode` config variable on `config.toml` for setting Mode of the Node: `full` | `validator` | `seed` (default: `full`)
[ADR-52](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-052-tendermint-mode.md)
* `BootstrapPeers` has been added as part of the new p2p stack. This will eventually replace
`Seeds`. Bootstrap peers are connected with on startup if needed for peer discovery. Unlike
persistent peers, there's no gaurantee that the node will remain connected with these peers.
* configuration values starting with `priv-validator-` have moved to the new
`priv-validator` section, without the `priv-validator-` prefix.
* The fast sync process as well as the blockchain package and service has all
been renamed to block sync
### Database Key Format Changes
The format of all tendermint on-disk database keys changes in
0.35. Upgrading nodes must either re-sync all data or run a migration
script provided in this release.
The script located in
`github.com/tendermint/tendermint/scripts/keymigrate/migrate.go` provides the
function `Migrate(context.Context, db.DB)` which you can operationalize as
makes sense for your deployment.
For ease of use the `tendermint` command includes a CLI version of the
migration script, which you can invoke, as in:
tendermint key-migrate
This reads the configuration file as normal and allows the `--db-backend` and
`--db-dir` flags to override the database location as needed.
The migration operation is intended to be idempotent, and should be safe to
rerun on the same database multiple times. As a safety measure, however, we
recommend that operators test out the migration on a copy of the database
first, if it is practical to do so, before applying it to the production data.
### CLI Changes
* You must now specify the node mode (validator|full|seed) in `tendermint init [mode]`
* The `--fast-sync` command line option has been renamed to `--blocksync.enable`
* If you had previously used `tendermint gen_node_key` to generate a new node
key, keep in mind that it no longer saves the output to a file. You can use
`tendermint init validator` or pipe the output of `tendermint gen_node_key` to
`$TMHOME/config/node_key.json`:
```
$ tendermint gen_node_key > $TMHOME/config/node_key.json
```
* CLI commands and flags are all now hyphen-case instead of snake_case.
Make sure to adjust any scripts that calls a cli command with snake_casing
### API Changes
The p2p layer was reimplemented as part of the 0.35 release cycle and
all reactors were refactored to accomodate the change. As part of that work these
implementations moved into the `internal` package and are no longer
considered part of the public Go API of tendermint. These packages
are:
- `p2p`
- `mempool`
- `consensus`
- `statesync`
- `blockchain`
- `evidence`
Accordingly, the `node` package changed to reduce access to
tendermint internals: applications that use tendermint as a library
will need to change to accommodate these changes. Most notably:
- The `Node` type has become internal, and all constructors return a
`service.Service` implementation.
- The `node.DefaultNewNode` and `node.NewNode` constructors are no
longer exported and have been replaced with `node.New` and
`node.NewDefault` which provide more functional interfaces.
To access any of the functionality previously available via the
`node.Node` type, use the `*local.Local` "RPC" client, that exposes
the full RPC interface provided as direct function calls. Import the
`github.com/tendermint/tendermint/rpc/client/local` package and pass
the node service as in the following:
```go
logger := log.NewNopLogger()
// Construct and start up a node with default settings.
node := node.NewDefault(logger)
// Construct a local (in-memory) RPC client to the node.
client := local.New(logger, node.(local.NodeService))
```
### gRPC Support
Mark gRPC in the RPC layer as deprecated and to be removed in 0.36.
### Peer Management Interface
When running with the new P2P Layer, the methods `UnsafeDialSeeds` and
`UnsafeDialPeers` RPC methods will always return an error. They are
deprecated and will be removed in 0.36 when the legacy peer stack is
removed.
Additionally the format of the Peer list returned in the `NetInfo`
method changes in this release to accommodate the different way that
the new stack tracks data about peers. This change affects users of
both stacks.
### Using the updated p2p library
The P2P library was reimplemented in this release. The new implementation is
enabled by default in this version of Tendermint. The legacy implementation is still
included in this version of Tendermint as a backstop to work around unforeseen
production issues. The new and legacy version are interoperable. If necessary,
you can enable the legacy implementation in the server configuration file.
To make use of the legacy P2P implemementation add or update the following field of
your server's configuration file under the `[p2p]` section:
```toml
[p2p]
...
use-legacy = true
...
```
If you need to do this, please consider filing an issue in the Tendermint repository
to let us know why. We plan to remove the legacy P2P code in the next (v0.36) release.
#### New p2p queue types
The new p2p implementation enables selection of the queue type to be used for
passing messages between peers.
The following values may be used when selecting which queue type to use:
* `fifo`: (**default**) An unbuffered and lossless queue that passes messages through
in the order in which they were received.
* `priority`: A priority queue of messages.
* `wdrr`: A queue implementing the Weighted Deficit Round Robin algorithm. A
weighted deficit round robin queue is created per peer. Each queue contains a
separate 'flow' for each of the channels of communication that exist between any two
peers. Tendermint maintains a channel per message type between peers. Each WDRR
queue maintains a shared buffered with a fixed capacity through which messages on different
flows are passed.
For more information on WDRR scheduling, see: https://en.wikipedia.org/wiki/Deficit_round_robin
To select a queue type, add or update the following field under the `[p2p]`
section of your server's configuration file.
```toml
[p2p]
...
queue-type = wdrr
...
```
### Support for Custom Reactor and Mempool Implementations
The changes to p2p layer removed existing support for custom
reactors. Based on our understanding of how this functionality was
used, the introduction of the prioritized mempool covers nearly all of
the use cases for custom reactors. If you are currently running custom
reactors and mempools and are having trouble seeing the migration path
for your project please feel free to reach out to the Tendermint Core
development team directly.
## v0.34.0
**Upgrading to Tendermint 0.34 requires a blockchain restart.**
This release is not compatible with previous blockchains due to changes to
the encoding format (see "Protocol Buffers," below) and the block header (see "Blockchain Protocol").
Note also that Tendermint 0.34 also requires Go 1.16 or higher.
### ABCI Changes
* The `ABCIVersion` is now `0.17.0`.
* New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`)
were added to support the new State Sync feature.
Previously, syncing a new node to a preexisting network could take days; but with State Sync,
new nodes are able to join a network in a matter of seconds.
Read [the spec](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md)
if you want to learn more about State Sync, or if you'd like your application to use it.
(If you don't want to support State Sync in your application, you can just implement these new
ABCI methods as no-ops, leaving them empty.)
* `KV.Pair` has been replaced with `abci.EventAttribute`. The `EventAttribute.Index` field
allows ABCI applications to dictate which events should be indexed.
* The blockchain can now start from an arbitrary initial height,
provided to the application via `RequestInitChain.InitialHeight`.
* ABCI evidence type is now an enum with two recognized types of evidence:
`DUPLICATE_VOTE` and `LIGHT_CLIENT_ATTACK`.
Applications should be able to handle these evidence types
(i.e., through slashing or other accountability measures).
* The [`PublicKey` type](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto#L13-L15)
(used in ABCI as part of `ValidatorUpdate`) now uses a `oneof` protobuf type.
Note that since Tendermint only supports ed25519 validator keys, there's only one
option in the `oneof`. For more, see "Protocol Buffers," below.
* The field `Proof`, on the ABCI type `ResponseQuery`, is now named `ProofOps`.
For more, see "Crypto," below.
* The method `SetOption` has been removed from the ABCI.Client interface. This feature was used in the early ABCI implementation's.
### P2P Protocol
The default codec is now proto3, not amino. The schema files can be found in the `/proto`
directory. For more, see "Protobuf," below.
### Blockchain Protocol
* `Header#LastResultsHash`, which is the root hash of a Merkle tree built from
`ResponseDeliverTx(Code, Data)` as of v0.34 also includes `GasWanted` and `GasUsed`
fields.
* Merkle hashes of empty trees previously returned nothing, but now return the hash of an empty input,
to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962).
This mainly affects `Header#DataHash`, `Header#LastResultsHash`, and
`Header#EvidenceHash`, which are often empty. Non-empty hashes can also be affected, e.g. if their
inputs depend on other (empty) Merkle hashes, giving different results.
### Transaction Indexing
Tendermint now relies on the application to tell it which transactions to index. This means that
in the `config.toml`, generated by Tendermint, there is no longer a way to specify which
transactions to index. `tx.height` and `tx.hash` will always be indexed when using the `kv` indexer.
Applications must now choose to either a) enable indexing for all transactions, or
b) allow node operators to decide which transactions to index.
Applications can notify Tendermint to index a specific transaction by setting
`Index: bool` to `true` in the Event Attribute:
```go
[]types.Event{
{
Type: "app",
Attributes: []types.EventAttribute{
{Key: []byte("creator"), Value: []byte("Cosmoshi Netowoko"), Index: true},
},
},
}
```
### Protocol Buffers
Tendermint 0.34 replaces Amino with Protocol Buffers for encoding.
This migration is extensive and results in a number of changes, however,
Tendermint only uses the types generated from Protocol Buffers for disk and
wire serialization.
**This means that these changes should not affect you as a Tendermint user.**
However, Tendermint users and contributors may note the following changes:
* Directory layout changes: All proto files have been moved under one directory, `/proto`.
This is in line with the recommended file layout by [Buf](https://buf.build).
For more, see the [Buf documentation](https://buf.build/docs/lint-checkers#file_layout).
* ABCI Changes: As noted in the "ABCI Changes" section above, the `PublicKey` type now uses
a `oneof` type.
For more on the Protobuf changes, please see our [blog post on this migration](https://medium.com/tendermint/tendermint-0-34-protocol-buffers-and-you-8c40558939ae).
### Consensus Parameters
Tendermint 0.34 includes new and updated consensus parameters.
#### Version Parameters (New)
* `AppVersion`, which is the version of the ABCI application.
#### Evidence Parameters
* `MaxBytes`, which caps the total amount of evidence. The default is 1048576 (1 MB).
### Crypto
#### Keys
* Keys no longer include a type prefix. For example, ed25519 pubkeys have been renamed from
`PubKeyEd25519` to `PubKey`. This reduces stutter (e.g., `ed25519.PubKey`).
* Keys are now byte slices (`[]byte`) instead of byte arrays (`[<size>]byte`).
* The multisig functionality that was previously in Tendermint now has
a new home within the Cosmos SDK:
[`cosmos/cosmos-sdk/types/multisig`](https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go).
#### `merkle` Package
* `SimpleHashFromMap()` and `SimpleProofsFromMap()` were removed.
* The prefix `Simple` has been removed. (For example, `SimpleProof` is now called `Proof`.)
* All protobuf messages have been moved to the `/proto` directory.
* The protobuf message `Proof` that contained multiple ProofOp's has been renamed to `ProofOps`.
As noted above, this affects the ABCI type `ResponseQuery`:
The field that was named Proof is now named `ProofOps`.
* `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, to conform with
[RFC-6962](https://tools.ietf.org/html/rfc6962).
### `libs` Package
The `bech32` package has moved to the Cosmos SDK:
[`cosmos/cosmos-sdk/types/bech32`](https://github.com/cosmos/cosmos-sdk/tree/4173ea5ebad906dd9b45325bed69b9c655504867/types/bech32).
### CLI
The `tendermint lite` command has been renamed to `tendermint light` and has a slightly different API.
### Light Client
We have a new, rewritten light client! You can
[read more](https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98)
about the justifications and details behind this change.
Other user-relevant changes include:
* The old `lite` package was removed; the new light client uses the `light` package.
* The `Verifier` was broken up into two pieces:
* Core verification logic (pure `VerifyX` functions)
* `Client` object, which represents the complete light client
* The new light clients stores headers & validator sets as `LightBlock`s
* The RPC client can be found in the `/rpc` directory.
* The HTTP(S) proxy is located in the `/proxy` directory.
### `state` Package
* A new field `State.InitialHeight` has been added to record the initial chain height, which must be `1`
(not `0`) if starting from height `1`. This can be configured via the genesis field `initial_height`.
* The `state` package now has a `Store` interface. All functions in
[state/store.go](https://github.com/tendermint/tendermint/blob/56911ee35298191c95ef1c7d3d5ec508237aaff4/state/store.go#L42-L42)
are now part of the interface. The interface returns errors on all methods and can be used by calling `state.NewStore(dbm.DB)`.
### `privval` Package
All requests are now accompanied by the chain ID from the network.
This is a optional field and can be ignored by key management systems;
however, if you are using the same key management system for multiple different
blockchains, we recommend that you check the chain ID.
### RPC
* `/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and
`/unsafe_write_heap_profile` were removed.
For profiling, please use the pprof server, which can
be enabled through `--rpc.pprof_laddr=X` flag or `pprof_laddr=X` config setting
in the rpc section.
* The `Content-Type` header returned on RPC calls is now (correctly) set as `application/json`.
### Version
Version is now set through Go linker flags `ld_flags`. Applications that are using tendermint as a library should set this at compile time.
Example:
```sh
go install -mod=readonly -ldflags "-X github.com/tendermint/tendermint/version.TMCoreSemVer=$(go list -m github.com/tendermint/tendermint | sed 's/ /\@/g') -s -w " -trimpath ./cmd
```
Additionally, the exported constant `version.Version` is now `version.TMCoreSemVer`.
## v0.33.4
### Go API
* `rpc/client` HTTP and local clients have been moved into `http` and `local`
subpackages, and their constructors have been renamed to `New()`.
- `rpc/client` HTTP and local clients have been moved into `http` and `local` subpackages, and their constructors have been renamed to `New()`.
### Protobuf Changes
When upgrading to version 0.33.4 you will have to fetch the `third_party`
directory along with the updated proto files.
When upgrading to version 0.33.4 you will have to fetch the `third_party` directory along with the updated proto files.
### Block Retention
## v0.33.1
ResponseCommit added a field for block retention. The application can provide information to Tendermint on how to prune blocks.
If an application would like to not prune any blocks pass a `0` in this field.
This release is compatible with the previous version. The only change that is required is if you are fetching the protobuf files for application use.
### Protobuf Changes
When upgrading to version 0.33.1 you will have to fetch the `third_party` directory along with the updated proto files.
```proto
message ResponseCommit {
// reserve 1
bytes data = 2; // the Merkle root hash
++ uint64 retain_height = 3; // the oldest block height to retain ++
}
```
## v0.33.0
This release is not compatible with previous blockchains due to commit becoming
signatures only and fields in the header have been removed.
### Blockchain Protocol
`TotalTxs` and `NumTxs` were removed from the header. `Commit` now consists
mostly of just signatures.
```go
type Commit struct {
Height int64
Round int
BlockID BlockID
Signatures []CommitSig
}
```
```go
type BlockIDFlag byte
const (
// BlockIDFlagAbsent - no vote was received from a validator.
BlockIDFlagAbsent BlockIDFlag = 0x01
// BlockIDFlagCommit - voted for the Commit.BlockID.
BlockIDFlagCommit = 0x02
// BlockIDFlagNil - voted for nil.
BlockIDFlagNil = 0x03
)
type CommitSig struct {
BlockIDFlag BlockIDFlag
ValidatorAddress Address
Timestamp time.Time
Signature []byte
}
```
See [\#63](https://github.com/tendermint/spec/pull/63) for the complete spec
change.
### P2P Protocol
The secret connection now includes a transcript hashing. If you want to
implement a handshake (or otherwise have an existing implementation), you'll
need to make the same changes that were made
[here](https://github.com/tendermint/tendermint/pull/3668).
This release is not compatible with previous blockchains due to commit becoming signatures only and fields in the header have been removed.
### Config Changes
You will need to generate a new config if you have used a prior version of tendermint.
Tags have been entirely renamed throughout the codebase to events and there
keys are called
[compositeKeys](https://github.com/tendermint/tendermint/blob/6d05c531f7efef6f0619155cf10ae8557dd7832f/docs/app-dev/indexing-transactions.md).
Evidence Params has been changed to include duration.
* `consensus_params.evidence.max_age_duration`.
* Renamed `consensus_params.evidence.max_age` to `max_age_num_blocks`.
- Tags have been entirely renamed throughout the codebase to events and there keys are called [compositeKeys](https://github.com/tendermint/tendermint/blob/6d05c531f7efef6f0619155cf10ae8557dd7832f/docs/app-dev/indexing-transactions.md).
- Evidence Params has been changed to include duration.
- `consensus_params.evidence.max_age_duration`.
- Renamed `consensus_params.evidence.max_age` to `max_age_num_blocks`.
### Go API
* `libs/common` has been removed in favor of specific pkgs.
* `async`
* `service`
* `rand`
* `net`
* `strings`
* `cmap`
* removal of `errors` pkg
- `libs/common` has been removed in favor of specific pkgs.
- `async`
- `service`
- `rand`
- `net`
- `strings`
- `cmap`
- removal of `errors` pkg
### RPC Changes
* `/validators` is now paginated (default: 30 vals per page)
* `/block_results` response format updated [see RPC docs for details](https://docs.tendermint.com/master/rpc/#/Info/block_results)
* Event suffix has been removed from the ID in event responses
* IDs are now integers not `json-client-XYZ`
- `/validators` is now paginated (default: 30 vals per page)
- `/block_results` response format updated [see RPC docs for details](https://docs.tendermint.com/master/rpc/#/Info/block_results)
- Event suffix has been removed from the ID in event responses
- IDs are now integers not `json-client-XYZ`
## v0.32.0
@@ -778,9 +117,9 @@ Prior to the update, suppose your `ResponseDeliverTx` look like:
```go
abci.ResponseDeliverTx{
Tags: []kv.Pair{
{Key: []byte("sender"), Value: []byte("foo")},
{Key: []byte("recipient"), Value: []byte("bar")},
{Key: []byte("amount"), Value: []byte("35")},
{Key: []byte("sender"), Value: []byte("foo")},
{Key: []byte("recipient"), Value: []byte("bar")},
{Key: []byte("amount"), Value: []byte("35")},
}
}
```
@@ -799,14 +138,14 @@ the following `Events`:
```go
abci.ResponseDeliverTx{
Events: []abci.Event{
{
Type: "transfer",
Attributes: kv.Pairs{
{Key: []byte("sender"), Value: []byte("foo")},
{Key: []byte("recipient"), Value: []byte("bar")},
{Key: []byte("amount"), Value: []byte("35")},
},
}
{
Type: "transfer",
Attributes: kv.Pairs{
{Key: []byte("sender"), Value: []byte("foo")},
{Key: []byte("recipient"), Value: []byte("bar")},
{Key: []byte("amount"), Value: []byte("35")},
},
}
}
```
@@ -834,7 +173,7 @@ the compilation tag:
Use `cleveldb` tag instead of `gcc` to compile Tendermint with CLevelDB or
use `make build_c` / `make install_c` (full instructions can be found at
<https://docs.tendermint.com/v0.35/introduction/install.html)
https://tendermint.com/docs/introduction/install.html#compile-with-cleveldb-support)
## v0.31.0
@@ -854,9 +193,9 @@ In this case, the WS client will receive an error with description:
"jsonrpc": "2.0",
"id": "{ID}#event",
"error": {
"code": -32000,
"msg": "Server error",
"data": "subscription was canceled (reason: client is not pulling messages fast enough)" // or "subscription was canceled (reason: Tendermint exited)"
"code": -32000,
"msg": "Server error",
"data": "subscription was cancelled (reason: client is not pulling messages fast enough)" // or "subscription was cancelled (reason: Tendermint exited)"
}
}
@@ -909,14 +248,14 @@ due to changes in how various data structures are hashed.
Any implementations of Tendermint blockchain verification, including lite clients,
will need to be updated. For specific details:
* [Merkle tree](https://github.com/tendermint/spec/blob/master/spec/blockchain/encoding.md#merkle-trees)
* [ConsensusParams](https://github.com/tendermint/spec/blob/master/spec/blockchain/state.md#consensusparams)
- [Merkle tree](https://github.com/tendermint/spec/blob/master/spec/blockchain/encoding.md#merkle-trees)
- [ConsensusParams](https://github.com/tendermint/spec/blob/master/spec/blockchain/state.md#consensusparams)
There was also a small change to field ordering in the vote struct. Any
implementations of an out-of-process validator (like a Key-Management Server)
will need to be updated. For specific details:
* [Vote](https://github.com/tendermint/spec/blob/master/spec/consensus/signing.md#votes)
- [Vote](https://github.com/tendermint/spec/blob/master/spec/consensus/signing.md#votes)
Finally, the proposer selection algorithm continues to evolve. See the
[work-in-progress
@@ -952,7 +291,7 @@ To upgrade manually, use the provided `privValUpgrade.go` script, with exact pat
to use the default paths, of `config/priv_validator_key.json` and
`data/priv_validator_state.json`, respectively:
```sh
```
go run scripts/privValUpgrade.go <old-path> <new-key-path> <new-state-path>
```
@@ -1022,8 +361,8 @@ old data to be compatible with the new version.
To reset the state do:
```sh
tendermint unsafe_reset_all
```
$ tendermint unsafe_reset_all
```
Here we summarize some other notable changes to be mindful of.
@@ -1060,20 +399,20 @@ the root of another. If you don't need this functionality, and you used to
return `<proof bytes>` here, you should instead return a single `ProofOp` with
just the `Data` field set:
```go
```
[]ProofOp{
ProofOp{
Data: <proof bytes>,
}
ProofOp{
Data: <proof bytes>,
}
}
```
For more information, see:
* [ADR-026](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/docs/architecture/adr-026-general-merkle-proof.md)
* [Relevant ABCI
- [ADR-026](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/docs/architecture/adr-026-general-merkle-proof.md)
- [Relevant ABCI
documentation](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/docs/spec/abci/apps.md#query-proofs)
* [Description of
- [Description of
keys](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/crypto/merkle/proof_key_path.go#L14)
### Go API Changes
@@ -1108,8 +447,8 @@ old data to be compatible with the new version.
To reset the state do:
```sh
tendermint unsafe_reset_all
```
$ tendermint unsafe_reset_all
```
Here we summarize some other notable changes to be mindful of.
@@ -1119,7 +458,7 @@ Here we summarize some other notable changes to be mindful of.
`p2p.max_num_peers` was removed in favor of `p2p.max_num_inbound_peers` and
`p2p.max_num_outbound_peers`.
```toml
```
# Maximum number of inbound peers
max_num_inbound_peers = 40

62
Vagrantfile vendored Normal file
View File

@@ -0,0 +1,62 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
config.vm.provider "virtualbox" do |v|
v.memory = 4096
v.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
# install base requirements
apt-get install -y --no-install-recommends wget curl jq zip \
make shellcheck bsdmainutils psmisc
apt-get install -y language-pack-en
# install docker
apt-get install -y --no-install-recommends apt-transport-https \
ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
apt-get install -y docker-ce
usermod -a -G docker vagrant
# install go
wget -q https://dl.google.com/go/go1.13.linux-amd64.tar.gz
tar -xvf go1.13.linux-amd64.tar.gz
mv go /usr/local
rm -f go1.13.linux-amd64.tar.gz
# install nodejs (for docs)
curl -sL https://deb.nodesource.com/setup_11.x | bash -
apt-get install -y nodejs
# cleanup
apt-get autoremove -y
# set env variables
echo 'export GOROOT=/usr/local/go' >> /home/vagrant/.bash_profile
echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile
echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> /home/vagrant/.bash_profile
echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile
echo 'cd go/src/github.com/tendermint/tendermint' >> /home/vagrant/.bash_profile
mkdir -p /home/vagrant/go/bin
mkdir -p /home/vagrant/go/src/github.com/tendermint
ln -s /vagrant /home/vagrant/go/src/github.com/tendermint/tendermint
chown -R vagrant:vagrant /home/vagrant/go
chown vagrant:vagrant /home/vagrant/.bash_profile
# get all deps and tools, ready to install/test
su - vagrant -c 'source /home/vagrant/.bash_profile'
su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make tools'
SHELL
end

View File

@@ -19,18 +19,19 @@ To get up and running quickly, see the [getting started guide](../docs/app-dev/g
A detailed description of the ABCI methods and message types is contained in:
- [The main spec](../spec/abci/abci.md)
- [A protobuf file](../proto/tendermint/abci/types.proto)
- [The main spec](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md)
- [A protobuf file](./types/types.proto)
- [A Go interface](./types/application.go)
## Protocol Buffers
To compile the protobuf file, run (from the root of the repo):
```sh
```
make protoc_abci
```
See `protoc --help` and [the Protocol Buffers site](https://developers.google.com/protocol-buffers)
for details on compiling for other languages. Note we also include a [GRPC](https://www.grpc.io/docs)
service definition.

View File

@@ -1,12 +1,10 @@
package abciclient
package abcicli
import (
"context"
"fmt"
"sync"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
)
@@ -15,57 +13,117 @@ const (
echoRetryIntervalSeconds = 1
)
//go:generate ../../scripts/mockery_generate.sh Client
// Client defines the interface for an ABCI client.
//
// NOTE these are client errors, eg. ABCI socket connectivity issues.
// Application-related errors are reflected in response via ABCI error codes
// and (potentially) error response.
// Client defines an interface for an ABCI client.
// All `Async` methods return a `ReqRes` object.
// All `Sync` methods return the appropriate protobuf ResponseXxx struct and an error.
// Note these are client errors, eg. ABCI socket connectivity issues.
// Application-related errors are reflected in response via ABCI error codes and logs.
type Client interface {
service.Service
types.Application
SetResponseCallback(Callback)
Error() error
Flush(context.Context) error
Echo(context.Context, string) (*types.ResponseEcho, error)
FlushAsync() *ReqRes
EchoAsync(msg string) *ReqRes
InfoAsync(types.RequestInfo) *ReqRes
SetOptionAsync(types.RequestSetOption) *ReqRes
DeliverTxAsync(types.RequestDeliverTx) *ReqRes
CheckTxAsync(types.RequestCheckTx) *ReqRes
QueryAsync(types.RequestQuery) *ReqRes
CommitAsync() *ReqRes
InitChainAsync(types.RequestInitChain) *ReqRes
BeginBlockAsync(types.RequestBeginBlock) *ReqRes
EndBlockAsync(types.RequestEndBlock) *ReqRes
FlushSync() error
EchoSync(msg string) (*types.ResponseEcho, error)
InfoSync(types.RequestInfo) (*types.ResponseInfo, error)
SetOptionSync(types.RequestSetOption) (*types.ResponseSetOption, error)
DeliverTxSync(types.RequestDeliverTx) (*types.ResponseDeliverTx, error)
CheckTxSync(types.RequestCheckTx) (*types.ResponseCheckTx, error)
QuerySync(types.RequestQuery) (*types.ResponseQuery, error)
CommitSync() (*types.ResponseCommit, error)
InitChainSync(types.RequestInitChain) (*types.ResponseInitChain, error)
BeginBlockSync(types.RequestBeginBlock) (*types.ResponseBeginBlock, error)
EndBlockSync(types.RequestEndBlock) (*types.ResponseEndBlock, error)
}
//----------------------------------------
// NewClient returns a new ABCI client of the specified transport type.
// It returns an error if the transport is not "socket" or "grpc"
func NewClient(logger log.Logger, addr, transport string, mustConnect bool) (Client, error) {
func NewClient(addr, transport string, mustConnect bool) (client Client, err error) {
switch transport {
case "socket":
return NewSocketClient(logger, addr, mustConnect), nil
client = NewSocketClient(addr, mustConnect)
case "grpc":
return NewGRPCClient(logger, addr, mustConnect), nil
client = NewGRPCClient(addr, mustConnect)
default:
return nil, fmt.Errorf("unknown abci transport %s", transport)
err = fmt.Errorf("unknown abci transport %s", transport)
}
return
}
type requestAndResponse struct {
//----------------------------------------
type Callback func(*types.Request, *types.Response)
//----------------------------------------
type ReqRes struct {
*types.Request
*types.Response
*sync.WaitGroup
*types.Response // Not set atomically, so be sure to use WaitGroup.
mtx sync.Mutex
signal chan struct{}
mtx sync.Mutex
done bool // Gets set to true once *after* WaitGroup.Done().
cb func(*types.Response) // A single callback that may be set.
}
func makeReqRes(req *types.Request) *requestAndResponse {
return &requestAndResponse{
Request: req,
Response: nil,
signal: make(chan struct{}),
func NewReqRes(req *types.Request) *ReqRes {
return &ReqRes{
Request: req,
WaitGroup: waitGroup1(),
Response: nil,
done: false,
cb: nil,
}
}
// markDone marks the ReqRes object as done.
func (r *requestAndResponse) markDone() {
r.mtx.Lock()
defer r.mtx.Unlock()
// Sets the callback for this ReqRes atomically.
// If reqRes is already done, calls cb immediately.
// NOTE: reqRes.cb should not change if reqRes.done.
// NOTE: only one callback is supported.
func (reqRes *ReqRes) SetCallback(cb func(res *types.Response)) {
reqRes.mtx.Lock()
close(r.signal)
if reqRes.done {
reqRes.mtx.Unlock()
cb(reqRes.Response)
return
}
reqRes.cb = cb
reqRes.mtx.Unlock()
}
func (reqRes *ReqRes) GetCallback() func(*types.Response) {
reqRes.mtx.Lock()
defer reqRes.mtx.Unlock()
return reqRes.cb
}
// NOTE: it should be safe to read reqRes.cb without locks after this.
func (reqRes *ReqRes) SetDone() {
reqRes.mtx.Lock()
reqRes.done = true
reqRes.mtx.Unlock()
}
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
}

View File

@@ -1,20 +0,0 @@
// Package abciclient provides an ABCI implementation in Go.
//
// There are 3 clients available:
// 1. socket (unix or TCP)
// 2. local (in memory)
// 3. gRPC
//
// ## Socket client
//
// The client blocks for enqueuing the request, for enqueuing the
// Flush to send the request, and for the Flush response to return.
//
// ## Local client
//
// The global mutex is locked during each call
//
// ## gRPC client
//
// The client waits for all calls to complete.
package abciclient

View File

@@ -1,48 +1,42 @@
package abciclient
package abcicli
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
"github.com/tendermint/tendermint/libs/service"
)
// A gRPC client.
var _ Client = (*grpcClient)(nil)
// A stripped copy of the remoteClient that makes
// synchronous calls using grpc
type grpcClient struct {
service.BaseService
logger log.Logger
mustConnect bool
client types.ABCIApplicationClient
conn *grpc.ClientConn
mtx sync.Mutex
addr string
err error
mtx sync.Mutex
addr string
err error
resCb func(*types.Request, *types.Response) // listens to all callbacks
}
var _ Client = (*grpcClient)(nil)
// NewGRPCClient creates a gRPC client, which will connect to addr upon the
// start. Note Client#Start returns an error if connection is unsuccessful and
// mustConnect is true.
func NewGRPCClient(logger log.Logger, addr string, mustConnect bool) Client {
func NewGRPCClient(addr string, mustConnect bool) Client {
cli := &grpcClient{
logger: logger,
addr: addr,
mustConnect: mustConnect,
}
cli.BaseService = *service.NewBaseService(logger, "grpcClient", cli)
cli.BaseService = *service.NewBaseService(nil, "grpcClient", cli)
return cli
}
@@ -50,52 +44,34 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) {
return tmnet.Connect(addr)
}
func (cli *grpcClient) OnStart(ctx context.Context) error {
timer := time.NewTimer(0)
defer timer.Stop()
func (cli *grpcClient) OnStart() error {
if err := cli.BaseService.OnStart(); err != nil {
return err
}
RETRY_LOOP:
for {
conn, err := grpc.Dial(cli.addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialerFunc),
)
conn, err := grpc.Dial(cli.addr, grpc.WithInsecure(), grpc.WithContextDialer(dialerFunc))
if err != nil {
if cli.mustConnect {
return err
}
cli.logger.Error("abci.grpcClient failed to connect, Retrying...", "addr", cli.addr, "err", err)
timer.Reset(time.Second * dialRetryIntervalSeconds)
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
continue RETRY_LOOP
}
cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr), "err", err)
time.Sleep(time.Second * dialRetryIntervalSeconds)
continue RETRY_LOOP
}
cli.logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr)
cli.Logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr)
client := types.NewABCIApplicationClient(conn)
cli.conn = conn
ENSURE_CONNECTED:
for {
_, err := client.Echo(ctx, &types.RequestEcho{Message: "hello"}, grpc.WaitForReady(true))
_, err := client.Echo(context.Background(), &types.RequestEcho{Message: "hello"}, grpc.WaitForReady(true))
if err == nil {
break ENSURE_CONNECTED
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
cli.logger.Error("Echo failed", "err", err)
timer.Reset(time.Second * echoRetryIntervalSeconds)
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
continue ENSURE_CONNECTED
}
cli.Logger.Error("Echo failed", "err", err)
time.Sleep(time.Second * echoRetryIntervalSeconds)
}
cli.client = client
@@ -104,81 +80,227 @@ RETRY_LOOP:
}
func (cli *grpcClient) OnStop() {
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.BaseService.OnStop()
if cli.conn != nil {
cli.err = cli.conn.Close()
cli.conn.Close()
}
}
func (cli *grpcClient) StopForError(err error) {
cli.mtx.Lock()
if !cli.IsRunning() {
return
}
if cli.err == nil {
cli.err = err
}
cli.mtx.Unlock()
cli.Logger.Error(fmt.Sprintf("Stopping abci.grpcClient for error: %v", err.Error()))
cli.Stop()
}
func (cli *grpcClient) Error() error {
cli.mtx.Lock()
defer cli.mtx.Unlock()
return cli.err
}
// Set listener for all responses
// NOTE: callback may get internally generated flush responses.
func (cli *grpcClient) SetResponseCallback(resCb Callback) {
cli.mtx.Lock()
cli.resCb = resCb
cli.mtx.Unlock()
}
//----------------------------------------
// GRPC calls are synchronous, but some callbacks expect to be called asynchronously
// (eg. the mempool expects to be able to lock to remove bad txs from cache).
// To accommodate, we finish each call in its own go-routine,
// which is expensive, but easy - if you want something better, use the socket protocol!
// maybe one day, if people really want it, we use grpc streams,
// but hopefully not :D
func (cli *grpcClient) EchoAsync(msg string) *ReqRes {
req := types.ToRequestEcho(msg)
res, err := cli.client.Echo(context.Background(), req.GetEcho(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_Echo{Echo: res}})
}
func (cli *grpcClient) FlushAsync() *ReqRes {
req := types.ToRequestFlush()
res, err := cli.client.Flush(context.Background(), req.GetFlush(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_Flush{Flush: res}})
}
func (cli *grpcClient) InfoAsync(params types.RequestInfo) *ReqRes {
req := types.ToRequestInfo(params)
res, err := cli.client.Info(context.Background(), req.GetInfo(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_Info{Info: res}})
}
func (cli *grpcClient) SetOptionAsync(params types.RequestSetOption) *ReqRes {
req := types.ToRequestSetOption(params)
res, err := cli.client.SetOption(context.Background(), req.GetSetOption(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_SetOption{SetOption: res}})
}
func (cli *grpcClient) DeliverTxAsync(params types.RequestDeliverTx) *ReqRes {
req := types.ToRequestDeliverTx(params)
res, err := cli.client.DeliverTx(context.Background(), req.GetDeliverTx(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_DeliverTx{DeliverTx: res}})
}
func (cli *grpcClient) CheckTxAsync(params types.RequestCheckTx) *ReqRes {
req := types.ToRequestCheckTx(params)
res, err := cli.client.CheckTx(context.Background(), req.GetCheckTx(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_CheckTx{CheckTx: res}})
}
func (cli *grpcClient) QueryAsync(params types.RequestQuery) *ReqRes {
req := types.ToRequestQuery(params)
res, err := cli.client.Query(context.Background(), req.GetQuery(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_Query{Query: res}})
}
func (cli *grpcClient) CommitAsync() *ReqRes {
req := types.ToRequestCommit()
res, err := cli.client.Commit(context.Background(), req.GetCommit(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_Commit{Commit: res}})
}
func (cli *grpcClient) InitChainAsync(params types.RequestInitChain) *ReqRes {
req := types.ToRequestInitChain(params)
res, err := cli.client.InitChain(context.Background(), req.GetInitChain(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_InitChain{InitChain: res}})
}
func (cli *grpcClient) BeginBlockAsync(params types.RequestBeginBlock) *ReqRes {
req := types.ToRequestBeginBlock(params)
res, err := cli.client.BeginBlock(context.Background(), req.GetBeginBlock(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_BeginBlock{BeginBlock: res}})
}
func (cli *grpcClient) EndBlockAsync(params types.RequestEndBlock) *ReqRes {
req := types.ToRequestEndBlock(params)
res, err := cli.client.EndBlock(context.Background(), req.GetEndBlock(), grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
}
return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_EndBlock{EndBlock: res}})
}
func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes {
reqres := NewReqRes(req)
reqres.Response = res // Set response
reqres.Done() // Release waiters
reqres.SetDone() // so reqRes.SetCallback will run the callback
// goroutine for callbacks
go func() {
cli.mtx.Lock()
defer cli.mtx.Unlock()
// Notify client listener if set
if cli.resCb != nil {
cli.resCb(reqres.Request, res)
}
// Notify reqRes listener if set
if cb := reqres.GetCallback(); cb != nil {
cb(res)
}
}()
return reqres
}
//----------------------------------------
func (cli *grpcClient) Flush(ctx context.Context) error { return nil }
func (cli *grpcClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
return cli.client.Echo(ctx, types.ToRequestEcho(msg).GetEcho(), grpc.WaitForReady(true))
func (cli *grpcClient) FlushSync() error {
return nil
}
func (cli *grpcClient) Info(ctx context.Context, params *types.RequestInfo) (*types.ResponseInfo, error) {
return cli.client.Info(ctx, types.ToRequestInfo(params).GetInfo(), grpc.WaitForReady(true))
func (cli *grpcClient) EchoSync(msg string) (*types.ResponseEcho, error) {
reqres := cli.EchoAsync(msg)
// StopForError should already have been called if error is set
return reqres.Response.GetEcho(), cli.Error()
}
func (cli *grpcClient) CheckTx(ctx context.Context, params *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
return cli.client.CheckTx(ctx, types.ToRequestCheckTx(params).GetCheckTx(), grpc.WaitForReady(true))
func (cli *grpcClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) {
reqres := cli.InfoAsync(req)
return reqres.Response.GetInfo(), cli.Error()
}
func (cli *grpcClient) Query(ctx context.Context, params *types.RequestQuery) (*types.ResponseQuery, error) {
return cli.client.Query(ctx, types.ToRequestQuery(params).GetQuery(), grpc.WaitForReady(true))
func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
reqres := cli.SetOptionAsync(req)
return reqres.Response.GetSetOption(), cli.Error()
}
func (cli *grpcClient) Commit(ctx context.Context) (*types.ResponseCommit, error) {
return cli.client.Commit(ctx, types.ToRequestCommit().GetCommit(), grpc.WaitForReady(true))
func (cli *grpcClient) DeliverTxSync(params types.RequestDeliverTx) (*types.ResponseDeliverTx, error) {
reqres := cli.DeliverTxAsync(params)
return reqres.Response.GetDeliverTx(), cli.Error()
}
func (cli *grpcClient) InitChain(ctx context.Context, params *types.RequestInitChain) (*types.ResponseInitChain, error) {
return cli.client.InitChain(ctx, types.ToRequestInitChain(params).GetInitChain(), grpc.WaitForReady(true))
func (cli *grpcClient) CheckTxSync(params types.RequestCheckTx) (*types.ResponseCheckTx, error) {
reqres := cli.CheckTxAsync(params)
return reqres.Response.GetCheckTx(), cli.Error()
}
func (cli *grpcClient) ListSnapshots(ctx context.Context, params *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
return cli.client.ListSnapshots(ctx, types.ToRequestListSnapshots(params).GetListSnapshots(), grpc.WaitForReady(true))
func (cli *grpcClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
reqres := cli.QueryAsync(req)
return reqres.Response.GetQuery(), cli.Error()
}
func (cli *grpcClient) OfferSnapshot(ctx context.Context, params *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
return cli.client.OfferSnapshot(ctx, types.ToRequestOfferSnapshot(params).GetOfferSnapshot(), grpc.WaitForReady(true))
func (cli *grpcClient) CommitSync() (*types.ResponseCommit, error) {
reqres := cli.CommitAsync()
return reqres.Response.GetCommit(), cli.Error()
}
func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, params *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
return cli.client.LoadSnapshotChunk(ctx, types.ToRequestLoadSnapshotChunk(params).GetLoadSnapshotChunk(), grpc.WaitForReady(true))
func (cli *grpcClient) InitChainSync(params types.RequestInitChain) (*types.ResponseInitChain, error) {
reqres := cli.InitChainAsync(params)
return reqres.Response.GetInitChain(), cli.Error()
}
func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, params *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
return cli.client.ApplySnapshotChunk(ctx, types.ToRequestApplySnapshotChunk(params).GetApplySnapshotChunk(), grpc.WaitForReady(true))
func (cli *grpcClient) BeginBlockSync(params types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
reqres := cli.BeginBlockAsync(params)
return reqres.Response.GetBeginBlock(), cli.Error()
}
func (cli *grpcClient) PrepareProposal(ctx context.Context, params *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
return cli.client.PrepareProposal(ctx, types.ToRequestPrepareProposal(params).GetPrepareProposal(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ProcessProposal(ctx context.Context, params *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
return cli.client.ProcessProposal(ctx, types.ToRequestProcessProposal(params).GetProcessProposal(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ExtendVote(ctx context.Context, params *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
return cli.client.ExtendVote(ctx, types.ToRequestExtendVote(params).GetExtendVote(), grpc.WaitForReady(true))
}
func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, params *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
return cli.client.VerifyVoteExtension(ctx, types.ToRequestVerifyVoteExtension(params).GetVerifyVoteExtension(), grpc.WaitForReady(true))
}
func (cli *grpcClient) FinalizeBlock(ctx context.Context, params *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
return cli.client.FinalizeBlock(ctx, types.ToRequestFinalizeBlock(params).GetFinalizeBlock(), grpc.WaitForReady(true))
func (cli *grpcClient) EndBlockSync(params types.RequestEndBlock) (*types.ResponseEndBlock, error) {
reqres := cli.EndBlockAsync(params)
return reqres.Response.GetEndBlock(), cli.Error()
}

View File

@@ -1,40 +1,255 @@
package abciclient
package abcicli
import (
"context"
"sync"
types "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
)
var _ Client = (*localClient)(nil)
// NOTE: use defer to unlock mutex because Application might panic (e.g., in
// case of malicious tx or query). It only makes sense for publicly exposed
// methods like CheckTx (/broadcast_tx_* RPC endpoint) or Query (/abci_query
// RPC endpoint), but defers are used everywhere for the sake of consistency.
type localClient struct {
service.BaseService
mtx *sync.Mutex
types.Application
Callback
}
var _ Client = (*localClient)(nil)
// NewLocalClient creates a local client, which will be directly calling the
// methods of the given app.
//
// The client methods ignore their context argument.
func NewLocalClient(logger log.Logger, app types.Application) Client {
func NewLocalClient(mtx *sync.Mutex, app types.Application) Client {
if mtx == nil {
mtx = new(sync.Mutex)
}
cli := &localClient{
mtx: mtx,
Application: app,
}
cli.BaseService = *service.NewBaseService(logger, "localClient", cli)
cli.BaseService = *service.NewBaseService(nil, "localClient", cli)
return cli
}
func (*localClient) OnStart(context.Context) error { return nil }
func (*localClient) OnStop() {}
func (*localClient) Error() error { return nil }
func (*localClient) Flush(context.Context) error { return nil }
func (*localClient) Echo(_ context.Context, msg string) (*types.ResponseEcho, error) {
func (app *localClient) SetResponseCallback(cb Callback) {
app.mtx.Lock()
app.Callback = cb
app.mtx.Unlock()
}
// TODO: change types.Application to include Error()?
func (app *localClient) Error() error {
return nil
}
func (app *localClient) FlushAsync() *ReqRes {
// Do nothing
return newLocalReqRes(types.ToRequestFlush(), nil)
}
func (app *localClient) EchoAsync(msg string) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.callback(
types.ToRequestEcho(msg),
types.ToResponseEcho(msg),
)
}
func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Info(req)
return app.callback(
types.ToRequestInfo(req),
types.ToResponseInfo(res),
)
}
func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.SetOption(req)
return app.callback(
types.ToRequestSetOption(req),
types.ToResponseSetOption(res),
)
}
func (app *localClient) DeliverTxAsync(params types.RequestDeliverTx) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.DeliverTx(params)
return app.callback(
types.ToRequestDeliverTx(params),
types.ToResponseDeliverTx(res),
)
}
func (app *localClient) CheckTxAsync(req types.RequestCheckTx) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.CheckTx(req)
return app.callback(
types.ToRequestCheckTx(req),
types.ToResponseCheckTx(res),
)
}
func (app *localClient) QueryAsync(req types.RequestQuery) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Query(req)
return app.callback(
types.ToRequestQuery(req),
types.ToResponseQuery(res),
)
}
func (app *localClient) CommitAsync() *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Commit()
return app.callback(
types.ToRequestCommit(),
types.ToResponseCommit(res),
)
}
func (app *localClient) InitChainAsync(req types.RequestInitChain) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.InitChain(req)
return app.callback(
types.ToRequestInitChain(req),
types.ToResponseInitChain(res),
)
}
func (app *localClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.BeginBlock(req)
return app.callback(
types.ToRequestBeginBlock(req),
types.ToResponseBeginBlock(res),
)
}
func (app *localClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.EndBlock(req)
return app.callback(
types.ToRequestEndBlock(req),
types.ToResponseEndBlock(res),
)
}
//-------------------------------------------------------
func (app *localClient) FlushSync() error {
return nil
}
func (app *localClient) EchoSync(msg string) (*types.ResponseEcho, error) {
return &types.ResponseEcho{Message: msg}, nil
}
func (app *localClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Info(req)
return &res, nil
}
func (app *localClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.SetOption(req)
return &res, nil
}
func (app *localClient) DeliverTxSync(req types.RequestDeliverTx) (*types.ResponseDeliverTx, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.DeliverTx(req)
return &res, nil
}
func (app *localClient) CheckTxSync(req types.RequestCheckTx) (*types.ResponseCheckTx, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.CheckTx(req)
return &res, nil
}
func (app *localClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Query(req)
return &res, nil
}
func (app *localClient) CommitSync() (*types.ResponseCommit, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.Commit()
return &res, nil
}
func (app *localClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.InitChain(req)
return &res, nil
}
func (app *localClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.BeginBlock(req)
return &res, nil
}
func (app *localClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res := app.Application.EndBlock(req)
return &res, nil
}
//-------------------------------------------------------
func (app *localClient) callback(req *types.Request, res *types.Response) *ReqRes {
app.Callback(req, res)
return newLocalReqRes(req, res)
}
func newLocalReqRes(req *types.Request, res *types.Response) *ReqRes {
reqRes := NewReqRes(req)
reqRes.Response = res
reqRes.SetDone()
return reqRes
}

View File

@@ -1,436 +0,0 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
types "github.com/tendermint/tendermint/abci/types"
)
// Client is an autogenerated mock type for the Client type
type Client struct {
mock.Mock
}
// ApplySnapshotChunk provides a mock function with given fields: _a0, _a1
func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseApplySnapshotChunk
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseApplySnapshotChunk)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestApplySnapshotChunk) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CheckTx provides a mock function with given fields: _a0, _a1
func (_m *Client) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseCheckTx
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseCheckTx)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCheckTx) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Commit provides a mock function with given fields: _a0
func (_m *Client) Commit(_a0 context.Context) (*types.ResponseCommit, error) {
ret := _m.Called(_a0)
var r0 *types.ResponseCommit
if rf, ok := ret.Get(0).(func(context.Context) *types.ResponseCommit); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseCommit)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Echo provides a mock function with given fields: _a0, _a1
func (_m *Client) Echo(_a0 context.Context, _a1 string) (*types.ResponseEcho, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseEcho
if rf, ok := ret.Get(0).(func(context.Context, string) *types.ResponseEcho); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseEcho)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Error provides a mock function with given fields:
func (_m *Client) Error() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// ExtendVote provides a mock function with given fields: _a0, _a1
func (_m *Client) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseExtendVote
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseExtendVote)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FinalizeBlock provides a mock function with given fields: _a0, _a1
func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseFinalizeBlock
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseFinalizeBlock)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestFinalizeBlock) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Flush provides a mock function with given fields: _a0
func (_m *Client) Flush(_a0 context.Context) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Info provides a mock function with given fields: _a0, _a1
func (_m *Client) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseInfo
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseInfo)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// InitChain provides a mock function with given fields: _a0, _a1
func (_m *Client) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseInitChain
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseInitChain)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IsRunning provides a mock function with given fields:
func (_m *Client) IsRunning() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// ListSnapshots provides a mock function with given fields: _a0, _a1
func (_m *Client) ListSnapshots(_a0 context.Context, _a1 *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseListSnapshots
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) *types.ResponseListSnapshots); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseListSnapshots)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestListSnapshots) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// LoadSnapshotChunk provides a mock function with given fields: _a0, _a1
func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseLoadSnapshotChunk
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseLoadSnapshotChunk)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestLoadSnapshotChunk) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// OfferSnapshot provides a mock function with given fields: _a0, _a1
func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseOfferSnapshot
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseOfferSnapshot)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestOfferSnapshot) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PrepareProposal provides a mock function with given fields: _a0, _a1
func (_m *Client) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponsePrepareProposal
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponsePrepareProposal)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestPrepareProposal) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ProcessProposal provides a mock function with given fields: _a0, _a1
func (_m *Client) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseProcessProposal
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) *types.ResponseProcessProposal); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseProcessProposal)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestProcessProposal) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Query provides a mock function with given fields: _a0, _a1
func (_m *Client) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseQuery
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) *types.ResponseQuery); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseQuery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestQuery) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Start provides a mock function with given fields: _a0
func (_m *Client) Start(_a0 context.Context) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// VerifyVoteExtension provides a mock function with given fields: _a0, _a1
func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
ret := _m.Called(_a0, _a1)
var r0 *types.ResponseVerifyVoteExtension
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseVerifyVoteExtension)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Wait provides a mock function with given fields:
func (_m *Client) Wait() {
_m.Called()
}
type NewClientT interface {
mock.TestingT
Cleanup(func())
}
// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewClient(t NewClientT) *Client {
mock := &Client{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -1,171 +1,186 @@
package abciclient
package abcicli
import (
"bufio"
"container/list"
"context"
"errors"
"fmt"
"io"
"net"
"reflect"
"sync"
"time"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
"github.com/tendermint/tendermint/libs/service"
"github.com/tendermint/tendermint/libs/timer"
)
// This is goroutine-safe, but users should beware that the application in
// general is not meant to be interfaced with concurrent callers.
const reqQueueSize = 256 // TODO make configurable
// const maxResponseSize = 1048576 // 1MB TODO make configurable
const flushThrottleMS = 20 // Don't wait longer than...
var _ Client = (*socketClient)(nil)
// This is goroutine-safe, but users should beware that
// the application in general is not meant to be interfaced
// with concurrent callers.
type socketClient struct {
service.BaseService
logger log.Logger
addr string
mustConnect bool
conn net.Conn
reqQueue chan *requestAndResponse
reqQueue chan *ReqRes
flushTimer *timer.ThrottleTimer
mtx sync.Mutex
err error
reqSent *list.List // list of requests sent, waiting for response
reqSent *list.List // list of requests sent, waiting for response
resCb func(*types.Request, *types.Response) // called on all requests, if set.
}
var _ Client = (*socketClient)(nil)
// NewSocketClient creates a new socket client, which connects to a given
// address. If mustConnect is true, the client will return an error upon start
// if it fails to connect.
func NewSocketClient(logger log.Logger, addr string, mustConnect bool) Client {
func NewSocketClient(addr string, mustConnect bool) Client {
cli := &socketClient{
logger: logger,
reqQueue: make(chan *requestAndResponse),
reqQueue: make(chan *ReqRes, reqQueueSize),
flushTimer: timer.NewThrottleTimer("socketClient", flushThrottleMS),
mustConnect: mustConnect,
addr: addr,
reqSent: list.New(),
addr: addr,
reqSent: list.New(),
resCb: nil,
}
cli.BaseService = *service.NewBaseService(logger, "socketClient", cli)
cli.BaseService = *service.NewBaseService(nil, "socketClient", cli)
return cli
}
// OnStart implements Service by connecting to the server and spawning reading
// and writing goroutines.
func (cli *socketClient) OnStart(ctx context.Context) error {
var (
err error
conn net.Conn
)
timer := time.NewTimer(0)
defer timer.Stop()
func (cli *socketClient) OnStart() error {
var err error
var conn net.Conn
RETRY_LOOP:
for {
conn, err = tmnet.Connect(cli.addr)
if err != nil {
if cli.mustConnect {
return 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 {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
continue
}
cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr), "err", err)
time.Sleep(time.Second * dialRetryIntervalSeconds)
continue RETRY_LOOP
}
cli.conn = conn
go cli.sendRequestsRoutine(ctx, conn)
go cli.recvResponseRoutine(ctx, conn)
go cli.sendRequestsRoutine(conn)
go cli.recvResponseRoutine(conn)
return nil
}
}
// OnStop implements Service by closing connection and flushing all queues.
func (cli *socketClient) OnStop() {
if cli.conn != nil {
cli.conn.Close()
}
cli.drainQueue()
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.flushQueue()
}
// Stop the client and set the error
func (cli *socketClient) StopForError(err error) {
if !cli.IsRunning() {
return
}
cli.mtx.Lock()
if cli.err == nil {
cli.err = err
}
cli.mtx.Unlock()
cli.Logger.Error(fmt.Sprintf("Stopping abci.socketClient for error: %v", err.Error()))
cli.Stop()
}
// Error returns an error if the client was stopped abruptly.
func (cli *socketClient) Error() error {
cli.mtx.Lock()
defer cli.mtx.Unlock()
return cli.err
}
// Set listener for all responses
// NOTE: callback may get internally generated flush responses.
func (cli *socketClient) SetResponseCallback(resCb Callback) {
cli.mtx.Lock()
cli.resCb = resCb
cli.mtx.Unlock()
}
//----------------------------------------
func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer) {
bw := bufio.NewWriter(conn)
func (cli *socketClient) sendRequestsRoutine(conn io.Writer) {
w := bufio.NewWriter(conn)
for {
select {
case <-ctx.Done():
case <-cli.flushTimer.Ch:
select {
case cli.reqQueue <- NewReqRes(types.ToRequestFlush()):
default:
// Probably will fill the buffer, or retry later.
}
case <-cli.Quit():
return
case reqres := <-cli.reqQueue:
// N.B. We must enqueue before sending out the request, otherwise the
// server may reply before we do it, and the receiver will fail for an
// unsolicited reply.
cli.trackRequest(reqres)
if err := types.WriteMessage(reqres.Request, bw); err != nil {
cli.stopForError(fmt.Errorf("write to buffer: %w", err))
cli.willSendReq(reqres)
err := types.WriteMessage(reqres.Request, w)
if err != nil {
cli.StopForError(fmt.Errorf("error writing msg: %v", err))
return
}
if err := bw.Flush(); err != nil {
cli.stopForError(fmt.Errorf("flush buffer: %w", err))
return
// cli.Logger.Debug("Sent request", "requestType", reflect.TypeOf(reqres.Request), "request", reqres.Request)
if _, ok := reqres.Request.Value.(*types.Request_Flush); ok {
err = w.Flush()
if err != nil {
cli.StopForError(fmt.Errorf("error flushing writer: %v", err))
return
}
}
}
}
}
func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader) {
r := bufio.NewReader(conn)
func (cli *socketClient) recvResponseRoutine(conn io.Reader) {
r := bufio.NewReader(conn) // Buffer reads
for {
if ctx.Err() != nil {
var res = &types.Response{}
err := types.ReadMessage(r, res)
if err != nil {
cli.StopForError(err)
return
}
res := &types.Response{}
if err := types.ReadMessage(r, res); err != nil {
cli.stopForError(fmt.Errorf("read message: %w", err))
return
}
switch r := res.Value.(type) {
case *types.Response_Exception: // app responded with error
case *types.Response_Exception:
// XXX After setting cli.err, release waiters (e.g. reqres.Done())
cli.stopForError(errors.New(r.Exception.Error))
cli.StopForError(errors.New(r.Exception.Error))
return
default:
if err := cli.didRecvResponse(res); err != nil {
cli.stopForError(err)
// cli.Logger.Debug("Received response", "responseType", reflect.TypeOf(res), "response", res)
err := cli.didRecvResponse(res)
if err != nil {
cli.StopForError(err)
return
}
}
}
}
func (cli *socketClient) trackRequest(reqres *requestAndResponse) {
// N.B. We must NOT hold the client state lock while checking this, or we
// may deadlock with shutdown.
if !cli.IsRunning() {
return
}
func (cli *socketClient) willSendReq(reqres *ReqRes) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.reqSent.PushBack(reqres)
@@ -175,192 +190,189 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error {
cli.mtx.Lock()
defer cli.mtx.Unlock()
// Get the first ReqRes.
// Get the first ReqRes
next := cli.reqSent.Front()
if next == nil {
return fmt.Errorf("unexpected %T when nothing expected", res.Value)
return fmt.Errorf("unexpected result type %v when nothing expected", reflect.TypeOf(res.Value))
}
reqres := next.Value.(*requestAndResponse)
reqres := next.Value.(*ReqRes)
if !resMatchesReq(reqres.Request, res) {
return fmt.Errorf("unexpected %T when response to %T expected", res.Value, reqres.Request.Value)
return fmt.Errorf("unexpected result type %v when response to %v expected",
reflect.TypeOf(res.Value), reflect.TypeOf(reqres.Request.Value))
}
reqres.Response = res
reqres.markDone() // release waiters
cli.reqSent.Remove(next) // pop first item from linked list
reqres.Response = res // Set response
reqres.Done() // Release waiters
cli.reqSent.Remove(next) // Pop first item from linked list
// Notify client listener if set (global callback).
if cli.resCb != nil {
cli.resCb(reqres.Request, res)
}
// Notify reqRes listener if set (request specific callback).
// NOTE: it is possible this callback isn't set on the reqres object.
// at this point, in which case it will be called after, when it is set.
if cb := reqres.GetCallback(); cb != nil {
cb(res)
}
return nil
}
//----------------------------------------
func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*types.Response, error) {
if !cli.IsRunning() {
return nil, errors.New("client has stopped")
}
reqres := makeReqRes(req)
select {
case cli.reqQueue <- reqres:
case <-ctx.Done():
return nil, fmt.Errorf("can't queue req: %w", ctx.Err())
}
select {
case <-reqres.signal:
if err := cli.Error(); err != nil {
return nil, err
}
return reqres.Response, nil
case <-ctx.Done():
return nil, ctx.Err()
}
func (cli *socketClient) EchoAsync(msg string) *ReqRes {
return cli.queueRequest(types.ToRequestEcho(msg))
}
// drainQueue marks as complete and discards all remaining pending requests
// from the queue.
func (cli *socketClient) drainQueue() {
cli.mtx.Lock()
defer cli.mtx.Unlock()
func (cli *socketClient) FlushAsync() *ReqRes {
return cli.queueRequest(types.ToRequestFlush())
}
// mark all in-flight messages as resolved (they will get cli.Error())
for req := cli.reqSent.Front(); req != nil; req = req.Next() {
reqres := req.Value.(*requestAndResponse)
reqres.markDone()
}
func (cli *socketClient) InfoAsync(req types.RequestInfo) *ReqRes {
return cli.queueRequest(types.ToRequestInfo(req))
}
func (cli *socketClient) SetOptionAsync(req types.RequestSetOption) *ReqRes {
return cli.queueRequest(types.ToRequestSetOption(req))
}
func (cli *socketClient) DeliverTxAsync(req types.RequestDeliverTx) *ReqRes {
return cli.queueRequest(types.ToRequestDeliverTx(req))
}
func (cli *socketClient) CheckTxAsync(req types.RequestCheckTx) *ReqRes {
return cli.queueRequest(types.ToRequestCheckTx(req))
}
func (cli *socketClient) QueryAsync(req types.RequestQuery) *ReqRes {
return cli.queueRequest(types.ToRequestQuery(req))
}
func (cli *socketClient) CommitAsync() *ReqRes {
return cli.queueRequest(types.ToRequestCommit())
}
func (cli *socketClient) InitChainAsync(req types.RequestInitChain) *ReqRes {
return cli.queueRequest(types.ToRequestInitChain(req))
}
func (cli *socketClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes {
return cli.queueRequest(types.ToRequestBeginBlock(req))
}
func (cli *socketClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes {
return cli.queueRequest(types.ToRequestEndBlock(req))
}
//----------------------------------------
func (cli *socketClient) Flush(ctx context.Context) error {
_, err := cli.doRequest(ctx, types.ToRequestFlush())
if err != nil {
func (cli *socketClient) FlushSync() error {
reqRes := cli.queueRequest(types.ToRequestFlush())
if err := cli.Error(); err != nil {
return err
}
return nil
reqRes.Wait() // NOTE: if we don't flush the queue, its possible to get stuck here
return cli.Error()
}
func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
res, err := cli.doRequest(ctx, types.ToRequestEcho(msg))
if err != nil {
return nil, err
}
return res.GetEcho(), nil
func (cli *socketClient) EchoSync(msg string) (*types.ResponseEcho, error) {
reqres := cli.queueRequest(types.ToRequestEcho(msg))
cli.FlushSync()
return reqres.Response.GetEcho(), cli.Error()
}
func (cli *socketClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) {
res, err := cli.doRequest(ctx, types.ToRequestInfo(req))
if err != nil {
return nil, err
}
return res.GetInfo(), nil
func (cli *socketClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) {
reqres := cli.queueRequest(types.ToRequestInfo(req))
cli.FlushSync()
return reqres.Response.GetInfo(), cli.Error()
}
func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req))
if err != nil {
return nil, err
}
return res.GetCheckTx(), nil
func (cli *socketClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
reqres := cli.queueRequest(types.ToRequestSetOption(req))
cli.FlushSync()
return reqres.Response.GetSetOption(), cli.Error()
}
func (cli *socketClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) {
res, err := cli.doRequest(ctx, types.ToRequestQuery(req))
if err != nil {
return nil, err
}
return res.GetQuery(), nil
func (cli *socketClient) DeliverTxSync(req types.RequestDeliverTx) (*types.ResponseDeliverTx, error) {
reqres := cli.queueRequest(types.ToRequestDeliverTx(req))
cli.FlushSync()
return reqres.Response.GetDeliverTx(), cli.Error()
}
func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) {
res, err := cli.doRequest(ctx, types.ToRequestCommit())
if err != nil {
return nil, err
}
return res.GetCommit(), nil
func (cli *socketClient) CheckTxSync(req types.RequestCheckTx) (*types.ResponseCheckTx, error) {
reqres := cli.queueRequest(types.ToRequestCheckTx(req))
cli.FlushSync()
return reqres.Response.GetCheckTx(), cli.Error()
}
func (cli *socketClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
res, err := cli.doRequest(ctx, types.ToRequestInitChain(req))
if err != nil {
return nil, err
}
return res.GetInitChain(), nil
func (cli *socketClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
reqres := cli.queueRequest(types.ToRequestQuery(req))
cli.FlushSync()
return reqres.Response.GetQuery(), cli.Error()
}
func (cli *socketClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
res, err := cli.doRequest(ctx, types.ToRequestListSnapshots(req))
if err != nil {
return nil, err
}
return res.GetListSnapshots(), nil
func (cli *socketClient) CommitSync() (*types.ResponseCommit, error) {
reqres := cli.queueRequest(types.ToRequestCommit())
cli.FlushSync()
return reqres.Response.GetCommit(), cli.Error()
}
func (cli *socketClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
res, err := cli.doRequest(ctx, types.ToRequestOfferSnapshot(req))
if err != nil {
return nil, err
}
return res.GetOfferSnapshot(), nil
func (cli *socketClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) {
reqres := cli.queueRequest(types.ToRequestInitChain(req))
cli.FlushSync()
return reqres.Response.GetInitChain(), cli.Error()
}
func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
res, err := cli.doRequest(ctx, types.ToRequestLoadSnapshotChunk(req))
if err != nil {
return nil, err
}
return res.GetLoadSnapshotChunk(), nil
func (cli *socketClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
reqres := cli.queueRequest(types.ToRequestBeginBlock(req))
cli.FlushSync()
return reqres.Response.GetBeginBlock(), cli.Error()
}
func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
res, err := cli.doRequest(ctx, types.ToRequestApplySnapshotChunk(req))
if err != nil {
return nil, err
}
return res.GetApplySnapshotChunk(), nil
func (cli *socketClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) {
reqres := cli.queueRequest(types.ToRequestEndBlock(req))
cli.FlushSync()
return reqres.Response.GetEndBlock(), cli.Error()
}
func (cli *socketClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
res, err := cli.doRequest(ctx, types.ToRequestPrepareProposal(req))
if err != nil {
return nil, err
//----------------------------------------
func (cli *socketClient) queueRequest(req *types.Request) *ReqRes {
reqres := NewReqRes(req)
// TODO: set cli.err if reqQueue times out
cli.reqQueue <- reqres
// Maybe auto-flush, or unset auto-flush
switch req.Value.(type) {
case *types.Request_Flush:
cli.flushTimer.Unset()
default:
cli.flushTimer.Set()
}
return res.GetPrepareProposal(), nil
return reqres
}
func (cli *socketClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
res, err := cli.doRequest(ctx, types.ToRequestProcessProposal(req))
if err != nil {
return nil, err
func (cli *socketClient) flushQueue() {
// mark all in-flight messages as resolved (they will get cli.Error())
for req := cli.reqSent.Front(); req != nil; req = req.Next() {
reqres := req.Value.(*ReqRes)
reqres.Done()
}
return res.GetProcessProposal(), nil
}
func (cli *socketClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
res, err := cli.doRequest(ctx, types.ToRequestExtendVote(req))
if err != nil {
return nil, err
// mark all queued messages as resolved
LOOP:
for {
select {
case reqres := <-cli.reqQueue:
reqres.Done()
default:
break LOOP
}
}
return res.GetExtendVote(), nil
}
func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
res, err := cli.doRequest(ctx, types.ToRequestVerifyVoteExtension(req))
if err != nil {
return nil, err
}
return res.GetVerifyVoteExtension(), nil
}
func (cli *socketClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
res, err := cli.doRequest(ctx, types.ToRequestFinalizeBlock(req))
if err != nil {
return nil, err
}
return res.GetFinalizeBlock(), nil
}
//----------------------------------------
@@ -373,6 +385,10 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
_, ok = res.Value.(*types.Response_Flush)
case *types.Request_Info:
_, ok = res.Value.(*types.Response_Info)
case *types.Request_SetOption:
_, ok = res.Value.(*types.Response_SetOption)
case *types.Request_DeliverTx:
_, ok = res.Value.(*types.Response_DeliverTx)
case *types.Request_CheckTx:
_, ok = res.Value.(*types.Response_CheckTx)
case *types.Request_Commit:
@@ -381,37 +397,10 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
_, ok = res.Value.(*types.Response_Query)
case *types.Request_InitChain:
_, ok = res.Value.(*types.Response_InitChain)
case *types.Request_ProcessProposal:
_, ok = res.Value.(*types.Response_ProcessProposal)
case *types.Request_PrepareProposal:
_, ok = res.Value.(*types.Response_PrepareProposal)
case *types.Request_ExtendVote:
_, ok = res.Value.(*types.Response_ExtendVote)
case *types.Request_VerifyVoteExtension:
_, ok = res.Value.(*types.Response_VerifyVoteExtension)
case *types.Request_ApplySnapshotChunk:
_, ok = res.Value.(*types.Response_ApplySnapshotChunk)
case *types.Request_LoadSnapshotChunk:
_, ok = res.Value.(*types.Response_LoadSnapshotChunk)
case *types.Request_ListSnapshots:
_, ok = res.Value.(*types.Response_ListSnapshots)
case *types.Request_OfferSnapshot:
_, ok = res.Value.(*types.Response_OfferSnapshot)
case *types.Request_FinalizeBlock:
_, ok = res.Value.(*types.Response_FinalizeBlock)
case *types.Request_BeginBlock:
_, ok = res.Value.(*types.Response_BeginBlock)
case *types.Request_EndBlock:
_, ok = res.Value.(*types.Response_EndBlock)
}
return ok
}
func (cli *socketClient) stopForError(err error) {
if !cli.IsRunning() {
return
}
cli.mtx.Lock()
cli.err = err
cli.mtx.Unlock()
cli.logger.Info("Stopping abci.socketClient", "reason", err)
cli.Stop()
}

View File

@@ -0,0 +1,126 @@
package abcicli_test
import (
"errors"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abcicli "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/abci/types"
tmrand "github.com/tendermint/tendermint/libs/rand"
"github.com/tendermint/tendermint/libs/service"
)
type errorStopper interface {
StopForError(error)
}
func TestSocketClientStopForErrorDeadlock(t *testing.T) {
c := abcicli.NewSocketClient(":80", false).(errorStopper)
err := errors.New("foo-tendermint")
// See Issue https://github.com/tendermint/abci/issues/114
doneChan := make(chan bool)
go func() {
defer close(doneChan)
c.StopForError(err)
c.StopForError(err)
}()
select {
case <-doneChan:
case <-time.After(time.Second * 4):
t.Fatalf("Test took too long, potential deadlock still exists")
}
}
func TestProperSyncCalls(t *testing.T) {
app := slowApp{}
s, c := setupClientServer(t, app)
defer s.Stop()
defer c.Stop()
resp := make(chan error, 1)
go func() {
// This is BeginBlockSync unrolled....
reqres := c.BeginBlockAsync(types.RequestBeginBlock{})
c.FlushSync()
res := reqres.Response.GetBeginBlock()
require.NotNil(t, res)
resp <- c.Error()
}()
select {
case <-time.After(time.Second):
require.Fail(t, "No response arrived")
case err, ok := <-resp:
require.True(t, ok, "Must not close channel")
assert.NoError(t, err, "This should return success")
}
}
func TestHangingSyncCalls(t *testing.T) {
app := slowApp{}
s, c := setupClientServer(t, app)
defer s.Stop()
defer c.Stop()
resp := make(chan error, 1)
go func() {
// Start BeginBlock and flush it
reqres := c.BeginBlockAsync(types.RequestBeginBlock{})
flush := c.FlushAsync()
// wait 20 ms for all events to travel socket, but
// no response yet from server
time.Sleep(20 * time.Millisecond)
// kill the server, so the connections break
s.Stop()
// wait for the response from BeginBlock
reqres.Wait()
flush.Wait()
resp <- c.Error()
}()
select {
case <-time.After(time.Second):
require.Fail(t, "No response arrived")
case err, ok := <-resp:
require.True(t, ok, "Must not close channel")
assert.Error(t, err, "We should get EOF error")
}
}
func setupClientServer(t *testing.T, app types.Application) (
service.Service, abcicli.Client) {
// some port between 20k and 30k
port := 20000 + tmrand.Int32()%10000
addr := fmt.Sprintf("localhost:%d", port)
s, err := server.NewServer(addr, "socket", app)
require.NoError(t, err)
err = s.Start()
require.NoError(t, err)
c := abcicli.NewSocketClient(addr, true)
err = c.Start()
require.NoError(t, err)
return s, c
}
type slowApp struct {
types.BaseApplication
}
func (slowApp) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
time.Sleep(200 * time.Millisecond)
return types.ResponseBeginBlock{}
}

View File

@@ -2,33 +2,33 @@ package main
import (
"bufio"
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/version"
tmos "github.com/tendermint/tendermint/libs/os"
abciclient "github.com/tendermint/tendermint/abci/client"
abcicli "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/example/counter"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/abci/server"
servertest "github.com/tendermint/tendermint/abci/tests/server"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/proto/tendermint/crypto"
"github.com/tendermint/tendermint/abci/version"
"github.com/tendermint/tendermint/crypto/merkle"
)
// client is a global variable so it can be reused by the console
var (
client abciclient.Client
client abcicli.Client
logger log.Logger
)
// flags
@@ -44,100 +44,124 @@ var (
flagHeight int
flagProve bool
// counter
flagSerial bool
// kvstore
flagPersist string
)
func RootCmmand(logger log.Logger) *cobra.Command {
return &cobra.Command{
Use: "abci-cli",
Short: "the ABCI CLI tool wraps an ABCI client",
Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers",
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
var RootCmd = &cobra.Command{
Use: "abci-cli",
Short: "the ABCI CLI tool wraps an ABCI client",
Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
switch cmd.Use {
case "kvstore", "version":
return nil
}
if client == nil {
var err error
client, err = abciclient.NewClient(logger.With("module", "abci-client"), flagAddress, flagAbci, false)
if err != nil {
return err
}
if err := client.Start(cmd.Context()); err != nil {
return err
}
}
switch cmd.Use {
case "counter", "kvstore": // for the examples apps, don't pre-run
return nil
},
}
case "version": // skip running for version command
return nil
}
if logger == nil {
allowLevel, err := log.AllowLevel(flagLogLevel)
if err != nil {
return err
}
logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), allowLevel)
}
if client == nil {
var err error
client, err = abcicli.NewClient(flagAddress, flagAbci, false)
if err != nil {
return err
}
client.SetLogger(logger.With("module", "abci-client"))
if err := client.Start(); err != nil {
return err
}
}
return nil
},
}
// Structure for data passed to print response.
type response struct {
// generic abci response
Data []byte
Code uint32
Info string
Log string
Status int32
Data []byte
Code uint32
Info string
Log string
Query *queryResponse
}
type queryResponse struct {
Key []byte
Value []byte
Height int64
ProofOps *crypto.ProofOps
Key []byte
Value []byte
Height int64
Proof *merkle.Proof
}
func Execute() error {
logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
if err != nil {
return err
}
cmd := RootCmmand(logger)
addGlobalFlags(cmd)
addCommands(cmd, logger)
return cmd.Execute()
addGlobalFlags()
addCommands()
return RootCmd.Execute()
}
func addGlobalFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&flagAddress,
func addGlobalFlags() {
RootCmd.PersistentFlags().StringVarP(&flagAddress,
"address",
"",
"tcp://0.0.0.0:26658",
"address of application socket")
cmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc")
cmd.PersistentFlags().BoolVarP(&flagVerbose,
RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc")
RootCmd.PersistentFlags().BoolVarP(&flagVerbose,
"verbose",
"v",
false,
"print the command and results as if it were a console session")
cmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
}
func addCommands(cmd *cobra.Command, logger log.Logger) {
cmd.AddCommand(batchCmd)
cmd.AddCommand(consoleCmd)
cmd.AddCommand(echoCmd)
cmd.AddCommand(infoCmd)
cmd.AddCommand(finalizeBlockCmd)
cmd.AddCommand(checkTxCmd)
cmd.AddCommand(commitCmd)
cmd.AddCommand(versionCmd)
cmd.AddCommand(testCmd)
cmd.AddCommand(prepareProposalCmd)
cmd.AddCommand(processProposalCmd)
cmd.AddCommand(getQueryCmd())
func addQueryFlags() {
queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with")
queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at")
queryCmd.PersistentFlags().BoolVarP(&flagProve,
"prove",
"",
false,
"whether or not to return a merkle proof of the query result")
}
func addCounterFlags() {
counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions")
}
func addKVStoreFlags() {
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
}
func addCommands() {
RootCmd.AddCommand(batchCmd)
RootCmd.AddCommand(consoleCmd)
RootCmd.AddCommand(echoCmd)
RootCmd.AddCommand(infoCmd)
RootCmd.AddCommand(setOptionCmd)
RootCmd.AddCommand(deliverTxCmd)
RootCmd.AddCommand(checkTxCmd)
RootCmd.AddCommand(commitCmd)
RootCmd.AddCommand(versionCmd)
RootCmd.AddCommand(testCmd)
addQueryFlags()
RootCmd.AddCommand(queryCmd)
// examples
cmd.AddCommand(getKVStoreCmd(logger))
addCounterFlags()
RootCmd.AddCommand(counterCmd)
addKVStoreFlags()
RootCmd.AddCommand(kvstoreCmd)
}
var batchCmd = &cobra.Command{
@@ -152,13 +176,13 @@ you'd like to run:
where example.file looks something like:
set_option serial on
check_tx 0x00
check_tx 0xff
finalize_block 0x00
commit
deliver_tx 0x00
check_tx 0x00
finalize_block 0x01 0x04 0xff
commit
deliver_tx 0x01
deliver_tx 0x04
info
`,
Args: cobra.ExactArgs(0),
@@ -174,7 +198,7 @@ This command opens an interactive console for running any of the other commands
without opening a new connection each time
`,
Args: cobra.ExactArgs(0),
ValidArgs: []string{"echo", "info", "query", "check_tx", "prepare_proposal", "process_proposal", "finalize_block", "commit"},
ValidArgs: []string{"echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"},
RunE: cmdConsole,
}
@@ -192,13 +216,20 @@ var infoCmd = &cobra.Command{
Args: cobra.ExactArgs(0),
RunE: cmdInfo,
}
var setOptionCmd = &cobra.Command{
Use: "set_option",
Short: "set an option on the application",
Long: "set an option on the application",
Args: cobra.ExactArgs(2),
RunE: cmdSetOption,
}
var finalizeBlockCmd = &cobra.Command{
Use: "finalize_block",
Short: "deliver a block of transactions to the application",
Long: "deliver a block of transactions to the application",
Args: cobra.MinimumNArgs(0),
RunE: cmdFinalizeBlock,
var deliverTxCmd = &cobra.Command{
Use: "deliver_tx",
Short: "deliver a new transaction to the application",
Long: "deliver a new transaction to the application",
Args: cobra.ExactArgs(1),
RunE: cmdDeliverTx,
}
var checkTxCmd = &cobra.Command{
@@ -223,59 +254,33 @@ var versionCmd = &cobra.Command{
Long: "print ABCI console version",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println(version.ABCIVersion)
fmt.Println(version.Version)
return nil
},
}
var prepareProposalCmd = &cobra.Command{
Use: "prepare_proposal",
Short: "prepare proposal",
Long: "prepare proposal",
Args: cobra.MinimumNArgs(0),
RunE: cmdPrepareProposal,
var queryCmd = &cobra.Command{
Use: "query",
Short: "query the application state",
Long: "query the application state",
Args: cobra.ExactArgs(1),
RunE: cmdQuery,
}
var processProposalCmd = &cobra.Command{
Use: "process_proposal",
Short: "process proposal",
Long: "process proposal",
Args: cobra.MinimumNArgs(0),
RunE: cmdProcessProposal,
var counterCmd = &cobra.Command{
Use: "counter",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: cmdCounter,
}
func getQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "query the application state",
Long: "query the application state",
Args: cobra.ExactArgs(1),
RunE: cmdQuery,
}
cmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with")
cmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at")
cmd.PersistentFlags().BoolVarP(&flagProve,
"prove",
"",
false,
"whether or not to return a merkle proof of the query result")
return cmd
}
func getKVStoreCmd(logger log.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "kvstore",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: makeKVStoreCmd(logger),
}
cmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
return cmd
var kvstoreCmd = &cobra.Command{
Use: "kvstore",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: cmdKVStore,
}
var testCmd = &cobra.Command{
@@ -316,57 +321,24 @@ func compose(fs []func() error) error {
}
func cmdTest(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
return compose(
[]func() error{
func() error { return servertest.InitChain(ctx, client) },
func() error { return servertest.Commit(ctx, client) },
func() error { return servertest.InitChain(client) },
func() error { return servertest.SetOption(client, "serial", "on") },
func() error { return servertest.Commit(client, nil) },
func() error { return servertest.DeliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil) },
func() error { return servertest.Commit(client, nil) },
func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeOK, nil) },
func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) },
func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil) },
func() error { return servertest.DeliverTx(client, []byte{0x01}, code.CodeTypeOK, nil) },
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x02}, code.CodeTypeOK, nil) },
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x03}, code.CodeTypeOK, nil) },
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x04}, code.CodeTypeOK, nil) },
func() error {
return servertest.FinalizeBlock(ctx, client, [][]byte{
[]byte("abc"),
}, []uint32{
code.CodeTypeBadNonce,
}, nil, nil)
},
func() error { return servertest.Commit(ctx, client) },
func() error {
return servertest.FinalizeBlock(ctx, client, [][]byte{
{0x00},
}, []uint32{
code.CodeTypeOK,
}, nil, []byte{0, 0, 0, 0, 0, 0, 0, 1})
},
func() error { return servertest.Commit(ctx, client) },
func() error {
return servertest.FinalizeBlock(ctx, client, [][]byte{
{0x00},
{0x01},
{0x00, 0x02},
{0x00, 0x03},
{0x00, 0x00, 0x04},
{0x00, 0x00, 0x06},
}, []uint32{
code.CodeTypeBadNonce,
code.CodeTypeOK,
code.CodeTypeOK,
code.CodeTypeOK,
code.CodeTypeOK,
code.CodeTypeBadNonce,
}, nil, []byte{0, 0, 0, 0, 0, 0, 0, 5})
},
func() error { return servertest.Commit(ctx, client) },
func() error {
return servertest.PrepareProposal(ctx, client, [][]byte{
{0x01},
}, []types.TxRecord_TxAction{
types.TxRecord_UNMODIFIED,
}, nil)
},
func() error {
return servertest.ProcessProposal(ctx, client, [][]byte{
{0x01},
}, types.ResponseProcessProposal_ACCEPT)
return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil)
},
func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) },
})
}
@@ -459,18 +431,16 @@ func muxOnCommands(cmd *cobra.Command, pArgs []string) error {
return cmdCheckTx(cmd, actualArgs)
case "commit":
return cmdCommit(cmd, actualArgs)
case "finalize_block":
return cmdFinalizeBlock(cmd, actualArgs)
case "deliver_tx":
return cmdDeliverTx(cmd, actualArgs)
case "echo":
return cmdEcho(cmd, actualArgs)
case "info":
return cmdInfo(cmd, actualArgs)
case "query":
return cmdQuery(cmd, actualArgs)
case "prepare_proposal":
return cmdPrepareProposal(cmd, actualArgs)
case "process_proposal":
return cmdProcessProposal(cmd, actualArgs)
case "set_option":
return cmdSetOption(cmd, actualArgs)
default:
return cmdUnimplemented(cmd, pArgs)
}
@@ -488,9 +458,13 @@ func cmdUnimplemented(cmd *cobra.Command, args []string) error {
})
fmt.Println("Available commands:")
for _, cmd := range cmd.Commands() {
fmt.Printf("%s: %s\n", cmd.Use, cmd.Short)
}
fmt.Printf("%s: %s\n", echoCmd.Use, echoCmd.Short)
fmt.Printf("%s: %s\n", infoCmd.Use, infoCmd.Short)
fmt.Printf("%s: %s\n", checkTxCmd.Use, checkTxCmd.Short)
fmt.Printf("%s: %s\n", deliverTxCmd.Use, deliverTxCmd.Short)
fmt.Printf("%s: %s\n", queryCmd.Use, queryCmd.Short)
fmt.Printf("%s: %s\n", commitCmd.Use, commitCmd.Short)
fmt.Printf("%s: %s\n", setOptionCmd.Use, setOptionCmd.Short)
fmt.Println("Use \"[command] --help\" for more information about a command.")
return nil
@@ -502,15 +476,13 @@ func cmdEcho(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
msg = args[0]
}
res, err := client.Echo(cmd.Context(), msg)
res, err := client.EchoSync(msg)
if err != nil {
return err
}
printResponse(cmd, args, response{
Data: []byte(res.Message),
})
return nil
}
@@ -520,7 +492,7 @@ func cmdInfo(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
version = args[0]
}
res, err := client.Info(cmd.Context(), &types.RequestInfo{Version: version})
res, err := client.InfoSync(types.RequestInfo{Version: version})
if err != nil {
return err
}
@@ -532,33 +504,48 @@ func cmdInfo(cmd *cobra.Command, args []string) error {
const codeBad uint32 = 10
// Append new txs to application
func cmdFinalizeBlock(cmd *cobra.Command, args []string) error {
txs := make([][]byte, len(args))
for i, arg := range args {
txBytes, err := stringOrHexToBytes(arg)
if err != nil {
return err
}
txs[i] = txBytes
// Set an option on the application
func cmdSetOption(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
printResponse(cmd, args, response{
Code: codeBad,
Log: "want at least arguments of the form: <key> <value>",
})
return nil
}
res, err := client.FinalizeBlock(cmd.Context(), &types.RequestFinalizeBlock{Txs: txs})
key, val := args[0], args[1]
_, err := client.SetOptionSync(types.RequestSetOption{Key: key, Value: val})
if err != nil {
return err
}
resps := make([]response, 0, len(res.TxResults)+1)
for _, tx := range res.TxResults {
resps = append(resps, response{
Code: tx.Code,
Data: tx.Data,
Info: tx.Info,
Log: tx.Log,
printResponse(cmd, args, response{Log: "OK (SetOption doesn't return anything.)"}) // NOTE: Nothing to show...
return nil
}
// Append a new tx to application
func cmdDeliverTx(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
printResponse(cmd, args, response{
Code: codeBad,
Log: "want the tx",
})
return nil
}
resps = append(resps, response{
Data: res.AppHash,
txBytes, err := stringOrHexToBytes(args[0])
if err != nil {
return err
}
res, err := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes})
if err != nil {
return err
}
printResponse(cmd, args, response{
Code: res.Code,
Data: res.Data,
Info: res.Info,
Log: res.Log,
})
printResponse(cmd, args, resps...)
return nil
}
@@ -575,24 +562,28 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
res, err := client.CheckTx(cmd.Context(), &types.RequestCheckTx{Tx: txBytes})
res, err := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes})
if err != nil {
return err
}
printResponse(cmd, args, response{
Code: res.Code,
Data: res.Data,
Info: res.Info,
Log: res.Log,
})
return nil
}
// Get application Merkle root hash
func cmdCommit(cmd *cobra.Command, args []string) error {
_, err := client.Commit(cmd.Context())
res, err := client.CommitSync()
if err != nil {
return err
}
printResponse(cmd, args, response{})
printResponse(cmd, args, response{
Data: res.Data,
})
return nil
}
@@ -611,7 +602,7 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
return err
}
resQuery, err := client.Query(cmd.Context(), &types.RequestQuery{
resQuery, err := client.QuerySync(types.RequestQuery{
Data: queryBytes,
Path: flagPath,
Height: int64(flagHeight),
@@ -625,164 +616,111 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
Info: resQuery.Info,
Log: resQuery.Log,
Query: &queryResponse{
Key: resQuery.Key,
Value: resQuery.Value,
Height: resQuery.Height,
ProofOps: resQuery.ProofOps,
Key: resQuery.Key,
Value: resQuery.Value,
Height: resQuery.Height,
Proof: resQuery.Proof,
},
})
return nil
}
func inTxArray(txByteArray [][]byte, tx []byte) bool {
for _, txTmp := range txByteArray {
if bytes.Equal(txTmp, tx) {
return true
}
func cmdCounter(cmd *cobra.Command, args []string) error {
app := counter.NewApplication(flagSerial)
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
}
return false
}
func cmdPrepareProposal(cmd *cobra.Command, args []string) error {
txsBytesArray := make([][]byte, len(args))
for i, arg := range args {
txBytes, err := stringOrHexToBytes(arg)
if err != nil {
return err
}
txsBytesArray[i] = txBytes
}
res, err := client.PrepareProposal(cmd.Context(), &types.RequestPrepareProposal{
Txs: txsBytesArray,
// kvstore has to have this parameter in order not to reject a tx as the default value is 0
MaxTxBytes: 65536,
})
// Start the listener
srv, err := server.NewServer(flagAddress, flagAbci, app)
if err != nil {
return err
}
resps := make([]response, 0, len(res.TxResults)+1)
for _, tx := range res.TxRecords {
existingTx := inTxArray(txsBytesArray, tx.Tx)
if tx.Action == types.TxRecord_UNKNOWN ||
(existingTx && tx.Action == types.TxRecord_ADDED) ||
(!existingTx && (tx.Action == types.TxRecord_UNMODIFIED || tx.Action == types.TxRecord_REMOVED)) {
resps = append(resps, response{
Code: codeBad,
Log: "Failed. Tx: " + string(tx.GetTx()) + " action: " + tx.Action.String(),
})
} else {
resps = append(resps, response{
Code: code.CodeTypeOK,
Log: "Succeeded. Tx: " + string(tx.Tx) + " action: " + tx.Action.String(),
})
}
}
printResponse(cmd, args, resps...)
return nil
}
func cmdProcessProposal(cmd *cobra.Command, args []string) error {
txsBytesArray := make([][]byte, len(args))
for i, arg := range args {
txBytes, err := stringOrHexToBytes(arg)
if err != nil {
return err
}
txsBytesArray[i] = txBytes
}
res, err := client.ProcessProposal(cmd.Context(), &types.RequestProcessProposal{
Txs: txsBytesArray,
})
if err != nil {
srv.SetLogger(logger.With("module", "abci-server"))
if err := srv.Start(); err != nil {
return err
}
printResponse(cmd, args, response{
Status: int32(res.Status),
// Stop upon receiving SIGTERM or CTRL-C.
tmos.TrapSignal(logger, func() {
// Cleanup
srv.Stop()
})
return nil
// Run forever.
select {}
}
func makeKVStoreCmd(logger log.Logger) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = kvstore.NewApplication()
} else {
app = kvstore.NewPersistentKVStoreApplication(logger, flagPersist)
}
func cmdKVStore(cmd *cobra.Command, args []string) error {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Start the listener
srv, err := server.NewServer(logger.With("module", "abci-server"), flagAddress, flagAbci, app)
if err != nil {
return err
}
ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGTERM)
defer cancel()
if err := srv.Start(ctx); err != nil {
return err
}
// Run forever.
<-ctx.Done()
return nil
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = kvstore.NewApplication()
} else {
app = kvstore.NewPersistentKVStoreApplication(flagPersist)
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
}
// Start the listener
srv, err := server.NewServer(flagAddress, flagAbci, app)
if err != nil {
return err
}
srv.SetLogger(logger.With("module", "abci-server"))
if err := srv.Start(); err != nil {
return err
}
// Stop upon receiving SIGTERM or CTRL-C.
tmos.TrapSignal(logger, func() {
// Cleanup
srv.Stop()
})
// Run forever.
select {}
}
//--------------------------------------------------------------------------------
func printResponse(cmd *cobra.Command, args []string, rsps ...response) {
func printResponse(cmd *cobra.Command, args []string, rsp response) {
if flagVerbose {
fmt.Println(">", cmd.Use, strings.Join(args, " "))
}
for _, rsp := range rsps {
// Always print the status code.
if rsp.Code == types.CodeTypeOK {
fmt.Printf("-> code: OK\n")
} else {
fmt.Printf("-> code: %d\n", rsp.Code)
}
// Always print the status code.
if rsp.Code == types.CodeTypeOK {
fmt.Printf("-> code: OK\n")
} else {
fmt.Printf("-> code: %d\n", rsp.Code)
if len(rsp.Data) != 0 {
// Do no print this line when using the finalize_block command
// because the string comes out as gibberish
if cmd.Use != "finalize_block" {
fmt.Printf("-> data: %s\n", rsp.Data)
}
fmt.Printf("-> data.hex: 0x%X\n", rsp.Data)
}
if rsp.Log != "" {
fmt.Printf("-> log: %s\n", rsp.Log)
}
if cmd.Use == "process_proposal" {
fmt.Printf("-> status: %s\n", types.ResponseProcessProposal_ProposalStatus_name[rsp.Status])
}
}
if rsp.Query != nil {
fmt.Printf("-> height: %d\n", rsp.Query.Height)
if rsp.Query.Key != nil {
fmt.Printf("-> key: %s\n", rsp.Query.Key)
fmt.Printf("-> key.hex: %X\n", rsp.Query.Key)
}
if rsp.Query.Value != nil {
fmt.Printf("-> value: %s\n", rsp.Query.Value)
fmt.Printf("-> value.hex: %X\n", rsp.Query.Value)
}
if rsp.Query.ProofOps != nil {
fmt.Printf("-> proof: %#v\n", rsp.Query.ProofOps)
}
if len(rsp.Data) != 0 {
// Do no print this line when using the commit command
// because the string comes out as gibberish
if cmd.Use != "commit" {
fmt.Printf("-> data: %s\n", rsp.Data)
}
fmt.Printf("-> data.hex: 0x%X\n", rsp.Data)
}
if rsp.Log != "" {
fmt.Printf("-> log: %s\n", rsp.Log)
}
if rsp.Query != nil {
fmt.Printf("-> height: %d\n", rsp.Query.Height)
if rsp.Query.Key != nil {
fmt.Printf("-> key: %s\n", rsp.Query.Key)
fmt.Printf("-> key.hex: %X\n", rsp.Query.Key)
}
if rsp.Query.Value != nil {
fmt.Printf("-> value: %s\n", rsp.Query.Value)
fmt.Printf("-> value.hex: %X\n", rsp.Query.Value)
}
if rsp.Query.Proof != nil {
fmt.Printf("-> proof: %#v\n", rsp.Query.Proof)
}
}
}

View File

@@ -0,0 +1,103 @@
package counter
import (
"encoding/binary"
"fmt"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
)
type Application struct {
types.BaseApplication
hashCount int
txCount int
serial bool
}
func NewApplication(serial bool) *Application {
return &Application{serial: serial}
}
func (app *Application) Info(req types.RequestInfo) types.ResponseInfo {
return types.ResponseInfo{Data: fmt.Sprintf("{\"hashes\":%v,\"txs\":%v}", app.hashCount, app.txCount)}
}
func (app *Application) SetOption(req types.RequestSetOption) types.ResponseSetOption {
key, value := req.Key, req.Value
if key == "serial" && value == "on" {
app.serial = true
} else {
/*
TODO Panic and have the ABCI server pass an exception.
The client can call SetOptionSync() and get an `error`.
return types.ResponseSetOption{
Error: fmt.Sprintf("Unknown key (%s) or value (%s)", key, value),
}
*/
return types.ResponseSetOption{}
}
return types.ResponseSetOption{}
}
func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
if app.serial {
if len(req.Tx) > 8 {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))}
}
tx8 := make([]byte, 8)
copy(tx8[len(tx8)-len(req.Tx):], req.Tx)
txValue := binary.BigEndian.Uint64(tx8)
if txValue != uint64(app.txCount) {
return types.ResponseDeliverTx{
Code: code.CodeTypeBadNonce,
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
}
}
app.txCount++
return types.ResponseDeliverTx{Code: code.CodeTypeOK}
}
func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
if app.serial {
if len(req.Tx) > 8 {
return types.ResponseCheckTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))}
}
tx8 := make([]byte, 8)
copy(tx8[len(tx8)-len(req.Tx):], req.Tx)
txValue := binary.BigEndian.Uint64(tx8)
if txValue < uint64(app.txCount) {
return types.ResponseCheckTx{
Code: code.CodeTypeBadNonce,
Log: fmt.Sprintf("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)}
}
}
return types.ResponseCheckTx{Code: code.CodeTypeOK}
}
func (app *Application) Commit() (resp types.ResponseCommit) {
app.hashCount++
if app.txCount == 0 {
return types.ResponseCommit{}
}
hash := make([]byte, 8)
binary.BigEndian.PutUint64(hash, uint64(app.txCount))
return types.ResponseCommit{Data: hash}
}
func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery {
switch reqQuery.Path {
case "hash":
return types.ResponseQuery{Value: []byte(fmt.Sprintf("%v", app.hashCount))}
case "tx":
return types.ResponseQuery{Value: []byte(fmt.Sprintf("%v", app.txCount))}
default:
return types.ResponseQuery{Log: fmt.Sprintf("Invalid query path. Expected hash or tx, got %v", reqQuery.Path)}
}
}

View File

@@ -1,97 +1,107 @@
package example
import (
"context"
"fmt"
"math/rand"
"net"
"os"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"golang.org/x/net/context"
"github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
abciclient "github.com/tendermint/tendermint/abci/client"
abcicli "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/example/kvstore"
abciserver "github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/abci/types"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func TestKVStore(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewNopLogger()
t.Log("### Testing KVStore")
testBulk(ctx, t, logger, kvstore.NewApplication())
fmt.Println("### Testing KVStore")
testStream(t, kvstore.NewApplication())
}
func TestBaseApp(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewNopLogger()
t.Log("### Testing BaseApp")
testBulk(ctx, t, logger, types.NewBaseApplication())
fmt.Println("### Testing BaseApp")
testStream(t, types.NewBaseApplication())
}
func TestGRPC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewNopLogger()
t.Log("### Testing GRPC")
testGRPCSync(ctx, t, logger, types.NewBaseApplication())
fmt.Println("### Testing GRPC")
testGRPCSync(t, types.NewGRPCApplication(types.NewBaseApplication()))
}
func testBulk(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) {
t.Helper()
func testStream(t *testing.T, app types.Application) {
numDeliverTxs := 20000
const numDeliverTxs = 700000
socketFile := fmt.Sprintf("test-%08x.sock", rand.Int31n(1<<30))
defer os.Remove(socketFile)
socket := fmt.Sprintf("unix://%v", socketFile)
// Start the listener
server := abciserver.NewSocketServer(logger.With("module", "abci-server"), socket, app)
t.Cleanup(server.Wait)
err := server.Start(ctx)
require.NoError(t, err)
server := abciserver.NewSocketServer("unix://test.sock", app)
server.SetLogger(log.TestingLogger().With("module", "abci-server"))
if err := server.Start(); err != nil {
require.NoError(t, err, "Error starting socket server")
}
defer server.Stop()
// Connect to the socket
client := abciclient.NewSocketClient(logger.With("module", "abci-client"), socket, false)
t.Cleanup(client.Wait)
err = client.Start(ctx)
require.NoError(t, err)
// Construct request
rfb := &types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)}
for counter := 0; counter < numDeliverTxs; counter++ {
rfb.Txs[counter] = []byte("test")
client := abcicli.NewSocketClient("unix://test.sock", false)
client.SetLogger(log.TestingLogger().With("module", "abci-client"))
if err := client.Start(); err != nil {
t.Fatalf("Error starting socket client: %v", err.Error())
}
// Send bulk request
res, err := client.FinalizeBlock(ctx, rfb)
require.NoError(t, err)
require.Equal(t, numDeliverTxs, len(res.TxResults), "Number of txs doesn't match")
for _, tx := range res.TxResults {
require.Equal(t, tx.Code, code.CodeTypeOK, "Tx failed")
defer client.Stop()
done := make(chan struct{})
counter := 0
client.SetResponseCallback(func(req *types.Request, res *types.Response) {
// Process response
switch r := res.Value.(type) {
case *types.Response_DeliverTx:
counter++
if r.DeliverTx.Code != code.CodeTypeOK {
t.Error("DeliverTx failed with ret_code", r.DeliverTx.Code)
}
if counter > numDeliverTxs {
t.Fatalf("Too many DeliverTx responses. Got %d, expected %d", counter, numDeliverTxs)
}
if counter == numDeliverTxs {
go func() {
time.Sleep(time.Second * 1) // Wait for a bit to allow counter overflow
close(done)
}()
return
}
case *types.Response_Flush:
// ignore
default:
t.Error("Unexpected response type", reflect.TypeOf(res.Value))
}
})
// Write requests
for counter := 0; counter < numDeliverTxs; counter++ {
// Send request
reqRes := client.DeliverTxAsync(types.RequestDeliverTx{Tx: []byte("test")})
_ = reqRes
// check err ?
// Sometimes send flush messages
if counter%123 == 0 {
client.FlushAsync()
// check err ?
}
}
// Send final flush message
err = client.Flush(ctx)
require.NoError(t, err)
client.FlushAsync()
<-done
}
//-------------------------
@@ -101,45 +111,46 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) {
return tmnet.Connect(addr)
}
func testGRPCSync(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) {
t.Helper()
numDeliverTxs := 680000
socketFile := fmt.Sprintf("/tmp/test-%08x.sock", rand.Int31n(1<<30))
defer os.Remove(socketFile)
socket := fmt.Sprintf("unix://%v", socketFile)
func testGRPCSync(t *testing.T, app types.ABCIApplicationServer) {
numDeliverTxs := 2000
// Start the listener
server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, app)
require.NoError(t, server.Start(ctx))
t.Cleanup(server.Wait)
server := abciserver.NewGRPCServer("unix://test.sock", app)
server.SetLogger(log.TestingLogger().With("module", "abci-server"))
if err := server.Start(); err != nil {
t.Fatalf("Error starting GRPC server: %v", err.Error())
}
defer server.Stop()
// Connect to the socket
conn, err := grpc.Dial(socket,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialerFunc),
)
require.NoError(t, err, "Error dialing GRPC server")
t.Cleanup(func() {
if err := conn.Close(); err != nil {
t.Error(err)
}
})
conn, err := grpc.Dial("unix://test.sock", grpc.WithInsecure(), grpc.WithContextDialer(dialerFunc))
if err != nil {
t.Fatalf("Error dialing GRPC server: %v", err.Error())
}
defer conn.Close()
client := types.NewABCIApplicationClient(conn)
// Construct request
rfb := types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)}
// Write requests
for counter := 0; counter < numDeliverTxs; counter++ {
rfb.Txs[counter] = []byte("test")
}
// Send request
response, err := client.DeliverTx(context.Background(), &types.RequestDeliverTx{Tx: []byte("test")})
if err != nil {
t.Fatalf("Error in GRPC DeliverTx: %v", err.Error())
}
counter++
if response.Code != code.CodeTypeOK {
t.Error("DeliverTx failed with ret_code", response.Code)
}
if counter > numDeliverTxs {
t.Fatal("Too many DeliverTx responses")
}
t.Log("response", counter)
if counter == numDeliverTxs {
go func() {
time.Sleep(time.Second * 1) // Wait for a bit to allow counter overflow
}()
}
// Send request
response, err := client.FinalizeBlock(ctx, &rfb)
require.NoError(t, err, "Error in GRPC FinalizeBlock")
require.Equal(t, numDeliverTxs, len(response.TxResults), "Number of txs returned via GRPC doesn't match")
for _, tx := range response.TxResults {
require.Equal(t, tx.Code, code.CodeTypeOK, "Tx failed")
}
}

1
abci/example/js/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1 @@
This example has been moved here: https://github.com/tendermint/js-abci/tree/master/example

View File

@@ -4,7 +4,7 @@ There are two app's here: the KVStoreApplication and the PersistentKVStoreApplic
## KVStoreApplication
The KVStoreApplication is a simple merkle key-value store.
The KVStoreApplication is a simple merkle key-value store.
Transactions of the form `key=value` are stored as key-value pairs in the tree.
Transactions without an `=` sign set the value to the key.
The app has no replay protection (other than what the mempool provides).
@@ -12,7 +12,7 @@ The app has no replay protection (other than what the mempool provides).
## PersistentKVStoreApplication
The PersistentKVStoreApplication wraps the KVStoreApplication
and provides three additional features:
and provides two additional features:
1) persistence of state across app restarts (using Tendermint's ABCI-Handshake mechanism)
2) validator set changes
@@ -21,10 +21,11 @@ The state is persisted in leveldb along with the last block committed,
and the Handshake allows any necessary blocks to be replayed.
Validator set changes are effected using the following transaction format:
```md
```
"val:pubkey1!power1,pubkey2!power2,pubkey3!power3"
```
where `pubkeyN` is a base64-encoded 32-byte ed25519 key and `powerN` is a new voting power for the validator with `pubkeyN` (possibly a new one).
To remove a validator from the validator set, set power to `0`.
There is no sybil protection against new validators joining.
There is no sybil protection against new validators joining.

View File

@@ -1,9 +1,6 @@
package kvstore
import (
"context"
mrand "math/rand"
"github.com/tendermint/tendermint/abci/types"
tmrand "github.com/tendermint/tendermint/libs/rand"
)
@@ -12,9 +9,8 @@ import (
// from the input value
func RandVal(i int) types.ValidatorUpdate {
pubkey := tmrand.Bytes(32)
// Random value between [0, 2^16 - 1]
power := mrand.Uint32() & (1<<16 - 1) // nolint:gosec // G404: Use of weak random number generator
v := types.UpdateValidator(pubkey, int64(power), "")
power := tmrand.Uint16() + 1
v := types.Ed25519ValidatorUpdate(pubkey, int64(power))
return v
}
@@ -33,9 +29,8 @@ func RandVals(cnt int) []types.ValidatorUpdate {
// InitKVStore initializes the kvstore app with some data,
// which allows tests to pass and is fine as long as you
// don't make any tx that modify the validator state
func InitKVStore(ctx context.Context, app *PersistentKVStoreApplication) error {
_, err := app.InitChain(ctx, &types.RequestInitChain{
func InitKVStore(app *PersistentKVStoreApplication) {
app.InitChain(types.RequestInitChain{
Validators: RandVals(1),
})
return err
}

View File

@@ -2,22 +2,15 @@ package kvstore
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/encoding"
"github.com/tendermint/tendermint/libs/log"
cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto"
"github.com/tendermint/tendermint/libs/kv"
"github.com/tendermint/tendermint/version"
)
@@ -25,7 +18,7 @@ var (
stateKey = []byte("stateKey")
kvPairPrefixKey = []byte("kvPairKey:")
ProtocolVersion uint64 = 0x1
ProtocolVersion version.Protocol = 0x1
)
type State struct {
@@ -57,10 +50,7 @@ func saveState(state State) {
if err != nil {
panic(err)
}
err = state.db.Set(stateKey, stateBytes)
if err != nil {
panic(err)
}
state.db.Set(stateKey, stateBytes)
}
func prefixKey(key []byte) []byte {
@@ -73,395 +63,103 @@ var _ types.Application = (*Application)(nil)
type Application struct {
types.BaseApplication
mu sync.Mutex
state State
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
logger log.Logger
// validator set
ValUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]cryptoproto.PublicKey
}
func NewApplication() *Application {
return &Application{
logger: log.NewNopLogger(),
state: loadState(dbm.NewMemDB()),
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
}
state := loadState(dbm.NewMemDB())
return &Application{state: state}
}
func (app *Application) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
app.mu.Lock()
defer app.mu.Unlock()
for _, v := range req.Validators {
r := app.updateValidator(v)
if r.IsErr() {
app.logger.Error("error updating validators", "r", r)
panic("problem updating validators")
}
}
return &types.ResponseInitChain{}, nil
}
func (app *Application) Info(_ context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) {
app.mu.Lock()
defer app.mu.Unlock()
return &types.ResponseInfo{
func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
return types.ResponseInfo{
Data: fmt.Sprintf("{\"size\":%v}", app.state.Size),
Version: version.ABCIVersion,
AppVersion: ProtocolVersion,
AppVersion: ProtocolVersion.Uint64(),
LastBlockHeight: app.state.Height,
LastBlockAppHash: app.state.AppHash,
}, nil
}
}
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
func (app *Application) handleTx(tx []byte) *types.ExecTxResult {
// if it starts with "val:", update the validator set
// format is "val:pubkey!power"
if isValidatorTx(tx) {
// update validators in the merkle tree
// and in app.ValUpdates
return app.execValidatorTx(tx)
}
if isPrepareTx(tx) {
return app.execPrepareTx(tx)
}
var key, value string
parts := bytes.Split(tx, []byte("="))
// tx is either "key=value" or just arbitrary bytes
func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
var key, value []byte
parts := bytes.Split(req.Tx, []byte("="))
if len(parts) == 2 {
key, value = string(parts[0]), string(parts[1])
key, value = parts[0], parts[1]
} else {
key, value = string(tx), string(tx)
key, value = req.Tx, req.Tx
}
err := app.state.db.Set(prefixKey([]byte(key)), []byte(value))
if err != nil {
panic(err)
}
app.state.db.Set(prefixKey(key), value)
app.state.Size++
events := []types.Event{
{
Type: "app",
Attributes: []types.EventAttribute{
{Key: "creator", Value: "Cosmoshi Netowoko", Index: true},
{Key: "key", Value: key, Index: true},
{Key: "index_key", Value: "index is working", Index: true},
{Key: "noindex_key", Value: "index is working", Index: false},
Attributes: []kv.Pair{
{Key: []byte("creator"), Value: []byte("Cosmoshi Netowoko")},
{Key: []byte("key"), Value: key},
},
},
}
return &types.ExecTxResult{Code: code.CodeTypeOK, Events: events}
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}
}
func (app *Application) Close() error {
app.mu.Lock()
defer app.mu.Unlock()
return app.state.db.Close()
func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
}
func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
app.mu.Lock()
defer app.mu.Unlock()
// reset valset changes
app.ValUpdates = make([]types.ValidatorUpdate, 0)
// Punish validators who committed equivocation.
for _, ev := range req.Misbehavior {
if ev.Type == types.MisbehaviorType_DUPLICATE_VOTE {
addr := string(ev.Validator.Address)
if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok {
app.updateValidator(types.ValidatorUpdate{
PubKey: pubKey,
Power: ev.Validator.Power - 1,
})
app.logger.Info("Decreased val power by 1 because of the equivocation",
"val", addr)
} else {
panic(fmt.Errorf("wanted to punish val %q but can't find it", addr))
}
}
}
respTxs := make([]*types.ExecTxResult, len(req.Txs))
for i, tx := range req.Txs {
respTxs[i] = app.handleTx(tx)
}
func (app *Application) Commit() types.ResponseCommit {
// Using a memdb - just return the big endian size of the db
appHash := make([]byte, 8)
binary.PutVarint(appHash, app.state.Size)
app.state.AppHash = appHash
app.state.Height++
return &types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates, AppHash: appHash}, nil
}
func (*Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
return &types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil
}
func (app *Application) Commit(_ context.Context) (*types.ResponseCommit, error) {
app.mu.Lock()
defer app.mu.Unlock()
saveState(app.state)
resp := &types.ResponseCommit{}
resp := types.ResponseCommit{Data: appHash}
if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks {
resp.RetainHeight = app.state.Height - app.RetainBlocks + 1
}
return resp, nil
return resp
}
// Returns an associated value or nil if missing.
func (app *Application) Query(_ context.Context, reqQuery *types.RequestQuery) (*types.ResponseQuery, error) {
app.mu.Lock()
defer app.mu.Unlock()
if reqQuery.Path == "/val" {
key := []byte("val:" + string(reqQuery.Data))
value, err := app.state.db.Get(key)
if err != nil {
panic(err)
}
return &types.ResponseQuery{
Key: reqQuery.Data,
Value: value,
}, nil
}
func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
if reqQuery.Prove {
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
resQuery := types.ResponseQuery{
Index: -1,
Key: reqQuery.Data,
Value: value,
Height: app.state.Height,
}
if value == nil {
resQuery.Log = "does not exist"
} else {
resQuery.Log = "exists"
}
resQuery.Index = -1 // TODO make Proof return index
resQuery.Key = reqQuery.Data
resQuery.Value = value
resQuery.Height = app.state.Height
return &resQuery, nil
return
}
resQuery.Key = reqQuery.Data
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
resQuery := types.ResponseQuery{
Key: reqQuery.Data,
Value: value,
Height: app.state.Height,
}
if value == nil {
resQuery.Log = "does not exist"
} else {
resQuery.Log = "exists"
}
resQuery.Value = value
resQuery.Height = app.state.Height
return &resQuery, nil
}
func (app *Application) PrepareProposal(_ context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
app.mu.Lock()
defer app.mu.Unlock()
return &types.ResponsePrepareProposal{
TxRecords: app.substPrepareTx(req.Txs, req.MaxTxBytes),
}, nil
}
func (*Application) ProcessProposal(_ context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
for _, tx := range req.Txs {
if len(tx) == 0 || isPrepareTx(tx) {
return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil
}
}
return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}, nil
}
//---------------------------------------------
// update validators
func (app *Application) Validators() (validators []types.ValidatorUpdate) {
app.mu.Lock()
defer app.mu.Unlock()
itr, err := app.state.db.Iterator(nil, nil)
if err != nil {
panic(err)
}
for ; itr.Valid(); itr.Next() {
if isValidatorTx(itr.Key()) {
validator := new(types.ValidatorUpdate)
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
if err != nil {
panic(err)
}
validators = append(validators, *validator)
}
}
if err = itr.Error(); err != nil {
panic(err)
}
return
}
func MakeValSetChangeTx(pubkey cryptoproto.PublicKey, power int64) []byte {
pk, err := encoding.PubKeyFromProto(pubkey)
if err != nil {
panic(err)
}
pubStr := base64.StdEncoding.EncodeToString(pk.Bytes())
return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
}
func isValidatorTx(tx []byte) bool {
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
}
// format is "val:pubkey!power"
// pubkey is a base64-encoded 32-byte ed25519 key
func (app *Application) execValidatorTx(tx []byte) *types.ExecTxResult {
tx = tx[len(ValidatorSetChangePrefix):]
// get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "!")
if len(pubKeyAndPower) != 2 {
return &types.ExecTxResult{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
}
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
if err != nil {
return &types.ExecTxResult{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return &types.ExecTxResult{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
}
// update
return app.updateValidator(types.UpdateValidator(pubkey, power, ""))
}
// add, update, or remove a validator
func (app *Application) updateValidator(v types.ValidatorUpdate) *types.ExecTxResult {
pubkey, err := encoding.PubKeyFromProto(v.PubKey)
if err != nil {
panic(fmt.Errorf("can't decode public key: %w", err))
}
key := []byte("val:" + string(pubkey.Bytes()))
if v.Power == 0 {
// remove validator
hasKey, err := app.state.db.Has(key)
if err != nil {
panic(err)
}
if !hasKey {
pubStr := base64.StdEncoding.EncodeToString(pubkey.Bytes())
return &types.ExecTxResult{
Code: code.CodeTypeUnauthorized,
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
}
if err = app.state.db.Delete(key); err != nil {
panic(err)
}
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
if err := types.WriteMessage(&v, value); err != nil {
return &types.ExecTxResult{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("error encoding validator: %v", err)}
}
if err = app.state.db.Set(key, value.Bytes()); err != nil {
panic(err)
}
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
// we only update the changes array if we successfully updated the tree
app.ValUpdates = append(app.ValUpdates, v)
return &types.ExecTxResult{Code: code.CodeTypeOK}
}
// -----------------------------
// prepare proposal machinery
const PreparePrefix = "prepare"
func isPrepareTx(tx []byte) bool {
return bytes.HasPrefix(tx, []byte(PreparePrefix))
}
// execPrepareTx is noop. tx data is considered as placeholder
// and is substitute at the PrepareProposal.
func (app *Application) execPrepareTx(tx []byte) *types.ExecTxResult {
// noop
return &types.ExecTxResult{}
}
// substPrepareTx substitutes all the transactions prefixed with 'prepare' in the
// proposal for transactions with the prefix stripped.
// It marks all of the original transactions as 'REMOVED' so that
// Tendermint will remove them from its mempool.
func (app *Application) substPrepareTx(blockData [][]byte, maxTxBytes int64) []*types.TxRecord {
trs := make([]*types.TxRecord, 0, len(blockData))
var removed []*types.TxRecord
var totalBytes int64
for _, tx := range blockData {
txMod := tx
action := types.TxRecord_UNMODIFIED
if isPrepareTx(tx) {
removed = append(removed, &types.TxRecord{
Tx: tx,
Action: types.TxRecord_REMOVED,
})
txMod = bytes.TrimPrefix(tx, []byte(PreparePrefix))
action = types.TxRecord_ADDED
}
totalBytes += int64(len(txMod))
if totalBytes > maxTxBytes {
break
}
trs = append(trs, &types.TxRecord{
Tx: txMod,
Action: action,
})
}
return append(trs, removed...)
return resQuery
}

View File

@@ -1,18 +1,18 @@
package kvstore
import (
"context"
"bytes"
"fmt"
"io/ioutil"
"sort"
"testing"
"github.com/fortytw2/leaktest"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
abciclient "github.com/tendermint/tendermint/abci/client"
abcicli "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/code"
abciserver "github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/abci/types"
@@ -23,43 +23,35 @@ const (
testValue = "def"
)
func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx []byte, key, value string) {
req := &types.RequestFinalizeBlock{Txs: [][]byte{tx}}
ar, err := app.FinalizeBlock(ctx, req)
require.NoError(t, err)
require.Equal(t, 1, len(ar.TxResults))
require.False(t, ar.TxResults[0].IsErr())
func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) {
req := types.RequestDeliverTx{Tx: tx}
ar := app.DeliverTx(req)
require.False(t, ar.IsErr(), ar)
// repeating tx doesn't raise error
ar, err = app.FinalizeBlock(ctx, req)
require.NoError(t, err)
require.Equal(t, 1, len(ar.TxResults))
require.False(t, ar.TxResults[0].IsErr())
ar = app.DeliverTx(req)
require.False(t, ar.IsErr(), ar)
// commit
_, err = app.Commit(ctx)
require.NoError(t, err)
app.Commit()
info, err := app.Info(ctx, &types.RequestInfo{})
require.NoError(t, err)
info := app.Info(types.RequestInfo{})
require.NotZero(t, info.LastBlockHeight)
// make sure query is fine
resQuery, err := app.Query(ctx, &types.RequestQuery{
resQuery := app.Query(types.RequestQuery{
Path: "/store",
Data: []byte(key),
})
require.NoError(t, err)
require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, key, string(resQuery.Key))
require.Equal(t, value, string(resQuery.Value))
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
// make sure proof is fine
resQuery, err = app.Query(ctx, &types.RequestQuery{
resQuery = app.Query(types.RequestQuery{
Path: "/store",
Data: []byte(key),
Prove: true,
})
require.NoError(t, err)
require.EqualValues(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, key, string(resQuery.Key))
require.Equal(t, value, string(resQuery.Value))
@@ -67,55 +59,43 @@ func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx []
}
func TestKVStoreKV(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewApplication()
key := testKey
value := key
tx := []byte(key)
testKVStore(ctx, t, kvstore, tx, key, value)
testKVStore(t, kvstore, tx, key, value)
value = testValue
tx = []byte(key + "=" + value)
testKVStore(ctx, t, kvstore, tx, key, value)
testKVStore(t, kvstore, tx, key, value)
}
func TestPersistentKVStoreKV(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dir := t.TempDir()
logger := log.NewNopLogger()
kvstore := NewPersistentKVStoreApplication(logger, dir)
key := testKey
value := key
tx := []byte(key)
testKVStore(ctx, t, kvstore, tx, key, value)
value = testValue
tx = []byte(key + "=" + value)
testKVStore(ctx, t, kvstore, tx, key, value)
}
func TestPersistentKVStoreInfo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dir := t.TempDir()
logger := log.NewNopLogger()
kvstore := NewPersistentKVStoreApplication(logger, dir)
if err := InitKVStore(ctx, kvstore); err != nil {
t.Fatal(err)
}
height := int64(0)
resInfo, err := kvstore.Info(ctx, &types.RequestInfo{})
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO
if err != nil {
t.Fatal(err)
}
kvstore := NewPersistentKVStoreApplication(dir)
key := testKey
value := key
tx := []byte(key)
testKVStore(t, kvstore, tx, key, value)
value = testValue
tx = []byte(key + "=" + value)
testKVStore(t, kvstore, tx, key, value)
}
func TestPersistentKVStoreInfo(t *testing.T) {
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO
if err != nil {
t.Fatal(err)
}
kvstore := NewPersistentKVStoreApplication(dir)
InitKVStore(kvstore)
height := int64(0)
resInfo := kvstore.Info(types.RequestInfo{})
if resInfo.LastBlockHeight != height {
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
}
@@ -123,19 +103,14 @@ func TestPersistentKVStoreInfo(t *testing.T) {
// make and apply block
height = int64(1)
hash := []byte("foo")
if _, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Hash: hash, Height: height}); err != nil {
t.Fatal(err)
header := types.Header{
Height: height,
}
kvstore.BeginBlock(types.RequestBeginBlock{Hash: hash, Header: header})
kvstore.EndBlock(types.RequestEndBlock{Height: header.Height})
kvstore.Commit()
if _, err := kvstore.Commit(ctx); err != nil {
t.Fatal(err)
}
resInfo, err = kvstore.Info(ctx, &types.RequestInfo{})
if err != nil {
t.Fatal(err)
}
resInfo = kvstore.Info(types.RequestInfo{})
if resInfo.LastBlockHeight != height {
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
}
@@ -144,22 +119,20 @@ func TestPersistentKVStoreInfo(t *testing.T) {
// add a validator, remove a validator, update a validator
func TestValUpdates(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewApplication()
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO
if err != nil {
t.Fatal(err)
}
kvstore := NewPersistentKVStoreApplication(dir)
// init with some validators
total := 10
nInit := 5
vals := RandVals(total)
// initialize with the first nInit
_, err := kvstore.InitChain(ctx, &types.RequestInitChain{
// iniitalize with the first nInit
kvstore.InitChain(types.RequestInitChain{
Validators: vals[:nInit],
})
if err != nil {
t.Fatal(err)
}
vals1, vals2 := vals[:nInit], kvstore.Validators()
valsEqual(t, vals1, vals2)
@@ -172,7 +145,7 @@ func TestValUpdates(t *testing.T) {
tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power)
tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power)
makeApplyBlock(ctx, t, kvstore, 1, diff, tx1, tx2)
makeApplyBlock(t, kvstore, 1, diff, tx1, tx2)
vals1, vals2 = vals[:nInit+2], kvstore.Validators()
valsEqual(t, vals1, vals2)
@@ -187,7 +160,7 @@ func TestValUpdates(t *testing.T) {
tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power)
tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power)
makeApplyBlock(ctx, t, kvstore, 2, diff, tx1, tx2, tx3)
makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3)
vals1 = append(vals[:nInit-2], vals[nInit+1]) // nolint: gocritic
vals2 = kvstore.Validators()
@@ -203,7 +176,7 @@ func TestValUpdates(t *testing.T) {
diff = []types.ValidatorUpdate{v1}
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
makeApplyBlock(ctx, t, kvstore, 3, diff, tx1)
makeApplyBlock(t, kvstore, 3, diff, tx1)
vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...)
vals2 = kvstore.Validators()
@@ -211,31 +184,34 @@ func TestValUpdates(t *testing.T) {
}
func makeApplyBlock(ctx context.Context, t *testing.T, kvstore types.Application, heightInt int, diff []types.ValidatorUpdate, txs ...[]byte) {
func makeApplyBlock(
t *testing.T,
kvstore types.Application,
heightInt int,
diff []types.ValidatorUpdate,
txs ...[]byte) {
// make and apply block
height := int64(heightInt)
hash := []byte("foo")
resFinalizeBlock, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{
Hash: hash,
header := types.Header{
Height: height,
Txs: txs,
})
if err != nil {
t.Fatal(err)
}
_, err = kvstore.Commit(ctx)
if err != nil {
t.Fatal(err)
kvstore.BeginBlock(types.RequestBeginBlock{Hash: hash, Header: header})
for _, tx := range txs {
if r := kvstore.DeliverTx(types.RequestDeliverTx{Tx: tx}); r.IsErr() {
t.Fatal(r)
}
}
resEndBlock := kvstore.EndBlock(types.RequestEndBlock{Height: header.Height})
kvstore.Commit()
valsEqual(t, diff, resFinalizeBlock.ValidatorUpdates)
valsEqual(t, diff, resEndBlock.ValidatorUpdates)
}
// order doesn't matter
func valsEqual(t *testing.T, vals1, vals2 []types.ValidatorUpdate) {
t.Helper()
if len(vals1) != len(vals2) {
t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1))
}
@@ -243,148 +219,122 @@ func valsEqual(t *testing.T, vals1, vals2 []types.ValidatorUpdate) {
sort.Sort(types.ValidatorUpdates(vals2))
for i, v1 := range vals1 {
v2 := vals2[i]
if !v1.PubKey.Equal(v2.PubKey) ||
if !bytes.Equal(v1.PubKey.Data, v2.PubKey.Data) ||
v1.Power != v2.Power {
t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power)
}
}
}
func makeSocketClientServer(
ctx context.Context,
t *testing.T,
logger log.Logger,
app types.Application,
name string,
) (abciclient.Client, service.Service, error) {
t.Helper()
ctx, cancel := context.WithCancel(ctx)
t.Cleanup(cancel)
t.Cleanup(leaktest.Check(t))
func makeSocketClientServer(app types.Application, name string) (abcicli.Client, service.Service, error) {
// Start the listener
socket := fmt.Sprintf("unix://%s.sock", name)
logger := log.TestingLogger()
server := abciserver.NewSocketServer(logger.With("module", "abci-server"), socket, app)
if err := server.Start(ctx); err != nil {
cancel()
server := abciserver.NewSocketServer(socket, app)
server.SetLogger(logger.With("module", "abci-server"))
if err := server.Start(); err != nil {
return nil, nil, err
}
// Connect to the socket
client := abciclient.NewSocketClient(logger.With("module", "abci-client"), socket, false)
if err := client.Start(ctx); err != nil {
cancel()
client := abcicli.NewSocketClient(socket, false)
client.SetLogger(logger.With("module", "abci-client"))
if err := client.Start(); err != nil {
server.Stop()
return nil, nil, err
}
return client, server, nil
}
func makeGRPCClientServer(
ctx context.Context,
t *testing.T,
logger log.Logger,
app types.Application,
name string,
) (abciclient.Client, service.Service, error) {
ctx, cancel := context.WithCancel(ctx)
t.Cleanup(cancel)
t.Cleanup(leaktest.Check(t))
func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, service.Service, error) {
// Start the listener
socket := fmt.Sprintf("unix://%s.sock", name)
logger := log.TestingLogger()
server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, app)
if err := server.Start(ctx); err != nil {
cancel()
gapp := types.NewGRPCApplication(app)
server := abciserver.NewGRPCServer(socket, gapp)
server.SetLogger(logger.With("module", "abci-server"))
if err := server.Start(); err != nil {
return nil, nil, err
}
client := abciclient.NewGRPCClient(logger.With("module", "abci-client"), socket, true)
if err := client.Start(ctx); err != nil {
cancel()
client := abcicli.NewGRPCClient(socket, true)
client.SetLogger(logger.With("module", "abci-client"))
if err := client.Start(); err != nil {
server.Stop()
return nil, nil, err
}
return client, server, nil
}
func TestClientServer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := log.NewNopLogger()
// set up socket app
kvstore := NewApplication()
client, server, err := makeSocketClientServer(ctx, t, logger, kvstore, "kvstore-socket")
require.NoError(t, err)
t.Cleanup(func() { cancel(); server.Wait() })
t.Cleanup(func() { cancel(); client.Wait() })
client, server, err := makeSocketClientServer(kvstore, "kvstore-socket")
require.Nil(t, err)
defer server.Stop()
defer client.Stop()
runClientTests(ctx, t, client)
runClientTests(t, client)
// set up grpc app
kvstore = NewApplication()
gclient, gserver, err := makeGRPCClientServer(ctx, t, logger, kvstore, "/tmp/kvstore-grpc")
require.NoError(t, err)
gclient, gserver, err := makeGRPCClientServer(kvstore, "kvstore-grpc")
require.Nil(t, err)
defer gserver.Stop()
defer gclient.Stop()
t.Cleanup(func() { cancel(); gserver.Wait() })
t.Cleanup(func() { cancel(); gclient.Wait() })
runClientTests(ctx, t, gclient)
runClientTests(t, gclient)
}
func runClientTests(ctx context.Context, t *testing.T, client abciclient.Client) {
func runClientTests(t *testing.T, client abcicli.Client) {
// run some tests....
key := testKey
value := key
tx := []byte(key)
testClient(ctx, t, client, tx, key, value)
testClient(t, client, tx, key, value)
value = testValue
tx = []byte(key + "=" + value)
testClient(ctx, t, client, tx, key, value)
testClient(t, client, tx, key, value)
}
func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []byte, key, value string) {
ar, err := app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}})
func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) {
ar, err := app.DeliverTxSync(types.RequestDeliverTx{Tx: tx})
require.NoError(t, err)
require.Equal(t, 1, len(ar.TxResults))
require.False(t, ar.TxResults[0].IsErr())
// repeating FinalizeBlock doesn't raise error
ar, err = app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}})
require.False(t, ar.IsErr(), ar)
// repeating tx doesn't raise error
ar, err = app.DeliverTxSync(types.RequestDeliverTx{Tx: tx})
require.NoError(t, err)
require.Equal(t, 1, len(ar.TxResults))
require.False(t, ar.TxResults[0].IsErr())
require.False(t, ar.IsErr(), ar)
// commit
_, err = app.Commit(ctx)
_, err = app.CommitSync()
require.NoError(t, err)
info, err := app.Info(ctx, &types.RequestInfo{})
info, err := app.InfoSync(types.RequestInfo{})
require.NoError(t, err)
require.NotZero(t, info.LastBlockHeight)
// make sure query is fine
resQuery, err := app.Query(ctx, &types.RequestQuery{
resQuery, err := app.QuerySync(types.RequestQuery{
Path: "/store",
Data: []byte(key),
})
require.NoError(t, err)
require.Nil(t, err)
require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, key, string(resQuery.Key))
require.Equal(t, value, string(resQuery.Value))
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
// make sure proof is fine
resQuery, err = app.Query(ctx, &types.RequestQuery{
resQuery, err = app.QuerySync(types.RequestQuery{
Path: "/store",
Data: []byte(key),
Prove: true,
})
require.NoError(t, err)
require.Nil(t, err)
require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, key, string(resQuery.Key))
require.Equal(t, value, string(resQuery.Value))

View File

@@ -1,13 +1,19 @@
package kvstore
import (
"context"
"bytes"
"encoding/base64"
"fmt"
"strconv"
"strings"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/code"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/log"
cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto"
tmtypes "github.com/tendermint/tendermint/types"
)
const (
@@ -19,28 +25,224 @@ const (
var _ types.Application = (*PersistentKVStoreApplication)(nil)
type PersistentKVStoreApplication struct {
*Application
app *Application
// validator set
ValUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]types.PubKey
logger log.Logger
}
func NewPersistentKVStoreApplication(logger log.Logger, dbDir string) *PersistentKVStoreApplication {
db, err := dbm.NewGoLevelDB("kvstore", dbDir)
func NewPersistentKVStoreApplication(dbDir string) *PersistentKVStoreApplication {
name := "kvstore"
db, err := dbm.NewGoLevelDB(name, dbDir)
if err != nil {
panic(err)
}
state := loadState(db)
return &PersistentKVStoreApplication{
Application: &Application{
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
state: loadState(db),
logger: logger,
},
app: &Application{state: state},
valAddrToPubKeyMap: make(map[string]types.PubKey),
logger: log.NewNopLogger(),
}
}
func (app *PersistentKVStoreApplication) OfferSnapshot(_ context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
return &types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT}, nil
func (app *PersistentKVStoreApplication) SetLogger(l log.Logger) {
app.logger = l
}
func (app *PersistentKVStoreApplication) ApplySnapshotChunk(_ context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
return &types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT}, nil
func (app *PersistentKVStoreApplication) Info(req types.RequestInfo) types.ResponseInfo {
res := app.app.Info(req)
res.LastBlockHeight = app.app.state.Height
res.LastBlockAppHash = app.app.state.AppHash
return res
}
func (app *PersistentKVStoreApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption {
return app.app.SetOption(req)
}
// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes
func (app *PersistentKVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
// if it starts with "val:", update the validator set
// format is "val:pubkey!power"
if isValidatorTx(req.Tx) {
// update validators in the merkle tree
// and in app.ValUpdates
return app.execValidatorTx(req.Tx)
}
// otherwise, update the key-value store
return app.app.DeliverTx(req)
}
func (app *PersistentKVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
return app.app.CheckTx(req)
}
// Commit will panic if InitChain was not called
func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit {
return app.app.Commit()
}
// When path=/val and data={validator address}, returns the validator update (types.ValidatorUpdate) varint encoded.
// For any other path, returns an associated value or nil if missing.
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
switch reqQuery.Path {
case "/val":
key := []byte("val:" + string(reqQuery.Data))
value, err := app.app.state.db.Get(key)
if err != nil {
panic(err)
}
resQuery.Key = reqQuery.Data
resQuery.Value = value
return
default:
return app.app.Query(reqQuery)
}
}
// Save the validators in the merkle tree
func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
for _, v := range req.Validators {
r := app.updateValidator(v)
if r.IsErr() {
app.logger.Error("Error updating validators", "r", r)
}
}
return types.ResponseInitChain{}
}
// Track the block hash and header information
func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
// reset valset changes
app.ValUpdates = make([]types.ValidatorUpdate, 0)
for _, ev := range req.ByzantineValidators {
if ev.Type == tmtypes.ABCIEvidenceTypeDuplicateVote {
// decrease voting power by 1
if ev.TotalVotingPower == 0 {
continue
}
app.updateValidator(types.ValidatorUpdate{
PubKey: app.valAddrToPubKeyMap[string(ev.Validator.Address)],
Power: ev.TotalVotingPower - 1,
})
}
}
return types.ResponseBeginBlock{}
}
// Update the validator set
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
}
//---------------------------------------------
// update validators
func (app *PersistentKVStoreApplication) Validators() (validators []types.ValidatorUpdate) {
itr, err := app.app.state.db.Iterator(nil, nil)
if err != nil {
panic(err)
}
for ; itr.Valid(); itr.Next() {
if isValidatorTx(itr.Key()) {
validator := new(types.ValidatorUpdate)
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator)
if err != nil {
panic(err)
}
validators = append(validators, *validator)
}
}
return
}
func MakeValSetChangeTx(pubkey types.PubKey, power int64) []byte {
pubStr := base64.StdEncoding.EncodeToString(pubkey.Data)
return []byte(fmt.Sprintf("val:%s!%d", pubStr, power))
}
func isValidatorTx(tx []byte) bool {
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix)
}
// format is "val:pubkey!power"
// pubkey is a base64-encoded 32-byte ed25519 key
func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {
tx = tx[len(ValidatorSetChangePrefix):]
//get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "!")
if len(pubKeyAndPower) != 2 {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)}
}
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
if err != nil {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)}
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
}
// update
return app.updateValidator(types.Ed25519ValidatorUpdate(pubkey, power))
}
// add, update, or remove a validator
func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
key := []byte("val:" + string(v.PubKey.Data))
pubkey := ed25519.PubKeyEd25519{}
copy(pubkey[:], v.PubKey.Data)
if v.Power == 0 {
// remove validator
hasKey, err := app.app.state.db.Has(key)
if err != nil {
panic(err)
}
if !hasKey {
pubStr := base64.StdEncoding.EncodeToString(v.PubKey.Data)
return types.ResponseDeliverTx{
Code: code.CodeTypeUnauthorized,
Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)}
}
app.app.state.db.Delete(key)
delete(app.valAddrToPubKeyMap, string(pubkey.Address()))
} else {
// add or update validator
value := bytes.NewBuffer(make([]byte, 0))
if err := types.WriteMessage(&v, value); err != nil {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Error encoding validator: %v", err)}
}
app.app.state.db.Set(key, value.Bytes())
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
// we only update the changes array if we successfully updated the tree
app.ValUpdates = append(app.ValUpdates, v)
return types.ResponseDeliverTx{Code: code.CodeTypeOK}
}

View File

@@ -0,0 +1,50 @@
from wire import decode_string
# map type_byte to message name
message_types = {
0x01: "echo",
0x02: "flush",
0x03: "info",
0x04: "set_option",
0x21: "deliver_tx",
0x22: "check_tx",
0x23: "commit",
0x24: "add_listener",
0x25: "rm_listener",
}
# return the decoded arguments of abci messages
class RequestDecoder():
def __init__(self, reader):
self.reader = reader
def echo(self):
return decode_string(self.reader)
def flush(self):
return
def info(self):
return
def set_option(self):
return decode_string(self.reader), decode_string(self.reader)
def deliver_tx(self):
return decode_string(self.reader)
def check_tx(self):
return decode_string(self.reader)
def commit(self):
return
def add_listener(self):
# TODO
return
def rm_listener(self):
# TODO
return

View File

@@ -0,0 +1,56 @@
# Simple read() method around a bytearray
class BytesBuffer():
def __init__(self, b):
self.buf = b
self.readCount = 0
def count(self):
return self.readCount
def reset_count(self):
self.readCount = 0
def size(self):
return len(self.buf)
def peek(self):
return self.buf[0]
def write(self, b):
# b should be castable to byte array
self.buf += bytearray(b)
def read(self, n):
if len(self.buf) < n:
print "reader err: buf less than n"
# TODO: exception
return
self.readCount += n
r = self.buf[:n]
self.buf = self.buf[n:]
return r
# Buffer bytes off a tcp connection and read them off in chunks
class ConnReader():
def __init__(self, conn):
self.conn = conn
self.buf = bytearray()
# blocking
def read(self, n):
while n > len(self.buf):
moreBuf = self.conn.recv(1024)
if not moreBuf:
raise IOError("dead connection")
self.buf = self.buf + bytearray(moreBuf)
r = self.buf[:n]
self.buf = self.buf[n:]
return r

View File

@@ -0,0 +1,202 @@
import socket
import select
import sys
from wire import decode_varint, encode
from reader import BytesBuffer
from msg import RequestDecoder, message_types
# hold the asyncronous state of a connection
# ie. we may not get enough bytes on one read to decode the message
class Connection():
def __init__(self, fd, app):
self.fd = fd
self.app = app
self.recBuf = BytesBuffer(bytearray())
self.resBuf = BytesBuffer(bytearray())
self.msgLength = 0
self.decoder = RequestDecoder(self.recBuf)
self.inProgress = False # are we in the middle of a message
def recv(this):
data = this.fd.recv(1024)
if not data: # what about len(data) == 0
raise IOError("dead connection")
this.recBuf.write(data)
# ABCI server responds to messges by calling methods on the app
class ABCIServer():
def __init__(self, app, port=5410):
self.app = app
# map conn file descriptors to (app, reqBuf, resBuf, msgDecoder)
self.appMap = {}
self.port = port
self.listen_backlog = 10
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listener.setblocking(0)
self.listener.bind(('', port))
self.listener.listen(self.listen_backlog)
self.shutdown = False
self.read_list = [self.listener]
self.write_list = []
def handle_new_connection(self, r):
new_fd, new_addr = r.accept()
new_fd.setblocking(0) # non-blocking
self.read_list.append(new_fd)
self.write_list.append(new_fd)
print 'new connection to', new_addr
self.appMap[new_fd] = Connection(new_fd, self.app)
def handle_conn_closed(self, r):
self.read_list.remove(r)
self.write_list.remove(r)
r.close()
print "connection closed"
def handle_recv(self, r):
# app, recBuf, resBuf, conn
conn = self.appMap[r]
while True:
try:
print "recv loop"
# check if we need more data first
if conn.inProgress:
if (conn.msgLength == 0 or conn.recBuf.size() < conn.msgLength):
conn.recv()
else:
if conn.recBuf.size() == 0:
conn.recv()
conn.inProgress = True
# see if we have enough to get the message length
if conn.msgLength == 0:
ll = conn.recBuf.peek()
if conn.recBuf.size() < 1 + ll:
# we don't have enough bytes to read the length yet
return
print "decoding msg length"
conn.msgLength = decode_varint(conn.recBuf)
# see if we have enough to decode the message
if conn.recBuf.size() < conn.msgLength:
return
# now we can decode the message
# first read the request type and get the particular msg
# decoder
typeByte = conn.recBuf.read(1)
typeByte = int(typeByte[0])
resTypeByte = typeByte + 0x10
req_type = message_types[typeByte]
if req_type == "flush":
# messages are length prefixed
conn.resBuf.write(encode(1))
conn.resBuf.write([resTypeByte])
conn.fd.send(str(conn.resBuf.buf))
conn.msgLength = 0
conn.inProgress = False
conn.resBuf = BytesBuffer(bytearray())
return
decoder = getattr(conn.decoder, req_type)
print "decoding args"
req_args = decoder()
print "got args", req_args
# done decoding message
conn.msgLength = 0
conn.inProgress = False
req_f = getattr(conn.app, req_type)
if req_args is None:
res = req_f()
elif isinstance(req_args, tuple):
res = req_f(*req_args)
else:
res = req_f(req_args)
if isinstance(res, tuple):
res, ret_code = res
else:
ret_code = res
res = None
print "called", req_type, "ret code:", ret_code
if ret_code != 0:
print "non-zero retcode:", ret_code
if req_type in ("echo", "info"): # these dont return a ret code
enc = encode(res)
# messages are length prefixed
conn.resBuf.write(encode(len(enc) + 1))
conn.resBuf.write([resTypeByte])
conn.resBuf.write(enc)
else:
enc, encRet = encode(res), encode(ret_code)
# messages are length prefixed
conn.resBuf.write(encode(len(enc) + len(encRet) + 1))
conn.resBuf.write([resTypeByte])
conn.resBuf.write(encRet)
conn.resBuf.write(enc)
except TypeError as e:
print "TypeError on reading from connection:", e
self.handle_conn_closed(r)
return
except ValueError as e:
print "ValueError on reading from connection:", e
self.handle_conn_closed(r)
return
except IOError as e:
print "IOError on reading from connection:", e
self.handle_conn_closed(r)
return
except Exception as e:
# sys.exc_info()[0] # TODO better
print "error reading from connection", str(e)
self.handle_conn_closed(r)
return
def main_loop(self):
while not self.shutdown:
r_list, w_list, _ = select.select(
self.read_list, self.write_list, [], 2.5)
for r in r_list:
if (r == self.listener):
try:
self.handle_new_connection(r)
# undo adding to read list ...
except NameError as e:
print "Could not connect due to NameError:", e
except TypeError as e:
print "Could not connect due to TypeError:", e
except:
print "Could not connect due to unexpected error:", sys.exc_info()[0]
else:
self.handle_recv(r)
def handle_shutdown(self):
for r in self.read_list:
r.close()
for w in self.write_list:
try:
w.close()
except Exception as e:
print(e) # TODO: add logging
self.shutdown = True

View File

@@ -0,0 +1,115 @@
# the decoder works off a reader
# the encoder returns bytearray
def hex2bytes(h):
return bytearray(h.decode('hex'))
def bytes2hex(b):
if type(b) in (str, unicode):
return "".join([hex(ord(c))[2:].zfill(2) for c in b])
else:
return bytes2hex(b.decode())
# expects uvarint64 (no crazy big nums!)
def uvarint_size(i):
if i == 0:
return 0
for j in xrange(1, 8):
if i < 1 << j * 8:
return j
return 8
# expects i < 2**size
def encode_big_endian(i, size):
if size == 0:
return bytearray()
return encode_big_endian(i / 256, size - 1) + bytearray([i % 256])
def decode_big_endian(reader, size):
if size == 0:
return 0
firstByte = reader.read(1)[0]
return firstByte * (256 ** (size - 1)) + decode_big_endian(reader, size - 1)
# ints are max 16 bytes long
def encode_varint(i):
negate = False
if i < 0:
negate = True
i = -i
size = uvarint_size(i)
if size == 0:
return bytearray([0])
big_end = encode_big_endian(i, size)
if negate:
size += 0xF0
return bytearray([size]) + big_end
# returns the int and whats left of the byte array
def decode_varint(reader):
size = reader.read(1)[0]
if size == 0:
return 0
negate = True if size > int(0xF0) else False
if negate:
size = size - 0xF0
i = decode_big_endian(reader, size)
if negate:
i = i * (-1)
return i
def encode_string(s):
size = encode_varint(len(s))
return size + bytearray(s)
def decode_string(reader):
length = decode_varint(reader)
return str(reader.read(length))
def encode_list(s):
b = bytearray()
map(b.extend, map(encode, s))
return encode_varint(len(s)) + b
def encode(s):
if s is None:
return bytearray()
if isinstance(s, int):
return encode_varint(s)
elif isinstance(s, str):
return encode_string(s)
elif isinstance(s, list):
return encode_list(s)
else:
print "UNSUPPORTED TYPE!", type(s), s
if __name__ == '__main__':
ns = [100, 100, 1000, 256]
ss = [2, 5, 5, 2]
bs = map(encode_big_endian, ns, ss)
ds = map(decode_big_endian, bs, ss)
print ns
print [i[0] for i in ds]
ss = ["abc", "hi there jim", "ok now what"]
e = map(encode_string, ss)
d = map(decode_string, e)
print ss
print [i[0] for i in d]

View File

@@ -0,0 +1,82 @@
import sys
from abci.wire import hex2bytes, decode_big_endian, encode_big_endian
from abci.server import ABCIServer
from abci.reader import BytesBuffer
class CounterApplication():
def __init__(self):
sys.exit("The python example is out of date. Upgrading the Python examples is currently left as an exercise to you.")
self.hashCount = 0
self.txCount = 0
self.serial = False
def echo(self, msg):
return msg, 0
def info(self):
return ["hashes:%d, txs:%d" % (self.hashCount, self.txCount)], 0
def set_option(self, key, value):
if key == "serial" and value == "on":
self.serial = True
return 0
def deliver_tx(self, txBytes):
if self.serial:
txByteArray = bytearray(txBytes)
if len(txBytes) >= 2 and txBytes[:2] == "0x":
txByteArray = hex2bytes(txBytes[2:])
txValue = decode_big_endian(
BytesBuffer(txByteArray), len(txBytes))
if txValue != self.txCount:
return None, 6
self.txCount += 1
return None, 0
def check_tx(self, txBytes):
if self.serial:
txByteArray = bytearray(txBytes)
if len(txBytes) >= 2 and txBytes[:2] == "0x":
txByteArray = hex2bytes(txBytes[2:])
txValue = decode_big_endian(
BytesBuffer(txByteArray), len(txBytes))
if txValue < self.txCount:
return 6
return 0
def commit(self):
self.hashCount += 1
if self.txCount == 0:
return "", 0
h = encode_big_endian(self.txCount, 8)
h.reverse()
return str(h), 0
def add_listener(self):
return 0
def rm_listener(self):
return 0
def event(self):
return
if __name__ == '__main__':
l = len(sys.argv)
if l == 1:
port = 26658
elif l == 2:
port = int(sys.argv[1])
else:
print "too many arguments"
quit()
print 'ABCI Demo APP (Python)'
app = CounterApplication()
server = ABCIServer(app, port)
server.main_loop()

View File

@@ -0,0 +1,50 @@
from .wire import decode_string
# map type_byte to message name
message_types = {
0x01: "echo",
0x02: "flush",
0x03: "info",
0x04: "set_option",
0x21: "deliver_tx",
0x22: "check_tx",
0x23: "commit",
0x24: "add_listener",
0x25: "rm_listener",
}
# return the decoded arguments of abci messages
class RequestDecoder():
def __init__(self, reader):
self.reader = reader
def echo(self):
return decode_string(self.reader)
def flush(self):
return
def info(self):
return
def set_option(self):
return decode_string(self.reader), decode_string(self.reader)
def deliver_tx(self):
return decode_string(self.reader)
def check_tx(self):
return decode_string(self.reader)
def commit(self):
return
def add_listener(self):
# TODO
return
def rm_listener(self):
# TODO
return

View File

@@ -0,0 +1,56 @@
# Simple read() method around a bytearray
class BytesBuffer():
def __init__(self, b):
self.buf = b
self.readCount = 0
def count(self):
return self.readCount
def reset_count(self):
self.readCount = 0
def size(self):
return len(self.buf)
def peek(self):
return self.buf[0]
def write(self, b):
# b should be castable to byte array
self.buf += bytearray(b)
def read(self, n):
if len(self.buf) < n:
print("reader err: buf less than n")
# TODO: exception
return
self.readCount += n
r = self.buf[:n]
self.buf = self.buf[n:]
return r
# Buffer bytes off a tcp connection and read them off in chunks
class ConnReader():
def __init__(self, conn):
self.conn = conn
self.buf = bytearray()
# blocking
def read(self, n):
while n > len(self.buf):
moreBuf = self.conn.recv(1024)
if not moreBuf:
raise IOError("dead connection")
self.buf = self.buf + bytearray(moreBuf)
r = self.buf[:n]
self.buf = self.buf[n:]
return r

View File

@@ -0,0 +1,196 @@
import socket
import select
import sys
import logging
from .wire import decode_varint, encode
from .reader import BytesBuffer
from .msg import RequestDecoder, message_types
# hold the asyncronous state of a connection
# ie. we may not get enough bytes on one read to decode the message
logger = logging.getLogger(__name__)
class Connection():
def __init__(self, fd, app):
self.fd = fd
self.app = app
self.recBuf = BytesBuffer(bytearray())
self.resBuf = BytesBuffer(bytearray())
self.msgLength = 0
self.decoder = RequestDecoder(self.recBuf)
self.inProgress = False # are we in the middle of a message
def recv(this):
data = this.fd.recv(1024)
if not data: # what about len(data) == 0
raise IOError("dead connection")
this.recBuf.write(data)
# ABCI server responds to messges by calling methods on the app
class ABCIServer():
def __init__(self, app, port=5410):
self.app = app
# map conn file descriptors to (app, reqBuf, resBuf, msgDecoder)
self.appMap = {}
self.port = port
self.listen_backlog = 10
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listener.setblocking(0)
self.listener.bind(('', port))
self.listener.listen(self.listen_backlog)
self.shutdown = False
self.read_list = [self.listener]
self.write_list = []
def handle_new_connection(self, r):
new_fd, new_addr = r.accept()
new_fd.setblocking(0) # non-blocking
self.read_list.append(new_fd)
self.write_list.append(new_fd)
print('new connection to', new_addr)
self.appMap[new_fd] = Connection(new_fd, self.app)
def handle_conn_closed(self, r):
self.read_list.remove(r)
self.write_list.remove(r)
r.close()
print("connection closed")
def handle_recv(self, r):
# app, recBuf, resBuf, conn
conn = self.appMap[r]
while True:
try:
print("recv loop")
# check if we need more data first
if conn.inProgress:
if (conn.msgLength == 0 or conn.recBuf.size() < conn.msgLength):
conn.recv()
else:
if conn.recBuf.size() == 0:
conn.recv()
conn.inProgress = True
# see if we have enough to get the message length
if conn.msgLength == 0:
ll = conn.recBuf.peek()
if conn.recBuf.size() < 1 + ll:
# we don't have enough bytes to read the length yet
return
print("decoding msg length")
conn.msgLength = decode_varint(conn.recBuf)
# see if we have enough to decode the message
if conn.recBuf.size() < conn.msgLength:
return
# now we can decode the message
# first read the request type and get the particular msg
# decoder
typeByte = conn.recBuf.read(1)
typeByte = int(typeByte[0])
resTypeByte = typeByte + 0x10
req_type = message_types[typeByte]
if req_type == "flush":
# messages are length prefixed
conn.resBuf.write(encode(1))
conn.resBuf.write([resTypeByte])
conn.fd.send(conn.resBuf.buf)
conn.msgLength = 0
conn.inProgress = False
conn.resBuf = BytesBuffer(bytearray())
return
decoder = getattr(conn.decoder, req_type)
print("decoding args")
req_args = decoder()
print("got args", req_args)
# done decoding message
conn.msgLength = 0
conn.inProgress = False
req_f = getattr(conn.app, req_type)
if req_args is None:
res = req_f()
elif isinstance(req_args, tuple):
res = req_f(*req_args)
else:
res = req_f(req_args)
if isinstance(res, tuple):
res, ret_code = res
else:
ret_code = res
res = None
print("called", req_type, "ret code:", ret_code, 'res:', res)
if ret_code != 0:
print("non-zero retcode:", ret_code)
if req_type in ("echo", "info"): # these dont return a ret code
enc = encode(res)
# messages are length prefixed
conn.resBuf.write(encode(len(enc) + 1))
conn.resBuf.write([resTypeByte])
conn.resBuf.write(enc)
else:
enc, encRet = encode(res), encode(ret_code)
# messages are length prefixed
conn.resBuf.write(encode(len(enc) + len(encRet) + 1))
conn.resBuf.write([resTypeByte])
conn.resBuf.write(encRet)
conn.resBuf.write(enc)
except IOError as e:
print("IOError on reading from connection:", e)
self.handle_conn_closed(r)
return
except Exception as e:
logger.exception("error reading from connection")
self.handle_conn_closed(r)
return
def main_loop(self):
while not self.shutdown:
r_list, w_list, _ = select.select(
self.read_list, self.write_list, [], 2.5)
for r in r_list:
if (r == self.listener):
try:
self.handle_new_connection(r)
# undo adding to read list ...
except NameError as e:
print("Could not connect due to NameError:", e)
except TypeError as e:
print("Could not connect due to TypeError:", e)
except:
print("Could not connect due to unexpected error:", sys.exc_info()[0])
else:
self.handle_recv(r)
def handle_shutdown(self):
for r in self.read_list:
r.close()
for w in self.write_list:
try:
w.close()
except Exception as e:
print(e) # TODO: add logging
self.shutdown = True

View File

@@ -0,0 +1,119 @@
# the decoder works off a reader
# the encoder returns bytearray
def hex2bytes(h):
return bytearray(h.decode('hex'))
def bytes2hex(b):
if type(b) in (str, str):
return "".join([hex(ord(c))[2:].zfill(2) for c in b])
else:
return bytes2hex(b.decode())
# expects uvarint64 (no crazy big nums!)
def uvarint_size(i):
if i == 0:
return 0
for j in range(1, 8):
if i < 1 << j * 8:
return j
return 8
# expects i < 2**size
def encode_big_endian(i, size):
if size == 0:
return bytearray()
return encode_big_endian(i // 256, size - 1) + bytearray([i % 256])
def decode_big_endian(reader, size):
if size == 0:
return 0
firstByte = reader.read(1)[0]
return firstByte * (256 ** (size - 1)) + decode_big_endian(reader, size - 1)
# ints are max 16 bytes long
def encode_varint(i):
negate = False
if i < 0:
negate = True
i = -i
size = uvarint_size(i)
if size == 0:
return bytearray([0])
big_end = encode_big_endian(i, size)
if negate:
size += 0xF0
return bytearray([size]) + big_end
# returns the int and whats left of the byte array
def decode_varint(reader):
size = reader.read(1)[0]
if size == 0:
return 0
negate = True if size > int(0xF0) else False
if negate:
size = size - 0xF0
i = decode_big_endian(reader, size)
if negate:
i = i * (-1)
return i
def encode_string(s):
size = encode_varint(len(s))
return size + bytearray(s, 'utf8')
def decode_string(reader):
length = decode_varint(reader)
raw_data = reader.read(length)
return raw_data.decode()
def encode_list(s):
b = bytearray()
list(map(b.extend, list(map(encode, s))))
return encode_varint(len(s)) + b
def encode(s):
print('encoding', repr(s))
if s is None:
return bytearray()
if isinstance(s, int):
return encode_varint(s)
elif isinstance(s, str):
return encode_string(s)
elif isinstance(s, list):
return encode_list(s)
elif isinstance(s, bytearray):
return encode_string(s)
else:
print("UNSUPPORTED TYPE!", type(s), s)
if __name__ == '__main__':
ns = [100, 100, 1000, 256]
ss = [2, 5, 5, 2]
bs = list(map(encode_big_endian, ns, ss))
ds = list(map(decode_big_endian, bs, ss))
print(ns)
print([i[0] for i in ds])
ss = ["abc", "hi there jim", "ok now what"]
e = list(map(encode_string, ss))
d = list(map(decode_string, e))
print(ss)
print([i[0] for i in d])

View File

@@ -0,0 +1,82 @@
import sys
from abci.wire import hex2bytes, decode_big_endian, encode_big_endian
from abci.server import ABCIServer
from abci.reader import BytesBuffer
class CounterApplication():
def __init__(self):
sys.exit("The python example is out of date. Upgrading the Python examples is currently left as an exercise to you.")
self.hashCount = 0
self.txCount = 0
self.serial = False
def echo(self, msg):
return msg, 0
def info(self):
return ["hashes:%d, txs:%d" % (self.hashCount, self.txCount)], 0
def set_option(self, key, value):
if key == "serial" and value == "on":
self.serial = True
return 0
def deliver_tx(self, txBytes):
if self.serial:
txByteArray = bytearray(txBytes)
if len(txBytes) >= 2 and txBytes[:2] == "0x":
txByteArray = hex2bytes(txBytes[2:])
txValue = decode_big_endian(
BytesBuffer(txByteArray), len(txBytes))
if txValue != self.txCount:
return None, 6
self.txCount += 1
return None, 0
def check_tx(self, txBytes):
if self.serial:
txByteArray = bytearray(txBytes)
if len(txBytes) >= 2 and txBytes[:2] == "0x":
txByteArray = hex2bytes(txBytes[2:])
txValue = decode_big_endian(
BytesBuffer(txByteArray), len(txBytes))
if txValue < self.txCount:
return 6
return 0
def commit(self):
self.hashCount += 1
if self.txCount == 0:
return "", 0
h = encode_big_endian(self.txCount, 8)
h.reverse()
return h.decode(), 0
def add_listener(self):
return 0
def rm_listener(self):
return 0
def event(self):
return
if __name__ == '__main__':
l = len(sys.argv)
if l == 1:
port = 26658
elif l == 2:
port = int(sys.argv[1])
else:
print("too many arguments")
quit()
print('ABCI Demo APP (Python)')
app = CounterApplication()
server = ABCIServer(app, port)
server.main_loop()

View File

@@ -1,83 +1,57 @@
package server
import (
"context"
"net"
"google.golang.org/grpc"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
"github.com/tendermint/tendermint/libs/service"
)
type GRPCServer struct {
service.BaseService
logger log.Logger
proto string
addr string
server *grpc.Server
proto string
addr string
listener net.Listener
server *grpc.Server
app types.Application
app types.ABCIApplicationServer
}
// NewGRPCServer returns a new gRPC ABCI server
func NewGRPCServer(logger log.Logger, protoAddr string, app types.Application) service.Service {
func NewGRPCServer(protoAddr string, app types.ABCIApplicationServer) service.Service {
proto, addr := tmnet.ProtocolAndAddress(protoAddr)
s := &GRPCServer{
logger: logger,
proto: proto,
addr: addr,
app: app,
proto: proto,
addr: addr,
listener: nil,
app: app,
}
s.BaseService = *service.NewBaseService(logger, "ABCIServer", s)
s.BaseService = *service.NewBaseService(nil, "ABCIServer", s)
return s
}
// OnStart starts the gRPC service.
func (s *GRPCServer) OnStart(ctx context.Context) error {
func (s *GRPCServer) OnStart() error {
ln, err := net.Listen(s.proto, s.addr)
if err != nil {
return err
}
s.listener = ln
s.server = grpc.NewServer()
types.RegisterABCIApplicationServer(s.server, &gRPCApplication{Application: s.app})
types.RegisterABCIApplicationServer(s.server, s.app)
s.logger.Info("Listening", "proto", s.proto, "addr", s.addr)
go func() {
go func() {
<-ctx.Done()
s.server.GracefulStop()
}()
s.Logger.Info("Listening", "proto", s.proto, "addr", s.addr)
go s.server.Serve(s.listener)
if err := s.server.Serve(ln); err != nil {
s.logger.Error("error serving gRPC server", "err", err)
}
}()
return nil
}
// OnStop stops the gRPC server.
func (s *GRPCServer) OnStop() { s.server.Stop() }
//-------------------------------------------------------
// gRPCApplication is a gRPC shim for Application
type gRPCApplication struct {
types.Application
}
func (app *gRPCApplication) Echo(_ context.Context, req *types.RequestEcho) (*types.ResponseEcho, error) {
return &types.ResponseEcho{Message: req.Message}, nil
}
func (app *gRPCApplication) Flush(_ context.Context, req *types.RequestFlush) (*types.ResponseFlush, error) {
return &types.ResponseFlush{}, nil
}
func (app *gRPCApplication) Commit(ctx context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) {
return app.Application.Commit(ctx)
func (s *GRPCServer) OnStop() {
s.server.Stop()
}

View File

@@ -12,18 +12,17 @@ import (
"fmt"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
)
func NewServer(logger log.Logger, protoAddr, transport string, app types.Application) (service.Service, error) {
func NewServer(protoAddr, transport string, app types.Application) (service.Service, error) {
var s service.Service
var err error
switch transport {
case "socket":
s = NewSocketServer(logger, protoAddr, app)
s = NewSocketServer(protoAddr, app)
case "grpc":
s = NewGRPCServer(logger, protoAddr, app)
s = NewGRPCServer(protoAddr, types.NewGRPCApplication(app))
default:
err = fmt.Errorf("unknown server type %s", transport)
}

View File

@@ -2,16 +2,15 @@ package server
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"sync"
"github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmlog "github.com/tendermint/tendermint/libs/log"
tmnet "github.com/tendermint/tendermint/libs/net"
"github.com/tendermint/tendermint/libs/service"
)
@@ -20,298 +19,234 @@ import (
type SocketServer struct {
service.BaseService
logger log.Logger
isLoggerSet bool
proto string
addr string
listener net.Listener
connsMtx sync.Mutex
connsClose map[int]func()
conns map[int]net.Conn
nextConnID int
app types.Application
appMtx sync.Mutex
app types.Application
}
func NewSocketServer(logger log.Logger, protoAddr string, app types.Application) service.Service {
func NewSocketServer(protoAddr string, app types.Application) service.Service {
proto, addr := tmnet.ProtocolAndAddress(protoAddr)
s := &SocketServer{
logger: logger,
proto: proto,
addr: addr,
listener: nil,
app: app,
connsClose: make(map[int]func()),
proto: proto,
addr: addr,
listener: nil,
app: app,
conns: make(map[int]net.Conn),
}
s.BaseService = *service.NewBaseService(logger, "ABCIServer", s)
s.BaseService = *service.NewBaseService(nil, "ABCIServer", s)
return s
}
func (s *SocketServer) OnStart(ctx context.Context) error {
func (s *SocketServer) SetLogger(l tmlog.Logger) {
s.BaseService.SetLogger(l)
s.isLoggerSet = true
}
func (s *SocketServer) OnStart() error {
ln, err := net.Listen(s.proto, s.addr)
if err != nil {
return err
}
s.listener = ln
go s.acceptConnectionsRoutine(ctx)
go s.acceptConnectionsRoutine()
return nil
}
func (s *SocketServer) OnStop() {
if err := s.listener.Close(); err != nil {
s.logger.Error("error closing listener", "err", err)
s.Logger.Error("Error closing listener", "err", err)
}
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
for _, closer := range s.connsClose {
closer()
for id, conn := range s.conns {
delete(s.conns, id)
if err := conn.Close(); err != nil {
s.Logger.Error("Error closing connection", "id", id, "conn", conn, "err", err)
}
}
}
func (s *SocketServer) addConn(closer func()) int {
func (s *SocketServer) addConn(conn net.Conn) int {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
connID := s.nextConnID
s.nextConnID++
s.connsClose[connID] = closer
s.conns[connID] = conn
return connID
}
// deletes conn even if close errs
func (s *SocketServer) rmConn(connID int) {
func (s *SocketServer) rmConn(connID int) error {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
if closer, ok := s.connsClose[connID]; ok {
closer()
delete(s.connsClose, connID)
conn, ok := s.conns[connID]
if !ok {
return fmt.Errorf("connection %d does not exist", connID)
}
delete(s.conns, connID)
return conn.Close()
}
func (s *SocketServer) acceptConnectionsRoutine(ctx context.Context) {
func (s *SocketServer) acceptConnectionsRoutine() {
for {
if ctx.Err() != nil {
return
}
// Accept a connection
s.logger.Info("Waiting for new connection...")
s.Logger.Info("Waiting for new connection...")
conn, err := s.listener.Accept()
if err != nil {
if !s.IsRunning() {
return // Ignore error from listener closing.
}
s.logger.Error("Failed to accept connection", "err", err)
s.Logger.Error("Failed to accept connection", "err", err)
continue
}
cctx, ccancel := context.WithCancel(ctx)
connID := s.addConn(ccancel)
s.Logger.Info("Accepted a new connection")
s.logger.Info("Accepted a new connection", "id", connID)
connID := s.addConn(conn)
closeConn := make(chan error, 2) // Push to signal connection closed
responses := make(chan *types.Response, 1000) // A channel to buffer responses
once := &sync.Once{}
closer := func(err error) {
ccancel()
once.Do(func() {
if cerr := conn.Close(); err != nil {
s.logger.Error("error closing connection",
"id", connID,
"close_err", cerr,
"err", err)
}
s.rmConn(connID)
switch {
case errors.Is(err, context.Canceled):
s.logger.Error("Connection terminated",
"id", connID,
"err", err)
case errors.Is(err, context.DeadlineExceeded):
s.logger.Error("Connection encountered timeout",
"id", connID,
"err", err)
case errors.Is(err, io.EOF):
s.logger.Error("Connection was closed by client",
"id", connID)
case err != nil:
s.logger.Error("Connection error",
"id", connID,
"err", err)
default:
s.logger.Error("Connection was closed",
"id", connID)
}
})
}
// Read requests from conn and deal with them
go s.handleRequests(cctx, closer, conn, responses)
go s.handleRequests(closeConn, conn, responses)
// Pull responses from 'responses' and write them to conn.
go s.handleResponses(cctx, closer, conn, responses)
go s.handleResponses(closeConn, conn, responses)
// Wait until signal to close connection
go s.waitForClose(closeConn, connID)
}
}
func (s *SocketServer) waitForClose(closeConn chan error, connID int) {
err := <-closeConn
switch {
case err == io.EOF:
s.Logger.Error("Connection was closed by client")
case err != nil:
s.Logger.Error("Connection error", "err", err)
default:
// never happens
s.Logger.Error("Connection was closed")
}
// Close the connection
if err := s.rmConn(connID); err != nil {
s.Logger.Error("Error closing connection", "err", err)
}
}
// Read requests from conn and deal with them
func (s *SocketServer) handleRequests(ctx context.Context, closer func(error), conn io.Reader, responses chan<- *types.Response) {
func (s *SocketServer) handleRequests(closeConn chan error, conn io.Reader, responses chan<- *types.Response) {
var count int
var bufReader = bufio.NewReader(conn)
defer func() {
// make sure to recover from any app-related panics to allow proper socket cleanup
if r := recover(); r != nil {
r := recover()
if r != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
closer(fmt.Errorf("recovered from panic: %v\n%s", r, buf))
err := fmt.Errorf("recovered from panic: %v\n%s", r, buf)
if !s.isLoggerSet {
fmt.Fprintln(os.Stderr, err)
}
closeConn <- err
s.appMtx.Unlock()
}
}()
for {
req := &types.Request{}
if err := types.ReadMessage(bufReader, req); err != nil {
closer(fmt.Errorf("error reading message: %w", err))
return
}
resp, err := s.processRequest(ctx, req)
var req = &types.Request{}
err := types.ReadMessage(bufReader, req)
if err != nil {
closer(err)
if err == io.EOF {
closeConn <- err
} else {
closeConn <- fmt.Errorf("error reading message: %w", err)
}
return
}
select {
case <-ctx.Done():
closer(ctx.Err())
return
case responses <- resp:
}
s.appMtx.Lock()
count++
s.handleRequest(req, responses)
s.appMtx.Unlock()
}
}
func (s *SocketServer) processRequest(ctx context.Context, req *types.Request) (*types.Response, error) {
func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types.Response) {
switch r := req.Value.(type) {
case *types.Request_Echo:
return types.ToResponseEcho(r.Echo.Message), nil
responses <- types.ToResponseEcho(r.Echo.Message)
case *types.Request_Flush:
return types.ToResponseFlush(), nil
responses <- types.ToResponseFlush()
case *types.Request_Info:
res, err := s.app.Info(ctx, r.Info)
if err != nil {
return nil, err
}
return types.ToResponseInfo(res), nil
res := s.app.Info(*r.Info)
responses <- types.ToResponseInfo(res)
case *types.Request_SetOption:
res := s.app.SetOption(*r.SetOption)
responses <- types.ToResponseSetOption(res)
case *types.Request_DeliverTx:
res := s.app.DeliverTx(*r.DeliverTx)
responses <- types.ToResponseDeliverTx(res)
case *types.Request_CheckTx:
res, err := s.app.CheckTx(ctx, r.CheckTx)
if err != nil {
return nil, err
}
return types.ToResponseCheckTx(res), nil
res := s.app.CheckTx(*r.CheckTx)
responses <- types.ToResponseCheckTx(res)
case *types.Request_Commit:
res, err := s.app.Commit(ctx)
if err != nil {
return nil, err
}
return types.ToResponseCommit(res), nil
res := s.app.Commit()
responses <- types.ToResponseCommit(res)
case *types.Request_Query:
res, err := s.app.Query(ctx, r.Query)
if err != nil {
return nil, err
}
return types.ToResponseQuery(res), nil
res := s.app.Query(*r.Query)
responses <- types.ToResponseQuery(res)
case *types.Request_InitChain:
res, err := s.app.InitChain(ctx, r.InitChain)
if err != nil {
return nil, err
}
return types.ToResponseInitChain(res), nil
case *types.Request_ListSnapshots:
res, err := s.app.ListSnapshots(ctx, r.ListSnapshots)
if err != nil {
return nil, err
}
return types.ToResponseListSnapshots(res), nil
case *types.Request_OfferSnapshot:
res, err := s.app.OfferSnapshot(ctx, r.OfferSnapshot)
if err != nil {
return nil, err
}
return types.ToResponseOfferSnapshot(res), nil
case *types.Request_PrepareProposal:
res, err := s.app.PrepareProposal(ctx, r.PrepareProposal)
if err != nil {
return nil, err
}
return types.ToResponsePrepareProposal(res), nil
case *types.Request_ProcessProposal:
res, err := s.app.ProcessProposal(ctx, r.ProcessProposal)
if err != nil {
return nil, err
}
return types.ToResponseProcessProposal(res), nil
case *types.Request_LoadSnapshotChunk:
res, err := s.app.LoadSnapshotChunk(ctx, r.LoadSnapshotChunk)
if err != nil {
return nil, err
}
return types.ToResponseLoadSnapshotChunk(res), nil
case *types.Request_ApplySnapshotChunk:
res, err := s.app.ApplySnapshotChunk(ctx, r.ApplySnapshotChunk)
if err != nil {
return nil, err
}
return types.ToResponseApplySnapshotChunk(res), nil
case *types.Request_ExtendVote:
res, err := s.app.ExtendVote(ctx, r.ExtendVote)
if err != nil {
return nil, err
}
return types.ToResponseExtendVote(res), nil
case *types.Request_VerifyVoteExtension:
res, err := s.app.VerifyVoteExtension(ctx, r.VerifyVoteExtension)
if err != nil {
return nil, err
}
return types.ToResponseVerifyVoteExtension(res), nil
case *types.Request_FinalizeBlock:
res, err := s.app.FinalizeBlock(ctx, r.FinalizeBlock)
if err != nil {
return nil, err
}
return types.ToResponseFinalizeBlock(res), nil
res := s.app.InitChain(*r.InitChain)
responses <- types.ToResponseInitChain(res)
case *types.Request_BeginBlock:
res := s.app.BeginBlock(*r.BeginBlock)
responses <- types.ToResponseBeginBlock(res)
case *types.Request_EndBlock:
res := s.app.EndBlock(*r.EndBlock)
responses <- types.ToResponseEndBlock(res)
default:
return types.ToResponseException("Unknown request"), errors.New("unknown request type")
responses <- types.ToResponseException("Unknown request")
}
}
// Pull responses from 'responses' and write them to conn.
func (s *SocketServer) handleResponses(
ctx context.Context,
closer func(error),
conn io.Writer,
responses <-chan *types.Response,
) {
bw := bufio.NewWriter(conn)
func (s *SocketServer) handleResponses(closeConn chan error, conn io.Writer, responses <-chan *types.Response) {
var count int
var bufWriter = bufio.NewWriter(conn)
for {
select {
case <-ctx.Done():
closer(ctx.Err())
var res = <-responses
err := types.WriteMessage(res, bufWriter)
if err != nil {
closeConn <- fmt.Errorf("error writing message: %w", err)
return
case res := <-responses:
if err := types.WriteMessage(res, bw); err != nil {
closer(fmt.Errorf("error writing message: %w", err))
return
}
if err := bw.Flush(); err != nil {
closer(fmt.Errorf("error writing message: %w", err))
}
if _, ok := res.Value.(*types.Response_Flush); ok {
err = bufWriter.Flush()
if err != nil {
closeConn <- fmt.Errorf("error flushing write buffer: %w", err)
return
}
}
count++
}
}

View File

@@ -1,40 +1,27 @@
package tests
import (
"context"
"testing"
"github.com/fortytw2/leaktest"
"github.com/stretchr/testify/assert"
abciclientent "github.com/tendermint/tendermint/abci/client"
abciclient "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/abci/example/kvstore"
abciserver "github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/libs/log"
)
func TestClientServerNoAddrPrefix(t *testing.T) {
t.Cleanup(leaktest.Check(t))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const (
addr = "localhost:26658"
transport = "socket"
)
addr := "localhost:26658"
transport := "socket"
app := kvstore.NewApplication()
logger := log.NewTestingLogger(t)
server, err := abciserver.NewServer(logger, addr, transport, app)
server, err := abciserver.NewServer(addr, transport, app)
assert.NoError(t, err, "expected no error on NewServer")
err = server.Start(ctx)
err = server.Start()
assert.NoError(t, err, "expected no error on server.Start")
t.Cleanup(server.Wait)
client, err := abciclientent.NewClient(logger, addr, transport, true)
client, err := abciclient.NewClient(addr, transport, true)
assert.NoError(t, err, "expected no error on NewClient")
err = client.Start(ctx)
err = client.Start()
assert.NoError(t, err, "expected no error on client.Start")
t.Cleanup(client.Wait)
}

Some files were not shown because too many files have changed in this diff Show More