initial: sovereign Mukan Network fork
Some checks are pending
docker-build-cometbft / vars (push) Waiting to run
docker-build-cometbft / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-cometbft / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-cometbft / merge-images (push) Blocked by required conditions
docker-build-e2e-node / vars (push) Waiting to run
docker-build-e2e-node / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-e2e-node / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-e2e-node / merge-images (push) Blocked by required conditions

This commit is contained in:
Mukan Erkin TÖRÜK 2026-05-11 03:18:27 +03:00
commit ef24c0b67e
1398 changed files with 272435 additions and 0 deletions

11
.clang-format Normal file
View file

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

5
.dockerignore Normal file
View file

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

16
.editorconfig Normal file
View file

@ -0,0 +1,16 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{sh,Makefile}]
indent_style = tab
[*.proto]
indent_style = space
indent_size = 2

12
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,12 @@
# 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.
* @cometbft/engineering @cometbft/devrel @cometbft/interchain-inc
/spec @cometbft/research @cometbft/engineering @cometbft/interchain-inc

83
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,83 @@
---
name: Bug report
about: Create a report to help us squash bugs!
labels: bug, needs-triage
---
<!--
Please fill in as much of the template below as you can.
If you have general questions, please create a new discussion:
https://github.com/cometbft/cometbft/discussions
Be ready for followup questions, and please respond in a timely manner. We might
ask you to provide additional logs and data (CometBFT & App).
-->
## Bug Report
### Setup
**CometBFT version** (use `cometbft version` or `git rev-parse --verify HEAD` if installed from source):
**Have you tried the latest version**: yes/no
**ABCI app** (name for built-in, URL for self-written if it's publicly available):
**Environment**:
- **OS** (e.g. from /etc/os-release):
- **Install tools**:
- **Others**:
**node command runtime flags**:
### Config
<!--
You can paste only the changes you've made.
-->
### What happened?
### What did you expect to happen?
### How to reproduce it
<!--
Provide a description here as minimally and precisely as possible as to how to
reproduce the issue. Ideally only using our kvstore application, as debugging
app chains is not within our team's scope.
-->
### Logs
<!--
Paste a small part showing an error (< 10 lines) or link a pastebin, gist, etc.
containing more of the log file).
-->
### `dump_consensus_state` output
<!--
Please provide the output from the `http://<ip>:<port>/dump_consensus_state` RPC
endpoint for consensus bugs.
-->
### Anything else we need to know
<!--
Is there any additional information not covered by the other sections that would
help us to triage/debug/fix this issue?
-->

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Ask a question
url: https://github.com/cometbft/cometbft/discussions
about: Please ask and answer questions here

View file

@ -0,0 +1,29 @@
---
name: Feature request
about: Create a proposal to request a feature
labels: enhancement, needs-triage
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for opening an issue! ✰
v Before smashing the submit button please review the template.
v Word of caution: poorly thought-out proposals may be rejected
v without deliberation
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
## Feature Request
### Summary
<!-- Short, concise description of the proposed feature -->
### Problem Definition
<!-- Why do we need this feature?
What problems may be addressed by introducing this feature?
What benefits does CometBFT stand to gain by including this feature?
Are there any disadvantages of including this feature? -->
### Proposal
<!-- Detailed description of requirements of implementation -->

28
.github/ISSUE_TEMPLATE/proposal.md vendored Normal file
View file

@ -0,0 +1,28 @@
---
name: Protocol change proposal
about: Create a proposal to request a change to the protocol
labels: protocol-change, needs-triage
---
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
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 CometBFT stand to gain by including this change?
Are there any disadvantages of including this change? -->
### Proposal
<!-- Detailed description of requirements of implementation -->

31
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,31 @@
<!--
Please add a reference to the issue that this PR addresses and indicate which
files are most critical to review. If it fully addresses a particular issue,
please include "Closes #XXX" (where "XXX" is the issue number).
If this PR is non-trivial/large/complex, please ensure that you have either
created an issue that the team's had a chance to respond to, or had some
discussion with the team prior to submitting substantial pull requests. The team
can be reached via GitHub Discussions or the Cosmos Network Discord server in
the #cometbft channel. GitHub Discussions is preferred over Discord as it
allows us to keep track of conversations topically.
https://github.com/cometbft/cometbft/discussions
If the work in this PR is not aligned with the team's current priorities, please
be advised that it may take some time before it is merged - especially if it has
not yet been discussed with the team.
See the project board for the team's current priorities:
https://github.com/orgs/cometbft/projects/1
-->
---
#### PR checklist
- [ ] Tests written/updated
- [ ] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog)
- [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments

69
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,69 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "main"
open-pull-requests-limit: 10
labels:
- dependencies
- automerge
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "v0.37.x"
open-pull-requests-limit: 10
labels:
- dependencies
- automerge
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
target-branch: "v0.34.x"
open-pull-requests-limit: 10
labels:
- dependencies
- automerge
###################################
##
## Update All Go Dependencies
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
target-branch: "main"
open-pull-requests-limit: 10
labels:
- dependencies
- automerge
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
target-branch: "v0.37.x"
# Only allow automated security-related dependency updates on release
# branches.
open-pull-requests-limit: 0
labels:
- dependencies
- automerge
- package-ecosystem: gomod
directory: "/"
schedule:
interval: daily
target-branch: "v0.34.x"
# Only allow automated security-related dependency updates on release
# branches.
open-pull-requests-limit: 0
labels:
- dependencies
- automerge

10
.github/issue_template.md vendored Normal file
View file

@ -0,0 +1,10 @@
---
labels: needs-triage
---
<!--
If you want to ask a general question, please create a new discussion instead of
an issue: https://github.com/cometbft/cometbft/discussions
-->

15
.github/linters/markdownlint.yml vendored Normal file
View file

@ -0,0 +1,15 @@
# markdownlint configuration for Super-Linter
# - https://github.com/DavidAnson/markdownlint
# - https://github.com/github/super-linter
# Default state for all rules
default: true
# See https://github.com/DavidAnson/markdownlint#rules--aliases for rules
MD007: {"indent": 4}
MD013: false
MD024: {siblings_only: true}
MD025: false
MD033: {no-inline-html: false}
no-hard-tabs: false
whitespace: false

9
.github/linters/yaml-lint.yml vendored Normal file
View file

@ -0,0 +1,9 @@
---
# 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

35
.github/mergify.yml vendored Normal file
View file

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

82
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,82 @@
name: Build
# Tests runs different tests (test_abci_apps, test_abci_cli, test_apps)
# This workflow runs on every push to v0.38.x and every pull request
# All jobs will pass without running if no *{.go, .mod, .sum} files have been modified
on:
pull_request:
push:
branches:
- v0.38.x
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@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
- 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@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
- 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@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
- 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 != ''"

68
.github/workflows/check-generated.yml vendored Normal file
View file

@ -0,0 +1,68 @@
# 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:
- v0.38.x
permissions:
contents: read
jobs:
check-mocks-metrics:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
- name: "Check generated mocks and metrics"
run: |
set -euo pipefail
make mockery metrics
git add .
if ! git diff HEAD --stat --exit-code ; then
echo ">> ERROR:"
echo ">>"
echo ">> Generated mocks and/or metrics require update (either Mockery or source files may have changed)."
echo ">> Ensure your tools are up-to-date, re-run 'make mockery metrics' and update this PR."
echo ">>"
git diff
exit 1
fi
check-proto:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
with:
fetch-depth: 1 # we need a .git directory to run git diff
- name: "Check protobuf generated code"
run: |
set -euo pipefail
make proto-gen
git add .
if ! git diff HEAD --stat --exit-code ; then
echo ">> ERROR:"
echo ">>"
echo ">> Protobuf generated code requires update (either tools or .proto files may have changed)."
echo ">> Ensure your tools are up-to-date, re-run 'make proto-gen' and update this PR."
echo ">>"
git diff
exit 1
fi

View file

@ -0,0 +1,98 @@
name: docker-build-cometbft
on:
workflow_dispatch:
push:
branches:
- main
- v0.38.x
- v1.x
- v2.x
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DOCKER_ORG: cometbft
DOCKER_IMAGE: cometbft
jobs:
vars:
runs-on: ubuntu-latest
outputs:
repo: ${{ steps.set.outputs.repo }}
tags: ${{ steps.set.outputs.tags }}
steps:
- id: set
run: |
REPO="${DOCKER_ORG}/${DOCKER_IMAGE}"
VERSION="${GITHUB_REF_NAME}"
TAGS="${REPO}:${VERSION}"
if [ "$VERSION" = "main" ]; then
TAGS="${TAGS:+$TAGS,}$REPO:latest"
fi
echo "repo=${REPO}" >> $GITHUB_OUTPUT
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
build-images:
needs: vars
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: amd64
- os: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.os }}
outputs:
digest-amd64: ${{ steps.digest.outputs.digest-amd64 }}
digest-arm64: ${{ steps.digest.outputs.digest-arm64 }}
steps:
- id: tags
run: |
TAGS=$(echo "${{ needs.vars.outputs.tags }}" | sed "s/[^,]*/&-${{ matrix.arch }}/g")
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
id: build
with:
platforms: linux/${{ matrix.arch }}
context: .
file: ./DOCKER/Dockerfile
push: true
tags: ${{ steps.tags.outputs.tags }}
- id: digest
run: echo "digest-${{ matrix.arch }}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
merge-images:
runs-on: ubuntu-latest
needs:
- vars
- build-images
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: |
TAGS="${{ needs.vars.outputs.tags }}"
docker buildx imagetools create \
$(printf -- '--tag %s ' ${TAGS//,/ }) \
${{ needs.vars.outputs.repo }}@${{ needs.build-images.outputs.digest-amd64 }} \
${{ needs.vars.outputs.repo }}@${{ needs.build-images.outputs.digest-arm64 }}

View file

@ -0,0 +1,98 @@
name: docker-build-e2e-node
on:
workflow_dispatch:
push:
branches:
- main
- v0.38.x
- v1.x
- v2.x
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DOCKER_ORG: cometbft
DOCKER_IMAGE: e2e-node
jobs:
vars:
runs-on: ubuntu-latest
outputs:
repo: ${{ steps.set.outputs.repo }}
tags: ${{ steps.set.outputs.tags }}
steps:
- id: set
run: |
REPO="${DOCKER_ORG}/${DOCKER_IMAGE}"
VERSION="${GITHUB_REF_NAME}"
TAGS="${REPO}:${VERSION}"
if [ "$VERSION" = "main" ]; then
TAGS="${TAGS:+$TAGS,}$REPO:latest"
fi
echo "repo=${REPO}" >> $GITHUB_OUTPUT
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
build-images:
needs: vars
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: amd64
- os: ubuntu-24.04-arm
arch: arm64
runs-on: ${{ matrix.os }}
outputs:
digest-amd64: ${{ steps.digest.outputs.digest-amd64 }}
digest-arm64: ${{ steps.digest.outputs.digest-arm64 }}
steps:
- id: tags
run: |
TAGS=$(echo "${{ needs.vars.outputs.tags }}" | sed "s/[^,]*/&-${{ matrix.arch }}/g")
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
id: build
with:
platforms: linux/${{ matrix.arch }}
context: .
file: ./test/e2e/docker/Dockerfile
push: true
tags: ${{ steps.tags.outputs.tags }}
- id: digest
run: echo "digest-${{ matrix.arch }}=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
merge-images:
runs-on: ubuntu-latest
needs:
- vars
- build-images
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: |
TAGS="${{ needs.vars.outputs.tags }}"
docker buildx imagetools create \
$(printf -- '--tag %s ' ${TAGS//,/ }) \
${{ needs.vars.outputs.repo }}@${{ needs.build-images.outputs.digest-amd64 }} \
${{ needs.vars.outputs.repo }}@${{ needs.build-images.outputs.digest-arm64 }}

20
.github/workflows/docs-toc.yml vendored Normal file
View file

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

View file

@ -0,0 +1,45 @@
# Manually run the nightly E2E tests for a particular branch, but test with
# multiple versions.
name: e2e-manual-multiversion
on:
workflow_dispatch:
jobs:
e2e-manual-multiversion-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', '05']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/checkout@v4
- 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
if: matrix.group != 5
working-directory: test/e2e
# When changing -g, also change the matrix groups above
# Generate multi-version tests with double the quantity of E2E nodes
# based on the current branch as compared to the latest version.
run: ./build/generator -g 5 -m "latest:1,local:2" -d networks/nightly/ -p
- name: Run p2p testnets (${{ matrix.group }})
if: matrix.group != 5
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml
- name: Run p2p testnets (regression)
if: matrix.group == 5
working-directory: test/e2e
run: ./run-multiple.sh networks_regressions/*.toml

43
.github/workflows/e2e-manual.yml vendored Normal file
View file

@ -0,0 +1,43 @@
# Runs randomly generated E2E testnets nightly on main
# 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', '05']
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/checkout@v4
- 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
if: matrix.group != 5
working-directory: test/e2e
# When changing -g, also change the matrix groups above
run: ./build/generator -g 5 -d networks/nightly/ -p
- name: Run p2p testnets (${{ matrix.group }})
if: matrix.group != 5
working-directory: test/e2e
run: ./run-multiple.sh networks/nightly/*-group${{ matrix.group }}-*.toml
- name: Run p2p testnets (regression)
if: matrix.group == 5
working-directory: test/e2e
run: ./run-multiple.sh networks_regressions/*.toml

36
.github/workflows/e2e.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: e2e
# Runs the CI end-to-end test network on all pushes to v0.38.x
# and every pull request, but only if any Go files have been changed.
on:
workflow_dispatch: # allow running workflow manually
pull_request:
push:
branches:
- v0.38.x
jobs:
e2e-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/checkout@v4
- 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 != ''"

93
.github/workflows/fuzz-nightly.yml vendored Normal file
View file

@ -0,0 +1,93 @@
# Runs fuzzing nightly.
name: Fuzz Tests
on:
workflow_dispatch: # allow running workflow manually
schedule:
- cron: '0 3 * * *'
pull_request:
branches:
- v0.38.x
paths:
- "test/fuzz/**/*.go"
jobs:
fuzz-nightly-test:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/checkout@v4
- 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-addrbook
working-directory: test/fuzz
run: timeout -s SIGINT --preserve-status 10m make fuzz-p2p-addrbook
continue-on-error: true
- name: Fuzz p2p-pex
working-directory: test/fuzz
run: timeout -s SIGINT --preserve-status 10m make fuzz-p2p-pex
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@v4
with:
name: crashers
path: test/fuzz/**/crashers
retention-days: 3
- name: Archive suppressions
uses: actions/upload-artifact@v4
with:
name: suppressions
path: test/fuzz/**/suppressions
retention-days: 3
- name: Set crashers count
working-directory: test/fuzz
run: echo "count=$(find . -type d -name 'crashers' | xargs -I % sh -c 'ls % | wc -l' | awk '{total += $1} END {print total}')" >> $GITHUB_OUTPUT
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 on failure
uses: slackapi/slack-github-action@v2.0.0
env:
BRANCH: ${{ github.ref_name }}
CRASHERS: ${{ needs.fuzz-nightly-test.outputs.crashers-count }}
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
blocks:
- type: "section"
text:
type: "mrkdwn"
text: ":skull: Nightly fuzz tests for `${{ env.BRANCH }}` failed with ${{ env.CRASHERS }} crasher(s). See the <${{ env.RUN_URL }}|run details>."

16
.github/workflows/janitor.yml vendored Normal file
View file

@ -0,0 +1,16 @@
name: Janitor
# Janitor cleans up previous runs of various workflows
# To add more workflows to cancel visit https://api.github.com/repos/cometbft/cometbft/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.12.1
with:
workflow_id: 1041851,1401230,2837803
access_token: ${{ github.token }}

35
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Golang Linter
# Lint runs golangci-lint over the entire CometBFT repository.
#
# This workflow is run on every pull request and push to v0.38.x.
#
# The `golangci` job will pass without running if no *.{go, mod, sum}
# files have been modified.
#
# To run this locally, simply run `make lint` from the root of the repo.
on:
pull_request:
push:
branches:
- v0.38.x
jobs:
golangci:
name: golangci-lint
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- name: Run linting
if: env.GIT_DIFF
run: |
make lint

33
.github/workflows/markdown-linter.yml vendored Normal file
View file

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

71
.github/workflows/pre-release.yml vendored Normal file
View file

@ -0,0 +1,71 @@
name: "Pre-release"
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+" # e.g. v0.37.0-alpha.1, v0.38.0-alpha.10
- "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" # e.g. v0.37.0-beta.1, v0.38.0-beta.10
- "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" # e.g. v0.37.0-rc1, v0.38.0-rc10
jobs:
prerelease:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: '1.22'
# Similar check to ./release-version.yml, but enforces this when pushing
# tags. The ./release-version.yml check can be bypassed and is mainly
# present for informational purposes.
- name: Check release version
run: |
# We strip the refs/tags/v prefix of the tag name.
TAG_VERSION=${GITHUB_REF#refs/tags/v}
# Get the version of the code, which has no "v" prefix.
CODE_VERSION=`go run ./cmd/cometbft/ version`
if [ "$TAG_VERSION" != "$CODE_VERSION" ]; then
echo ""
echo "Tag version ${TAG_VERSION} does not match code version ${CODE_VERSION}"
echo ""
echo "Please either fix the release tag or the version of the software in version/version.go."
exit 1
fi
- name: Generate release notes
run: |
VERSION="${GITHUB_REF#refs/tags/}"
CHANGELOG_URL="https://github.com/cometbft/cometbft/blob/${VERSION}/CHANGELOG.md"
echo "See the [CHANGELOG](${CHANGELOG_URL}) for changes available in this pre-release, but not yet officially released." > ../release_notes.md
- name: Release
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean --release-notes ../release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
prerelease-success:
needs: prerelease
if: ${{ success() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack upon pre-release
uses: slackapi/slack-github-action@v2.0.0
env:
RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
blocks:
- type: "section"
text:
type: "mrkdwn"
text: ":sparkles: New CometBFT pre-release: <${{ env.RELEASE_URL }}|${{ github.ref_name }}>"

21
.github/workflows/proto-lint.yml vendored Normal file
View file

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

33
.github/workflows/release-version.yml vendored Normal file
View file

@ -0,0 +1,33 @@
# Checks that, if we're working on a release branch and are about to cut a
# release, we have set the version correctly.
name: Check release version
on:
push:
branches:
- 'release/**'
jobs:
check-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Check version
run: |
# We strip the refs/heads/release/v prefix of the branch name.
BRANCH_VERSION=${GITHUB_REF#refs/heads/release/v}
# Get the version of the code, which has no "v" prefix.
CODE_VERSION=`go run ./cmd/cometbft/ version`
if [ "$BRANCH_VERSION" != "$CODE_VERSION" ]; then
echo ""
echo "Branch version ${BRANCH_VERSION} does not match code version ${CODE_VERSION}"
echo ""
echo "Please either fix the release branch naming (which must have a 'release/v' prefix)"
echo "or the version of the software in version/version.go."
exit 1
fi

72
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,72 @@
name: "Release"
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: '1.22'
# Similar check to ./release-version.yml, but enforces this when pushing
# tags. The ./release-version.yml check can be bypassed and is mainly
# present for informational purposes.
- name: Check release version
run: |
# We strip the refs/tags/v prefix of the tag name.
TAG_VERSION=${GITHUB_REF#refs/tags/v}
# Get the version of the code, which has no "v" prefix.
CODE_VERSION=`go run ./cmd/cometbft/ version`
if [ "$TAG_VERSION" != "$CODE_VERSION" ]; then
echo ""
echo "Tag version ${TAG_VERSION} does not match code version ${CODE_VERSION}"
echo ""
echo "Please either fix the release tag or the version of the software in version/version.go."
exit 1
fi
- name: Generate release notes
run: |
VERSION="${GITHUB_REF#refs/tags/}"
VERSION_REF="${VERSION//[\.]/}"
CHANGELOG_URL="https://github.com/cometbft/cometbft/blob/${VERSION}/CHANGELOG.md#${VERSION_REF}"
echo "See the [CHANGELOG](${CHANGELOG_URL}) for this release." > ../release_notes.md
- name: Release
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean --release-notes ../release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-success:
needs: release
if: ${{ success() }}
runs-on: ubuntu-latest
steps:
- name: Notify Slack upon release
uses: slackapi/slack-github-action@v2.0.0
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
blocks:
- type: "section"
text:
type: "mrkdwn"
text: ":rocket: New CometBFT release: <${{ env.RELEASE_URL }}|${{ github.ref_name }}>"

20
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: "Close stale pull requests"
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-pr-message: "This pull request has been automatically marked as stale because it has not had
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: "wip"

33
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Test
on:
pull_request:
push:
paths:
- "**.go"
branches:
- v0.38.x
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
part: ["00", "01", "02", "03", "04", "05"]
steps:
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: actions/checkout@v4
- uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
**/**.go
"!test/"
go.mod
go.sum
Makefile
- name: Run Go Tests
run: |
make test-group-${{ matrix.part }} NUM_SPLIT=6
if: env.GIT_DIFF

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
build/
*.test
*.bench
.DS_Store
*.log

167
.golangci.yml Normal file
View file

@ -0,0 +1,167 @@
version: "2"
linters:
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- dupl
- goconst
- misspell
- nakedret
- nolintlint
- prealloc
- staticcheck
- unconvert
settings:
depguard:
rules:
main:
files:
- $all
- '!$test'
allow:
- $gostd
- github.com/cometbft
- github.com/cosmos
- github.com/btcsuite/btcd/btcec/v2
- github.com/BurntSushi/toml
- github.com/go-git/go-git/v5
- github.com/go-kit
- github.com/go-logfmt/logfmt
- github.com/gofrs/uuid
- github.com/google
- github.com/gorilla/websocket
- github.com/informalsystems/tm-load-test/pkg/loadtest
- github.com/hashicorp/golang-lru/v2
- github.com/lib/pq
- github.com/libp2p/go-buffer-pool
- github.com/Masterminds/semver/v3
- github.com/minio/highwayhash
- github.com/oasisprotocol/curve25519-voi
- github.com/pkg/errors
- github.com/prometheus
- github.com/rcrowley/go-metrics
- github.com/rs/cors
- github.com/snikch/goodman
- github.com/spf13
- github.com/stretchr/testify/require
- github.com/syndtr/goleveldb
- github.com/decred/dcrd/dcrec/secp256k1/v4
- google.golang.org/grpc
- google.golang.org/protobuf/proto
- golang.org/x/sync
- golang.org/x/crypto
- golang.org/x/net
- gonum.org/v1/gonum/stat
- google.golang.org/protobuf/types/known/timestampp
test:
files:
- $test
allow:
- $gostd
- github.com/cosmos
- github.com/cometbft
- github.com/adlio/schema
- github.com/btcsuite/btcd
- github.com/fortytw2/leaktest
- github.com/go-kit
- github.com/google/uuid
- github.com/gorilla/websocket
- github.com/lib/pq
- github.com/oasisprotocol/curve25519-voi/primitives/merlin
- github.com/ory/dockertest
- github.com/pkg/errors
- github.com/prometheus/client_golang/prometheus/promhttp
- github.com/spf13
- github.com/stretchr/testify
- github.com/decred/dcrd/dcrec/secp256k1/v4
- google.golang.org/grpc
- google.golang.org/protobuf/proto
- google.golang.org/protobuf/types/known/timestampp
- gonum.org/v1/gonum/stat
- golang.org/x/sync
- golang.org/x/crypto
- golang.org/x/net
dogsled:
max-blank-identifiers: 3
gosec:
excludes:
- G115
misspell:
locale: US
revive:
enable-all-rules: true
rules:
- name: comment-spacings
disabled: true
- name: max-public-structs
disabled: true
- name: cognitive-complexity
disabled: true
- name: argument-limit
disabled: true
- name: cyclomatic
disabled: true
- name: deep-exit
disabled: true
- name: file-header
disabled: true
- name: function-length
disabled: true
- name: function-result-limit
disabled: true
- name: line-length-limit
disabled: true
- name: flag-parameter
disabled: true
- name: add-constant
disabled: true
- name: empty-lines
disabled: true
- name: import-shadowing
disabled: true
- name: modifies-value-receiver
disabled: true
- name: confusing-naming
disabled: true
- name: defer
disabled: true
- name: unchecked-type-assertion
disabled: true
- name: unhandled-error
arguments:
- fmt.Printf
- fmt.Print
- fmt.Println
disabled: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- gosec
path: _test\.go
- linters:
- goconst
path: (.+)_test\.go
paths:
- third_party$
- builtin$
- examples$
issues:
max-same-issues: 50
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

37
.goreleaser.yml Normal file
View file

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

17
.markdownlint.yml Normal file
View file

@ -0,0 +1,17 @@
# markdownlint configuration
# https://github.com/DavidAnson/markdownlint
# Default state for all rules
default: true
# See https://github.com/DavidAnson/markdownlint#rules--aliases for rules
MD001: false
MD007: {indent: 4}
MD013: false
MD024: {siblings_only: true}
MD025: false
MD033: false
MD036: false
MD010: false
MD012: false
MD028: false

6
.markdownlintignore Normal file
View file

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

17
.md-link-check.json Normal file
View file

@ -0,0 +1,17 @@
{
"retryOn429": true,
"retryCount": 5,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [200, 206, 503],
"httpHeaders": [
{
"urls": [
"https://docs.github.com/",
"https://help.github.com/"
],
"headers": {
"Accept-Encoding": "zstd, br, gzip, deflate"
}
}
]
}

4
.mockery.yml Normal file
View file

@ -0,0 +1,4 @@
issue-845-fix: True
resolve-type-alias: False
case: underscore # deprecated
replace-type: github.com/cometbft/cometbft/p2p/internal/nodekey.ID=github.com/cometbft/cometbft/p2p.ID

932
CHANGELOG.md Normal file
View file

@ -0,0 +1,932 @@
# CHANGELOG
## UNRELEASED
### DEPENDENCIES
### BUG FIXES
### IMPROVEMENTS
- `[statesync]` Add configurable `max-snapshot-chunks` parameter to validate max amount of chunks in a `SnapshotResponse`.
([\#5548](https://github.com/cometbft/cometbft/pull/5548))
### FEATURES
### BUG-FIXES
### STATE-BREAKING
### API-BREAKING
## v0.38.19
*October 14, 2025*
This release fixes two security issues, including ([ASA-2025-003](https://github.com/cometbft/cometbft/security/advisories/GHSA-hrhf-2vcr-ghch)).
Users are encouraged to upgrade as soon as possible.
Additionally included is a bug fix to properly prune extended commits (with
vote extensions).
### BUG-FIXES
- `[consensus]` Reject oversized proposals
([\#5324](https://github.com/cometbft/cometbft/pull/5324))
- `[store]` Prune extended commits properly
([5275](https://github.com/cometbft/cometbft/issues/5275))
- `[bits]` Validate BitArray mismatched Bits and Elems length
([ASA-2025-003](https://github.com/cometbft/cometbft/security/advisories/GHSA-hrhf-2vcr-ghch))
## v0.38.18
*July 3, 2025*
Adds precommit metrics and reindex CLI command.
### IMPROVEMENTS
- Adds metrics that emit precommit data; precommit quorum delay from proposal, and precommit vote count and stake weight within timeout commit period.
([\#5251](https://github.com/cometbft/cometbft/issues/5251))
## v0.38.17
*February 3, 2025*
This release fixes two security issues (ASA-2025-001, ASA-2025-002). Users are
encouraged to upgrade as soon as possible.
### BUG FIXES
- `[blocksync]` Ban peer if it reports height lower than what was previously reported
([ASA-2025-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-22qq-3xwm-r5x4))
- `[types]` Check that `Part.Index` equals `Part.Proof.Index`
([ASA-2025-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-r3r4-g7hq-pq4f))
### DEPENDENCIES
- `[go/runtime]` Bump minimum Go version to 1.22.11
([\#4891](https://github.com/cometbft/cometbft/pull/4891))
## v0.38.16
*December 20 2024*
This release:
- fixes a bug that caused a node produce errors caused by the sending of next PEX requests too soon.
As a consequence of this incorrect behavior a node would be marked as BAD.
- Adds a proper description of `ExtendedVoteInfo` and `VoteInfo` in the spec.
### BUG FIXES
- `[mocks]` Mockery `v2.49.0` broke the mocks. We had to add a `.mockery.yaml` to
properly handle this change.
([\#4521](https://github.com/cometbft/cometbft/pull/4521))
## v0.38.15
*November 6, 2024*
This release supersedes [`v0.38.14`](#v03814), which mistakenly updated the Go version to
`1.23`, introducing an unintended breaking change. It sets the Go version back
to `1.22.7` by reverting [\#4297](https://github.com/cometbft/cometbft/pull/4297).
The release includes the bug fixes, performance improvements, and importantly,
the fix for the security vulnerability in the vote extensions (VE) validation
logic that were part of `v0.38.14`. For more details, please refer to [ASA-2024-011](https://github.com/cometbft/cometbft/security/advisories/GHSA-p7mv-53f2-4cwj).
## v0.38.14
*November 6, 2024*
This release fixes a security vulnerability in the vote extensions (VE)
validation logic. For more details, please refer to
[ASA-2024-011](https://github.com/cometbft/cometbft/security/advisories/GHSA-p7mv-53f2-4cwj).
We recommend upgrading ASAP if youre using vote extensions (VE).
### BUG FIXES
- `[consensus]` Do not panic if the validator index of a `Vote` message is out
of bounds, when vote extensions are enabled
([\#ABC-0021](https://github.com/cometbft/cometbft/security/advisories/GHSA-p7mv-53f2-4cwj))
### DEPENDENCIES
- Bump cometbft-db version to v0.15.0
([\#4297](https://github.com/cometbft/cometbft/pull/4297))
- `[go/runtime]` Bump Go version to 1.23
([\#4297](https://github.com/cometbft/cometbft/pull/4297))
### IMPROVEMENTS
- `[p2p]` fix exponential backoff logic to increase reconnect retries close to 24 hours
([\#3519](https://github.com/cometbft/cometbft/issues/3519))
## v0.38.13
*October 24, 2024*
This patch release addresses the issue where tx_search was not returning all results, which only arises when upgrading
to CometBFT-DB version 0.13 or later. It includes a fix in the state indexer to resolve this problem. We recommend
upgrading to this patch release if you are affected by this issue.
### BUG FIXES
- `[metrics]` Call unused `rejected_txs` metric in mempool
([\#4019](https://github.com/cometbft/cometbft/pull/4019))
- `[state/indexer]` Fix the tx_search results not returning all results by changing the logic in the indexer to copy the key and values instead of reusing an iterator. This issue only arises when upgrading to cometbft-db v0.13 or later.
([\#4295](https://github.com/cometbft/cometbft/issues/4295)). Special thanks to @faddat for reporting the issue.
### DEPENDENCIES
- `[go/runtime]` Bump Go version to 1.22
([\#4073](https://github.com/cometbft/cometbft/pull/4073))
- Bump cometbft-db version to v0.14.1
([\#4321](https://github.com/cometbft/cometbft/pull/4321))
### FEATURES
- `[crypto]` use decred secp256k1 directly ([#4294](https://github.com/cometbft/cometbft/pull/4294))
### IMPROVEMENTS
- `[metrics]` Add `evicted_txs` metric to mempool
([\#4019](https://github.com/cometbft/cometbft/pull/4019))
- `[log]` Change "mempool is full" log to debug level
([\#4123](https://github.com/cometbft/cometbft/pull/4123)) Special thanks to @yihuang.
## v0.38.12
*September 3, 2024*
This release includes a security fix for the light client and is recommended
for all users.
### BUG FIXES
- `[light]` Cross-check proposer priorities in retrieved validator sets
([\#ASA-2024-009](https://github.com/cometbft/cometbft/security/advisories/GHSA-g5xx-c4hv-9ccc))
- `[privval]` Ignore duplicate privval listen when already connected ([\#3828](https://github.com/cometbft/cometbft/issues/3828)
### DEPENDENCIES
- `[crypto/secp256k1]` Adjust to breaking interface changes in
`btcec/v2` latest release, while avoiding breaking changes to
local CometBFT functions
([\#3728](https://github.com/cometbft/cometbft/pull/3728))
- pinned mockery's version to v2.49.2 to prevent potential
changes in mocks after each new release of mockery
([\#4605](https://github.com/cometbft/cometbft/pull/4605))
### IMPROVEMENTS
- `[types]` Check that proposer is one of the validators in `ValidateBasic`
([\#ASA-2024-009](https://github.com/cometbft/cometbft/security/advisories/GHSA-g5xx-c4hv-9ccc))
- `[e2e]` Add `log_level` option to manifest file
([#3819](https://github.com/cometbft/cometbft/pull/3819)).
- `[e2e]` Add `log_format` option to manifest file
([#3836](https://github.com/cometbft/cometbft/issues/3836)).
## v0.38.11
*August 12, 2024*
This release fixes a panic in consensus where CometBFT would previously panic
if there's no extension signature in non-nil Precommit EVEN IF vote extensions
themselves are disabled.
It also includes a few other bug fixes and performance improvements.
### BUG FIXES
- `[types]` Only check IFF vote is a non-nil Precommit if extensionsEnabled
types ([\#3565](https://github.com/cometbft/cometbft/issues/3565))
### IMPROVEMENTS
- `[indexer]` Fixed ineffective select break statements; they now
point to their enclosing for loop label to exit
([\#3544](https://github.com/cometbft/cometbft/issues/3544))
## v0.38.10
*July 16, 2024*
This release fixes a bug in `v0.38.x` that prevented ABCI responses from being
correctly read when upgrading from `v0.37.x` or below. It also includes a few other
bug fixes and performance improvements.
### BUG FIXES
- `[p2p]` Node respects configured `max_num_outbound_peers` limit when dialing
peers provided by a seed node
([\#486](https://github.com/cometbft/cometbft/issues/486))
- `[rpc]` Fix an issue where a legacy ABCI response, created on `v0.37` or before, is not returned properly in `v0.38` and up
on the `/block_results` RPC endpoint.
([\#3002](https://github.com/cometbft/cometbft/issues/3002))
- `[blocksync]` Do not stay in blocksync if the node's validator voting power
is high enough to block the chain while it is not online
([\#3406](https://github.com/cometbft/cometbft/pull/3406))
### IMPROVEMENTS
- `[p2p/conn]` Update send monitor, used for sending rate limiting, once per batch of packets sent
([\#3382](https://github.com/cometbft/cometbft/pull/3382))
- `[libs/pubsub]` Allow dash (`-`) in event tags
([\#3401](https://github.com/cometbft/cometbft/issues/3401))
- `[p2p/conn]` Remove the usage of a synchronous pool of buffers in secret connection, storing instead the buffer in the connection struct. This reduces the synchronization primitive usage, speeding up the code.
([\#3403](https://github.com/cometbft/cometbft/issues/3403))
## v0.38.9
*July 1, 2024*
This release reverts the API-breaking change to the Mempool interface introduced in the last patch
release (v0.38.8) while still keeping the performance improvement added to the mempool. It also
includes a minor fix to the RPC endpoints /tx and /tx_search.
### BREAKING CHANGES
- `[mempool]` Revert adding the method `PreUpdate()` to the `Mempool` interface, recently introduced
in the previous patch release (v0.38.8). Its logic is now moved into the `Lock` method. With this change,
the `Mempool` interface is the same as in v0.38.7.
([\#3361](https://github.com/cometbft/cometbft/pull/3361))
### BUG FIXES
- `[rpc]` Fix nil pointer error in `/tx` and `/tx_search` when block is
absent ([\#3352](https://github.com/cometbft/cometbft/issues/3352))
## v0.38.8
*June 27, 2024*
This release contains a few bug fixes and performance improvements.
### BREAKING CHANGES
- `[mempool]` Add to the `Mempool` interface a new method `PreUpdate()`. This method should be
called before acquiring the mempool lock, to signal that a new update is coming. Also add to
`ErrMempoolIsFull` a new field `RecheckFull`.
([\#3314](https://github.com/cometbft/cometbft/pull/3314))
### BUG FIXES
- `[blockstore]` Added peer banning in blockstore
([\#ABC-0013](https://github.com/cometbft/cometbft/security/advisories/GHSA-hg58-rf2h-6rr7))
- `[blockstore]` Send correct error message when vote extensions do not align with received packet
([\#ABC-0014](https://github.com/cometbft/cometbft/security/advisories/GHSA-hg58-rf2h-6rr7))
- [`mempool`] Fix data race when rechecking with async ABCI client
([\#1827](https://github.com/cometbft/cometbft/issues/1827))
- `[consensus]` Fix a race condition in the consensus timeout ticker. Race is caused by two timeouts being scheduled at the same time.
([\#3092](https://github.com/cometbft/cometbft/pull/2136))
- `[types]` Do not batch verify a commit if the validator set keys have different
types. ([\#3195](https://github.com/cometbft/cometbft/issues/3195)
### IMPROVEMENTS
- `[config]` Added `recheck_timeout` mempool parameter to set how much time to wait for recheck
responses from the app (only applies to non-local ABCI clients).
([\#1827](https://github.com/cometbft/cometbft/issues/1827/))
- `[rpc]` Add a configurable maximum batch size for RPC requests.
([\#2867](https://github.com/cometbft/cometbft/pull/2867)).
- `[event-bus]` Remove the debug logs in PublishEventTx, which were noticed production slowdowns.
([\#2911](https://github.com/cometbft/cometbft/pull/2911))
- `[state/execution]` Cache the block hash computation inside of the Block Type, so we only compute it once.
([\#2924](https://github.com/cometbft/cometbft/pull/2924))
- `[consensus/state]` Remove a redundant `VerifyBlock` call in `FinalizeCommit`
([\#2928](https://github.com/cometbft/cometbft/pull/2928))
- `[p2p/channel]` Speedup `ProtoIO` writer creation time, and thereby speedup channel writing by 5%.
([\#2949](https://github.com/cometbft/cometbft/pull/2949))
- `[p2p/conn]` Minor speedup (3%) to connection.WritePacketMsgTo, by removing MinInt calls.
([\#2952](https://github.com/cometbft/cometbft/pull/2952))
- `[internal/bits]` 10x speedup creating initialized bitArrays, which speedsup extendedCommit.BitArray(). This is used in consensus vote gossip.
([\#2959](https://github.com/cometbft/cometbft/pull/2841)).
- `[blockstore]` Remove a redundant `Header.ValidateBasic` call in `LoadBlockMeta`, 75% reducing this time.
([\#2964](https://github.com/cometbft/cometbft/pull/2964))
- `[p2p/conn]` Speedup connection.WritePacketMsgTo, by reusing internal buffers rather than re-allocating.
([\#2986](https://github.com/cometbft/cometbft/pull/2986))
- [`blockstore`] Use LRU caches in blockstore, significiantly improving consensus gossip routine performance
([\#3003](https://github.com/cometbft/cometbft/issues/3003)
- [`consensus`] Improve performance of consensus metrics by lowering string operations
([\#3017](https://github.com/cometbft/cometbft/issues/3017)
- [`protoio`] Remove one allocation and new object call from `ReadMsg`,
leading to a 4% p2p message reading performance gain.
([\#3018](https://github.com/cometbft/cometbft/issues/3018)
- `[mempool]` Before updating the mempool, consider it as full if rechecking is still in progress.
This will stop accepting transactions in the mempool if the node can't keep up with re-CheckTx.
([\#3314](https://github.com/cometbft/cometbft/pull/3314))
## v0.38.7
*April 26, 2024*
This release contains a few bug fixes and performance improvements.
### BUG FIXES
- [`mempool`] Panic when a CheckTx request to the app returns an error
([\#2225](https://github.com/cometbft/cometbft/pull/2225))
- [`bits`] prevent `BitArray.UnmarshalJSON` from crashing on 0 bits
([\#2774](https://github.com/cometbft/cometbft/pull/2774))
### FEATURES
- [`node`] Add `BootstrapStateWithGenProvider` to boostrap state using a custom
genesis doc provider ([\#2793](https://github.com/cometbft/cometbft/pull/2793))
### IMPROVEMENTS
- `[state/indexer]` Lower the heap allocation of transaction searches
([\#2839](https://github.com/cometbft/cometbft/pull/2839))
- `[internal/bits]` 10x speedup and remove heap overhead of bitArray.PickRandom (used extensively in consensus gossip)
([\#2841](https://github.com/cometbft/cometbft/pull/2841)).
- `[libs/json]` Lower the memory overhead of JSON encoding by using JSON encoders internally
([\#2846](https://github.com/cometbft/cometbft/pull/2846)).
## v0.38.6
*March 12, 2024*
This release fixes a security bug in the light client. It also introduces many
improvements to the block sync in collaboration with the
[Osmosis](https://osmosis.zone/) team.
### BUG FIXES
- `[privval]` Retry accepting a connection ([\#2047](https://github.com/cometbft/cometbft/pull/2047))
- `[state]` Fix rollback to a specific height
([\#2136](https://github.com/cometbft/cometbft/pull/2136))
### FEATURES
- `[e2e]` Add `block_max_bytes` option to the manifest file.
([\#2362](https://github.com/cometbft/cometbft/pull/2362))
### IMPROVEMENTS
- `[blocksync]` Avoid double-calling `types.BlockFromProto` for performance
reasons ([\#2016](https://github.com/cometbft/cometbft/pull/2016))
- `[e2e]` Add manifest option `load_max_txs` to limit the number of transactions generated by the
`load` command. ([\#2094](https://github.com/cometbft/cometbft/pull/2094))
- `[jsonrpc]` enable HTTP basic auth in websocket client ([#2434](https://github.com/cometbft/cometbft/pull/2434))
- `[blocksync]` make the max number of downloaded blocks dynamic.
Previously it was a const 600. Now it's `peersCount * maxPendingRequestsPerPeer (20)`
[\#2467](https://github.com/cometbft/cometbft/pull/2467)
- `[blocksync]` Request a block from peer B if we are approaching pool's height
(less than 50 blocks) and the current peer A is slow in sending us the
block [\#2475](https://github.com/cometbft/cometbft/pull/2475)
- `[blocksync]` Request the block N from peer B immediately after getting
`NoBlockResponse` from peer A
[\#2475](https://github.com/cometbft/cometbft/pull/2475)
- `[blocksync]` Sort peers by download rate (the fastest peer is picked first)
[\#2475](https://github.com/cometbft/cometbft/pull/2475)
## v0.38.5
*January 24, 2024*
This release fixes a problem introduced in `v0.38.3`: if an application
updates the value of ConsensusParam `VoteExtensionsEnableHeight` to the same value
(actually a "noop" update) this is accepted in `v0.38.2` but rejected under some
conditions in `v0.38.3` and `v0.38.4`. Even if rejecting a useless update would make sense
in general, in a point release we should not reject a set of inputs to
a function that was previuosly accepted (unless there is a good reason
for it). The goal of this release is to accept again all "noop" updates, like `v0.38.2` did.
### IMPROVEMENTS
- `[consensus]` Add `chain_size_bytes` metric for measuring the size of the blockchain in bytes
([\#2093](https://github.com/cometbft/cometbft/pull/2093))
## v0.38.4
*January 22, 2024*
This release is aimed at those projects that have a dependency on CometBFT,
release line `v0.38.x`, and make use of function `SaveBlockStoreState` in package
`github.com/cometbft/cometbft/store`. This function changed its signature in `v0.38.3`.
This new release reverts the signature change so that upgrading to the latest release
of CometBFT on `v0.38.x` does not require any change in the code depending on CometBFT.
### IMPROVEMENTS
- `[e2e]` Add manifest option `VoteExtensionsUpdateHeight` to test
vote extension activation via `InitChain` and `FinalizeBlock`.
Also, extend the manifest generator to produce different values
of this new option
([\#2065](https://github.com/cometbft/cometbft/pull/2065))
## v0.38.3
*January 17, 2024*
This release addresses a high impact security issue reported in advisory
([ASA-2024-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-qr8r-m495-7hc4)).
There are other non-security bugs fixes that have been addressed since
`v0.38.2` was released, as well as some improvements.
Please check the list below for further details.
### BUG FIXES
- `[consensus]` Fix for "Validation of `VoteExtensionsEnableHeight` can cause chain halt"
([ASA-2024-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-qr8r-m495-7hc4))
- `[mempool]` Fix data races in `CListMempool` by making atomic the types of `height`, `txsBytes`, and
`notifiedTxsAvailable`. ([\#642](https://github.com/cometbft/cometbft/pull/642))
- `[mempool]` The calculation method of tx size returned by calling proxyapp should be consistent with that of mempool
([\#1687](https://github.com/cometbft/cometbft/pull/1687))
- `[evidence]` When `VerifyCommitLight` & `VerifyCommitLightTrusting` are called as part
of evidence verification, all signatures present in the evidence must be verified
([\#1749](https://github.com/cometbft/cometbft/pull/1749))
- `[crypto]` `SupportsBatchVerifier` returns false
if public key is nil instead of dereferencing nil.
([\#1825](https://github.com/cometbft/cometbft/pull/1825))
- `[blocksync]` wait for `poolRoutine` to stop in `(*Reactor).OnStop`
([\#1879](https://github.com/cometbft/cometbft/pull/1879))
### IMPROVEMENTS
- `[types]` Validate `Validator#Address` in `ValidateBasic` ([\#1715](https://github.com/cometbft/cometbft/pull/1715))
- `[abci]` Increase ABCI socket message size limit to 2GB ([\#1730](https://github.com/cometbft/cometbft/pull/1730): @troykessler)
- `[state]` Save the state using a single DB batch ([\#1735](https://github.com/cometbft/cometbft/pull/1735))
- `[store]` Save block using a single DB batch if block is less than 640kB, otherwise each block part is saved individually
([\#1755](https://github.com/cometbft/cometbft/pull/1755))
- `[rpc]` Support setting proxy from env to `DefaultHttpClient`.
([\#1900](https://github.com/cometbft/cometbft/pull/1900))
- `[rpc]` Use default port for HTTP(S) URLs when there is no explicit port ([\#1903](https://github.com/cometbft/cometbft/pull/1903))
- `[crypto/merkle]` faster calculation of hashes ([#1921](https://github.com/cometbft/cometbft/pull/1921))
## v0.38.2
*November 27, 2023*
This release provides the **nop** mempool for applications that want to build their own mempool.
Using this mempool effectively disables all mempool functionality in CometBFT, including transaction dissemination and the `broadcast_tx_*` endpoints.
Also fixes a small bug in the mempool for an experimental feature.
### BUG FIXES
- `[mempool]` Avoid infinite wait in transaction sending routine when
using experimental parameters to limiting transaction gossiping to peers
([\#1654](https://github.com/cometbft/cometbft/pull/1654))
### FEATURES
- `[mempool]` Add `nop` mempool ([\#1643](https://github.com/cometbft/cometbft/pull/1643))
If you want to use it, change mempool's `type` to `nop`:
```toml
[mempool]
# The type of mempool for this node to use.
#
# Possible types:
# - "flood" : concurrent linked list mempool with flooding gossip protocol
# (default)
# - "nop" : nop-mempool (short for no operation; the ABCI app is responsible
# for storing, disseminating and proposing txs). "create_empty_blocks=false"
# is not supported.
type = "nop"
```
## v0.38.1
*November 17, 2023*
This release contains, among other things, an opt-in, experimental feature to
help reduce the bandwidth consumption associated with the mempool's transaction
gossip.
### BUG FIXES
- `[state/indexer]` Respect both height params while querying for events
([\#1529](https://github.com/cometbft/cometbft/pull/1529))
### FEATURES
- `[metrics]` Add metric for mempool size in bytes `SizeBytes`.
([\#1512](https://github.com/cometbft/cometbft/pull/1512))
### IMPROVEMENTS
- `[mempool]` Add experimental feature to limit the number of persistent peers and non-persistent
peers to which the node gossip transactions.
([\#1558](https://github.com/cometbft/cometbft/pull/1558))
([\#1584](https://github.com/cometbft/cometbft/pull/1584))
- `[config]` Add mempool parameters `experimental_max_gossip_connections_to_persistent_peers` and
`experimental_max_gossip_connections_to_non_persistent_peers` for limiting the number of peers to
which the node gossip transactions.
([\#1558](https://github.com/cometbft/cometbft/pull/1558))
([\#1584](https://github.com/cometbft/cometbft/pull/1584))
## v0.38.0
*September 12, 2023*
This release includes the second part of ABCI++, called ABCI 2.0.
ABCI 2.0 introduces ABCI methods `ExtendVote` and `VerifyVoteExtension`.
These new methods allow the application to add data (opaque to CometBFT),
called _vote extensions_ to precommit votes sent by validators.
These vote extensions are made available to the proposer(s) of the next height.
Additionally, ABCI 2.0 coalesces `BeginBlock`, `DeliverTx`, and `EndBlock`
into one method, `FinalizeBlock`, whose `Request*` and `Response*`
data structures contain the sum of all data previously contained
in the respective `Request*` and `Response*` data structures in
`BeginBlock`, `DeliverTx`, and `EndBlock`.
See the [specification](./spec/abci/) for more details on ABCI 2.0.
### BREAKING CHANGES
- `[mempool]` Remove priority mempool.
([\#260](https://github.com/cometbft/cometbft/issues/260))
- `[config]` Remove `Version` field from `MempoolConfig`.
([\#260](https://github.com/cometbft/cometbft/issues/260))
- `[protobuf]` Remove fields `sender`, `priority`, and `mempool_error` from
`ResponseCheckTx`. ([\#260](https://github.com/cometbft/cometbft/issues/260))
- `[crypto/merkle]` Do not allow verification of Merkle Proofs against empty trees (`nil` root). `Proof.ComputeRootHash` now panics when it encounters an error, but `Proof.Verify` does not panic
([\#558](https://github.com/cometbft/cometbft/issues/558))
- `[state/kvindexer]` Remove the function type from the event key stored in the database. This should be breaking only
for people who forked CometBFT and interact directly with the indexers kvstore.
([\#774](https://github.com/cometbft/cometbft/pull/774))
- `[rpc]` Removed `begin_block_events` and `end_block_events` from `BlockResultsResponse`.
The events are merged into one field called `finalize_block_events`.
([\#9427](https://github.com/tendermint/tendermint/issues/9427))
- `[pubsub]` Added support for big integers and big floats in the pubsub event query system.
Breaking changes: function `Number` in package `libs/pubsub/query/syntax` changed its return value.
([\#797](https://github.com/cometbft/cometbft/pull/797))
- `[kvindexer]` Added support for big integers and big floats in the kvindexer.
Breaking changes: function `Number` in package `libs/pubsub/query/syntax` changed its return value.
([\#797](https://github.com/cometbft/cometbft/pull/797))
- `[mempool]` Application can now set `ConsensusParams.Block.MaxBytes` to -1
to have visibility on all transactions in the
mempool at `PrepareProposal` time.
This means that the total size of transactions sent via `RequestPrepareProposal`
might exceed `RequestPrepareProposal.max_tx_bytes`.
If that is the case, the application MUST make sure that the total size of transactions
returned in `ResponsePrepareProposal.txs` does not exceed `RequestPrepareProposal.max_tx_bytes`,
otherwise CometBFT will panic.
([\#980](https://github.com/cometbft/cometbft/issues/980))
- `[node/state]` Add Go API to bootstrap block store and state store to a height. Make sure block sync starts syncing from bootstrapped height.
([\#1057](https://github.com/tendermint/tendermint/pull/#1057)) (@yihuang)
- `[state/store]` Added Go functions to save height at which offline state sync is performed.
([\#1057](https://github.com/tendermint/tendermint/pull/#1057)) (@jmalicevic)
- `[p2p]` Remove UPnP functionality
([\#1113](https://github.com/cometbft/cometbft/issues/1113))
- `[node]` Removed `ConsensusState()` accessor from `Node`
struct - all access to consensus state should go via the reactor
([\#1120](https://github.com/cometbft/cometbft/pull/1120))
- `[state]` Signature of `ExtendVote` changed in `BlockExecutor`.
It now includes the block whose precommit will be extended, an the state object.
([\#1270](https://github.com/cometbft/cometbft/pull/1270))
- `[state]` Move pruneBlocks from node/state to state/execution.
([\#6541](https://github.com/tendermint/tendermint/pull/6541))
- `[abci]` Move `app_hash` parameter from `Commit` to `FinalizeBlock`
([\#8664](https://github.com/tendermint/tendermint/pull/8664))
- `[abci]` Introduce `FinalizeBlock` which condenses `BeginBlock`, `DeliverTx`
and `EndBlock` into a single method call
([\#9468](https://github.com/tendermint/tendermint/pull/9468))
- `[p2p]` Remove unused p2p/trust package
([\#9625](https://github.com/tendermint/tendermint/pull/9625))
- `[rpc]` Remove global environment and replace with constructor
([\#9655](https://github.com/tendermint/tendermint/pull/9655))
- `[node]` Move DBContext and DBProvider from the node package to the config
package. ([\#9655](https://github.com/tendermint/tendermint/pull/9655))
- `[inspect]` Add a new `inspect` command for introspecting
the state and block store of a crashed tendermint node.
([\#9655](https://github.com/tendermint/tendermint/pull/9655))
- `[metrics]` Move state-syncing and block-syncing metrics to
their respective packages. Move labels from block_syncing
-> blocksync_syncing and state_syncing -> statesync_syncing
([\#9682](https://github.com/tendermint/tendermint/pull/9682))
### BUG FIXES
- `[kvindexer]` Forward porting the fixes done to the kvindexer in 0.37 in PR \#77
([\#423](https://github.com/cometbft/cometbft/pull/423))
- `[consensus]` Unexpected error conditions in `ApplyBlock` are non-recoverable, so ignoring the error and carrying on is a bug. We replaced a `return` that disregarded the error by a `panic`.
([\#496](https://github.com/cometbft/cometbft/pull/496))
- `[consensus]` Rename `(*PeerState).ToJSON` to `MarshalJSON` to fix a logging data race
([\#524](https://github.com/cometbft/cometbft/pull/524))
- `[light]` Fixed an edge case where a light client would panic when attempting
to query a node that (1) has started from a non-zero height and (2) does
not yet have any data. The light client will now, correctly, not panic
_and_ keep the node in its list of providers in the same way it would if
it queried a node starting from height zero that does not yet have data
([\#575](https://github.com/cometbft/cometbft/issues/575))
- `[abci]` Restore the snake_case naming in JSON serialization of
`ExecTxResult` ([\#855](https://github.com/cometbft/cometbft/issues/855)).
- `[consensus]` Avoid recursive call after rename to (*PeerState).MarshalJSON
([\#863](https://github.com/cometbft/cometbft/pull/863))
- `[mempool/clist_mempool]` Prevent a transaction to appear twice in the mempool
([\#890](https://github.com/cometbft/cometbft/pull/890): @otrack)
- `[docker]` Ensure Docker image uses consistent version of Go.
([\#9462](https://github.com/tendermint/tendermint/pull/9462))
- `[abci-cli]` Fix broken abci-cli help command.
([\#9717](https://github.com/tendermint/tendermint/pull/9717))
### DEPRECATIONS
- `[rpc/grpc]` Mark the gRPC broadcast API as deprecated.
It will be superseded by a broader API as part of
[\#81](https://github.com/cometbft/cometbft/issues/81)
([\#650](https://github.com/cometbft/cometbft/issues/650))
### FEATURES
- `[node/state]` Add Go API to bootstrap block store and state store to a height
([\#1057](https://github.com/tendermint/tendermint/pull/#1057)) (@yihuang)
- `[proxy]` Introduce `NewConnSyncLocalClientCreator`, which allows local ABCI
clients to have the same concurrency model as remote clients (i.e. one mutex
per client "connection", for each of the four ABCI "connections").
([tendermint/tendermint\#9830](https://github.com/tendermint/tendermint/pull/9830)
and [\#1145](https://github.com/cometbft/cometbft/pull/1145))
- `[proxy]` Introduce `NewUnsyncLocalClientCreator`, which allows local ABCI
clients to have the same concurrency model as remote clients (i.e. one
mutex per client "connection", for each of the four ABCI "connections").
([\#9830](https://github.com/tendermint/tendermint/pull/9830))
- `[abci]` New ABCI methods `VerifyVoteExtension` and `ExtendVote` allow validators to validate the vote extension data attached to a pre-commit message and allow applications to let their validators do more than just validate within consensus ([\#9836](https://github.com/tendermint/tendermint/pull/9836))
### IMPROVEMENTS
- `[blocksync]` Generate new metrics during BlockSync
([\#543](https://github.com/cometbft/cometbft/pull/543))
- `[jsonrpc/client]` Improve the error message for client errors stemming from
bad HTTP responses.
([cometbft/cometbft\#638](https://github.com/cometbft/cometbft/pull/638))
- `[rpc]` Remove response data from response failure logs in order
to prevent large quantities of log data from being produced
([\#654](https://github.com/cometbft/cometbft/issues/654))
- `[pubsub/kvindexer]` Numeric query conditions and event values are represented as big floats with default precision of 125.
Integers are read as "big ints" and represented with as many bits as they need when converting to floats.
([\#797](https://github.com/cometbft/cometbft/pull/797))
- `[node]` Make handshake cancelable ([cometbft/cometbft\#857](https://github.com/cometbft/cometbft/pull/857))
- `[consensus]` New metrics (counters) to track duplicate votes and block parts.
([\#896](https://github.com/cometbft/cometbft/pull/896))
- `[mempool]` Application can now set `ConsensusParams.Block.MaxBytes` to -1
to gain more control on the max size of transactions in a block.
It also allows the application to have visibility on all transactions in the
mempool at `PrepareProposal` time.
([\#980](https://github.com/cometbft/cometbft/pull/980))
- `[node]` Close evidence.db OnStop ([cometbft/cometbft\#1210](https://github.com/cometbft/cometbft/pull/1210): @chillyvee)
- `[state]` Make logging `block_app_hash` and `app_hash` consistent by logging them both as hex.
([\#1264](https://github.com/cometbft/cometbft/pull/1264))
- `[crypto/merkle]` Improve HashAlternatives performance
([\#6443](https://github.com/tendermint/tendermint/pull/6443))
- `[p2p/pex]` Improve addrBook.hash performance
([\#6509](https://github.com/tendermint/tendermint/pull/6509))
- `[crypto/merkle]` Improve HashAlternatives performance
([\#6513](https://github.com/tendermint/tendermint/pull/6513))
- `[pubsub]` Performance improvements for the event query API
([\#7319](https://github.com/tendermint/tendermint/pull/7319))
## v0.37.0
*March 6, 2023*
This is the first CometBFT release with ABCI 1.0, which introduces the
`PrepareProposal` and `ProcessProposal` methods, with the aim of expanding the
range of use cases that application developers can address. This is the first
change to ABCI towards ABCI++, and the full range of ABCI++ functionality will
only become available in the next major release with ABCI 2.0. See the
[specification](./spec/abci/) for more details.
In the v0.34.27 release, the CometBFT Go module is still
`github.com/tendermint/tendermint` to facilitate ease of upgrading for users,
but in this release we have changed this to `github.com/cometbft/cometbft`.
Please also see our [upgrading guidelines](./UPGRADING.md) for more details on
upgrading from the v0.34 release series.
Also see our [QA results](https://docs.cometbft.com/v0.37/qa/v037/cometbft) for
the v0.37 release.
We'd love your feedback on this release! Please reach out to us via one of our
communication channels, such as [GitHub
Discussions](https://github.com/cometbft/cometbft/discussions), with any of your
questions, comments and/or concerns.
See below for more details.
### BREAKING CHANGES
- The `TMHOME` environment variable was renamed to `CMTHOME`, and all environment variables starting with `TM_` are instead prefixed with `CMT_`
([\#211](https://github.com/cometbft/cometbft/issues/211))
- `[p2p]` Reactor `Send`, `TrySend` and `Receive` renamed to `SendEnvelope`,
`TrySendEnvelope` and `ReceiveEnvelope` to allow metrics to be appended to
messages and measure bytes sent/received.
([\#230](https://github.com/cometbft/cometbft/pull/230))
- Bump minimum Go version to 1.20
([\#385](https://github.com/cometbft/cometbft/issues/385))
- [config] The boolean key `fastsync` is deprecated and replaced by
`block_sync`. ([\#9259](https://github.com/tendermint/tendermint/pull/9259))
At the same time, `block_sync` is also deprecated. In the next release,
BlocSync will always be enabled and `block_sync` will be removed.
([\#409](https://github.com/cometbft/cometbft/issues/409))
- `[abci]` Make length delimiter encoding consistent
(`uint64`) between ABCI and P2P wire-level protocols
([\#5783](https://github.com/tendermint/tendermint/pull/5783))
- `[abci]` Change the `key` and `value` fields from
`[]byte` to `string` in the `EventAttribute` type.
([\#6403](https://github.com/tendermint/tendermint/pull/6403))
- `[abci/counter]` Delete counter example app
([\#6684](https://github.com/tendermint/tendermint/pull/6684))
- `[abci]` Renamed `EvidenceType` to `MisbehaviorType` and `Evidence`
to `Misbehavior` as a more accurate label of their contents.
([\#8216](https://github.com/tendermint/tendermint/pull/8216))
- `[abci]` Added cli commands for `PrepareProposal` and `ProcessProposal`.
([\#8656](https://github.com/tendermint/tendermint/pull/8656))
- `[abci]` Added cli commands for `PrepareProposal` and `ProcessProposal`.
([\#8901](https://github.com/tendermint/tendermint/pull/8901))
- `[abci]` Renamed `LastCommitInfo` to `CommitInfo` in preparation for vote
extensions. ([\#9122](https://github.com/tendermint/tendermint/pull/9122))
- Change spelling from British English to American. Rename
`Subscription.Cancelled()` to `Subscription.Canceled()` in `libs/pubsub`
([\#9144](https://github.com/tendermint/tendermint/pull/9144))
- `[abci]` Removes unused Response/Request `SetOption` from ABCI
([\#9145](https://github.com/tendermint/tendermint/pull/9145))
- `[config]` Rename the fastsync section and the
fast\_sync key blocksync and block\_sync respectively
([\#9259](https://github.com/tendermint/tendermint/pull/9259))
- `[types]` Reduce the use of protobuf types in core logic. `ConsensusParams`,
`BlockParams`, `ValidatorParams`, `EvidenceParams`, `VersionParams` have
become native types. They still utilize protobuf when being sent over
the wire or written to disk. Moved `ValidateConsensusParams` inside
(now native type) `ConsensusParams`, and renamed it to `ValidateBasic`.
([\#9287](https://github.com/tendermint/tendermint/pull/9287))
- `[abci/params]` Deduplicate `ConsensusParams` and `BlockParams` so
only `types` proto definitions are use. Remove `TimeIotaMs` and use
a hard-coded 1 millisecond value to ensure monotonically increasing
block times. Rename `AppVersion` to `App` so as to not stutter.
([\#9287](https://github.com/tendermint/tendermint/pull/9287))
- `[abci]` New ABCI methods `PrepareProposal` and `ProcessProposal` which give
the app control over transactions proposed and allows for verification of
proposed blocks. ([\#9301](https://github.com/tendermint/tendermint/pull/9301))
### BUG FIXES
- `[consensus]` Fixed a busy loop that happened when sending of a block part failed by sleeping in case of error.
([\#4](https://github.com/informalsystems/tendermint/pull/4))
- `[state/kvindexer]` Fixed the default behaviour of the kvindexer to index and
query attributes by events in which they occur. In 0.34.25 this was mitigated
by a separated RPC flag. @jmalicevic
([\#77](https://github.com/cometbft/cometbft/pull/77))
- `[state/kvindexer]` Resolved crashes when event values contained slashes,
introduced after adding event sequences in
[\#77](https://github.com/cometbft/cometbft/pull/77). @jmalicevic
([\#382](https://github.com/cometbft/cometbft/pull/382))
- `[consensus]` ([\#386](https://github.com/cometbft/cometbft/pull/386)) Short-term fix for the case when `needProofBlock` cannot find previous block meta by defaulting to the creation of a new proof block. (@adizere)
- Special thanks to the [Vega.xyz](https://vega.xyz/) team, and in particular to Zohar (@ze97286), for reporting the problem and working with us to get to a fix.
- `[docker]` enable cross platform build using docker buildx
([\#9073](https://github.com/tendermint/tendermint/pull/9073))
- `[consensus]` fix round number of `enterPropose`
when handling `RoundStepNewRound` timeout.
([\#9229](https://github.com/tendermint/tendermint/pull/9229))
- `[docker]` ensure Docker image uses consistent version of Go
([\#9462](https://github.com/tendermint/tendermint/pull/9462))
- `[p2p]` prevent peers who have errored from being added to `peer_set`
([\#9500](https://github.com/tendermint/tendermint/pull/9500))
- `[blocksync]` handle the case when the sending
queue is full: retry block request after a timeout
([\#9518](https://github.com/tendermint/tendermint/pull/9518))
### FEATURES
- `[abci]` New ABCI methods `PrepareProposal` and `ProcessProposal` which give
the app control over transactions proposed and allows for verification of
proposed blocks. ([\#9301](https://github.com/tendermint/tendermint/pull/9301))
### IMPROVEMENTS
- `[e2e]` Add functionality for uncoordinated (minor) upgrades
([\#56](https://github.com/tendermint/tendermint/pull/56))
- `[tools/tm-signer-harness]` Remove the folder as it is unused
([\#136](https://github.com/cometbft/cometbft/issues/136))
- `[p2p]` Reactor `Send`, `TrySend` and `Receive` renamed to `SendEnvelope`,
`TrySendEnvelope` and `ReceiveEnvelope` to allow metrics to be appended to
messages and measure bytes sent/received.
([\#230](https://github.com/cometbft/cometbft/pull/230))
- `[abci]` Added `AbciVersion` to `RequestInfo` allowing
applications to check ABCI version when connecting to CometBFT.
([\#5706](https://github.com/tendermint/tendermint/pull/5706))
- `[cli]` add `--hard` flag to rollback command (and a boolean to the `RollbackState` method). This will rollback
state and remove the last block. This command can be triggered multiple times. The application must also rollback
state to the same height.
([\#9171](https://github.com/tendermint/tendermint/pull/9171))
- `[crypto]` Update to use btcec v2 and the latest btcutil.
([\#9250](https://github.com/tendermint/tendermint/pull/9250))
- `[rpc]` Added `header` and `header_by_hash` queries to the RPC client
([\#9276](https://github.com/tendermint/tendermint/pull/9276))
- `[proto]` Migrate from `gogo/protobuf` to `cosmos/gogoproto`
([\#9356](https://github.com/tendermint/tendermint/pull/9356))
- `[rpc]` Enable caching of RPC responses
([\#9650](https://github.com/tendermint/tendermint/pull/9650))
- `[consensus]` Save peer LastCommit correctly to achieve 50% reduction in gossiped precommits.
([\#9760](https://github.com/tendermint/tendermint/pull/9760))
## v0.34.27
*Feb 27, 2023*
This is the first official release of CometBFT - a fork of [Tendermint
Core](https://github.com/tendermint/tendermint). This particular release is
intended to be compatible with the Tendermint Core v0.34 release series.
For details as to how to upgrade to CometBFT from Tendermint Core, please see
our [upgrading guidelines](./UPGRADING.md).
If you have any questions, comments, concerns or feedback on this release, we
would love to hear from you! Please contact us via [GitHub
Discussions](https://github.com/cometbft/cometbft/discussions),
[Discord](https://discord.gg/cosmosnetwork) (in the `#cometbft` channel) or
[Telegram](https://t.me/CometBFT).
Special thanks to @wcsiu, @ze97286, @faddat and @JayT106 for their contributions
to this release!
### BREAKING CHANGES
- Rename binary to `cometbft` and Docker image to `cometbft/cometbft`
([\#152](https://github.com/cometbft/cometbft/pull/152))
- The `TMHOME` environment variable was renamed to `CMTHOME`, and all
environment variables starting with `TM_` are instead prefixed with `CMT_`
([\#211](https://github.com/cometbft/cometbft/issues/211))
- Use Go 1.19 to build CometBFT, since Go 1.18 has reached end-of-life.
([\#360](https://github.com/cometbft/cometbft/issues/360))
### BUG FIXES
- `[consensus]` Fixed a busy loop that happened when sending of a block part
failed by sleeping in case of error.
([\#4](https://github.com/informalsystems/tendermint/pull/4))
- `[state/kvindexer]` Resolved crashes when event values contained slashes,
introduced after adding event sequences.
(\#[383](https://github.com/cometbft/cometbft/pull/383): @jmalicevic)
- `[consensus]` Short-term fix for the case when `needProofBlock` cannot find
previous block meta by defaulting to the creation of a new proof block.
([\#386](https://github.com/cometbft/cometbft/pull/386): @adizere)
- Special thanks to the [Vega.xyz](https://vega.xyz/) team, and in particular
to Zohar (@ze97286), for reporting the problem and working with us to get to
a fix.
- `[p2p]` Correctly use non-blocking `TrySendEnvelope` method when attempting to
send messages, as opposed to the blocking `SendEnvelope` method. It is unclear
whether this has a meaningful impact on P2P performance, but this patch does
correct the underlying behaviour to what it should be
([tendermint/tendermint\#9936](https://github.com/tendermint/tendermint/pull/9936))
### DEPENDENCIES
- Replace [tm-db](https://github.com/tendermint/tm-db) with
[cometbft-db](https://github.com/cometbft/cometbft-db)
([\#160](https://github.com/cometbft/cometbft/pull/160))
- Bump tm-load-test to v1.3.0 to remove implicit dependency on Tendermint Core
([\#165](https://github.com/cometbft/cometbft/pull/165))
- `[crypto]` Update to use btcec v2 and the latest btcutil
([tendermint/tendermint\#9787](https://github.com/tendermint/tendermint/pull/9787):
@wcsiu)
### FEATURES
- `[rpc]` Add `match_event` query parameter to indicate to the RPC that it
should match events _within_ attributes, not only within a height
([tendermint/tendermint\#9759](https://github.com/tendermint/tendermint/pull/9759))
### IMPROVEMENTS
- `[e2e]` Add functionality for uncoordinated (minor) upgrades
([\#56](https://github.com/tendermint/tendermint/pull/56))
- `[tools/tm-signer-harness]` Remove the folder as it is unused
([\#136](https://github.com/cometbft/cometbft/issues/136))
- Append the commit hash to the version of CometBFT being built
([\#204](https://github.com/cometbft/cometbft/pull/204))
- `[mempool/v1]` Suppress "rejected bad transaction" in priority mempool logs by
reducing log level from info to debug
([\#314](https://github.com/cometbft/cometbft/pull/314): @JayT106)
- `[consensus]` Add `consensus_block_gossip_parts_received` and
`consensus_step_duration_seconds` metrics in order to aid in investigating the
impact of database compaction on consensus performance
([tendermint/tendermint\#9733](https://github.com/tendermint/tendermint/pull/9733))
- `[state/kvindexer]` Add `match.event` keyword to support condition evaluation
based on the event the attributes belong to
([tendermint/tendermint\#9759](https://github.com/tendermint/tendermint/pull/9759))
- `[p2p]` Reduce log spam through reducing log level of "Dialing peer" and
"Added peer" messages from info to debug
([tendermint/tendermint\#9764](https://github.com/tendermint/tendermint/pull/9764):
@faddat)
- `[consensus]` Reduce bandwidth consumption of consensus votes by roughly 50%
through fixing a small logic bug
([tendermint/tendermint\#9776](https://github.com/tendermint/tendermint/pull/9776))
---
CometBFT is a fork of [Tendermint Core](https://github.com/tendermint/tendermint) as of late December 2022.
## Bug bounty
Friendly reminder, we have a [bug bounty program](https://hackerone.com/cosmos).
## Previous changes
For changes released before the creation of CometBFT, please refer to the Tendermint Core [CHANGELOG.md](https://github.com/tendermint/tendermint/blob/a9feb1c023e172b542c972605311af83b777855b/CHANGELOG.md).

109
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,109 @@
# The CometBFT Code of Conduct
This code of conduct applies to all projects run by the CometBFT team and
hence to CometBFT.
----
# Conduct
## Contact: conduct@informal.systems
* We are committed to providing a friendly, safe and welcoming environment for
all, regardless of level of experience, gender, gender identity and
expression, sexual orientation, disability, personal appearance, body size,
race, ethnicity, age, religion, nationality, or other similar characteristics.
* On Slack, please avoid using overtly sexual nicknames or other nicknames that
might detract from a friendly, safe and welcoming environment for all.
* Please be kind and courteous. Theres no need to be mean or rude.
* Respect that people have differences of opinion and that every design or
implementation choice carries a trade-off and numerous costs. There is seldom
a right answer.
* 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 behavior. We interpret the term “harassment” as including
the definition in the [Citizen Code of Conduct][ccoc]; 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 get in touch with one of the channel admins or the contact address 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.
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing
behavior is not welcome.
----
# 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 CometBFT/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.)
2. Remarks that moderators find inappropriate, whether listed in the code of
conduct or not, are also not allowed.
3. Moderators will first respond to such remarks with a warning.
4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of
the communication channel to cool off.
5. If the user comes back and continues to make trouble, they will be banned,
i.e., indefinitely excluded.
6. Moderators may choose at their discretion to un-ban the user if it was a
first offense and they offer the offended party a genuine apology.
7. If a moderator bans someone and you think it was unjustified, please take it
up with that moderator, or with a different moderator, in private. Complaints
about bans in-channel are not allowed.
8. Moderators are held to a higher standard than other community members. If a
moderator creates an inappropriate situation, they should expect less leeway
than others.
In the CometBFT/Cosmos community we strive to go the extra step to look out
for each other. Dont just aim to be technically unimpeachable, try to be your
best self. In particular, avoid flirting with offensive or sensitive issues,
particularly if theyre off-topic; this all too often leads to unnecessary
fights, hurt feelings, and damaged trust; worse, it can drive people away
from the community entirely.
And if someone takes issue with something you said or did, resist the urge to be
defensive. Just stop doing what it was they complained about and apologize. Even
if you feel you were misinterpreted or unfairly accused, chances are good there
was something you couldve communicated better — remember that its your
responsibility to make your fellow Cosmonauts comfortable. Everyone wants to
get along and we are all here first and foremost because we want to talk
about cool technology. You will find that people will be eager to assume
good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official CometBFT/Cosmos
venues. For other projects adopting the CometBFT/Cosmos Code of Conduct,
please contact the maintainers of those projects for enforcement. If you wish to
use this code of conduct for your own project, consider explicitly mentioning
your moderation policy or making a copy with your own moderation policy so as to
avoid confusion.
\*Adapted from the [Node.js Policy on Trolling][node-trolling-policy], the
[Contributor Covenant v1.3.0][ccov] and the [Rust Code of Conduct][rust-coc].
[ccoc]: https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md
[node-trolling-policy]: http://blog.izs.me/post/30036893703/policy-on-trolling
[ccov]: http://contributor-covenant.org/version/1/3/0/
[rust-coc]: https://www.rust-lang.org/en-US/conduct.html

381
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,381 @@
# Contributing
Thank you for your interest in contributing to CometBFT! Before contributing, it
may be helpful to understand the goal of the project. The goal of CometBFT 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
CometBFT project or a potential fork. When targeting the main CometBFT project,
the following process leads to the best chance of landing changes in `main`.
All work on the code base should be motivated by a [GitHub
Issue](https://github.com/cometbft/cometbft/issues).
[Search](https://github.com/cometbft/cometbft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
is a good place to start when looking for places to contribute. If you would
like to work on an issue which already exists, please indicate so by leaving a
comment.
All new contributions should start with a [GitHub
Issue](https://github.com/cometbft/cometbft/issues/new/choose). The issue helps
capture the problem you're trying to solve and allows for early feedback. Once
the issue is created the process can proceed in different directions depending
on how well defined the problem and potential solution are. If the change is
simple and well understood, maintainers 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/cometbft/cometbft/tree/main/docs/rfc) in the
CometBFT repo. 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 as well as proposed solution are well understood,
changes should start with a [draft
pull request](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
against `main`. The draft signals that work is underway. When the work
is ready for feedback, hitting "Ready for Review" will signal to the
maintainers to take a look.
![Contributing flow](./docs/imgs/contributing.png)
Each stage of the process is aimed at creating feedback cycles which align contributors and maintainers to make sure:
- Contributors dont waste their time implementing/proposing features which wont land in `main`.
- Maintainers have the necessary context in order to support and review contributions.
## Forking
Please note that Go requires code to live under absolute paths, which complicates forking.
While my fork lives at `https://github.com/ebuchman/cometbft`,
the code should never exist at `$GOPATH/src/github.com/ebuchman/cometbft`.
Instead, we use `git remote` to add the fork as a new remote for the original repo,
`$GOPATH/src/github.com/cometbft/cometbft`, and do all the work there.
For instance, to create a fork and work on a branch of it, I would:
- Create the fork on GitHub, using the fork button.
- Go to the original repo checked out locally (i.e. `$GOPATH/src/github.com/cometbft/cometbft`)
- `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 CometBFT version.
So I can `git push -u origin main` to update my fork, and make pull requests to CometBFT from there.
Of course, replace `ebuchman` with your git handle.
To pull in updates from the origin repo, run
- `git fetch upstream`
- `git rebase upstream/main` (or whatever branch you want)
## Dependencies
We use [go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
That said, the `main` branch of every CometBFT repository should just build
with `go get`, which means they should be kept up-to-date with their
dependencies so we can get away with telling people they can just `go get` our
software.
Since some dependencies are not under our control, a third party may break our
build, in which case we can fall back on `go mod tidy`. Even for dependencies under our control, go helps us to
keep multiple repos in sync as they evolve. Anything with an executable, such
as apps, tools, and the core, should use dep.
Run `go list -u -m all` to get a list of dependencies that may not be
up-to-date.
When updating dependencies, please only update the particular dependencies you
need. Instead of running `go get -u=patch`, which will update anything,
specify exactly the dependency you want to update.
## Protobuf
We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along
with [`gogoproto`](https://github.com/cosmos/gogoproto) to generate code for use
across CometBFT.
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:
```bash
# Lint all of the .proto files
make proto-lint
# Check if any of your local changes (prior to committing to the Git repository)
# are breaking
make proto-check-breaking
# Generate Go code from the .proto files
make proto-gen
```
To automatically format `.proto` files, you will need
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) installed. Once
installed, you can run:
```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",
]
}
}
```
## Changelog
Every PR with types `fix`, `feat`, `deps`, and `refactor` should include an entry in `CHANGELOG.md`. Commits on the
`main` branch should be placed under `UNRELEASED` within the correct category.
The categories include `DEPENDENCIES`, `IMPROVEMENTS`, `FEATURES`, `BUG-FIXES`, `STATE-BREAKING`, `API-BREAKING`.
For examples, see the [CHANGELOG.md](CHANGELOG.md) file.
A feature can also be worked on a feature branch, if its size and/or risk
justifies it (see [below](#branching-model-and-release)).
### What does a good changelog entry look like?
Changelog entries should answer the question: "what is important about this
change for users to know?" or "what problem does this solve for users?". It
should not simply be a reiteration of the title of the associated PR, unless the
title of the PR _very_ clearly explains the benefit of a change to a user.
Some good examples of changelog entry descriptions:
```md
- [consensus] \#1111 Small transaction throughput improvement (approximately
3-5\% from preliminary tests) through refactoring the way we use channels
- [mempool] \#1112 Refactor Go API to be able to easily swap out the current
mempool implementation in CometBFT forks
- [p2p] \#1113 Automatically ban peers when their messages are unsolicited or
are received too frequently
```
Some bad examples of changelog entry descriptions:
```md
- [consensus] \#1111 Refactor channel usage
- [mempool] \#1112 Make API generic
- [p2p] \#1113 Ban for PEX message abuse
```
For more on how to write good changelog entries, see:
- <https://keepachangelog.com>
- <https://docs.gitlab.com/ee/development/changelog.html#writing-good-changelog-entries>
- <https://depfu.com/blog/what-makes-a-good-changelog>
### Changelog entry format
Changelog entries should be formatted as follows:
```md
- [module] \#xxx Some description of the change (@contributor)
```
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 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. There is no need to include the full link, as this will be added
automatically during release. But please include the backslash and pound, eg.
`\#2313`.
Changelog entries should be ordered alphabetically according to the `module`,
and numerically according to the pull-request number.
Changes with multiple classifications should be doubly included (eg. a bug fix
that is also a breaking change should be recorded under both).
Breaking changes are further subdivided according to the APIs/users they impact.
Any change that affects 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.
## Branching Model and Release
The main development branch is `main`.
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 `main`.
If a feature's size is big and/or its risk is high, it can be implemented in a
feature branch. While the feature work is in progress, pull requests are open
and squash merged against the feature branch. Branch `main` is periodically
merged (merge commit) into the feature branch, to reduce branch divergence. When
the feature is complete, the feature branch is merged back (merge commit) into
`main`. The moment of the final merge can be carefully chosen so as to land
different features in different releases.
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 `main`, which must never fail `make test`.
_Never_ force push `main`, unless fixing broken git history (which we rarely do
anyways).
To begin contributing, create a development branch either on
`github.com/cometbft/cometbft`, or your fork (using `git remote add origin`).
Make changes, and before submitting a pull request, update the changelog to
record your change. Also, run either `git rebase` or `git merge` on top of the
latest `main`. (Since pull requests are squash-merged, either is fine!)
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 its
software (ABCI application, CometBFT blockchain, light client, wallet).
Sometimes (often!) pull requests get out-of-date with `main`, as other people
merge different pull requests to `main`. It is our convention that pull request
authors are responsible for updating their branches with `main`. (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 `main` (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 main_.
You can do this by cherry-picking your commit off `main`:
```sh
$ git checkout rc1/v0.33.5
$ git checkout -b {new branch name}
$ git cherry-pick {commit SHA from main}
# 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.
### 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 CometBFT 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 `main`; this can be accomplished
by using the "squash and merge" button on GitHub. Be sure to edit your commit
message, though!
## Testing
### Unit tests
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 CometBFT network.
See [README](./test/e2e/README.md) for details.
Run:
```sh
cd test/e2e && \
make && \
./build/runner -f networks/ci.toml
```
### 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}`
### 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:
```bash
npm i -g dredd
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.

1
DOCKER/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
cometbft

61
DOCKER/Dockerfile Normal file
View file

@ -0,0 +1,61 @@
# Use a build arg to ensure that both stages use the same,
# hopefully current, go version.
ARG GOLANG_BASE_IMAGE=golang:1.22-alpine
# stage 1 Generate CometBFT Binary
FROM --platform=$BUILDPLATFORM $GOLANG_BASE_IMAGE as builder
RUN apk update && \
apk upgrade && \
apk --no-cache add make git
COPY / /cometbft
WORKDIR /cometbft
RUN TARGETPLATFORM=$TARGETPLATFORM make build-linux
# stage 2
FROM $GOLANG_BASE_IMAGE
LABEL maintainer="hello@informal.systems"
# CometBFT will be looking for the genesis file in /cometbft/config/genesis.json
# (unless you change `genesis_file` in config.toml). You can put your config.toml and
# private validator file into /cometbft/config.
#
# The /cometbft/data dir is used by CometBFT to store state.
ENV CMTHOME /cometbft
# OS environment setup
# Set user right away for determinism, create directory for persistence and give our user ownership
# jq and curl used for extracting `pub_key` from private validator while
# deploying CometBFT with Kubernetes. It is nice to have bash so the users
# could execute bash commands.
RUN apk update && \
apk upgrade && \
apk --no-cache add curl jq bash && \
addgroup tmuser && \
adduser -S -G tmuser tmuser -h "$CMTHOME"
# Run the container with tmuser by default. (UID=100, GID=1000)
USER tmuser
WORKDIR $CMTHOME
# p2p, rpc and prometheus port
EXPOSE 26656 26657 26660
STOPSIGNAL SIGTERM
COPY --from=builder /cometbft/build/cometbft /usr/bin/cometbft
# You can overwrite these before the first run to influence
# config.json and genesis.json. Additionally, you can override
# CMD to add parameters to `cometbft node`.
ENV PROXY_APP=kvstore MONIKER=dockernode CHAIN_ID=dockerchain
COPY ./DOCKER/docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node"]
# Expose the data directory as a volume since there's mutable state in there
VOLUME [ "$CMTHOME" ]

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 /cometbft
WORKDIR /cometbft
CMD ["/usr/bin/make", "build", "COMETBFT_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 cometbft/testing -f ./Dockerfile.testing .
build_amazonlinux_buildimage:
docker build -t "cometbft/cometbft:build_c-amazonlinux" -f Dockerfile.build_c-amazonlinux .
.PHONY: build push build_testing build_amazonlinux_buildimage

56
DOCKER/README.md Normal file
View file

@ -0,0 +1,56 @@
# Docker
## Supported tags and respective `Dockerfile` links
DockerHub tags for official releases are [here](https://hub.docker.com/r/cometbft/cometbft/tags/). The "latest" tag will always point to the highest version number.
Official releases can be found [here](https://github.com/cometbft/cometbft/releases).
The Dockerfile for CometBFT is not expected to change in the near future. The main file used for all builds can be found [here](https://raw.githubusercontent.com/cometbft/cometbft/main/DOCKER/Dockerfile).
Respective versioned files can be found at `https://raw.githubusercontent.com/cometbft/cometbft/vX.XX.XX/DOCKER/Dockerfile` (replace the Xs with the version number).
## Quick reference
- **Where to get help:** <https://cometbft.com/>
- **Where to file issues:** <https://github.com/cometbft/cometbft/issues>
- **Supported Docker versions:** [the latest release](https://github.com/moby/moby/releases) (down to 1.6 on a best-effort basis)
## CometBFT
CometBFT 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 more background, see the [the docs](https://docs.cometbft.com/v0.38.x/introduction/#quick-start).
To get started developing applications, see the [application developers guide](https://docs.cometbft.com/v0.38.x/introduction/quick-start.html).
## How to use this image
### Start one instance of the CometBFT with the `kvstore` app
A quick example of a built-in app and CometBFT in one container.
```sh
docker run -it --rm -v "/tmp:/cometbft" cometbft/cometbft init
docker run -it --rm -v "/tmp:/cometbft" cometbft/cometbft node --proxy_app=kvstore
```
## Local cluster
To run a 4-node network, see the `Makefile` in the root of [the repo](https://github.com/cometbft/cometbft/blob/v0.38.x/Makefile) and run:
```sh
make build-linux
make build-docker-localnode
make localnet-start
```
Note that this will build and use a different image than the ones provided here.
## License
- CometBFT's license is [Apache 2.0](https://github.com/cometbft/cometbft/blob/v0.38.x/LICENSE).
## Contributing
Contributions are most welcome! See the [contributing file](https://github.com/cometbft/cometbft/blob/v0.38.x/CONTRIBUTING.md) for more information.

23
DOCKER/build.sh Executable file
View file

@ -0,0 +1,23 @@
#!/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 "cometbft/cometbft" \
-t "cometbft/cometbft:$TAG" \
-t "cometbft/cometbft:$TAG_NO_PATCH" .
fi

23
DOCKER/docker-entrypoint.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
set -e
if [ ! -d "$CMTHOME/config" ]; then
echo "Running cometbft init to create (default) configuration for docker run."
cometbft init
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/' \
"$CMTHOME/config/config.toml"
jq ".chain_id = \"$CHAIN_ID\" | .consensus_params.block.time_iota_ms = \"500\"" \
"$CMTHOME/config/genesis.json" > "$CMTHOME/config/genesis.json.new"
mv "$CMTHOME/config/genesis.json.new" "$CMTHOME/config/genesis.json"
fi
exec cometbft "$@"

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 "cometbft/cometbft:latest"
docker push "cometbft/cometbft:$TAG"
docker push "cometbft/cometbft:$TAG_NO_PATCH"
fi

674
LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

376
Makefile Normal file
View file

@ -0,0 +1,376 @@
include common.mk
PACKAGES=$(shell go list ./...)
BUILDDIR?=$(CURDIR)/build
OUTPUT?=$(BUILDDIR)/cometbft
HTTPS_GIT := https://github.com/cometbft/cometbft.git
CGO_ENABLED ?= 0
# Process Docker environment varible TARGETPLATFORM
# in order to build binary with correspondent ARCH
# by default will always build for linux/amd64
TARGETPLATFORM ?=
GOOS ?= linux
GOARCH ?= amd64
GOARM ?=
ifeq (linux/arm,$(findstring linux/arm,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=arm
GOARM=7
endif
ifeq (linux/arm/v6,$(findstring linux/arm/v6,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=arm
GOARM=6
endif
ifeq (linux/arm64,$(findstring linux/arm64,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=arm64
GOARM=7
endif
ifeq (linux/386,$(findstring linux/386,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=386
endif
ifeq (linux/amd64,$(findstring linux/amd64,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=amd64
endif
ifeq (linux/mips,$(findstring linux/mips,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=mips
endif
ifeq (linux/mipsle,$(findstring linux/mipsle,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=mipsle
endif
ifeq (linux/mips64,$(findstring linux/mips64,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=mips64
endif
ifeq (linux/mips64le,$(findstring linux/mips64le,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=mips64le
endif
ifeq (linux/riscv64,$(findstring linux/riscv64,$(TARGETPLATFORM)))
GOOS=linux
GOARCH=riscv64
endif
#? all: Run target build, test and install
all: build test install
.PHONY: all
include tests.mk
###############################################################################
### Build CometBFT ###
###############################################################################
#? build: Build CometBFT
build:
CGO_ENABLED=$(CGO_ENABLED) go build $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' -o $(OUTPUT) ./cmd/cometbft/
.PHONY: build
#? install: Install CometBFT to GOBIN
install:
CGO_ENABLED=$(CGO_ENABLED) go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/cometbft
.PHONY: install
###############################################################################
### Metrics ###
###############################################################################
#? metrics: Generate 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: Generate test data for metrics
testdata-metrics:
ls ./scripts/metricsgen/testdata | xargs -I{} go generate -v -run="scripts/metricsgen" ./scripts/metricsgen/testdata/{}
.PHONY: testdata-metrics
###############################################################################
### Mocks ###
###############################################################################
#? mockery: Generate test mocks
mockery:
go generate -run="./scripts/mockery_generate.sh" ./...
.PHONY: mockery
###############################################################################
### Protobuf ###
###############################################################################
#? check-proto-deps: Check protobuf deps
check-proto-deps:
ifeq (,$(shell which protoc-gen-gogofaster))
@go install github.com/cosmos/gogoproto/protoc-gen-gogofaster@latest
endif
.PHONY: check-proto-deps
#? check-proto-format-deps: Check protobuf format deps
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: Generate protobuf files
proto-gen: check-proto-deps
@echo "Generating Protobuf files"
@go run github.com/bufbuild/buf/cmd/buf@latest generate
@mv ./proto/tendermint/abci/types.pb.go ./abci/types/
@cp ./proto/tendermint/rpc/grpc/types.pb.go ./rpc/grpc
.PHONY: proto-gen
# These targets are provided for convenience and are intended for local
# execution only.
#? proto-lint: Lint protobuf files
proto-lint: check-proto-deps
@echo "Linting Protobuf files"
@go run github.com/bufbuild/buf/cmd/buf@latest lint
.PHONY: proto-lint
#? proto-format: Format protobuf files
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 for breaking changes in Protobuf files against local branch. This is only useful if your changes have not yet been committed
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@latest breaking --against ".git"
.PHONY: proto-check-breaking
proto-check-breaking-ci:
@go run github.com/bufbuild/buf/cmd/buf@latest breaking --against $(HTTPS_GIT)#branch=v0.34.x
.PHONY: proto-check-breaking-ci
###############################################################################
### Build ABCI ###
###############################################################################
#? build_abci: Build abci
build_abci:
@go build -mod=readonly -i ./abci/cmd/...
.PHONY: build_abci
#? install_abci: Install abci
install_abci:
@go install -mod=readonly ./abci/cmd/...
.PHONY: install_abci
###############################################################################
### Distribution ###
###############################################################################
# dist builds binaries for all platforms and packages them for distribution
# TODO add abci to these scripts
#? dist: Build binaries for all platforms and package them for distribution
dist:
@BUILD_TAGS=$(BUILD_TAGS) sh -c "'$(CURDIR)/scripts/dist.sh'"
.PHONY: dist
#? go-mod-cache: Download go modules to local cache
go-mod-cache: go.sum
@echo "--> Download go modules to local cache"
@go mod download
.PHONY: go-mod-cache
#? go.sum: Ensure dependencies have not been modified
go.sum: go.mod
@echo "--> Ensure dependencies have not been modified"
@go mod verify
@go mod tidy
#? draw_deps: Generate deps graph
draw_deps:
@# requires brew install graphviz or apt-get install graphviz
go get github.com/RobotsAndPencils/goviz
@goviz -i github.com/cometbft/cometbft/cmd/cometbft -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 $(OUTPUT) ./cmd/cometbft/ 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
###############################################################################
### Libs ###
###############################################################################
#? gen_certs: Generate certificates for TLS testing in remotedb and RPC server
gen_certs: clean_certs
certstrap init --common-name "cometbft.com" --passphrase ""
certstrap request-cert --common-name "server" -ip "127.0.0.1" --passphrase ""
certstrap sign "server" --CA "cometbft.com" --passphrase ""
mv out/server.crt rpc/jsonrpc/server/test.crt
mv out/server.key rpc/jsonrpc/server/test.key
rm -rf out
.PHONY: gen_certs
#? clean_certs: Delete generated certificates
clean_certs:
rm -f rpc/jsonrpc/server/test.crt
rm -f rpc/jsonrpc/server/test.key
.PHONY: clean_certs
###############################################################################
### Formatting, linting, and vetting ###
###############################################################################
format:
find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofmt -w -s
find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs goimports -w -local github.com/cometbft/cometbft
.PHONY: format
#? lint: Run latest golangci-lint linter
lint:
@echo "--> Running linter"
@go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run
.PHONY: lint
#? lint: Run latest golangci-lint linter and apply fixes
lint-fix:
@echo "--> Running linter"
@go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run --fix
.PHONY: lint-fix
#? lint-typo: Run codespell to check typos
lint-typo:
which codespell || pip3 install codespell
@codespell
.PHONY: lint-typo
#? lint-typo: Run codespell to auto fix typos
lint-fix-typo:
@codespell -w
.PHONY: lint-fix-typo
DESTINATION = ./index.html.md
###############################################################################
### Documentation ###
###############################################################################
#? check-docs-toc: Verify that important design docs have ToC entries.
check-docs-toc:
@./docs/presubmit.sh
.PHONY: check-docs-toc
###############################################################################
### Docker image ###
###############################################################################
# On Linux, you may need to run `DOCKER_BUILDKIT=1 make build-docker` for this
# to work.
#? build-docker: Build docker image cometbft/cometbft
build-docker:
docker build \
--label=cometbft \
--tag="cometbft/cometbft" \
-f DOCKER/Dockerfile .
.PHONY: build-docker
###############################################################################
### Local testnet using docker ###
###############################################################################
#? build-linux: Build linux binary on other platforms
build-linux:
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) $(MAKE) build
.PHONY: build-linux
#? build-docker-localnode: Build the "localnode" docker image
build-docker-localnode:
@cd networks/local && make
.PHONY: build-docker-localnode
# Runs `make build COMETBFT_BUILD_OPTIONS=cleveldb` from within an Amazon
# Linux (v2)-based Docker build container in order to build an Amazon
# Linux-compatible binary. Produces a compatible binary at ./build/cometbft
build_c-amazonlinux:
$(MAKE) -C ./DOCKER build_amazonlinux_buildimage
docker run --rm -it -v `pwd`:/cometbft cometbft/cometbft:build_c-amazonlinux
.PHONY: build_c-amazonlinux
#? localnet-start: 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:/cometbft:Z cometbft/localnode testnet --config /etc/cometbft/config-template.toml --o . --starting-ip-address 192.167.10.2; fi
docker compose up -d
.PHONY: localnet-start
#? localnet-stop: Stop testnet
localnet-stop:
docker compose down
.PHONY: localnet-stop
#? build-contract-tests-hooks: Build hooks for dredd, to skip or add information on some steps
build-contract-tests-hooks:
ifeq ($(OS),Windows_NT)
go build -mod=readonly $(BUILD_FLAGS) -o build/contract_tests.exe ./cmd/contract_tests
else
go build -mod=readonly $(BUILD_FLAGS) -o build/contract_tests ./cmd/contract_tests
endif
.PHONY: build-contract-tests-hooks
#? contract-tests: Run a nodejs tool to test endpoints against a localnet
# The command takes care of starting and stopping the network
# prerequisits: build-contract-tests-hooks build-linux
# the two build commands were not added to let this command run from generic containers or machines.
# The binaries should be built beforehand
contract-tests:
dredd
.PHONY: contract-tests
# 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=15m -race -coverprofile=$(BUILDDIR)/$*.profile.out
#? help: Get more info on make commands.
help: Makefile
@echo " Choose a command run in comebft:"
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'
.PHONY: help

13
NOTICE Normal file
View file

@ -0,0 +1,13 @@
NOTICE
About Cosmos Labs
Cosmos Labs is the development and growth organization behind the Cosmos stack of technologies and ecosystem, the world leading blockchain platform powering more than 200 production chains in finance, payments, and real-world assets. Cosmos Labs leads the development of the Cosmos technology stack, including the Cosmos SDK, CometBFT, and IBC protocols that enable sovereign, interoperable blockchains, in tandem with the Interchain Foundation. Cosmos Labs offers blockchain solutions for enterprises and finance, learn more by visiting: https://cosmos.network/, https://cosmoslabs.io/
Licence
This product This product includes software developed by Cosmos Labs and is licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Attribution
If you distribute this software or derivative works, you must include a copy of this NOTICE file (or equivalent attribution) in your distribution, as required by Section 4(d) of the Apache License, Version 2.0.

30
README.md Normal file
View file

@ -0,0 +1,30 @@
<h1 align="center">
<b>Mukan Consensus</b>
</h1>
<p align="center">
The sovereign consensus engine of the <b>Mukan Network</b>, forked from CometBFT.
</p>
## Overview
**Mukan Consensus** is the Byzantine Fault Tolerant (BFT) consensus engine powering the Mukan Network. It is a permanent hard-fork of [CometBFT v0.38.21](https://github.com/cometbft/cometbft), evolved to support Mukan Network's unique **PoW/PoJ (Proof of Justice)** hybrid consensus model.
### Key Differences from CometBFT
- All upstream dependencies updated to reference the sovereign Mukan Network stack (`git.cw.tr/mukan-network/...`) instead of the original Cosmos GitHub paths.
- Future: Native integration with the PoJ mining protocol and Mukan-specific block validation rules.
## Integration
Mukan Consensus is the consensus core used by the [Mukan SDK](https://git.cw.tr/mukan-network/mukan-sdk).
```go
require git.cw.tr/mukan-network/mukan-consensus v0.38.21-mukan.1
```
## License
Licensed under the **GNU General Public License v3.0 (GPLv3)**.
*Original CometBFT components remain under their respective Apache 2.0 licenses where applicable.*

385
RELEASES.md Normal file
View file

@ -0,0 +1,385 @@
# Releases
CometBFT uses modified [semantic versioning](https://semver.org/) with each
release following a `vX.Y.Z` format. CometBFT is currently on major version 0
and uses the minor version to signal breaking changes. The `main` branch is
used for active development and thus it is not advisable to build against it.
The latest changes are always initially merged into `main`. Releases are
specified using tags and are built from long-lived "backport" branches that are
cut from `main` 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). CometBFT only maintains the last two releases at a time (the
oldest release is predominantly just security patches).
## Backporting
As non-breaking changes land on `main`, 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.38.x, add the label
`S:backport-to-v0.38.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 `main` so that `go mod` is able to order the branches correctly. You
should tag `main` with a "dev" tag that is "greater than" the backport
branches tags. Otherwise, `go mod` does not 'know' whether commits on `main`
come before or after the release.
In the following example, we'll assume that we're making a backport branch for
the 0.38.x line.
1. Start on `main`
2. Ensure that there is a [branch protection
rule](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule) for the
branch you are about to create (you will need admin access to the repository
in order to do this).
3. Create and push the backport branch:
```sh
git checkout -b v0.38.x
git push origin v0.38.x
```
4. Create a PR to update the documentation directory for the backport branch.
We rewrite any URLs pointing to `main` to point to the backport branch,
so that generated documentation will link to the correct versions of files
elsewhere in the repository. The following files are to be excluded from this
search:
* [`README.md`](./README.md)
* [`CHANGELOG.md`](./CHANGELOG.md)
* [`UPGRADING.md`](./UPGRADING.md)
The following links are to always point to `main`, regardless of where they
occur in the codebase:
* `https://github.com/cometbft/cometbft/blob/main/LICENSE`
Be sure to search for all of the following links and replace `main` with your
corresponding branch label or version (e.g. `v0.38.x` or `v0.38`):
* `github.com/cometbft/cometbft/blob/main` ->
`github.com/cometbft/cometbft/blob/v0.38.x`
* `github.com/cometbft/cometbft/tree/main` ->
`github.com/cometbft/cometbft/tree/v0.38.x`
* `docs.cometbft.com/main` -> `docs.cometbft.com/v0.38`
Once you have updated all of the relevant documentation:
```sh
# Create and push the PR.
git checkout -b update-docs-v038x
git commit -m "Update docs for v0.38.x backport branch."
git push -u origin update-docs-v038x
```
Be sure to merge this PR before making other changes on the newly-created
backport branch.
5. Ensure that the RPC docs' `version` field in `rpc/openapi/openapi.yaml` has
been updated from `main` to the backport branch version.
6. Prepare the [CometBFT documentation
repository](https://github.com/cometbft/cometbft-docs) to build the release
branch's version by updating the
[VERSIONS](https://github.com/cometbft/cometbft-docs/blob/main/VERSIONS)
file.
After doing these steps, go back to `main` and do the following:
1. Create a new workflow to run e2e nightlies for the new backport branch. (See
[e2e-nightly-main.yml][e2e] for an example.)
2. 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 `backport-to-v0.38.x`
[label](https://github.com/cometbft/cometbft/labels) so the bot can be triggered.
3. 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/cometbft/cometbft/blob/main/.github/workflows/e2e-nightly-main.yml
## Pre-releases
Before creating an official release, especially a minor release, we may want to
create an alpha or beta version, or release candidate (RC) for our friends and
partners to test out. We use git tags to create pre-releases, and we build them
off of backport branches, for example:
* `v0.38.0-alpha.1` - The first alpha release of `v0.38.0`. Subsequent alpha
releases will be numbered `v0.38.0-alpha.2`, `v0.38.0-alpha.3`, etc.
Alpha releases are to be considered the _most_ unstable of pre-releases, and
are most likely not yet properly QA'd. These are made available to allow early
adopters to start integrating and testing new functionality before we're done
with QA.
* `v0.38.0-beta.1` - The first beta release of `v0.38.0`. Subsequent beta
releases will be numbered `v0.38.0-beta.2`, `v0.38.0-beta.3`, etc.
Beta releases can be considered more stable than alpha releases in that we
will have QA'd them better than alpha releases, but there still may be
minor breaking API changes if users have strong demands for such changes.
* `v0.38.0-rc1` - The first release candidate (RC) of `v0.38.0`. Subsequent RCs
will be numbered `v0.38.0-rc2`, `v0.38.0-rc3`, etc.
RCs are considered more stable than beta releases in that we will have
completed our QA on them. APIs will most likely be stable at this point. The
difference between an RC and a release is that there may still be small
changes (bug fixes, features) that may make their way into the series before
cutting a final release.
(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 pre-release 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.38.x`).
2. Run the integration tests and the E2E nightlies
(which can be triggered from the GitHub UI;
e.g., <https://github.com/cometbft/cometbft/actions/workflows/e2e-manual.yml>).
3. Prepare the pre-release documentation:
* Build the changelog with [unclog] _without_ doing an unclog release, and
commit the built changelog. This ensures that all changelog entries appear
under an "Unreleased" heading in the pre-release's changelog. The changes
are only considered officially "released" once we cut a regular (final)
release.
* Ensure that `UPGRADING.md` is up-to-date and includes notes on any breaking
changes or other upgrading flows.
4. Prepare the versioning:
* 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
5. Open a PR with these changes against the backport branch.
6. Once these changes have landed on the backport branch, be sure to pull them back down locally.
7. Once you have the changes locally, create the new tag, specifying a name and a tag "message":
`git tag -a v0.38.0-rc1 -m "Release Candidate v0.38.0-rc1`
8. Push the tag back up to origin:
`git push origin v0.38.0-rc1`
Now the tag should be available on the repo's releases page.
9. Future pre-releases will continue to be built off of this branch.
## 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.38.x`)
2. Run integration tests (`make test_integrations`) and the e2e nightlies.
3. Prepare the release:
* Do a [release][unclog-release] with [unclog] for the desired version,
ensuring that you write up a good summary of the major highlights of the
release that users would be interested in.
* Build the changelog using unclog, and commit the built changelog.
* 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.38.0`.
* `git tag -a v0.38.0 -m 'Release v0.38.0'`
* `git push origin v0.38.0`
6. Make sure that `main` is updated with the latest `CHANGELOG.md`, `CHANGELOG_PENDING.md`, and `UPGRADING.md`.
## Patch release
Patch releases are done differently from minor releases: They are built off of
long-lived backport branches, rather than from main. As non-breaking changes
land on `main`, 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.38.x`
2. Run integration tests (`make test_integrations`) and the nightlies.
3. Check out a new branch and prepare the release:
* Do a [release][unclog-release] with [unclog] for the desired version,
ensuring that you write up a good summary of the major highlights of the
release that users would be interested in.
* Build the changelog using unclog, and commit the built changelog.
* 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.38.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.38.1 -m 'Release v0.38.1'`
* `git push origin v0.38.1`
6. Create a pull request back to main 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 main.
## 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 CometBFT is well
tested, stable, and suitable for adoption by the various diverse projects that
rely on CometBFT.
### Feature Freeze
Ahead of any minor version release of CometBFT, 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 CometBFT 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 CometBFT
functions at the scale it will be used by application developers and node
operators.
### Nightly End-To-End Tests
The CometBFT team maintains [a set of end-to-end
tests](https://github.com/cometbft/cometbft/blob/main/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 CometBFT 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
CometBFT to be considered stable.
### Upgrade Harness
The CometBFT team is creating an upgrade test harness to exercise the workflow
of stopping an instance of CometBFT 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 CometBFT 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 CometBFT end-to-end tests run a small network (~10s of nodes) to exercise
basic consensus interactions. Real world deployments of CometBFT 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 CometBFT experience in production.
#### 200 Node Testnet
To test the stability and performance of CometBFT in a real world scenario,
a 200 node test network is run. The network comprises 5 seed nodes, 175
validators and 20 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 CometBFT frequently see new nodes arrive and old
nodes exit the network. The rotating node testnet ensures that CometBFT 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
consensus it is stopped. Once stopped, a new node is started and
takes its place. This network is run for several days.
#### Vote-extension Testnet
CometBFT v0.38.0 introduced **vote-extensions**, which are added as the name suggests, to precommit votes sent by validators.
The Vote-extension Testnet is used to determine how vote-extensions affect the performance of CometBFT, under various settings.
The application used in the experiment is the same used on the (#200-node-testnet), but is configured differently to gauge de effects of varying vote extension sizes.
In the (#200-node-testnet) the application extends pre-commit votes with a 64 bit number encoded with variable compression.
In the Vote-extension Testnet, pre-commit votes are extended with a non-compressed extension of configurable size.
Experiments are run with multiple sizes to determine their impact and, for comparison sake, we include a run with the same settings as in the (#200-node-testnet).
The testnet consists of 175 validators, 20 non-validator full-nodes, and 5 seed nodes.
All 195 full-nodes begin by dialing a subset of the seed nodes to discover peers.
Once all full-nodes are started, a 5 minute period is waited before starting an experiment.
For each experiment, the load generators issue requests at a constant rate during 150 seconds, then wait for 5 minutes to allow the system to quiesce, then repeat the load generation; the load generation step is repeated 5 times for each experiment.
#### Network Partition Testnet
CometBFT 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 CometBFT 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
CometBFT 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.
[unclog]: https://github.com/informalsystems/unclog
[unclog-release]: https://github.com/informalsystems/unclog#releasing-a-new-versions-change-set

33
SECURITY.md Normal file
View file

@ -0,0 +1,33 @@
# How to Report a Security Bug
If you believe you have found a security vulnerability in the Interchain Stack,
you can report it to our primary vulnerability disclosure channel, the [Cosmos
HackerOne Bug Bounty program][h1].
If you prefer to report an issue via email, you may send a bug report to
<security@interchain.io> with the issue details, reproduction, impact, and other
information. Please submit only one unique email thread per vulnerability. Any
issues reported via email are ineligible for bounty rewards.
Artifacts from an email report are saved at the time the email is triaged.
Please note: our team is not able to monitor dynamic content (e.g. a Google Docs
link that is edited after receipt) throughout the lifecycle of a report. If you
would like to share additional information or modify previous information,
please include it in an additional reply as an additional attachment.
Please **DO NOT** file a public issue in this repository to report a security
vulnerability.
## Coordinated Vulnerability Disclosure Policy and Safe Harbor
For the most up-to-date version of the policies that govern vulnerability
disclosure, please consult the [HackerOne program page][h1-policy].
The policy hosted on HackerOne is the official Coordinated Vulnerability
Disclosure policy and Safe Harbor for the Interchain Stack, and the teams and
infrastructure it supports, and it supersedes previous security policies that
have been used in the past by individual teams and projects with targets in
scope of the program.
[h1]: https://hackerone.com/cosmos?type=team
[h1-policy]: https://hackerone.com/cosmos?type=team&view_policy=true

162
STYLE_GUIDE.md Normal file
View file

@ -0,0 +1,162 @@
# Go Coding Style Guide
In order to keep our code looking good with lots of programmers working on it, it helps to have a "style guide", so all
the code generally looks quite similar. This doesn't mean there is only one "right way" to write code, or even that this
standard is better than your style. But if we agree to a number of stylistic practices, it makes it much easier to read
and modify new code. Please feel free to make suggestions if there's something you would like to add or modify.
We expect all contributors to be familiar with [Effective Go](https://golang.org/doc/effective_go.html)
(and it's recommended reading for all Go programmers anyways). Additionally, we generally agree with the suggestions
in [Uber's style guide](https://github.com/uber-go/guide/blob/master/style.md) and use that as a starting point.
## Code Structure
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)
* Initialization / Start and stop of the service
* Msgs/Events
* Public Functions (In order of most important)
* Private/helper functions
* Auxiliary structs and function (can also be above private functions or in a separate file)
## 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.
## 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.
## 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.
## 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
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 "CometBFT", "Basecoin", "Protobuf", etc except in command lines: `cometbft --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/cometbft/cometbft-db"
* cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
* cmtcfg "github.com/cometbft/cometbft/config"
* cmttypes "github.com/cometbft/cometbft/types"
* Never use anonymous imports (the `.`), for example, `cmtlibs/common` or anything else.
* When importing a pkg from the `cmt/libs` directory, prefix the pkg alias with cmt.
* cmtbits "github.com/cometbft/cometbft/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
## 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>
) 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,
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`
## 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
variable taking the form `FlagXxx` or `flagXxx`.
* 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 CometBFT 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
agreement on style, should be formatted according to
[EditorConfig](http://editorconfig.org/) config:
```toml
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab
[*.sh]
indent_style = tab
[*.proto]
indent_style = space
indent_size = 2
```
Make sure the file above (`.editorconfig`) are in the root directory of your
repo and you have a [plugin for your
editor](http://editorconfig.org/#download) installed.

156
UPGRADING.md Normal file
View file

@ -0,0 +1,156 @@
# Upgrading CometBFT
This guide provides instructions for upgrading to specific versions of CometBFT.
## v0.38.13
It is recommended that CometBFT be built with Go v1.22+ since v1.21 is no longer
supported.
## v0.38.0
This release introduces state machine-breaking changes, as well as substantial changes
on the ABCI interface and indexing. It therefore requires a
coordinated upgrade.
### Config Changes
* The field `Version` in the mempool section has been removed. The priority
mempool (what was called version `v1`) has been removed (see below), thus
there is only one implementation of the mempool available (what was called
`v0`).
* Config fields `TTLDuration` and `TTLNumBlocks`, which were only used by the
priority mempool, have been removed.
### Mempool Changes
* The priority mempool (what was referred in the code as version `v1`) has been
removed. There is now only one mempool (what was called version `v0`), that
is, the default implementation as a queue of transactions.
* In the protobuf message `ResponseCheckTx`, fields `sender`, `priority`, and
`mempool_error`, which were only used by the priority mempool, were removed
but still kept in the message as "reserved".
### ABCI Changes
* The `ABCIVersion` is now `2.0.0`.
* Added new ABCI methods `ExtendVote`, and `VerifyVoteExtension`.
Applications upgrading to v0.38.0 must implement these methods as described
[here](./spec/abci/abci%2B%2B_comet_expected_behavior.md#adapting-existing-applications-that-use-abci)
* Removed methods `BeginBlock`, `DeliverTx`, `EndBlock`, and replaced them by
method `FinalizeBlock`. Applications upgrading to `v0.38.0` must refactor
the logic handling the methods removed to handle `FinalizeBlock`.
* 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`.
* Field `signed_last_block` in structure `VoteInfo` has been replaced by the
more expressive `block_id_flag`. Applications willing to keep the semantics
of `signed_last_block` can now use the following predicate
* `voteInfo.block_id_flag != BlockIDFlagAbsent`
* For further details, please see the updated [specification](spec/abci/README.md)
## v0.37.0
This release introduces state machine-breaking changes, and therefore requires a
coordinated upgrade.
### Go API
When upgrading from the v0.34 release series, please note that the Go module has
now changed to `github.com/cometbft/cometbft`.
### ABCI Changes
* The `ABCIVersion` is now `1.0.0`.
* Added new ABCI methods `PrepareProposal` and `ProcessProposal`. For details,
please see the [spec](spec/abci/README.md). Applications upgrading to
v0.37.0 must implement these methods, at the very minimum, as described
[here](./spec/abci/abci++_app_requirements.md)
* Deduplicated `ConsensusParams` and `BlockParams`.
In the v0.34 branch they are defined both in `abci/types.proto` and `types/params.proto`.
The definitions in `abci/types.proto` have been removed.
In-process applications should make sure they are not using the deleted
version of those structures.
* In v0.34, messages on the wire used to be length-delimited with `int64` varint
values, which was inconsistent with the `uint64` varint length delimiters used
in the P2P layer. Both now consistently use `uint64` varint length delimiters.
* Added `AbciVersion` to `RequestInfo`.
Applications should check that CometBFT's ABCI version matches the one they expect
in order to ensure compatibility.
* The `SetOption` method has been removed from the ABCI `Client` interface.
The corresponding Protobuf types have been deprecated.
* The `key` and `value` fields in the `EventAttribute` type have been changed
from type `bytes` to `string`. As per the [Protocol Buffers updating
guidelines](https://developers.google.com/protocol-buffers/docs/proto3#updating),
this should have no effect on the wire-level encoding for UTF8-encoded
strings.
### RPC
If you rely on the `/tx_search` or `/block_search` endpoints for event querying,
please note that the default behaviour of these endpoints has changed in a way
that might break your queries. The original behaviour was poorly specified,
which did not respect event boundaries.
Please see
[tendermint/tendermint\#9712](https://github.com/tendermint/tendermint/issues/9712)
for context on the bug that was addressed that resulted in this behaviour
change.
## v0.34.27
This is the first official release of CometBFT, forked originally from
[Tendermint Core v0.34.24][v03424] and subsequently updated in Informal Systems'
public fork of Tendermint Core for [v0.34.25][v03425] and [v0.34.26][v03426].
### Upgrading from Tendermint Core
If you already make use of Tendermint Core (either the original Tendermint Core
v0.34.24, or Informal Systems' public fork), you can upgrade to CometBFT
v0.34.27 by replacing your dependency in your `go.mod` file:
```bash
go mod edit -replace github.com/tendermint/tendermint=github.com/cometbft/cometbft@v0.34.27
```
We make use of the original module URL in order to minimize the impact of
switching to CometBFT. This is only possible in our v0.34 release series, and we
will be switching our module URL to `github.com/cometbft/cometbft` in the next
major release.
### Home directory
CometBFT, by default, will consider its home directory in `~/.cometbft` from now
on instead of `~/.tendermint`.
### Environment variables
The environment variable prefixes have now changed from `TM` to `CMT`. For
example, `TMHOME` becomes `CMTHOME`.
We have implemented a fallback check in case `TMHOME` is still set and `CMTHOME`
is not, but you will start to see a warning message in the logs if the old
`TMHOME` variable is set. This fallback check will be removed entirely in a
subsequent major release of CometBFT.
### Building CometBFT
CometBFT must be compiled using Go 1.19 or higher. The use of Go 1.18 is not
supported, since this version has reached end-of-life with the release of [Go 1.20][go120].
### Troubleshooting
If you run into any trouble with this upgrade, please [contact us][discussions].
---
For historical upgrading instructions for Tendermint Core v0.34.24 and earlier,
please see the [Tendermint Core upgrading instructions][tmupgrade].
[v03424]: https://github.com/tendermint/tendermint/releases/tag/v0.34.24
[v03425]: https://github.com/informalsystems/tendermint/releases/tag/v0.34.25
[v03426]: https://github.com/informalsystems/tendermint/releases/tag/v0.34.26
[discussions]: https://github.com/cometbft/cometbft/discussions
[tmupgrade]: https://github.com/tendermint/tendermint/blob/35581cf54ec436b8c37fabb43fdaa3f48339a170/UPGRADING.md
[go120]: https://go.dev/blog/go1.20

35
abci/README.md Normal file
View file

@ -0,0 +1,35 @@
# Application BlockChain Interface (ABCI)
Blockchains are systems for multi-master state machine replication.
**ABCI** is an interface that defines the boundary between the replication engine (the blockchain),
and the state machine (the application).
Using a socket protocol, a consensus engine running in one process
can manage an application state running in another.
Previously, the ABCI was referred to as TMSP.
## Installation & Usage
To get up and running quickly, see the [getting started guide](../docs/app-dev/getting-started.md) along with the [abci-cli documentation](../docs/app-dev/abci-cli.md) which will go through the examples found in the [examples](./example/) directory.
## Specification
A detailed description of the ABCI methods and message types is contained in:
- [The main spec](https://github.com/cometbft/cometbft/blob/v0.38.x/spec/abci/README.md)
- [A protobuf file](../proto/tendermint/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.

133
abci/client/client.go Normal file
View file

@ -0,0 +1,133 @@
package abcicli
import (
"context"
"fmt"
"sync"
"github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/service"
cmtsync "github.com/cometbft/cometbft/libs/sync"
)
const (
dialRetryIntervalSeconds = 3
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.
type Client interface {
service.Service
types.Application
// TODO: remove as each method now returns an error
Error() error
// TODO: remove as this is not implemented
Flush(context.Context) error
Echo(context.Context, string) (*types.ResponseEcho, error)
// FIXME: All other operations are run synchronously and rely
// on the caller to dictate concurrency (i.e. run a go routine),
// with the exception of `CheckTxAsync` which we maintain
// for the v0 mempool. We should explore refactoring the
// mempool to remove this vestige behavior.
SetResponseCallback(Callback)
CheckTxAsync(context.Context, *types.RequestCheckTx) (*ReqRes, 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(addr, transport string, mustConnect bool) (client Client, err error) {
switch transport {
case "socket":
client = NewSocketClient(addr, mustConnect)
case "grpc":
client = NewGRPCClient(addr, mustConnect)
default:
err = fmt.Errorf("unknown abci transport %s", transport)
}
return
}
type Callback func(*types.Request, *types.Response)
type ReqRes struct {
*types.Request
*sync.WaitGroup
*types.Response // Not set atomically, so be sure to use WaitGroup.
mtx cmtsync.Mutex
// callbackInvoked as a variable to track if the callback was already
// invoked during the regular execution of the request. This variable
// allows clients to set the callback simultaneously without potentially
// invoking the callback twice by accident, once when 'SetCallback' is
// called and once during the normal request.
callbackInvoked bool
cb func(*types.Response) // A single callback that may be set.
}
func NewReqRes(req *types.Request) *ReqRes {
return &ReqRes{
Request: req,
WaitGroup: waitGroup1(),
Response: nil,
callbackInvoked: false,
cb: nil,
}
}
// Sets sets the callback. If reqRes is already done, it will call the cb
// immediately. Note, reqRes.cb should not change if reqRes.done and only one
// callback is supported.
func (r *ReqRes) SetCallback(cb func(res *types.Response)) {
r.mtx.Lock()
if r.callbackInvoked {
r.mtx.Unlock()
cb(r.Response)
return
}
r.cb = cb
r.mtx.Unlock()
}
// InvokeCallback invokes a thread-safe execution of the configured callback
// if non-nil.
func (r *ReqRes) InvokeCallback() {
r.mtx.Lock()
defer r.mtx.Unlock()
if r.cb != nil {
r.cb(r.Response)
}
r.callbackInvoked = true
}
// GetCallback returns the configured callback of the ReqRes object which may be
// nil. Note, it is not safe to concurrently call this in cases where it is
// marked done and SetCallback is called before calling GetCallback as that
// will invoke the callback twice and create a potential race condition.
//
// ref: https://github.com/tendermint/tendermint/issues/5439
func (r *ReqRes) GetCallback() func(*types.Response) {
r.mtx.Lock()
defer r.mtx.Unlock()
return r.cb
}
func waitGroup1() (wg *sync.WaitGroup) {
wg = &sync.WaitGroup{}
wg.Add(1)
return
}

247
abci/client/grpc_client.go Normal file
View file

@ -0,0 +1,247 @@
package abcicli
import (
"context"
"fmt"
"net"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/cometbft/cometbft/abci/types"
cmtnet "github.com/cometbft/cometbft/libs/net"
"github.com/cometbft/cometbft/libs/service"
)
var _ Client = (*grpcClient)(nil)
// A stripped copy of the remoteClient that makes
// synchronous calls using grpc
type grpcClient struct {
service.BaseService
mustConnect bool
client types.ABCIClient
conn *grpc.ClientConn
chReqRes chan *ReqRes // dispatches "async" responses to callbacks *in order*, needed by mempool
mtx sync.Mutex
addr string
err error
resCb func(*types.Request, *types.Response) // listens to all callbacks
}
func NewGRPCClient(addr string, mustConnect bool) Client {
cli := &grpcClient{
addr: addr,
mustConnect: mustConnect,
// Buffering the channel is needed to make calls appear asynchronous,
// which is required when the caller makes multiple async calls before
// processing callbacks (e.g. due to holding locks). 64 means that a
// caller can make up to 64 async calls before a callback must be
// processed (otherwise it deadlocks). It also means that we can make 64
// gRPC calls while processing a slow callback at the channel head.
chReqRes: make(chan *ReqRes, 64),
}
cli.BaseService = *service.NewBaseService(nil, "grpcClient", cli)
return cli
}
func dialerFunc(_ context.Context, addr string) (net.Conn, error) {
return cmtnet.Connect(addr)
}
func (cli *grpcClient) OnStart() error {
if err := cli.BaseService.OnStart(); err != nil {
return err
}
// This processes asynchronous request/response messages and dispatches
// them to callbacks.
go func() {
// Use a separate function to use defer for mutex unlocks (this handles panics)
callCb := func(reqres *ReqRes) {
cli.mtx.Lock()
defer cli.mtx.Unlock()
reqres.Done()
// Notify client listener if set
if cli.resCb != nil {
cli.resCb(reqres.Request, reqres.Response)
}
// Notify reqRes listener if set
reqres.InvokeCallback()
}
for reqres := range cli.chReqRes {
if reqres != nil {
callCb(reqres)
} else {
cli.Logger.Error("Received nil reqres")
}
}
}()
RETRY_LOOP:
for {
conn, err := grpc.NewClient(cli.addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialerFunc),
)
if err != nil {
if cli.mustConnect {
return err
}
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)
client := types.NewABCIClient(conn)
cli.conn = conn
ENSURE_CONNECTED:
for {
_, err := client.Echo(context.Background(), &types.RequestEcho{Message: "hello"}, grpc.WaitForReady(true))
if err == nil {
break ENSURE_CONNECTED
}
cli.Logger.Error("Echo failed", "err", err)
time.Sleep(time.Second * echoRetryIntervalSeconds)
}
cli.client = client
return nil
}
}
func (cli *grpcClient) OnStop() {
cli.BaseService.OnStop()
if cli.conn != nil {
cli.conn.Close()
}
close(cli.chReqRes)
}
func (cli *grpcClient) 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.grpcClient for error: %v", err.Error()))
if err := cli.Stop(); err != nil {
cli.Logger.Error("Error stopping abci.grpcClient", "err", err)
}
}
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()
}
//----------------------------------------
func (cli *grpcClient) CheckTxAsync(ctx context.Context, req *types.RequestCheckTx) (*ReqRes, error) {
res, err := cli.client.CheckTx(ctx, req, grpc.WaitForReady(true))
if err != nil {
cli.StopForError(err)
return nil, err
}
return cli.finishAsyncCall(types.ToRequestCheckTx(req), &types.Response{Value: &types.Response_CheckTx{CheckTx: res}}), nil
}
// finishAsyncCall creates a ReqRes for an async call, and immediately populates it
// with the response. We don't complete it until it's been ordered via the channel.
func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes {
reqres := NewReqRes(req)
reqres.Response = res
cli.chReqRes <- reqres // use channel for async responses, since they must be ordered
return reqres
}
//----------------------------------------
func (cli *grpcClient) Flush(ctx context.Context) error {
_, err := cli.client.Flush(ctx, types.ToRequestFlush().GetFlush(), grpc.WaitForReady(true))
return err
}
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) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) {
return cli.client.Info(ctx, req, grpc.WaitForReady(true))
}
func (cli *grpcClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
return cli.client.CheckTx(ctx, req, grpc.WaitForReady(true))
}
func (cli *grpcClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) {
return cli.client.Query(ctx, types.ToRequestQuery(req).GetQuery(), grpc.WaitForReady(true))
}
func (cli *grpcClient) Commit(ctx context.Context, _ *types.RequestCommit) (*types.ResponseCommit, error) {
return cli.client.Commit(ctx, types.ToRequestCommit().GetCommit(), grpc.WaitForReady(true))
}
func (cli *grpcClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
return cli.client.InitChain(ctx, types.ToRequestInitChain(req).GetInitChain(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
return cli.client.ListSnapshots(ctx, types.ToRequestListSnapshots(req).GetListSnapshots(), grpc.WaitForReady(true))
}
func (cli *grpcClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
return cli.client.OfferSnapshot(ctx, types.ToRequestOfferSnapshot(req).GetOfferSnapshot(), grpc.WaitForReady(true))
}
func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
return cli.client.LoadSnapshotChunk(ctx, types.ToRequestLoadSnapshotChunk(req).GetLoadSnapshotChunk(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
return cli.client.ApplySnapshotChunk(ctx, types.ToRequestApplySnapshotChunk(req).GetApplySnapshotChunk(), grpc.WaitForReady(true))
}
func (cli *grpcClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
return cli.client.PrepareProposal(ctx, types.ToRequestPrepareProposal(req).GetPrepareProposal(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
return cli.client.ProcessProposal(ctx, types.ToRequestProcessProposal(req).GetProcessProposal(), grpc.WaitForReady(true))
}
func (cli *grpcClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
return cli.client.ExtendVote(ctx, types.ToRequestExtendVote(req).GetExtendVote(), grpc.WaitForReady(true))
}
func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
return cli.client.VerifyVoteExtension(ctx, types.ToRequestVerifyVoteExtension(req).GetVerifyVoteExtension(), grpc.WaitForReady(true))
}
func (cli *grpcClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
return cli.client.FinalizeBlock(ctx, types.ToRequestFinalizeBlock(req).GetFinalizeBlock(), grpc.WaitForReady(true))
}

View file

@ -0,0 +1,80 @@
package abcicli_test
import (
"fmt"
"math/rand"
"net"
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"golang.org/x/net/context"
"github.com/cometbft/cometbft/libs/log"
cmtnet "github.com/cometbft/cometbft/libs/net"
abciserver "github.com/cometbft/cometbft/abci/server"
"github.com/cometbft/cometbft/abci/types"
)
func TestGRPC(t *testing.T) {
app := types.NewBaseApplication()
numCheckTxs := 2000
socketFile := fmt.Sprintf("/tmp/test-%08x.sock", rand.Int31n(1<<30))
defer os.Remove(socketFile)
socket := fmt.Sprintf("unix://%v", socketFile)
// Start the listener
server := abciserver.NewGRPCServer(socket, app)
server.SetLogger(log.TestingLogger().With("module", "abci-server"))
err := server.Start()
require.NoError(t, err)
t.Cleanup(func() {
if err := server.Stop(); err != nil {
t.Error(err)
}
})
// Connect to the socket
//nolint:staticcheck // SA1019 Existing use of deprecated but supported dial option.
conn, err := grpc.Dial(socket, grpc.WithInsecure(), grpc.WithContextDialer(dialerFunc))
require.NoError(t, err)
t.Cleanup(func() {
if err := conn.Close(); err != nil {
t.Error(err)
}
})
client := types.NewABCIClient(conn)
// Write requests
for counter := 0; counter < numCheckTxs; counter++ {
// Send request
response, err := client.CheckTx(context.Background(), &types.RequestCheckTx{Tx: []byte("test")})
require.NoError(t, err)
counter++
if response.Code != 0 {
t.Error("CheckTx failed with ret_code", response.Code)
}
if counter > numCheckTxs {
t.Fatal("Too many CheckTx responses")
}
t.Log("response", counter)
if counter == numCheckTxs {
go func() {
time.Sleep(time.Second * 1) // Wait for a bit to allow counter overflow
}()
}
}
}
func dialerFunc(_ context.Context, addr string) (net.Conn, error) {
return cmtnet.Connect(addr)
}

186
abci/client/local_client.go Normal file
View file

@ -0,0 +1,186 @@
package abcicli
import (
"context"
types "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/service"
cmtsync "github.com/cometbft/cometbft/libs/sync"
)
// 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 *cmtsync.Mutex
types.Application
Callback
}
var _ Client = (*localClient)(nil)
// NewLocalClient creates a local client, which wraps the application interface that
// Tendermint as the client will call to the application as the server. The only
// difference, is that the local client has a global mutex which enforces serialization
// of all the ABCI calls from Tendermint to the Application.
func NewLocalClient(mtx *cmtsync.Mutex, app types.Application) Client {
if mtx == nil {
mtx = new(cmtsync.Mutex)
}
cli := &localClient{
mtx: mtx,
Application: app,
}
cli.BaseService = *service.NewBaseService(nil, "localClient", cli)
return cli
}
func (app *localClient) SetResponseCallback(cb Callback) {
app.mtx.Lock()
app.Callback = cb
app.mtx.Unlock()
}
func (app *localClient) CheckTxAsync(ctx context.Context, req *types.RequestCheckTx) (*ReqRes, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
res, err := app.Application.CheckTx(ctx, req)
if err != nil {
return nil, err
}
return app.callback(
types.ToRequestCheckTx(req),
types.ToResponseCheckTx(res),
), nil
}
func (app *localClient) callback(req *types.Request, res *types.Response) *ReqRes {
app.Callback(req, res)
rr := newLocalReqRes(req, res)
rr.callbackInvoked = true
return rr
}
func newLocalReqRes(req *types.Request, res *types.Response) *ReqRes {
reqRes := NewReqRes(req)
reqRes.Response = res
return reqRes
}
//-------------------------------------------------------
func (app *localClient) Error() error {
return nil
}
func (app *localClient) Flush(context.Context) error {
return nil
}
func (app *localClient) Echo(_ context.Context, msg string) (*types.ResponseEcho, error) {
return &types.ResponseEcho{Message: msg}, nil
}
func (app *localClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.Info(ctx, req)
}
func (app *localClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.CheckTx(ctx, req)
}
func (app *localClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.Query(ctx, req)
}
func (app *localClient) Commit(ctx context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.Commit(ctx, req)
}
func (app *localClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.InitChain(ctx, req)
}
func (app *localClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.ListSnapshots(ctx, req)
}
func (app *localClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.OfferSnapshot(ctx, req)
}
func (app *localClient) LoadSnapshotChunk(ctx context.Context,
req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.LoadSnapshotChunk(ctx, req)
}
func (app *localClient) ApplySnapshotChunk(ctx context.Context,
req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.ApplySnapshotChunk(ctx, req)
}
func (app *localClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.PrepareProposal(ctx, req)
}
func (app *localClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.ProcessProposal(ctx, req)
}
func (app *localClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.ExtendVote(ctx, req)
}
func (app *localClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.VerifyVoteExtension(ctx, req)
}
func (app *localClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.Application.FinalizeBlock(ctx, req)
}

711
abci/client/mocks/client.go Normal file
View file

@ -0,0 +1,711 @@
// Code generated by mockery v2.53.0. DO NOT EDIT.
package mocks
import (
context "context"
abcicli "github.com/cometbft/cometbft/abci/client"
log "github.com/cometbft/cometbft/libs/log"
mock "github.com/stretchr/testify/mock"
types "github.com/cometbft/cometbft/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)
if len(ret) == 0 {
panic("no return value specified for ApplySnapshotChunk")
}
var r0 *types.ResponseApplySnapshotChunk
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for CheckTx")
}
var r0 *types.ResponseCheckTx
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) (*types.ResponseCheckTx, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// CheckTxAsync provides a mock function with given fields: _a0, _a1
func (_m *Client) CheckTxAsync(_a0 context.Context, _a1 *types.RequestCheckTx) (*abcicli.ReqRes, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for CheckTxAsync")
}
var r0 *abcicli.ReqRes
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) (*abcicli.ReqRes, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *abcicli.ReqRes); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*abcicli.ReqRes)
}
}
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, _a1
func (_m *Client) Commit(_a0 context.Context, _a1 *types.RequestCommit) (*types.ResponseCommit, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Commit")
}
var r0 *types.ResponseCommit
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCommit) (*types.ResponseCommit, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCommit) *types.ResponseCommit); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseCommit)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCommit) error); ok {
r1 = rf(_a0, _a1)
} 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)
if len(ret) == 0 {
panic("no return value specified for Echo")
}
var r0 *types.ResponseEcho
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (*types.ResponseEcho, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 no fields
func (_m *Client) Error() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Error")
}
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)
if len(ret) == 0 {
panic("no return value specified for ExtendVote")
}
var r0 *types.ResponseExtendVote
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) (*types.ResponseExtendVote, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for FinalizeBlock")
}
var r0 *types.ResponseFinalizeBlock
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for Flush")
}
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)
if len(ret) == 0 {
panic("no return value specified for Info")
}
var r0 *types.ResponseInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) (*types.ResponseInfo, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for InitChain")
}
var r0 *types.ResponseInitChain
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) (*types.ResponseInitChain, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 no fields
func (_m *Client) IsRunning() bool {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for IsRunning")
}
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)
if len(ret) == 0 {
panic("no return value specified for ListSnapshots")
}
var r0 *types.ResponseListSnapshots
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) (*types.ResponseListSnapshots, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for LoadSnapshotChunk")
}
var r0 *types.ResponseLoadSnapshotChunk
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for OfferSnapshot")
}
var r0 *types.ResponseOfferSnapshot
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// OnReset provides a mock function with no fields
func (_m *Client) OnReset() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for OnReset")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// OnStart provides a mock function with no fields
func (_m *Client) OnStart() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for OnStart")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// OnStop provides a mock function with no fields
func (_m *Client) OnStop() {
_m.Called()
}
// 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)
if len(ret) == 0 {
panic("no return value specified for PrepareProposal")
}
var r0 *types.ResponsePrepareProposal
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for ProcessProposal")
}
var r0 *types.ResponseProcessProposal
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) (*types.ResponseProcessProposal, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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)
if len(ret) == 0 {
panic("no return value specified for Query")
}
var r0 *types.ResponseQuery
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) (*types.ResponseQuery, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// Quit provides a mock function with no fields
func (_m *Client) Quit() <-chan struct{} {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Quit")
}
var r0 <-chan struct{}
if rf, ok := ret.Get(0).(func() <-chan struct{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan struct{})
}
}
return r0
}
// Reset provides a mock function with no fields
func (_m *Client) Reset() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Reset")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// SetLogger provides a mock function with given fields: _a0
func (_m *Client) SetLogger(_a0 log.Logger) {
_m.Called(_a0)
}
// SetResponseCallback provides a mock function with given fields: _a0
func (_m *Client) SetResponseCallback(_a0 abcicli.Callback) {
_m.Called(_a0)
}
// Start provides a mock function with no fields
func (_m *Client) Start() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Stop provides a mock function with no fields
func (_m *Client) Stop() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Stop")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// String provides a mock function with no fields
func (_m *Client) String() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for String")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
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)
if len(ret) == 0 {
panic("no return value specified for VerifyVoteExtension")
}
var r0 *types.ResponseVerifyVoteExtension
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// 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.
// The first argument is typically a *testing.T value.
func NewClient(t interface {
mock.TestingT
Cleanup(func())
}) *Client {
mock := &Client{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,515 @@
package abcicli
import (
"bufio"
"container/list"
"context"
"errors"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/cometbft/cometbft/abci/types"
cmtnet "github.com/cometbft/cometbft/libs/net"
"github.com/cometbft/cometbft/libs/service"
"github.com/cometbft/cometbft/libs/timer"
)
const (
reqQueueSize = 256 // TODO make configurable
flushThrottleMS = 20 // Don't wait longer than...
)
// socketClient is the client side implementation of the Tendermint
// Socket Protocol (TSP). It is used by an instance of Tendermint to pass
// ABCI requests to an out of process application running the socketServer.
//
// This is goroutine-safe. All calls are serialized to the server through an unbuffered queue. The socketClient
// tracks responses and expects them to respect the order of the requests sent.
type socketClient struct {
service.BaseService
addr string
mustConnect bool
conn net.Conn
reqQueue chan *ReqRes
flushTimer *timer.ThrottleTimer
mtx sync.Mutex
err error
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 else it will continue to retry.
func NewSocketClient(addr string, mustConnect bool) Client {
cli := &socketClient{
reqQueue: make(chan *ReqRes, reqQueueSize),
flushTimer: timer.NewThrottleTimer("socketClient", flushThrottleMS),
mustConnect: mustConnect,
addr: addr,
reqSent: list.New(),
resCb: nil,
}
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() error {
var (
err error
conn net.Conn
)
for {
conn, err = cmtnet.Connect(cli.addr)
if err != nil {
if cli.mustConnect {
return err
}
cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying after %vs...",
cli.addr, dialRetryIntervalSeconds), "err", err)
time.Sleep(time.Second * dialRetryIntervalSeconds)
continue
}
cli.conn = 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.flushQueue()
cli.flushTimer.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
}
//----------------------------------------
// SetResponseCallback sets a callback, which will be executed for each
// non-error & non-empty response from the server.
//
// 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) CheckTxAsync(ctx context.Context, req *types.RequestCheckTx) (*ReqRes, error) {
return cli.queueRequest(ctx, types.ToRequestCheckTx(req))
}
//----------------------------------------
func (cli *socketClient) sendRequestsRoutine(conn io.Writer) {
w := bufio.NewWriter(conn)
for {
select {
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)
err := types.WriteMessage(reqres.Request, w)
if err != nil {
cli.stopForError(fmt.Errorf("write to buffer: %w", err))
return
}
// If it's a flush request, flush the current buffer.
if _, ok := reqres.Request.Value.(*types.Request_Flush); ok {
err = w.Flush()
if err != nil {
cli.stopForError(fmt.Errorf("flush buffer: %w", err))
return
}
}
case <-cli.flushTimer.Ch: // flush queue
select {
case cli.reqQueue <- NewReqRes(types.ToRequestFlush()):
default:
// Probably will fill the buffer, or retry later.
}
case <-cli.Quit():
return
}
}
}
func (cli *socketClient) recvResponseRoutine(conn io.Reader) {
r := bufio.NewReader(conn)
for {
if !cli.IsRunning() {
return
}
res := &types.Response{}
err := types.ReadMessage(r, res)
if err != nil {
cli.stopForError(fmt.Errorf("read message: %w", err))
return
}
switch r := res.Value.(type) {
case *types.Response_Exception: // app responded with error
// XXX After setting cli.err, release waiters (e.g. reqres.Done())
cli.stopForError(errors.New(r.Exception.Error))
return
default:
err := cli.didRecvResponse(res)
if err != nil {
cli.stopForError(err)
return
}
}
}
}
func (cli *socketClient) trackRequest(reqres *ReqRes) {
// N.B. We must NOT hold the client state lock while checking this, or we
// may deadlock with shutdown.
if !cli.IsRunning() {
return
}
cli.mtx.Lock()
defer cli.mtx.Unlock()
cli.reqSent.PushBack(reqres)
}
func (cli *socketClient) didRecvResponse(res *types.Response) error {
cli.mtx.Lock()
defer cli.mtx.Unlock()
// Get the first ReqRes.
next := cli.reqSent.Front()
if next == nil {
return fmt.Errorf("unexpected response %T when no call was made", res.Value)
}
reqres := next.Value.(*ReqRes)
if !resMatchesReq(reqres.Request, res) {
return fmt.Errorf("unexpected response %T to the request %T", res.Value, reqres.Request.Value)
}
reqres.Response = res
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.
reqres.InvokeCallback()
return nil
}
//----------------------------------------
func (cli *socketClient) Flush(ctx context.Context) error {
reqRes, err := cli.queueRequest(ctx, types.ToRequestFlush())
if err != nil {
return err
}
reqRes.Wait()
return nil
}
func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestEcho(msg))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetEcho(), cli.Error()
}
func (cli *socketClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestInfo(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetInfo(), cli.Error()
}
func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestCheckTx(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetCheckTx(), cli.Error()
}
func (cli *socketClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestQuery(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetQuery(), cli.Error()
}
func (cli *socketClient) Commit(ctx context.Context, _ *types.RequestCommit) (*types.ResponseCommit, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestCommit())
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetCommit(), cli.Error()
}
func (cli *socketClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestInitChain(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetInitChain(), cli.Error()
}
func (cli *socketClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestListSnapshots(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetListSnapshots(), cli.Error()
}
func (cli *socketClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestOfferSnapshot(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetOfferSnapshot(), cli.Error()
}
func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestLoadSnapshotChunk(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetLoadSnapshotChunk(), cli.Error()
}
func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestApplySnapshotChunk(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetApplySnapshotChunk(), cli.Error()
}
func (cli *socketClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestPrepareProposal(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetPrepareProposal(), cli.Error()
}
func (cli *socketClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestProcessProposal(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetProcessProposal(), cli.Error()
}
func (cli *socketClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestExtendVote(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetExtendVote(), cli.Error()
}
func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestVerifyVoteExtension(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetVerifyVoteExtension(), cli.Error()
}
func (cli *socketClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
reqRes, err := cli.queueRequest(ctx, types.ToRequestFinalizeBlock(req))
if err != nil {
return nil, err
}
if err := cli.Flush(ctx); err != nil {
return nil, err
}
return reqRes.Response.GetFinalizeBlock(), cli.Error()
}
func (cli *socketClient) queueRequest(ctx context.Context, req *types.Request) (*ReqRes, error) {
reqres := NewReqRes(req)
// TODO: set cli.err if reqQueue times out
select {
case cli.reqQueue <- reqres:
case <-ctx.Done():
return nil, ctx.Err()
}
// Maybe auto-flush, or unset auto-flush
switch req.Value.(type) {
case *types.Request_Flush:
cli.flushTimer.Unset()
default:
cli.flushTimer.Set()
}
return reqres, nil
}
// flushQueue marks as complete and discards all remaining pending requests
// from the queue.
func (cli *socketClient) flushQueue() {
cli.mtx.Lock()
defer cli.mtx.Unlock()
// 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()
}
// mark all queued messages as resolved
LOOP:
for {
select {
case reqres := <-cli.reqQueue:
reqres.Done()
default:
break LOOP
}
}
}
//----------------------------------------
func resMatchesReq(req *types.Request, res *types.Response) (ok bool) {
switch req.Value.(type) {
case *types.Request_Echo:
_, ok = res.Value.(*types.Response_Echo)
case *types.Request_Flush:
_, ok = res.Value.(*types.Response_Flush)
case *types.Request_Info:
_, ok = res.Value.(*types.Response_Info)
case *types.Request_CheckTx:
_, ok = res.Value.(*types.Response_CheckTx)
case *types.Request_Commit:
_, ok = res.Value.(*types.Response_Commit)
case *types.Request_Query:
_, ok = res.Value.(*types.Response_Query)
case *types.Request_InitChain:
_, ok = res.Value.(*types.Response_InitChain)
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_ExtendVote:
_, ok = res.Value.(*types.Response_ExtendVote)
case *types.Request_VerifyVoteExtension:
_, ok = res.Value.(*types.Response_VerifyVoteExtension)
case *types.Request_PrepareProposal:
_, ok = res.Value.(*types.Response_PrepareProposal)
case *types.Request_ProcessProposal:
_, ok = res.Value.(*types.Response_ProcessProposal)
case *types.Request_FinalizeBlock:
_, ok = res.Value.(*types.Response_FinalizeBlock)
}
return ok
}
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()))
if err := cli.Stop(); err != nil {
cli.Logger.Error("Error stopping abci.socketClient", "err", err)
}
}

View file

@ -0,0 +1,239 @@
package abcicli_test
import (
"context"
"fmt"
"math/rand"
"os"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abcicli "github.com/cometbft/cometbft/abci/client"
"github.com/cometbft/cometbft/abci/server"
"github.com/cometbft/cometbft/abci/types"
cmtrand "github.com/cometbft/cometbft/libs/rand"
"github.com/cometbft/cometbft/libs/service"
)
func TestCalls(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app := types.BaseApplication{}
_, c := setupClientServer(t, app)
resp := make(chan error, 1)
go func() {
res, err := c.Echo(ctx, "hello")
require.NoError(t, err)
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 TestHangingAsyncCalls(t *testing.T) {
app := slowApp{}
s, c := setupClientServer(t, app)
resp := make(chan error, 1)
go func() {
// Call CheckTx
reqres, err := c.CheckTxAsync(context.Background(), &types.RequestCheckTx{})
require.NoError(t, err)
// wait 50 ms for all events to travel socket, but
// no response yet from server
time.Sleep(50 * time.Millisecond)
// kill the server, so the connections break
err = s.Stop()
require.NoError(t, err)
// wait for the response from CheckTx
reqres.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 TestBulk(t *testing.T) {
const numTxs = 700000
// use a socket instead of a port
socketFile := fmt.Sprintf("test-%08x.sock", rand.Int31n(1<<30))
defer os.Remove(socketFile)
socket := fmt.Sprintf("unix://%v", socketFile)
app := types.NewBaseApplication()
// Start the listener
server := server.NewSocketServer(socket, app)
t.Cleanup(func() {
if err := server.Stop(); err != nil {
t.Log(err)
}
})
err := server.Start()
require.NoError(t, err)
// Connect to the socket
client := abcicli.NewSocketClient(socket, false)
t.Cleanup(func() {
if err := client.Stop(); err != nil {
t.Log(err)
}
})
err = client.Start()
require.NoError(t, err)
// Construct request
rfb := &types.RequestFinalizeBlock{Txs: make([][]byte, numTxs)}
for counter := 0; counter < numTxs; counter++ {
rfb.Txs[counter] = []byte("test")
}
// Send bulk request
res, err := client.FinalizeBlock(context.Background(), rfb)
require.NoError(t, err)
require.Equal(t, numTxs, len(res.TxResults), "Number of txs doesn't match")
for _, tx := range res.TxResults {
require.Equal(t, uint32(0), tx.Code, "Tx failed")
}
// Send final flush message
err = client.Flush(context.Background())
require.NoError(t, err)
}
func setupClientServer(t *testing.T, app types.Application) (
service.Service, abcicli.Client,
) {
t.Helper()
// some port between 20k and 30k
port := 20000 + cmtrand.Int32()%10000
addr := fmt.Sprintf("localhost:%d", port)
s := server.NewSocketServer(addr, app)
err := s.Start()
require.NoError(t, err)
t.Cleanup(func() {
if err := s.Stop(); err != nil {
t.Log(err)
}
})
c := abcicli.NewSocketClient(addr, true)
err = c.Start()
require.NoError(t, err)
t.Cleanup(func() {
if err := c.Stop(); err != nil {
t.Log(err)
}
})
return s, c
}
type slowApp struct {
types.BaseApplication
}
func (slowApp) CheckTx(context.Context, *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
time.Sleep(time.Second)
return &types.ResponseCheckTx{}, nil
}
// TestCallbackInvokedWhenSetLaet ensures that the callback is invoked when
// set after the client completes the call into the app. Currently this
// test relies on the callback being allowed to be invoked twice if set multiple
// times, once when set early and once when set late.
func TestCallbackInvokedWhenSetLate(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(1)
app := blockedABCIApplication{
wg: wg,
}
_, c := setupClientServer(t, app)
reqRes, err := c.CheckTxAsync(ctx, &types.RequestCheckTx{})
require.NoError(t, err)
done := make(chan struct{})
cb := func(_ *types.Response) {
close(done)
}
reqRes.SetCallback(cb)
app.wg.Done()
<-done
var called bool
cb = func(_ *types.Response) {
called = true
}
reqRes.SetCallback(cb)
require.True(t, called)
}
type blockedABCIApplication struct {
wg *sync.WaitGroup
types.BaseApplication
}
func (b blockedABCIApplication) CheckTxAsync(ctx context.Context, r *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
b.wg.Wait()
return b.CheckTx(ctx, r)
}
// TestCallbackInvokedWhenSetEarly ensures that the callback is invoked when
// set before the client completes the call into the app.
func TestCallbackInvokedWhenSetEarly(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(1)
app := blockedABCIApplication{
wg: wg,
}
_, c := setupClientServer(t, app)
reqRes, err := c.CheckTxAsync(ctx, &types.RequestCheckTx{})
require.NoError(t, err)
done := make(chan struct{})
cb := func(_ *types.Response) {
close(done)
}
reqRes.SetCallback(cb)
app.wg.Done()
called := func() bool {
select {
case <-done:
return true
default:
return false
}
}
require.Eventually(t, called, time.Second, time.Millisecond*25)
}

View file

@ -0,0 +1,797 @@
package main
import (
"bufio"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/cometbft/cometbft/libs/log"
cmtos "github.com/cometbft/cometbft/libs/os"
abcicli "github.com/cometbft/cometbft/abci/client"
"github.com/cometbft/cometbft/abci/example/kvstore"
"github.com/cometbft/cometbft/abci/server"
servertest "github.com/cometbft/cometbft/abci/tests/server"
"github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/abci/version"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
)
// client is a global variable so it can be reused by the console
var (
client abcicli.Client
logger log.Logger
)
// flags
var (
// global
flagAddress string
flagAbci string
flagVerbose bool // for the println output
flagLogLevel string // for the logger
// query
flagPath string
flagHeight int
flagProve bool
// kvstore
flagPersist string
)
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", "help [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
Query *queryResponse
}
type queryResponse struct {
Key []byte
Value []byte
Height int64
ProofOps *crypto.ProofOps
}
func Execute() error {
addGlobalFlags()
addCommands()
return RootCmd.Execute()
}
func addGlobalFlags() {
RootCmd.PersistentFlags().StringVarP(&flagAddress,
"address",
"",
"tcp://0.0.0.0:26658",
"address of application socket")
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")
RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
}
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 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(checkTxCmd)
RootCmd.AddCommand(commitCmd)
RootCmd.AddCommand(versionCmd)
RootCmd.AddCommand(testCmd)
RootCmd.AddCommand(prepareProposalCmd)
RootCmd.AddCommand(processProposalCmd)
addQueryFlags()
RootCmd.AddCommand(queryCmd)
RootCmd.AddCommand(finalizeBlockCmd)
// examples
addKVStoreFlags()
RootCmd.AddCommand(kvstoreCmd)
}
var batchCmd = &cobra.Command{
Use: "batch",
Short: "run a batch of abci commands against an application",
Long: `run a batch of abci commands against an application
This command is run by piping in a file containing a series of commands
you'd like to run:
abci-cli batch < example.file
where example.file looks something like:
check_tx 0x00
check_tx 0xff
finalize_block 0x00
check_tx 0x00
finalize_block 0x01 0x04 0xff
info
`,
Args: cobra.ExactArgs(0),
RunE: cmdBatch,
}
var consoleCmd = &cobra.Command{
Use: "console",
Short: "start an interactive ABCI console for multiple commands",
Long: `start an interactive ABCI console for multiple commands
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", "finalize_block", "check_tx", "prepare_proposal", "process_proposal", "commit", "query"},
RunE: cmdConsole,
}
var echoCmd = &cobra.Command{
Use: "echo",
Short: "have the application echo a message",
Long: "have the application echo a message",
Args: cobra.ExactArgs(1),
RunE: cmdEcho,
}
var infoCmd = &cobra.Command{
Use: "info",
Short: "get some info about the application",
Long: "get some info about the application",
Args: cobra.ExactArgs(0),
RunE: cmdInfo,
}
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(1),
RunE: cmdFinalizeBlock,
}
var checkTxCmd = &cobra.Command{
Use: "check_tx",
Short: "validate a transaction",
Long: "validate a transaction",
Args: cobra.ExactArgs(1),
RunE: cmdCheckTx,
}
var commitCmd = &cobra.Command{
Use: "commit",
Short: "commit the application state and return the Merkle root hash",
Long: "commit the application state and return the Merkle root hash",
Args: cobra.ExactArgs(0),
RunE: cmdCommit,
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "print ABCI console version",
Long: "print ABCI console version",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
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 processProposalCmd = &cobra.Command{
Use: "process_proposal",
Short: "process proposal",
Long: "process proposal",
Args: cobra.MinimumNArgs(0),
RunE: cmdProcessProposal,
}
var queryCmd = &cobra.Command{
Use: "query",
Short: "query the application state",
Long: "query the application state",
Args: cobra.ExactArgs(1),
RunE: cmdQuery,
}
var kvstoreCmd = &cobra.Command{
Use: "kvstore",
Short: "ABCI demo example",
Long: "ABCI demo example",
Args: cobra.ExactArgs(0),
RunE: cmdKVStore,
}
var testCmd = &cobra.Command{
Use: "test",
Short: "run integration tests",
Long: "run integration tests",
Args: cobra.ExactArgs(0),
RunE: cmdTest,
}
// Generates new Args array based off of previous call args to maintain flag persistence
func persistentArgs(line []byte) []string {
// generate the arguments to run from original os.Args
// to maintain flag arguments
args := os.Args
args = args[:len(args)-1] // remove the previous command argument
if len(line) > 0 { // prevents introduction of extra space leading to argument parse errors
args = append(args, strings.Split(string(line), " ")...)
}
return args
}
//--------------------------------------------------------------------------------
func compose(fs []func() error) error {
if len(fs) == 0 {
return nil
}
err := fs[0]()
if err == nil {
return compose(fs[1:])
}
return err
}
func cmdTest(cmd *cobra.Command, _ []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.FinalizeBlock(ctx, client, [][]byte{
[]byte("abc"),
}, []uint32{
kvstore.CodeTypeInvalidTxFormat,
}, nil, nil)
},
func() error { return servertest.Commit(ctx, client) },
func() error {
return servertest.FinalizeBlock(ctx, client, [][]byte{
{0x00},
}, []uint32{
kvstore.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{
kvstore.CodeTypeInvalidTxFormat,
kvstore.CodeTypeOK,
kvstore.CodeTypeOK,
kvstore.CodeTypeOK,
kvstore.CodeTypeOK,
kvstore.CodeTypeInvalidTxFormat,
}, 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},
}, [][]byte{{0x01}}, nil)
},
func() error {
return servertest.ProcessProposal(ctx, client, [][]byte{
{0x01},
}, types.ResponseProcessProposal_ACCEPT)
},
})
}
func cmdBatch(cmd *cobra.Command, _ []string) error {
bufReader := bufio.NewReader(os.Stdin)
LOOP:
for {
line, more, err := bufReader.ReadLine()
switch {
case more:
return errors.New("input line is too long")
case err == io.EOF:
break LOOP
case len(line) == 0:
continue
case err != nil:
return err
}
cmdArgs := persistentArgs(line)
if err := muxOnCommands(cmd, cmdArgs); err != nil {
return err
}
fmt.Println()
}
return nil
}
func cmdConsole(cmd *cobra.Command, _ []string) error {
for {
fmt.Printf("> ")
bufReader := bufio.NewReader(os.Stdin)
line, more, err := bufReader.ReadLine()
if more {
return errors.New("input is too long")
} else if err != nil {
return err
}
pArgs := persistentArgs(line)
if err := muxOnCommands(cmd, pArgs); err != nil {
return err
}
}
}
func muxOnCommands(cmd *cobra.Command, pArgs []string) error {
if len(pArgs) < 2 {
return errors.New("expecting persistent args of the form: abci-cli [command] <...>")
}
// TODO: this parsing is fragile
args := []string{}
for i := 0; i < len(pArgs); i++ {
arg := pArgs[i]
// check for flags
if strings.HasPrefix(arg, "-") {
// if it has an equal, we can just skip
if strings.Contains(arg, "=") {
continue
}
// if its a boolean, we can just skip
_, err := cmd.Flags().GetBool(strings.TrimLeft(arg, "-"))
if err == nil {
continue
}
// otherwise, we need to skip the next one too
i++
continue
}
// append the actual arg
args = append(args, arg)
}
var subCommand string
var actualArgs []string
if len(args) > 1 {
subCommand = args[1]
}
if len(args) > 2 {
actualArgs = args[2:]
}
cmd.Use = subCommand // for later print statements ...
switch strings.ToLower(subCommand) {
case "check_tx":
return cmdCheckTx(cmd, actualArgs)
case "commit":
return cmdCommit(cmd, actualArgs)
case "finalize_block":
return cmdFinalizeBlock(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)
default:
return cmdUnimplemented(cmd, pArgs)
}
}
func cmdUnimplemented(cmd *cobra.Command, args []string) error {
msg := "unimplemented command"
if len(args) > 0 {
msg += fmt.Sprintf(" args: [%s]", strings.Join(args, " "))
}
printResponse(cmd, args, response{
Code: codeBad,
Log: msg,
})
fmt.Println("Available commands:")
fmt.Printf("%s: %s\n", echoCmd.Use, echoCmd.Short)
fmt.Printf("%s: %s\n", checkTxCmd.Use, checkTxCmd.Short)
fmt.Printf("%s: %s\n", commitCmd.Use, commitCmd.Short)
fmt.Printf("%s: %s\n", finalizeBlockCmd.Use, finalizeBlockCmd.Short)
fmt.Printf("%s: %s\n", infoCmd.Use, infoCmd.Short)
fmt.Printf("%s: %s\n", queryCmd.Use, queryCmd.Short)
fmt.Printf("%s: %s\n", prepareProposalCmd.Use, prepareProposalCmd.Short)
fmt.Printf("%s: %s\n", processProposalCmd.Use, processProposalCmd.Short)
fmt.Println("Use \"[command] --help\" for more information about a command.")
return nil
}
// Have the application echo a message
func cmdEcho(cmd *cobra.Command, args []string) error {
msg := ""
if len(args) > 0 {
msg = args[0]
}
res, err := client.Echo(cmd.Context(), msg)
if err != nil {
return err
}
printResponse(cmd, args, response{
Data: []byte(res.Message),
})
return nil
}
// Get some info from the application
func cmdInfo(cmd *cobra.Command, args []string) error {
var version string
if len(args) == 1 {
version = args[0]
}
res, err := client.Info(cmd.Context(), &types.RequestInfo{Version: version})
if err != nil {
return err
}
printResponse(cmd, args, response{
Data: []byte(res.Data),
})
return nil
}
const codeBad uint32 = 10
// Append new txs to application
func cmdFinalizeBlock(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
printResponse(cmd, args, response{
Code: codeBad,
Log: "Must provide at least one transaction",
})
return nil
}
txs := make([][]byte, len(args))
for i, arg := range args {
txBytes, err := stringOrHexToBytes(arg)
if err != nil {
return err
}
txs[i] = txBytes
}
res, err := client.FinalizeBlock(cmd.Context(), &types.RequestFinalizeBlock{Txs: txs})
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,
})
}
resps = append(resps, response{
Data: res.AppHash,
})
printResponse(cmd, args, resps...)
return nil
}
// Validate a tx
func cmdCheckTx(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
printResponse(cmd, args, response{
Code: codeBad,
Info: "want the tx",
})
return nil
}
txBytes, err := stringOrHexToBytes(args[0])
if err != nil {
return err
}
res, err := client.CheckTx(cmd.Context(), &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(), &types.RequestCommit{})
if err != nil {
return err
}
printResponse(cmd, args, response{})
return nil
}
// Query application state
func cmdQuery(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
printResponse(cmd, args, response{
Code: codeBad,
Info: "want the query",
Log: "",
})
return nil
}
queryBytes, err := stringOrHexToBytes(args[0])
if err != nil {
return err
}
resQuery, err := client.Query(cmd.Context(), &types.RequestQuery{
Data: queryBytes,
Path: flagPath,
Height: int64(flagHeight),
Prove: flagProve,
})
if err != nil {
return err
}
printResponse(cmd, args, response{
Code: resQuery.Code,
Info: resQuery.Info,
Log: resQuery.Log,
Query: &queryResponse{
Key: resQuery.Key,
Value: resQuery.Value,
Height: resQuery.Height,
ProofOps: resQuery.ProofOps,
},
})
return nil
}
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,
})
if err != nil {
return err
}
resps := make([]response, 0, len(res.Txs))
for _, tx := range res.Txs {
resps = append(resps, response{
Code: 0, // CodeOK
Log: "Succeeded. Tx: " + string(tx),
})
}
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 {
return err
}
printResponse(cmd, args, response{
Status: int32(res.Status),
})
return nil
}
func cmdKVStore(*cobra.Command, []string) error {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
var err error
flagPersist, err = os.MkdirTemp("", "persistent_kvstore_tmp")
if err != nil {
return err
}
}
app = kvstore.NewPersistentApplication(flagPersist)
// 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.
cmtos.TrapSignal(logger, func() {
// Cleanup
if err := srv.Stop(); err != nil {
logger.Error("Error while stopping server", "err", err)
}
})
// Run forever.
select {}
}
//--------------------------------------------------------------------------------
func printResponse(cmd *cobra.Command, args []string, rsps ...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)
}
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)
}
}
}
}
// NOTE: s is interpreted as a string unless prefixed with 0x
func stringOrHexToBytes(s string) ([]byte, error) {
if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
b, err := hex.DecodeString(s[2:])
if err != nil {
err = fmt.Errorf("error decoding hex argument: %s", err.Error())
return nil, err
}
return b, nil
}
if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
err := fmt.Errorf("invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
return nil, err
}
return []byte(s[1 : len(s)-1]), nil
}

14
abci/cmd/abci-cli/main.go Normal file
View file

@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
err := Execute()
if err != nil {
fmt.Print(err)
os.Exit(1)
}
}

View file

@ -0,0 +1,17 @@
# KVStore
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).
Validator set changes are effected using the following transaction format:
```md
"val:pubkeytype1!pubkey1!power1,pubkeytype2!pubkey2!power2,pubkeytype3!pubkey3!power3"
```
where `pubkeyN` is a base64-encoded 32-byte key, `pubkeytypeN` is a string representing the key type,
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.

View file

@ -0,0 +1,10 @@
package kvstore
// Return codes for the examples
const (
CodeTypeOK uint32 = 0
CodeTypeEncodingError uint32 = 1
CodeTypeInvalidTxFormat uint32 = 2
CodeTypeUnauthorized uint32 = 3
CodeTypeExecuted uint32 = 5
)

View file

@ -0,0 +1,80 @@
package kvstore
import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/cometbft/cometbft/abci/types"
cryptoencoding "github.com/cometbft/cometbft/crypto/encoding"
cmtrand "github.com/cometbft/cometbft/libs/rand"
"github.com/cometbft/cometbft/proto/tendermint/crypto"
)
// RandVal creates one random validator, with a key derived
// from the input value
func RandVal() types.ValidatorUpdate {
pubkey := cmtrand.Bytes(32)
power := cmtrand.Uint16() + 1
v := types.UpdateValidator(pubkey, int64(power), "")
return v
}
// RandVals returns a list of cnt validators for initializing
// the application. Note that the keys are deterministically
// derived from the index in the array, while the power is
// random (Change this if not desired)
func RandVals(cnt int) []types.ValidatorUpdate {
res := make([]types.ValidatorUpdate, cnt)
for i := 0; i < cnt; i++ {
res[i] = RandVal()
}
return res
}
// 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 *Application) error {
_, err := app.InitChain(ctx, &types.RequestInitChain{
Validators: RandVals(1),
})
return err
}
// Create a new transaction
func NewTx(key, value string) []byte {
return []byte(strings.Join([]string{key, value}, "="))
}
func NewRandomTx(size int) []byte {
if size < 4 {
panic("random tx size must be greater than 3")
}
return NewTx(cmtrand.Str(2), cmtrand.Str(size-3))
}
func NewRandomTxs(n int) [][]byte {
txs := make([][]byte, n)
for i := 0; i < n; i++ {
txs[i] = NewRandomTx(10)
}
return txs
}
func NewTxFromID(i int) []byte {
return []byte(fmt.Sprintf("%d=%d", i, i))
}
// Create a transaction to add/remove/update a validator
// To remove, set power to 0.
func MakeValSetChangeTx(pubkey crypto.PublicKey, power int64) []byte {
pk, err := cryptoencoding.PubKeyFromProto(pubkey)
if err != nil {
panic(err)
}
pubStr := base64.StdEncoding.EncodeToString(pk.Bytes())
pubTypeStr := pk.Type()
return []byte(fmt.Sprintf("%s%s!%s!%d", ValidatorPrefix, pubTypeStr, pubStr, power))
}

View file

@ -0,0 +1,553 @@
package kvstore
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"strconv"
"strings"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/abci/types"
cryptoencoding "github.com/cometbft/cometbft/crypto/encoding"
"github.com/cometbft/cometbft/libs/log"
cryptoproto "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/cometbft/cometbft/version"
)
var (
stateKey = []byte("stateKey")
kvPairPrefixKey = []byte("kvPairKey:")
)
const (
ValidatorPrefix = "val="
AppVersion uint64 = 1
)
var _ types.Application = (*Application)(nil)
// Application is the kvstore state machine. It complies with the abci.Application interface.
// It takes transactions in the form of key=value and saves them in a database. This is
// a somewhat trivial example as there is no real state execution
type Application struct {
types.BaseApplication
state State
RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
stagedTxs [][]byte
logger log.Logger
// validator set
valUpdates []types.ValidatorUpdate
valAddrToPubKeyMap map[string]cryptoproto.PublicKey
// If true, the app will generate block events in BeginBlock. Used to test the event indexer
// Should be false by default to avoid generating too much data.
genBlockEvents bool
}
// NewApplication creates an instance of the kvstore from the provided database
func NewApplication(db dbm.DB) *Application {
return &Application{
logger: log.NewNopLogger(),
state: loadState(db),
valAddrToPubKeyMap: make(map[string]cryptoproto.PublicKey),
}
}
// NewPersistentApplication creates a new application using the goleveldb database engine
func NewPersistentApplication(dbDir string) *Application {
name := "kvstore"
db, err := dbm.NewGoLevelDB(name, dbDir)
if err != nil {
panic(fmt.Errorf("failed to create persistent app at %s: %w", dbDir, err))
}
return NewApplication(db)
}
// NewInMemoryApplication creates a new application from an in memory database.
// Nothing will be persisted.
func NewInMemoryApplication() *Application {
return NewApplication(dbm.NewMemDB())
}
func (app *Application) SetGenBlockEvents() {
app.genBlockEvents = true
}
// Info returns information about the state of the application. This is generally used everytime a Tendermint instance
// begins and let's the application know what Tendermint versions it's interacting with. Based from this information,
// Tendermint will ensure it is in sync with the application by potentially replaying the blocks it has. If the
// Application returns a 0 appBlockHeight, Tendermint will call InitChain to initialize the application with consensus related data
func (app *Application) Info(context.Context, *types.RequestInfo) (*types.ResponseInfo, error) {
// Tendermint expects the application to persist validators, on start-up we need to reload them to memory if they exist
if len(app.valAddrToPubKeyMap) == 0 && app.state.Height > 0 {
validators := app.getValidators()
for _, v := range validators {
pubkey, err := cryptoencoding.PubKeyFromProto(v.PubKey)
if err != nil {
panic(fmt.Errorf("can't decode public key: %w", err))
}
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
}
return &types.ResponseInfo{
Data: fmt.Sprintf("{\"size\":%v}", app.state.Size),
Version: version.ABCIVersion,
AppVersion: AppVersion,
LastBlockHeight: app.state.Height,
LastBlockAppHash: app.state.Hash(),
}, nil
}
// InitChain takes the genesis validators and stores them in the kvstore. It returns the application hash in the
// case that the application starts prepopulated with values. This method is called whenever a new instance of the application
// starts (i.e. app height = 0).
func (app *Application) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) {
for _, v := range req.Validators {
app.updateValidator(v)
}
appHash := make([]byte, 8)
binary.PutVarint(appHash, app.state.Size)
return &types.ResponseInitChain{
AppHash: appHash,
}, nil
}
// CheckTx handles inbound transactions or in the case of recheckTx assesses old transaction validity after a state transition.
// As this is called frequently, it's preferably to keep the check as stateless and as quick as possible.
// Here we check that the transaction has the correctly key=value format.
// For the KVStore we check that each transaction has the valid tx format:
// - Contains one and only one `=`
// - `=` is not the first or last byte.
// - if key is `val` that the validator update transaction is also valid
func (app *Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
// If it is a validator update transaction, check that it is correctly formatted
if isValidatorTx(req.Tx) {
if _, _, _, err := parseValidatorTx(req.Tx); err != nil {
//nolint:nilerr
return &types.ResponseCheckTx{Code: CodeTypeInvalidTxFormat}, nil
}
} else if !isValidTx(req.Tx) {
return &types.ResponseCheckTx{Code: CodeTypeInvalidTxFormat}, nil
}
return &types.ResponseCheckTx{Code: CodeTypeOK, GasWanted: 1}, nil
}
// Tx must have a format like key:value or key=value. That is:
// - it must have one and only one ":" or "="
// - It must not begin or end with these special characters
func isValidTx(tx []byte) bool {
if bytes.Count(tx, []byte(":")) == 1 && bytes.Count(tx, []byte("=")) == 0 {
if !bytes.HasPrefix(tx, []byte(":")) && !bytes.HasSuffix(tx, []byte(":")) {
return true
}
} else if bytes.Count(tx, []byte("=")) == 1 && bytes.Count(tx, []byte(":")) == 0 {
if !bytes.HasPrefix(tx, []byte("=")) && !bytes.HasSuffix(tx, []byte("=")) {
return true
}
}
return false
}
// PrepareProposal is called when the node is a proposer. CometBFT stages a set of transactions to the application. As the
// KVStore has two accepted formats, `:` and `=`, we modify all instances of `:` with `=` to make it consistent. Note: this is
// quite a trivial example of transaction modification.
// NOTE: we assume that CometBFT will never provide more transactions than can fit in a block.
func (app *Application) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
return &types.ResponsePrepareProposal{Txs: app.formatTxs(ctx, req.Txs)}, nil
}
// formatTxs validates and excludes invalid transactions
// also substitutes all the transactions with x:y to x=y
func (app *Application) formatTxs(ctx context.Context, blockData [][]byte) [][]byte {
txs := make([][]byte, 0, len(blockData))
for _, tx := range blockData {
if resp, err := app.CheckTx(ctx, &types.RequestCheckTx{Tx: tx}); err == nil && resp.Code == CodeTypeOK {
txs = append(txs, bytes.Replace(tx, []byte(":"), []byte("="), 1))
}
}
return txs
}
// ProcessProposal is called whenever a node receives a complete proposal. It allows the application to validate the proposal.
// Only validators who can vote will have this method called. For the KVstore we reuse CheckTx.
func (app *Application) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
for _, tx := range req.Txs {
// As CheckTx is a full validity check we can simply reuse this
if resp, err := app.CheckTx(ctx, &types.RequestCheckTx{Tx: tx}); err != nil || resp.Code != CodeTypeOK {
return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil
}
}
return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}, nil
}
// FinalizeBlock executes the block against the application state. It punishes validators who equivocated and
// updates validators according to transactions in a block. The rest of the transactions are regular key value
// updates and are cached in memory and will be persisted once Commit is called.
// ConsensusParams are never changed.
func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
// reset valset changes
app.valUpdates = make([]types.ValidatorUpdate, 0)
app.stagedTxs = make([][]byte, 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.valUpdates = append(app.valUpdates, 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 {
if isValidatorTx(tx) {
keyType, pubKey, power, err := parseValidatorTx(tx)
if err != nil {
panic(err)
}
app.valUpdates = append(app.valUpdates, types.UpdateValidator(pubKey, power, keyType))
} else {
app.stagedTxs = append(app.stagedTxs, tx)
}
var key, value string
parts := bytes.Split(tx, []byte("="))
if len(parts) == 2 {
key, value = string(parts[0]), string(parts[1])
} else {
key, value = string(tx), string(tx)
}
respTxs[i] = &types.ExecTxResult{
Code: CodeTypeOK,
// With every transaction we can emit a series of events. To make it simple, we just emit the same events.
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},
},
},
{
Type: "app",
Attributes: []types.EventAttribute{
{Key: "creator", Value: "Cosmoshi", Index: true},
{Key: "key", Value: value, Index: true},
{Key: "index_key", Value: "index is working", Index: true},
{Key: "noindex_key", Value: "index is working", Index: false},
},
},
},
}
app.state.Size++
}
app.state.Height = req.Height
response := &types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.valUpdates, AppHash: app.state.Hash()}
if !app.genBlockEvents {
return response, nil
}
if app.state.Height%2 == 0 {
response.Events = []types.Event{
{
Type: "begin_event",
Attributes: []types.EventAttribute{
{
Key: "foo",
Value: "100",
Index: true,
},
{
Key: "bar",
Value: "200",
Index: true,
},
},
},
{
Type: "begin_event",
Attributes: []types.EventAttribute{
{
Key: "foo",
Value: "200",
Index: true,
},
{
Key: "bar",
Value: "300",
Index: true,
},
},
},
}
} else {
response.Events = []types.Event{
{
Type: "begin_event",
Attributes: []types.EventAttribute{
{
Key: "foo",
Value: "400",
Index: true,
},
{
Key: "bar",
Value: "300",
Index: true,
},
},
},
}
}
return response, nil
}
// Commit is called after FinalizeBlock and after Tendermint state which includes the updates to
// AppHash, ConsensusParams and ValidatorSet has occurred.
// The KVStore persists the validator updates and the new key values
func (app *Application) Commit(context.Context, *types.RequestCommit) (*types.ResponseCommit, error) {
// apply the validator updates to state (note this is really the validator set at h + 2)
for _, valUpdate := range app.valUpdates {
app.updateValidator(valUpdate)
}
// persist all the staged txs in the kvstore
for _, tx := range app.stagedTxs {
parts := bytes.Split(tx, []byte("="))
if len(parts) != 2 {
panic(fmt.Sprintf("unexpected tx format. Expected 2 got %d: %s", len(parts), parts))
}
key, value := string(parts[0]), string(parts[1])
err := app.state.db.Set(prefixKey([]byte(key)), []byte(value))
if err != nil {
panic(err)
}
}
// persist the state (i.e. size and height)
saveState(app.state)
resp := &types.ResponseCommit{}
if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks {
resp.RetainHeight = app.state.Height - app.RetainBlocks + 1
}
return resp, nil
}
// Returns an associated value or nil if missing.
func (app *Application) Query(_ context.Context, reqQuery *types.RequestQuery) (*types.ResponseQuery, error) {
resQuery := &types.ResponseQuery{}
if reqQuery.Path == "/val" {
key := []byte(ValidatorPrefix + string(reqQuery.Data))
value, err := app.state.db.Get(key)
if err != nil {
panic(err)
}
return &types.ResponseQuery{
Key: reqQuery.Data,
Value: value,
}, nil
}
if reqQuery.Prove {
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
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
}
resQuery.Key = reqQuery.Data
value, err := app.state.db.Get(prefixKey(reqQuery.Data))
if err != nil {
panic(err)
}
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) Close() error {
return app.state.db.Close()
}
func isValidatorTx(tx []byte) bool {
return strings.HasPrefix(string(tx), ValidatorPrefix)
}
func parseValidatorTx(tx []byte) (string, []byte, int64, error) {
tx = tx[len(ValidatorPrefix):]
// get the pubkey and power
typePubKeyAndPower := strings.Split(string(tx), "!")
if len(typePubKeyAndPower) != 3 {
return "", nil, 0, fmt.Errorf("expected 'pubkeytype!pubkey!power'. Got %v", typePubKeyAndPower)
}
keyType, pubkeyS, powerS := typePubKeyAndPower[0], typePubKeyAndPower[1], typePubKeyAndPower[2]
// decode the pubkey
pubkey, err := base64.StdEncoding.DecodeString(pubkeyS)
if err != nil {
return "", nil, 0, fmt.Errorf("pubkey (%s) is invalid base64", pubkeyS)
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return "", nil, 0, fmt.Errorf("power (%s) is not an int", powerS)
}
if power < 0 {
return "", nil, 0, fmt.Errorf("power can not be less than 0, got %d", power)
}
return keyType, pubkey, power, nil
}
// add, update, or remove a validator
func (app *Application) updateValidator(v types.ValidatorUpdate) {
pubkey, err := cryptoencoding.PubKeyFromProto(v.PubKey)
if err != nil {
panic(fmt.Errorf("can't decode public key: %w", err))
}
key := []byte(ValidatorPrefix + 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())
app.logger.Info("tried to remove non existent validator. Skipping...", "pubKey", 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 {
panic(err)
}
if err = app.state.db.Set(key, value.Bytes()); err != nil {
panic(err)
}
app.valAddrToPubKeyMap[string(pubkey.Address())] = v.PubKey
}
}
func (app *Application) getValidators() (validators []types.ValidatorUpdate) {
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
}
// -----------------------------
type State struct {
db dbm.DB
// Size is essentially the amount of transactions that have been processes.
// This is used for the appHash
Size int64 `json:"size"`
Height int64 `json:"height"`
}
func loadState(db dbm.DB) State {
var state State
state.db = db
stateBytes, err := db.Get(stateKey)
if err != nil {
panic(err)
}
if len(stateBytes) == 0 {
return state
}
err = json.Unmarshal(stateBytes, &state)
if err != nil {
panic(err)
}
return state
}
func saveState(state State) {
stateBytes, err := json.Marshal(state)
if err != nil {
panic(err)
}
err = state.db.Set(stateKey, stateBytes)
if err != nil {
panic(err)
}
}
// Hash returns the hash of the application state. This is computed
// as the size or number of transactions processed within the state. Note that this isn't
// a strong guarantee of state machine replication because states could
// have different kv values but still have the same size.
// This function is used as the "AppHash"
func (s State) Hash() []byte {
appHash := make([]byte, 8)
binary.PutVarint(appHash, s.Size)
return appHash
}
func prefixKey(key []byte) []byte {
return append(kvPairPrefixKey, key...)
}

View file

@ -0,0 +1,337 @@
package kvstore
import (
"context"
"fmt"
"sort"
"testing"
"github.com/stretchr/testify/require"
"github.com/cometbft/cometbft/libs/log"
"github.com/cometbft/cometbft/libs/service"
abcicli "github.com/cometbft/cometbft/abci/client"
abciserver "github.com/cometbft/cometbft/abci/server"
"github.com/cometbft/cometbft/abci/types"
)
const (
testKey = "abc"
testValue = "def"
)
func TestKVStoreKV(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewInMemoryApplication()
tx := []byte(testKey + ":" + testValue)
testKVStore(ctx, t, kvstore, tx, testKey, testValue)
tx = []byte(testKey + "=" + testValue)
testKVStore(ctx, t, kvstore, tx, testKey, testValue)
}
func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx []byte, key, value string) {
checkTxResp, err := app.CheckTx(ctx, &types.RequestCheckTx{Tx: tx})
require.NoError(t, err)
require.Equal(t, uint32(0), checkTxResp.Code)
ppResp, err := app.PrepareProposal(ctx, &types.RequestPrepareProposal{Txs: [][]byte{tx}})
require.NoError(t, err)
require.Len(t, ppResp.Txs, 1)
req := &types.RequestFinalizeBlock{Height: 1, Txs: ppResp.Txs}
ar, err := app.FinalizeBlock(ctx, req)
require.NoError(t, err)
require.Equal(t, 1, len(ar.TxResults))
require.False(t, ar.TxResults[0].IsErr())
// commit
_, err = app.Commit(ctx, &types.RequestCommit{})
require.NoError(t, err)
info, err := app.Info(ctx, &types.RequestInfo{})
require.NoError(t, err)
require.NotZero(t, info.LastBlockHeight)
// make sure query is fine
resQuery, err := app.Query(ctx, &types.RequestQuery{
Path: "/store",
Data: []byte(key),
})
require.NoError(t, err)
require.Equal(t, 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{
Path: "/store",
Data: []byte(key),
Prove: true,
})
require.NoError(t, err)
require.EqualValues(t, CodeTypeOK, resQuery.Code)
require.Equal(t, key, string(resQuery.Key))
require.Equal(t, value, string(resQuery.Value))
require.EqualValues(t, info.LastBlockHeight, resQuery.Height)
}
func TestPersistentKVStoreEmptyTX(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewPersistentApplication(t.TempDir())
tx := []byte("")
reqCheck := types.RequestCheckTx{Tx: tx}
resCheck, err := kvstore.CheckTx(ctx, &reqCheck)
require.NoError(t, err)
require.Equal(t, resCheck.Code, CodeTypeInvalidTxFormat)
txs := make([][]byte, 0, 4)
txs = append(txs, []byte("key=value"), []byte("key:val"), []byte(""), []byte("kee=value"))
reqPrepare := types.RequestPrepareProposal{Txs: txs, MaxTxBytes: 10 * 1024}
resPrepare, err := kvstore.PrepareProposal(ctx, &reqPrepare)
require.NoError(t, err)
require.Equal(t, len(reqPrepare.Txs)-1, len(resPrepare.Txs), "Empty transaction not properly removed")
}
func TestPersistentKVStoreKV(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewPersistentApplication(t.TempDir())
key := testKey
value := testValue
testKVStore(ctx, t, kvstore, NewTx(key, value), key, value)
}
func TestPersistentKVStoreInfo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewPersistentApplication(t.TempDir())
require.NoError(t, InitKVStore(ctx, kvstore))
height := int64(0)
resInfo, err := kvstore.Info(ctx, &types.RequestInfo{})
require.NoError(t, err)
if resInfo.LastBlockHeight != height {
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight)
}
// 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)
}
_, err = kvstore.Commit(ctx, &types.RequestCommit{})
require.NoError(t, err)
resInfo, err = kvstore.Info(ctx, &types.RequestInfo{})
require.NoError(t, err)
require.Equal(t, height, resInfo.LastBlockHeight)
}
// add a validator, remove a validator, update a validator
func TestValUpdates(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewInMemoryApplication()
// init with some validators
total := 10
nInit := 5
vals := RandVals(total)
// initialize with the first nInit
_, err := kvstore.InitChain(ctx, &types.RequestInitChain{
Validators: vals[:nInit],
})
require.NoError(t, err)
vals1, vals2 := vals[:nInit], kvstore.getValidators()
valsEqual(t, vals1, vals2)
var v1, v2, v3 types.ValidatorUpdate
// add some validators
v1, v2 = vals[nInit], vals[nInit+1]
diff := []types.ValidatorUpdate{v1, v2}
tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power)
tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power)
makeApplyBlock(ctx, t, kvstore, 1, diff, tx1, tx2)
vals1, vals2 = vals[:nInit+2], kvstore.getValidators()
valsEqual(t, vals1, vals2)
// remove some validators
v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit]
v1.Power = 0
v2.Power = 0
v3.Power = 0
diff = []types.ValidatorUpdate{v1, v2, v3}
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power)
tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power)
makeApplyBlock(ctx, t, kvstore, 2, diff, tx1, tx2, tx3)
vals1 = append(vals[:nInit-2], vals[nInit+1]) //nolint: gocritic
vals2 = kvstore.getValidators()
valsEqual(t, vals1, vals2)
// update some validators
v1 = vals[0]
if v1.Power == 5 {
v1.Power = 6
} else {
v1.Power = 5
}
diff = []types.ValidatorUpdate{v1}
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power)
makeApplyBlock(ctx, t, kvstore, 3, diff, tx1)
vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...)
vals2 = kvstore.getValidators()
valsEqual(t, vals1, vals2)
}
func TestCheckTx(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kvstore := NewInMemoryApplication()
val := RandVal()
testCases := []struct {
expCode uint32
tx []byte
}{
{CodeTypeOK, NewTx("hello", "world")},
{CodeTypeInvalidTxFormat, []byte("hello")},
{CodeTypeOK, []byte("space:jam")},
{CodeTypeInvalidTxFormat, []byte("=hello")},
{CodeTypeInvalidTxFormat, []byte("hello=")},
{CodeTypeOK, []byte("a=b")},
{CodeTypeInvalidTxFormat, []byte("val=hello")},
{CodeTypeInvalidTxFormat, []byte("val=hi!5")},
{CodeTypeOK, MakeValSetChangeTx(val.PubKey, 10)},
}
for idx, tc := range testCases {
resp, err := kvstore.CheckTx(ctx, &types.RequestCheckTx{Tx: tc.tx})
require.NoError(t, err, idx)
fmt.Println(string(tc.tx))
require.Equal(t, tc.expCode, resp.Code, idx)
}
}
func TestClientServer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// set up socket app
kvstore := NewInMemoryApplication()
client, _, err := makeClientServer(t, kvstore, "kvstore-socket", "socket")
require.NoError(t, err)
runClientTests(ctx, t, client)
// set up grpc app
kvstore = NewInMemoryApplication()
gclient, _, err := makeClientServer(t, kvstore, t.TempDir(), "grpc")
require.NoError(t, err)
runClientTests(ctx, t, gclient)
}
func makeApplyBlock(
ctx context.Context,
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,
Height: height,
Txs: txs,
})
require.NoError(t, err)
_, err = kvstore.Commit(ctx, &types.RequestCommit{})
require.NoError(t, err)
valsEqual(t, diff, resFinalizeBlock.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))
}
sort.Sort(types.ValidatorUpdates(vals1))
sort.Sort(types.ValidatorUpdates(vals2))
for i, v1 := range vals1 {
v2 := vals2[i]
if !v1.PubKey.Equal(v2.PubKey) ||
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 makeClientServer(t *testing.T, app types.Application, name, transport string) (abcicli.Client, service.Service, error) {
// Start the listener
addr := fmt.Sprintf("unix://%s.sock", name)
logger := log.TestingLogger()
server, err := abciserver.NewServer(addr, transport, app)
require.NoError(t, err)
server.SetLogger(logger.With("module", "abci-server"))
if err := server.Start(); err != nil {
return nil, nil, err
}
t.Cleanup(func() {
if err := server.Stop(); err != nil {
t.Error(err)
}
})
// Connect to the client
client, err := abcicli.NewClient(addr, transport, false)
require.NoError(t, err)
client.SetLogger(logger.With("module", "abci-client"))
if err := client.Start(); err != nil {
return nil, nil, err
}
t.Cleanup(func() {
if err := client.Stop(); err != nil {
t.Error(err)
}
})
return client, server, nil
}
func runClientTests(ctx context.Context, t *testing.T, client abcicli.Client) {
// run some tests....
tx := []byte(testKey + ":" + testValue)
testKVStore(ctx, t, client, tx, testKey, testValue)
tx = []byte(testKey + "=" + testValue)
testKVStore(ctx, t, client, tx, testKey, testValue)
}
func TestTxGeneration(t *testing.T) {
require.Len(t, NewRandomTx(20), 20)
require.Len(t, NewRandomTxs(10), 10)
}

View file

@ -0,0 +1,76 @@
package server
import (
"context"
"net"
"google.golang.org/grpc"
"github.com/cometbft/cometbft/abci/types"
cmtnet "github.com/cometbft/cometbft/libs/net"
"github.com/cometbft/cometbft/libs/service"
)
type GRPCServer struct {
service.BaseService
proto string
addr string
listener net.Listener
server *grpc.Server
app types.Application
}
// NewGRPCServer returns a new gRPC ABCI server
func NewGRPCServer(protoAddr string, app types.Application) service.Service {
proto, addr := cmtnet.ProtocolAndAddress(protoAddr)
s := &GRPCServer{
proto: proto,
addr: addr,
listener: nil,
app: app,
}
s.BaseService = *service.NewBaseService(nil, "ABCIServer", s)
return s
}
// OnStart starts the gRPC service.
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.RegisterABCIServer(s.server, &gRPCApplication{s.app})
s.Logger.Info("Listening", "proto", s.proto, "addr", s.addr)
go func() {
if err := s.server.Serve(s.listener); 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, *types.RequestFlush) (*types.ResponseFlush, error) {
return &types.ResponseFlush{}, nil
}

31
abci/server/server.go Normal file
View file

@ -0,0 +1,31 @@
/*
Package server is used to start a new ABCI server.
It contains two server implementation:
- gRPC server
- socket server
*/
package server
import (
"fmt"
"github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/service"
)
// NewServer is a utility function for out of process applications to set up either a socket or
// grpc server that can listen to requests from the equivalent Tendermint client
func NewServer(protoAddr, transport string, app types.Application) (service.Service, error) {
var s service.Service
var err error
switch transport {
case "socket":
s = NewSocketServer(protoAddr, app)
case "grpc":
s = NewGRPCServer(protoAddr, app)
default:
err = fmt.Errorf("unknown server type %s", transport)
}
return s, err
}

View file

@ -0,0 +1,335 @@
package server
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime"
"github.com/cometbft/cometbft/abci/types"
cmtlog "github.com/cometbft/cometbft/libs/log"
cmtnet "github.com/cometbft/cometbft/libs/net"
"github.com/cometbft/cometbft/libs/service"
cmtsync "github.com/cometbft/cometbft/libs/sync"
)
// SocketServer is the server-side implementation of the TSP (Tendermint Socket Protocol)
// for out-of-process go applications. Note, in the case of an application written in golang,
// the developer may also run both Tendermint and the application within the same process.
//
// The socket server deliver
type SocketServer struct {
service.BaseService
isLoggerSet bool
proto string
addr string
listener net.Listener
connsMtx cmtsync.Mutex
conns map[int]net.Conn
nextConnID int
appMtx cmtsync.Mutex
app types.Application
}
const responseBufferSize = 1000
// NewSocketServer creates a server from a golang-based out-of-process application.
func NewSocketServer(protoAddr string, app types.Application) service.Service {
proto, addr := cmtnet.ProtocolAndAddress(protoAddr)
s := &SocketServer{
proto: proto,
addr: addr,
listener: nil,
app: app,
conns: make(map[int]net.Conn),
}
s.BaseService = *service.NewBaseService(nil, "ABCIServer", s)
return s
}
func (s *SocketServer) SetLogger(l cmtlog.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()
return nil
}
func (s *SocketServer) OnStop() {
if err := s.listener.Close(); err != nil {
s.Logger.Error("Error closing listener", "err", err)
}
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
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(conn net.Conn) int {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
connID := s.nextConnID
s.nextConnID++
s.conns[connID] = conn
return connID
}
// deletes conn even if close errs
func (s *SocketServer) rmConn(connID int) error {
s.connsMtx.Lock()
defer s.connsMtx.Unlock()
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() {
for {
// Accept a 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)
continue
}
s.Logger.Info("Accepted a new connection")
connID := s.addConn(conn)
closeConn := make(chan error, 2) // Push to signal connection closed
responses := make(chan *types.Response, responseBufferSize) // A channel to buffer responses
// Read requests from conn and deal with them
go s.handleRequests(closeConn, conn, responses)
// Pull responses from 'responses' and write them to conn.
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(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.
// In the case of a panic, we do not notify the client by passing an exception so
// presume that the client is still running and retying to connect
r := recover()
if r != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
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 {
var req = &types.Request{}
err := types.ReadMessage(bufReader, req)
if err != nil {
if err == io.EOF {
closeConn <- err
} else {
closeConn <- fmt.Errorf("error reading message: %w", err)
}
return
}
s.appMtx.Lock()
count++
resp, err := s.handleRequest(context.TODO(), req)
if err != nil {
// any error either from the application or because of an unknown request
// throws an exception back to the client. This will stop the server and
// should also halt the client.
responses <- types.ToResponseException(err.Error())
} else {
responses <- resp
}
s.appMtx.Unlock()
}
}
// handleRequests takes a request and calls the application passing the returned
func (s *SocketServer) handleRequest(ctx context.Context, req *types.Request) (*types.Response, error) {
switch r := req.Value.(type) {
case *types.Request_Echo:
return types.ToResponseEcho(r.Echo.Message), nil
case *types.Request_Flush:
return types.ToResponseFlush(), nil
case *types.Request_Info:
res, err := s.app.Info(ctx, r.Info)
if err != nil {
return nil, err
}
return types.ToResponseInfo(res), nil
case *types.Request_CheckTx:
res, err := s.app.CheckTx(ctx, r.CheckTx)
if err != nil {
return nil, err
}
return types.ToResponseCheckTx(res), nil
case *types.Request_Commit:
res, err := s.app.Commit(ctx, r.Commit)
if err != nil {
return nil, err
}
return types.ToResponseCommit(res), nil
case *types.Request_Query:
res, err := s.app.Query(ctx, r.Query)
if err != nil {
return nil, err
}
return types.ToResponseQuery(res), nil
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_FinalizeBlock:
res, err := s.app.FinalizeBlock(ctx, r.FinalizeBlock)
if err != nil {
return nil, err
}
return types.ToResponseFinalizeBlock(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
default:
return nil, fmt.Errorf("unknown request from client: %T", req)
}
}
// Pull responses from 'responses' and write them to conn.
func (s *SocketServer) handleResponses(closeConn chan error, conn io.Writer, responses <-chan *types.Response) {
var count int
var bufWriter = bufio.NewWriter(conn)
for {
var res = <-responses
err := types.WriteMessage(res, bufWriter)
if err != nil {
closeConn <- fmt.Errorf("error writing message: %w", err)
return
}
if _, ok := res.Value.(*types.Response_Flush); ok {
err = bufWriter.Flush()
if err != nil {
closeConn <- fmt.Errorf("error flushing write buffer: %w", err)
return
}
}
// If the application has responded with an exception, the server returns the error
// back to the client and closes the connection. The receiving Tendermint client should
// log the error and gracefully terminate
if e, ok := res.Value.(*types.Response_Exception); ok {
closeConn <- errors.New(e.Exception.Error)
}
count++
}
}

View file

@ -0,0 +1 @@
package benchmarks

View file

@ -0,0 +1,55 @@
package main
import (
"bufio"
"fmt"
"log"
"github.com/cometbft/cometbft/abci/types"
cmtnet "github.com/cometbft/cometbft/libs/net"
)
func main() {
conn, err := cmtnet.Connect("unix://test.sock")
if err != nil {
log.Fatal(err.Error())
}
// Read a bunch of responses
go func() {
counter := 0
for {
var res = &types.Response{}
err := types.ReadMessage(conn, res)
if err != nil {
log.Fatal(err.Error())
}
counter++
if counter%1000 == 0 {
fmt.Println("Read", counter)
}
}
}()
// Write a bunch of requests
counter := 0
for i := 0; ; i++ {
var bufWriter = bufio.NewWriter(conn)
var req = types.ToRequestEcho("foobar")
err := types.WriteMessage(req, bufWriter)
if err != nil {
log.Fatal(err.Error())
}
err = bufWriter.Flush()
if err != nil {
log.Fatal(err.Error())
}
counter++
if counter%1000 == 0 {
fmt.Println("Write", counter)
}
}
}

View file

@ -0,0 +1,69 @@
package main
import (
"bufio"
"fmt"
"io"
"log"
"reflect"
"github.com/cometbft/cometbft/abci/types"
cmtnet "github.com/cometbft/cometbft/libs/net"
)
func main() {
conn, err := cmtnet.Connect("unix://test.sock")
if err != nil {
log.Fatal(err.Error())
}
// Make a bunch of requests
counter := 0
for i := 0; ; i++ {
req := types.ToRequestEcho("foobar")
_, err := makeRequest(conn, req)
if err != nil {
log.Fatal(err.Error())
}
counter++
if counter%1000 == 0 {
fmt.Println(counter)
}
}
}
func makeRequest(conn io.ReadWriter, req *types.Request) (*types.Response, error) {
var bufWriter = bufio.NewWriter(conn)
// Write desired request
err := types.WriteMessage(req, bufWriter)
if err != nil {
return nil, err
}
err = types.WriteMessage(types.ToRequestFlush(), bufWriter)
if err != nil {
return nil, err
}
err = bufWriter.Flush()
if err != nil {
return nil, err
}
// Read desired response
var res = &types.Response{}
err = types.ReadMessage(conn, res)
if err != nil {
return nil, err
}
var resFlush = &types.Response{}
err = types.ReadMessage(conn, resFlush)
if err != nil {
return nil, err
}
if _, ok := resFlush.Value.(*types.Response_Flush); !ok {
return nil, fmt.Errorf("expected flush response but got something else: %v", reflect.TypeOf(resFlush))
}
return res, nil
}

View file

@ -0,0 +1,39 @@
package tests
import (
"testing"
"github.com/stretchr/testify/assert"
abciclient "github.com/cometbft/cometbft/abci/client"
"github.com/cometbft/cometbft/abci/example/kvstore"
abciserver "github.com/cometbft/cometbft/abci/server"
)
func TestClientServerNoAddrPrefix(t *testing.T) {
t.Helper()
addr := "localhost:26658"
transport := "socket"
app := kvstore.NewInMemoryApplication()
server, err := abciserver.NewServer(addr, transport, app)
assert.NoError(t, err, "expected no error on NewServer")
err = server.Start()
assert.NoError(t, err, "expected no error on server.Start")
t.Cleanup(func() {
if err := server.Stop(); err != nil {
t.Error(err)
}
})
client, err := abciclient.NewClient(addr, transport, true)
assert.NoError(t, err, "expected no error on NewClient")
err = client.Start()
assert.NoError(t, err, "expected no error on client.Start")
t.Cleanup(func() {
if err := client.Stop(); err != nil {
t.Error(err)
}
})
}

114
abci/tests/server/client.go Normal file
View file

@ -0,0 +1,114 @@
package testsuite
import (
"bytes"
"context"
"errors"
"fmt"
abcicli "github.com/cometbft/cometbft/abci/client"
"github.com/cometbft/cometbft/abci/types"
cmtrand "github.com/cometbft/cometbft/libs/rand"
)
func InitChain(ctx context.Context, client abcicli.Client) error {
total := 10
vals := make([]types.ValidatorUpdate, total)
for i := 0; i < total; i++ {
pubkey := cmtrand.Bytes(33)
power := cmtrand.Int()
vals[i] = types.UpdateValidator(pubkey, int64(power), "")
}
_, err := client.InitChain(ctx, &types.RequestInitChain{
Validators: vals,
})
if err != nil {
fmt.Printf("Failed test: InitChain - %v\n", err)
return err
}
fmt.Println("Passed test: InitChain")
return nil
}
func Commit(ctx context.Context, client abcicli.Client) error {
_, err := client.Commit(ctx, &types.RequestCommit{})
if err != nil {
fmt.Println("Failed test: Commit")
fmt.Printf("error while committing: %v\n", err)
return err
}
fmt.Println("Passed test: Commit")
return nil
}
func FinalizeBlock(ctx context.Context, client abcicli.Client, txBytes [][]byte, codeExp []uint32, dataExp []byte, hashExp []byte) error {
res, _ := client.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: txBytes})
appHash := res.AppHash
for i, tx := range res.TxResults {
code, data, log := tx.Code, tx.Data, tx.Log
if code != codeExp[i] {
fmt.Println("Failed test: FinalizeBlock")
fmt.Printf("FinalizeBlock response code was unexpected. Got %v expected %v. Log: %v\n",
code, codeExp, log)
return errors.New("FinalizeBlock error")
}
if !bytes.Equal(data, dataExp) {
fmt.Println("Failed test: FinalizeBlock")
fmt.Printf("FinalizeBlock response data was unexpected. Got %X expected %X\n",
data, dataExp)
return errors.New("FinalizeBlock error")
}
}
if !bytes.Equal(appHash, hashExp) {
fmt.Println("Failed test: FinalizeBlock")
fmt.Printf("Application hash was unexpected. Got %X expected %X\n", appHash, hashExp)
return errors.New("FinalizeBlock error")
}
fmt.Println("Passed test: FinalizeBlock")
return nil
}
func PrepareProposal(ctx context.Context, client abcicli.Client, txBytes [][]byte, txExpected [][]byte, _ []byte) error {
res, _ := client.PrepareProposal(ctx, &types.RequestPrepareProposal{Txs: txBytes})
for i, tx := range res.Txs {
if !bytes.Equal(tx, txExpected[i]) {
fmt.Println("Failed test: PrepareProposal")
fmt.Printf("PrepareProposal transaction was unexpected. Got %x expected %x.",
tx, txExpected[i])
return errors.New("PrepareProposal error")
}
}
fmt.Println("Passed test: PrepareProposal")
return nil
}
func ProcessProposal(ctx context.Context, client abcicli.Client, txBytes [][]byte, statusExp types.ResponseProcessProposal_ProposalStatus) error {
res, _ := client.ProcessProposal(ctx, &types.RequestProcessProposal{Txs: txBytes})
if res.Status != statusExp {
fmt.Println("Failed test: ProcessProposal")
fmt.Printf("ProcessProposal response status was unexpected. Got %v expected %v.",
res.Status, statusExp)
return errors.New("ProcessProposal error")
}
fmt.Println("Passed test: ProcessProposal")
return nil
}
func CheckTx(ctx context.Context, client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error {
res, _ := client.CheckTx(ctx, &types.RequestCheckTx{Tx: txBytes})
code, data, log := res.Code, res.Data, res.Log
if code != codeExp {
fmt.Println("Failed test: CheckTx")
fmt.Printf("CheckTx response code was unexpected. Got %v expected %v. Log: %v\n",
code, codeExp, log)
return errors.New("checkTx")
}
if !bytes.Equal(data, dataExp) {
fmt.Println("Failed test: CheckTx")
fmt.Printf("CheckTx response data was unexpected. Got %X expected %X\n",
data, dataExp)
return errors.New("checkTx")
}
fmt.Println("Passed test: CheckTx")
return nil
}

View file

@ -0,0 +1,13 @@
echo hello
info
prepare_proposal "abc=123"
process_proposal "abc==456"
process_proposal "abc=123"
finalize_block "abc=123"
commit
info
query "abc"
finalize_block "def=xyz" "ghi=123"
commit
query "def"

View file

@ -0,0 +1,62 @@
> echo hello
-> code: OK
-> data: hello
-> data.hex: 0x68656C6C6F
> info
-> code: OK
-> data: {"size":0}
-> data.hex: 0x7B2273697A65223A307D
> prepare_proposal "abc=123"
-> code: OK
-> log: Succeeded. Tx: abc=123
> process_proposal "abc==456"
-> code: OK
-> status: REJECT
> process_proposal "abc=123"
-> code: OK
-> status: ACCEPT
> finalize_block "abc=123"
-> code: OK
-> code: OK
-> data.hex: 0x0200000000000000
> commit
-> code: OK
> info
-> code: OK
-> data: {"size":1}
-> data.hex: 0x7B2273697A65223A317D
> query "abc"
-> code: OK
-> log: exists
-> height: 0
-> key: abc
-> key.hex: 616263
-> value: 123
-> value.hex: 313233
> finalize_block "def=xyz" "ghi=123"
-> code: OK
-> code: OK
-> code: OK
-> data.hex: 0x0600000000000000
> commit
-> code: OK
> query "def"
-> code: OK
-> log: exists
-> height: 0
-> key: def
-> key.hex: 646566
-> value: xyz
-> value.hex: 78797A

View file

@ -0,0 +1,9 @@
check_tx "abc"
check_tx "def=567"
finalize_block "def=567"
commit
finalize_block "hello=world"
commit
finalize_block "first=second"
commit
info

View file

@ -0,0 +1,35 @@
> check_tx "abc"
-> code: 2
> check_tx "def=567"
-> code: OK
> finalize_block "def=567"
-> code: OK
-> code: OK
-> data.hex: 0x0200000000000000
> commit
-> code: OK
> finalize_block "hello=world"
-> code: OK
-> code: OK
-> data.hex: 0x0400000000000000
> commit
-> code: OK
> finalize_block "first=second"
-> code: OK
-> code: OK
-> data.hex: 0x0600000000000000
> commit
-> code: OK
> info
-> code: OK
-> data: {"size":3}
-> data.hex: 0x7B2273697A65223A337D

71
abci/tests/test_cli/test.sh Executable file
View file

@ -0,0 +1,71 @@
#! /bin/bash
set -e
# Get the root directory.
export PATH="$GOBIN:$PATH"
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
DIR="$( cd -P "$( dirname "$SOURCE" )/../.." && pwd )"
# Change into that dir because we expect that.
cd "$DIR" || exit
function testExample() {
N=$1
INPUT=$2
APP="$3 $4"
echo "Example $N: $APP"
$APP &> /dev/null &
sleep 2
abci-cli --log_level=error --verbose batch < "$INPUT" > "${INPUT}.out.new"
killall "$3"
pre=$(shasum < "${INPUT}.out")
post=$(shasum < "${INPUT}.out.new")
if [[ "$pre" != "$post" ]]; then
echo "You broke the tutorial"
echo "Got:"
cat "${INPUT}.out.new"
echo "Expected:"
cat "${INPUT}.out"
echo "Diff:"
diff -u "${INPUT}.out" "${INPUT}.out.new"
exit 1
fi
rm "${INPUT}".out.new
}
function testHelp() {
INPUT=$1
APP="$2 $3"
echo "Test: $APP"
$APP &> "${INPUT}.new" &
sleep 2
pre=$(shasum < "${INPUT}")
post=$(shasum < "${INPUT}.new")
if [[ "$pre" != "$post" ]]; then
echo "You broke the tutorial"
echo "Got:"
cat "${INPUT}.new"
echo "Expected:"
cat "${INPUT}"
echo "Diff:"
diff "${INPUT}" "${INPUT}.new"
exit 1
fi
rm "${INPUT}".new
}
testExample 1 tests/test_cli/ex1.abci abci-cli kvstore
testExample 2 tests/test_cli/ex2.abci abci-cli kvstore
testHelp tests/test_cli/testHelp.out abci-cli help
echo ""
echo "PASS"

View file

@ -0,0 +1,30 @@
the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers
Usage:
abci-cli [command]
Available Commands:
batch run a batch of abci commands against an application
check_tx validate a transaction
commit commit the application state and return the Merkle root hash
completion Generate the autocompletion script for the specified shell
console start an interactive ABCI console for multiple commands
echo have the application echo a message
finalize_block deliver a block of transactions to the application
help Help about any command
info get some info about the application
kvstore ABCI demo example
prepare_proposal prepare proposal
process_proposal process proposal
query query the application state
test run integration tests
version print ABCI console version
Flags:
--abci string either socket or grpc (default "socket")
--address string address of application socket (default "tcp://0.0.0.0:26658")
-h, --help help for abci-cli
--log_level string set the logger level (default "debug")
-v, --verbose print the command and results as if it were a console session
Use "abci-cli [command] --help" for more information about a command.

1
abci/tests/tests.go Normal file
View file

@ -0,0 +1 @@
package tests

119
abci/types/application.go Normal file
View file

@ -0,0 +1,119 @@
package types
import "context"
//go:generate ../../scripts/mockery_generate.sh Application
// Application is an interface that enables any finite, deterministic state machine
// to be driven by a blockchain-based replication engine via the ABCI.
type Application interface {
// Info/Query Connection
Info(context.Context, *RequestInfo) (*ResponseInfo, error) // Return application info
Query(context.Context, *RequestQuery) (*ResponseQuery, error) // Query for state
// Mempool Connection
CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTx, error) // Validate a tx for the mempool
// Consensus Connection
InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from CometBFT
PrepareProposal(context.Context, *RequestPrepareProposal) (*ResponsePrepareProposal, error)
ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error)
// Deliver the decided block with its txs to the Application
FinalizeBlock(context.Context, *RequestFinalizeBlock) (*ResponseFinalizeBlock, error)
// Create application specific vote extension
ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error)
// Verify application's vote extension data
VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error)
// Commit the state and return the application Merkle root hash
Commit(context.Context, *RequestCommit) (*ResponseCommit, error)
// State Sync Connection
ListSnapshots(context.Context, *RequestListSnapshots) (*ResponseListSnapshots, error) // List available snapshots
OfferSnapshot(context.Context, *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) // Offer a snapshot to the application
LoadSnapshotChunk(context.Context, *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) // Load a snapshot chunk
ApplySnapshotChunk(context.Context, *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) // Apply a shapshot chunk
}
//-------------------------------------------------------
// BaseApplication is a base form of Application
var _ Application = (*BaseApplication)(nil)
type BaseApplication struct{}
func NewBaseApplication() *BaseApplication {
return &BaseApplication{}
}
func (BaseApplication) Info(context.Context, *RequestInfo) (*ResponseInfo, error) {
return &ResponseInfo{}, nil
}
func (BaseApplication) CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTx, error) {
return &ResponseCheckTx{Code: CodeTypeOK}, nil
}
func (BaseApplication) Commit(context.Context, *RequestCommit) (*ResponseCommit, error) {
return &ResponseCommit{}, nil
}
func (BaseApplication) Query(context.Context, *RequestQuery) (*ResponseQuery, error) {
return &ResponseQuery{Code: CodeTypeOK}, nil
}
func (BaseApplication) InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) {
return &ResponseInitChain{}, nil
}
func (BaseApplication) ListSnapshots(context.Context, *RequestListSnapshots) (*ResponseListSnapshots, error) {
return &ResponseListSnapshots{}, nil
}
func (BaseApplication) OfferSnapshot(context.Context, *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) {
return &ResponseOfferSnapshot{}, nil
}
func (BaseApplication) LoadSnapshotChunk(context.Context, *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) {
return &ResponseLoadSnapshotChunk{}, nil
}
func (BaseApplication) ApplySnapshotChunk(context.Context, *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) {
return &ResponseApplySnapshotChunk{}, nil
}
func (BaseApplication) PrepareProposal(_ context.Context, req *RequestPrepareProposal) (*ResponsePrepareProposal, error) {
txs := make([][]byte, 0, len(req.Txs))
var totalBytes int64
for _, tx := range req.Txs {
totalBytes += int64(len(tx))
if totalBytes > req.MaxTxBytes {
break
}
txs = append(txs, tx)
}
return &ResponsePrepareProposal{Txs: txs}, nil
}
func (BaseApplication) ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) {
return &ResponseProcessProposal{Status: ResponseProcessProposal_ACCEPT}, nil
}
func (BaseApplication) ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error) {
return &ResponseExtendVote{}, nil
}
func (BaseApplication) VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) {
return &ResponseVerifyVoteExtension{
Status: ResponseVerifyVoteExtension_ACCEPT,
}, nil
}
func (BaseApplication) FinalizeBlock(_ context.Context, req *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) {
txs := make([]*ExecTxResult, len(req.Txs))
for i := range req.Txs {
txs[i] = &ExecTxResult{Code: CodeTypeOK}
}
return &ResponseFinalizeBlock{
TxResults: txs,
}, nil
}

229
abci/types/messages.go Normal file
View file

@ -0,0 +1,229 @@
package types
import (
"io"
"math"
"github.com/cosmos/gogoproto/proto"
"github.com/cometbft/cometbft/libs/protoio"
)
const (
maxMsgSize = math.MaxInt32 // 2GB
)
// WriteMessage writes a varint length-delimited protobuf message.
func WriteMessage(msg proto.Message, w io.Writer) error {
protoWriter := protoio.NewDelimitedWriter(w)
_, err := protoWriter.WriteMsg(msg)
return err
}
// ReadMessage reads a varint length-delimited protobuf message.
func ReadMessage(r io.Reader, msg proto.Message) error {
_, err := protoio.NewDelimitedReader(r, maxMsgSize).ReadMsg(msg)
return err
}
//----------------------------------------
func ToRequestEcho(message string) *Request {
return &Request{
Value: &Request_Echo{&RequestEcho{Message: message}},
}
}
func ToRequestFlush() *Request {
return &Request{
Value: &Request_Flush{&RequestFlush{}},
}
}
func ToRequestInfo(req *RequestInfo) *Request {
return &Request{
Value: &Request_Info{req},
}
}
func ToRequestCheckTx(req *RequestCheckTx) *Request {
return &Request{
Value: &Request_CheckTx{req},
}
}
func ToRequestCommit() *Request {
return &Request{
Value: &Request_Commit{&RequestCommit{}},
}
}
func ToRequestQuery(req *RequestQuery) *Request {
return &Request{
Value: &Request_Query{req},
}
}
func ToRequestInitChain(req *RequestInitChain) *Request {
return &Request{
Value: &Request_InitChain{req},
}
}
func ToRequestListSnapshots(req *RequestListSnapshots) *Request {
return &Request{
Value: &Request_ListSnapshots{req},
}
}
func ToRequestOfferSnapshot(req *RequestOfferSnapshot) *Request {
return &Request{
Value: &Request_OfferSnapshot{req},
}
}
func ToRequestLoadSnapshotChunk(req *RequestLoadSnapshotChunk) *Request {
return &Request{
Value: &Request_LoadSnapshotChunk{req},
}
}
func ToRequestApplySnapshotChunk(req *RequestApplySnapshotChunk) *Request {
return &Request{
Value: &Request_ApplySnapshotChunk{req},
}
}
func ToRequestPrepareProposal(req *RequestPrepareProposal) *Request {
return &Request{
Value: &Request_PrepareProposal{req},
}
}
func ToRequestProcessProposal(req *RequestProcessProposal) *Request {
return &Request{
Value: &Request_ProcessProposal{req},
}
}
func ToRequestExtendVote(req *RequestExtendVote) *Request {
return &Request{
Value: &Request_ExtendVote{req},
}
}
func ToRequestVerifyVoteExtension(req *RequestVerifyVoteExtension) *Request {
return &Request{
Value: &Request_VerifyVoteExtension{req},
}
}
func ToRequestFinalizeBlock(req *RequestFinalizeBlock) *Request {
return &Request{
Value: &Request_FinalizeBlock{req},
}
}
//----------------------------------------
func ToResponseException(errStr string) *Response {
return &Response{
Value: &Response_Exception{&ResponseException{Error: errStr}},
}
}
func ToResponseEcho(message string) *Response {
return &Response{
Value: &Response_Echo{&ResponseEcho{Message: message}},
}
}
func ToResponseFlush() *Response {
return &Response{
Value: &Response_Flush{&ResponseFlush{}},
}
}
func ToResponseInfo(res *ResponseInfo) *Response {
return &Response{
Value: &Response_Info{res},
}
}
func ToResponseCheckTx(res *ResponseCheckTx) *Response {
return &Response{
Value: &Response_CheckTx{res},
}
}
func ToResponseCommit(res *ResponseCommit) *Response {
return &Response{
Value: &Response_Commit{res},
}
}
func ToResponseQuery(res *ResponseQuery) *Response {
return &Response{
Value: &Response_Query{res},
}
}
func ToResponseInitChain(res *ResponseInitChain) *Response {
return &Response{
Value: &Response_InitChain{res},
}
}
func ToResponseListSnapshots(res *ResponseListSnapshots) *Response {
return &Response{
Value: &Response_ListSnapshots{res},
}
}
func ToResponseOfferSnapshot(res *ResponseOfferSnapshot) *Response {
return &Response{
Value: &Response_OfferSnapshot{res},
}
}
func ToResponseLoadSnapshotChunk(res *ResponseLoadSnapshotChunk) *Response {
return &Response{
Value: &Response_LoadSnapshotChunk{res},
}
}
func ToResponseApplySnapshotChunk(res *ResponseApplySnapshotChunk) *Response {
return &Response{
Value: &Response_ApplySnapshotChunk{res},
}
}
func ToResponsePrepareProposal(res *ResponsePrepareProposal) *Response {
return &Response{
Value: &Response_PrepareProposal{res},
}
}
func ToResponseProcessProposal(res *ResponseProcessProposal) *Response {
return &Response{
Value: &Response_ProcessProposal{res},
}
}
func ToResponseExtendVote(res *ResponseExtendVote) *Response {
return &Response{
Value: &Response_ExtendVote{res},
}
}
func ToResponseVerifyVoteExtension(res *ResponseVerifyVoteExtension) *Response {
return &Response{
Value: &Response_VerifyVoteExtension{res},
}
}
func ToResponseFinalizeBlock(res *ResponseFinalizeBlock) *Response {
return &Response{
Value: &Response_FinalizeBlock{res},
}
}

114
abci/types/messages_test.go Normal file
View file

@ -0,0 +1,114 @@
package types
import (
"bytes"
"encoding/json"
"strings"
"testing"
"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/assert"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
)
func TestMarshalJSON(t *testing.T) {
b, err := json.Marshal(&ExecTxResult{Code: 1})
assert.NoError(t, err)
// include empty fields.
assert.True(t, strings.Contains(string(b), "code"))
r1 := ResponseCheckTx{
Code: 1,
Data: []byte("hello"),
GasWanted: 43,
Events: []Event{
{
Type: "testEvent",
Attributes: []EventAttribute{
{Key: "pho", Value: "bo"},
},
},
},
}
b, err = json.Marshal(&r1)
assert.Nil(t, err)
var r2 ResponseCheckTx
err = json.Unmarshal(b, &r2)
assert.Nil(t, err)
assert.Equal(t, r1, r2)
}
func TestWriteReadMessageSimple(t *testing.T) {
cases := []proto.Message{
&RequestEcho{
Message: "Hello",
},
}
for _, c := range cases {
buf := new(bytes.Buffer)
err := WriteMessage(c, buf)
assert.Nil(t, err)
msg := new(RequestEcho)
err = ReadMessage(buf, msg)
assert.Nil(t, err)
assert.True(t, proto.Equal(c, msg))
}
}
func TestWriteReadMessage(t *testing.T) {
cases := []proto.Message{
&cmtproto.Header{
Height: 4,
ChainID: "test",
},
// TODO: add the rest
}
for _, c := range cases {
buf := new(bytes.Buffer)
err := WriteMessage(c, buf)
assert.Nil(t, err)
msg := new(cmtproto.Header)
err = ReadMessage(buf, msg)
assert.Nil(t, err)
assert.True(t, proto.Equal(c, msg))
}
}
func TestWriteReadMessage2(t *testing.T) {
phrase := "hello-world"
cases := []proto.Message{
&ResponseCheckTx{
Data: []byte(phrase),
Log: phrase,
GasWanted: 10,
Events: []Event{
{
Type: "testEvent",
Attributes: []EventAttribute{
{Key: "abc", Value: "def"},
},
},
},
},
// TODO: add the rest
}
for _, c := range cases {
buf := new(bytes.Buffer)
err := WriteMessage(c, buf)
assert.Nil(t, err)
msg := new(ResponseCheckTx)
err = ReadMessage(buf, msg)
assert.Nil(t, err)
assert.True(t, proto.Equal(c, msg))
}
}

View file

@ -0,0 +1,449 @@
// Code generated by mockery v2.53.0. DO NOT EDIT.
package mocks
import (
context "context"
types "github.com/cometbft/cometbft/abci/types"
mock "github.com/stretchr/testify/mock"
)
// Application is an autogenerated mock type for the Application type
type Application struct {
mock.Mock
}
// ApplySnapshotChunk provides a mock function with given fields: _a0, _a1
func (_m *Application) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ApplySnapshotChunk")
}
var r0 *types.ResponseApplySnapshotChunk
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for CheckTx")
}
var r0 *types.ResponseCheckTx
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) (*types.ResponseCheckTx, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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, _a1
func (_m *Application) Commit(_a0 context.Context, _a1 *types.RequestCommit) (*types.ResponseCommit, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Commit")
}
var r0 *types.ResponseCommit
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCommit) (*types.ResponseCommit, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCommit) *types.ResponseCommit); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.ResponseCommit)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCommit) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ExtendVote provides a mock function with given fields: _a0, _a1
func (_m *Application) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ExtendVote")
}
var r0 *types.ResponseExtendVote
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) (*types.ResponseExtendVote, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for FinalizeBlock")
}
var r0 *types.ResponseFinalizeBlock
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// Info provides a mock function with given fields: _a0, _a1
func (_m *Application) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Info")
}
var r0 *types.ResponseInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) (*types.ResponseInfo, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for InitChain")
}
var r0 *types.ResponseInitChain
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) (*types.ResponseInitChain, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// ListSnapshots provides a mock function with given fields: _a0, _a1
func (_m *Application) ListSnapshots(_a0 context.Context, _a1 *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ListSnapshots")
}
var r0 *types.ResponseListSnapshots
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) (*types.ResponseListSnapshots, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) LoadSnapshotChunk(_a0 context.Context, _a1 *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for LoadSnapshotChunk")
}
var r0 *types.ResponseLoadSnapshotChunk
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) OfferSnapshot(_a0 context.Context, _a1 *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for OfferSnapshot")
}
var r0 *types.ResponseOfferSnapshot
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for PrepareProposal")
}
var r0 *types.ResponsePrepareProposal
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for ProcessProposal")
}
var r0 *types.ResponseProcessProposal
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) (*types.ResponseProcessProposal, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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 *Application) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for Query")
}
var r0 *types.ResponseQuery
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) (*types.ResponseQuery, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// VerifyVoteExtension provides a mock function with given fields: _a0, _a1
func (_m *Application) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for VerifyVoteExtension")
}
var r0 *types.ResponseVerifyVoteExtension
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error)); ok {
return rf(_a0, _a1)
}
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)
}
}
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
}
// NewApplication creates a new instance of Application. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewApplication(t interface {
mock.TestingT
Cleanup(func())
}) *Application {
mock := &Application{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

44
abci/types/pubkey.go Normal file
View file

@ -0,0 +1,44 @@
package types
import (
fmt "fmt"
"github.com/cometbft/cometbft/crypto/ed25519"
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
"github.com/cometbft/cometbft/crypto/secp256k1"
)
func Ed25519ValidatorUpdate(pk []byte, power int64) ValidatorUpdate {
pke := ed25519.PubKey(pk)
pkp, err := cryptoenc.PubKeyToProto(pke)
if err != nil {
panic(err)
}
return ValidatorUpdate{
// Address:
PubKey: pkp,
Power: power,
}
}
func UpdateValidator(pk []byte, power int64, keyType string) ValidatorUpdate {
switch keyType {
case "", ed25519.KeyType:
return Ed25519ValidatorUpdate(pk, power)
case secp256k1.KeyType:
pke := secp256k1.PubKey(pk)
pkp, err := cryptoenc.PubKeyToProto(pke)
if err != nil {
panic(err)
}
return ValidatorUpdate{
// Address:
PubKey: pkp,
Power: power,
}
default:
panic(fmt.Sprintf("key type %s not supported", keyType))
}
}

170
abci/types/types.go Normal file
View file

@ -0,0 +1,170 @@
package types
import (
"bytes"
"encoding/json"
"github.com/cosmos/gogoproto/jsonpb"
)
const (
CodeTypeOK uint32 = 0
)
// IsOK returns true if Code is OK.
func (r ResponseCheckTx) IsOK() bool {
return r.Code == CodeTypeOK
}
// IsErr returns true if Code is something other than OK.
func (r ResponseCheckTx) IsErr() bool {
return r.Code != CodeTypeOK
}
// IsOK returns true if Code is OK.
func (r ExecTxResult) IsOK() bool {
return r.Code == CodeTypeOK
}
// IsErr returns true if Code is something other than OK.
func (r ExecTxResult) IsErr() bool {
return r.Code != CodeTypeOK
}
// IsOK returns true if Code is OK.
func (r ResponseQuery) IsOK() bool {
return r.Code == CodeTypeOK
}
// IsErr returns true if Code is something other than OK.
func (r ResponseQuery) IsErr() bool {
return r.Code != CodeTypeOK
}
// IsAccepted returns true if Code is ACCEPT
func (r ResponseProcessProposal) IsAccepted() bool {
return r.Status == ResponseProcessProposal_ACCEPT
}
// IsStatusUnknown returns true if Code is UNKNOWN
func (r ResponseProcessProposal) IsStatusUnknown() bool {
return r.Status == ResponseProcessProposal_UNKNOWN
}
func (r ResponseVerifyVoteExtension) IsAccepted() bool {
return r.Status == ResponseVerifyVoteExtension_ACCEPT
}
// IsStatusUnknown returns true if Code is Unknown
func (r ResponseVerifyVoteExtension) IsStatusUnknown() bool {
return r.Status == ResponseVerifyVoteExtension_UNKNOWN
}
//---------------------------------------------------------------------------
// override JSON marshaling so we emit defaults (ie. disable omitempty)
var (
jsonpbMarshaller = jsonpb.Marshaler{
EnumsAsInts: true,
EmitDefaults: true,
}
jsonpbUnmarshaller = jsonpb.Unmarshaler{}
)
func (r *ResponseCheckTx) MarshalJSON() ([]byte, error) {
s, err := jsonpbMarshaller.MarshalToString(r)
return []byte(s), err
}
func (r *ResponseCheckTx) UnmarshalJSON(b []byte) error {
reader := bytes.NewBuffer(b)
return jsonpbUnmarshaller.Unmarshal(reader, r)
}
func (r *ExecTxResult) MarshalJSON() ([]byte, error) {
s, err := jsonpbMarshaller.MarshalToString(r)
return []byte(s), err
}
func (r *ExecTxResult) UnmarshalJSON(b []byte) error {
reader := bytes.NewBuffer(b)
return jsonpbUnmarshaller.Unmarshal(reader, r)
}
func (r *ResponseQuery) MarshalJSON() ([]byte, error) {
s, err := jsonpbMarshaller.MarshalToString(r)
return []byte(s), err
}
func (r *ResponseQuery) UnmarshalJSON(b []byte) error {
reader := bytes.NewBuffer(b)
return jsonpbUnmarshaller.Unmarshal(reader, r)
}
func (r *ResponseCommit) MarshalJSON() ([]byte, error) {
s, err := jsonpbMarshaller.MarshalToString(r)
return []byte(s), err
}
func (r *ResponseCommit) UnmarshalJSON(b []byte) error {
reader := bytes.NewBuffer(b)
return jsonpbUnmarshaller.Unmarshal(reader, r)
}
func (r *EventAttribute) MarshalJSON() ([]byte, error) {
s, err := jsonpbMarshaller.MarshalToString(r)
return []byte(s), err
}
func (r *EventAttribute) UnmarshalJSON(b []byte) error {
reader := bytes.NewBuffer(b)
return jsonpbUnmarshaller.Unmarshal(reader, r)
}
// Some compile time assertions to ensure we don't
// have accidental runtime surprises later on.
// jsonEncodingRoundTripper ensures that asserted
// interfaces implement both MarshalJSON and UnmarshalJSON
type jsonRoundTripper interface {
json.Marshaler
json.Unmarshaler
}
var _ jsonRoundTripper = (*ResponseCommit)(nil)
var _ jsonRoundTripper = (*ResponseQuery)(nil)
var _ jsonRoundTripper = (*ExecTxResult)(nil)
var _ jsonRoundTripper = (*ResponseCheckTx)(nil)
var _ jsonRoundTripper = (*EventAttribute)(nil)
// deterministicExecTxResult constructs a copy of response that omits
// non-deterministic fields. The input response is not modified.
func deterministicExecTxResult(response *ExecTxResult) *ExecTxResult {
return &ExecTxResult{
Code: response.Code,
Data: response.Data,
GasWanted: response.GasWanted,
GasUsed: response.GasUsed,
}
}
// MarshalTxResults encodes the the TxResults as a list of byte
// slices. It strips off the non-deterministic pieces of the TxResults
// so that the resulting data can be used for hash comparisons and used
// in Merkle proofs.
func MarshalTxResults(r []*ExecTxResult) ([][]byte, error) {
s := make([][]byte, len(r))
for i, e := range r {
d := deterministicExecTxResult(e)
b, err := d.Marshal()
if err != nil {
return nil, err
}
s[i] = b
}
return s, nil
}
// -----------------------------------------------
// construct Result data

16606
abci/types/types.pb.go Normal file

File diff suppressed because it is too large Load diff

74
abci/types/types_test.go Normal file
View file

@ -0,0 +1,74 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/merkle"
)
func TestHashAndProveResults(t *testing.T) {
trs := []*abci.ExecTxResult{
// Note, these tests rely on the first two entries being in this order.
{Code: 0, Data: nil},
{Code: 0, Data: []byte{}},
{Code: 0, Data: []byte("one")},
{Code: 14, Data: nil},
{Code: 14, Data: []byte("foo")},
{Code: 14, Data: []byte("bar")},
}
// Nil and []byte{} should produce the same bytes
bz0, err := trs[0].Marshal()
require.NoError(t, err)
bz1, err := trs[1].Marshal()
require.NoError(t, err)
require.Equal(t, bz0, bz1)
// Make sure that we can get a root hash from results and verify proofs.
rs, err := abci.MarshalTxResults(trs)
require.NoError(t, err)
root := merkle.HashFromByteSlices(rs)
assert.NotEmpty(t, root)
_, proofs := merkle.ProofsFromByteSlices(rs)
for i, tr := range trs {
bz, err := tr.Marshal()
require.NoError(t, err)
valid := proofs[i].Verify(root, bz)
assert.NoError(t, valid, "%d", i)
}
}
func TestHashDeterministicFieldsOnly(t *testing.T) {
tr1 := abci.ExecTxResult{
Code: 1,
Data: []byte("transaction"),
Log: "nondeterministic data: abc",
Info: "nondeterministic data: abc",
GasWanted: 1000,
GasUsed: 1000,
Events: []abci.Event{},
Codespace: "nondeterministic.data.abc",
}
tr2 := abci.ExecTxResult{
Code: 1,
Data: []byte("transaction"),
Log: "nondeterministic data: def",
Info: "nondeterministic data: def",
GasWanted: 1000,
GasUsed: 1000,
Events: []abci.Event{},
Codespace: "nondeterministic.data.def",
}
r1, err := abci.MarshalTxResults([]*abci.ExecTxResult{&tr1})
require.NoError(t, err)
r2, err := abci.MarshalTxResults([]*abci.ExecTxResult{&tr2})
require.NoError(t, err)
require.Equal(t, merkle.HashFromByteSlices(r1), merkle.HashFromByteSlices(r2))
}

31
abci/types/util.go Normal file
View file

@ -0,0 +1,31 @@
package types
import (
"sort"
)
//------------------------------------------------------------------------------
// ValidatorUpdates is a list of validators that implements the Sort interface
type ValidatorUpdates []ValidatorUpdate
var _ sort.Interface = (ValidatorUpdates)(nil)
// All these methods for ValidatorUpdates:
// Len, Less and Swap
// are for ValidatorUpdates to implement sort.Interface
// which will be used by the sort package.
// See Issue https://github.com/tendermint/abci/issues/212
func (v ValidatorUpdates) Len() int {
return len(v)
}
// XXX: doesn't distinguish same validator with different power
func (v ValidatorUpdates) Less(i, j int) bool {
return v[i].PubKey.Compare(v[j].PubKey) <= 0
}
func (v ValidatorUpdates) Swap(i, j int) {
v[i], v[j] = v[j], v[i]
}

9
abci/version/version.go Normal file
View file

@ -0,0 +1,9 @@
package version
import (
"github.com/cometbft/cometbft/version"
)
// TODO: eliminate this after some version refactor
const Version = version.ABCISemVer

53
blocksync/errors.go Normal file
View file

@ -0,0 +1,53 @@
package blocksync
import (
"errors"
"fmt"
"github.com/cosmos/gogoproto/proto"
)
var (
// ErrNilMessage is returned when provided message is empty
ErrNilMessage = errors.New("message cannot be nil")
)
// ErrInvalidBase is returned when peer informs of a status with invalid height
type ErrInvalidHeight struct {
Height int64
Reason string
}
func (e ErrInvalidHeight) Error() string {
return fmt.Sprintf("invalid height %v: %s", e.Height, e.Reason)
}
// ErrInvalidBase is returned when peer informs of a status with invalid base
type ErrInvalidBase struct {
Base int64
Reason string
}
func (e ErrInvalidBase) Error() string {
return fmt.Sprintf("invalid base %v: %s", e.Base, e.Reason)
}
type ErrUnknownMessageType struct {
Msg proto.Message
}
func (e ErrUnknownMessageType) Error() string {
return fmt.Sprintf("unknown message type %T", e.Msg)
}
type ErrReactorValidation struct {
Err error
}
func (e ErrReactorValidation) Error() string {
return fmt.Sprintf("reactor validation error: %v", e.Err)
}
func (e ErrReactorValidation) Unwrap() error {
return e.Err
}

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