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
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:
commit
ef24c0b67e
1398 changed files with 272435 additions and 0 deletions
11
.clang-format
Normal file
11
.clang-format
Normal 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
5
.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
build
|
||||
test/e2e/build
|
||||
test/e2e/networks
|
||||
test/logs
|
||||
test/p2p/data
|
||||
16
.editorconfig
Normal file
16
.editorconfig
Normal 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
12
.github/CODEOWNERS
vendored
Normal 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
83
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal 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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||
29
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal 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
28
.github/ISSUE_TEMPLATE/proposal.md
vendored
Normal 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
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
69
.github/dependabot.yml
vendored
Normal 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
10
.github/issue_template.md
vendored
Normal 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
15
.github/linters/markdownlint.yml
vendored
Normal 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
9
.github/linters/yaml-lint.yml
vendored
Normal 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
35
.github/mergify.yml
vendored
Normal 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
82
.github/workflows/build.yml
vendored
Normal 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
68
.github/workflows/check-generated.yml
vendored
Normal 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
|
||||
98
.github/workflows/docker-build-cometbft.yml
vendored
Normal file
98
.github/workflows/docker-build-cometbft.yml
vendored
Normal 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 }}
|
||||
98
.github/workflows/docker-build-e2e-node.yml
vendored
Normal file
98
.github/workflows/docker-build-e2e-node.yml
vendored
Normal 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
20
.github/workflows/docs-toc.yml
vendored
Normal 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
|
||||
45
.github/workflows/e2e-manual-multiversion.yml
vendored
Normal file
45
.github/workflows/e2e-manual-multiversion.yml
vendored
Normal 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
43
.github/workflows/e2e-manual.yml
vendored
Normal 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
36
.github/workflows/e2e.yml
vendored
Normal 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
93
.github/workflows/fuzz-nightly.yml
vendored
Normal 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
16
.github/workflows/janitor.yml
vendored
Normal 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
35
.github/workflows/lint.yml
vendored
Normal 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
33
.github/workflows/markdown-linter.yml
vendored
Normal 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
71
.github/workflows/pre-release.yml
vendored
Normal 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
21
.github/workflows/proto-lint.yml
vendored
Normal 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
33
.github/workflows/release-version.yml
vendored
Normal 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
72
.github/workflows/release.yml
vendored
Normal 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
20
.github/workflows/stale.yml
vendored
Normal 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
33
.github/workflows/tests.yml
vendored
Normal 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
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
build/
|
||||
*.test
|
||||
*.bench
|
||||
.DS_Store
|
||||
*.log
|
||||
167
.golangci.yml
Normal file
167
.golangci.yml
Normal 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
37
.goreleaser.yml
Normal 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
17
.markdownlint.yml
Normal 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
6
.markdownlintignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
docs/node_modules
|
||||
CHANGELOG.md
|
||||
docs/architecture/*
|
||||
crypto/secp256k1/**
|
||||
scripts/*
|
||||
.github
|
||||
17
.md-link-check.json
Normal file
17
.md-link-check.json
Normal 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
4
.mockery.yml
Normal 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
932
CHANGELOG.md
Normal 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 you’re 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
109
CODE_OF_CONDUCT.md
Normal 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. There’s 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 don’t 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 you’re a regular contributor or a newcomer, we care about
|
||||
making this community a safe place for you and we’ve got your back.
|
||||
|
||||
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing
|
||||
behavior is not welcome.
|
||||
|
||||
----
|
||||
|
||||
# Moderation
|
||||
|
||||
These are the policies for upholding our community’s 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. Don’t just aim to be technically unimpeachable, try to be your
|
||||
best self. In particular, avoid flirting with offensive or sensitive issues,
|
||||
particularly if they’re 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 could’ve communicated better — remember that it’s 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
381
CONTRIBUTING.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
Each stage of the process is aimed at creating feedback cycles which align contributors and maintainers to make sure:
|
||||
|
||||
- Contributors don’t waste their time implementing/proposing features which won’t 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
1
DOCKER/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
cometbft
|
||||
61
DOCKER/Dockerfile
Normal file
61
DOCKER/Dockerfile
Normal 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" ]
|
||||
|
||||
28
DOCKER/Dockerfile.build_c-amazonlinux
Normal file
28
DOCKER/Dockerfile.build_c-amazonlinux
Normal 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
16
DOCKER/Dockerfile.testing
Normal 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
13
DOCKER/Makefile
Normal 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
56
DOCKER/README.md
Normal 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
23
DOCKER/build.sh
Executable 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
23
DOCKER/docker-entrypoint.sh
Executable 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
22
DOCKER/push.sh
Executable 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
674
LICENSE
Normal 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
376
Makefile
Normal 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
13
NOTICE
Normal 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
30
README.md
Normal 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
385
RELEASES.md
Normal 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
33
SECURITY.md
Normal 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
162
STYLE_GUIDE.md
Normal 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 shouldn’t 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
156
UPGRADING.md
Normal 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
35
abci/README.md
Normal 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
133
abci/client/client.go
Normal 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
247
abci/client/grpc_client.go
Normal 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))
|
||||
}
|
||||
80
abci/client/grpc_client_test.go
Normal file
80
abci/client/grpc_client_test.go
Normal 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
186
abci/client/local_client.go
Normal 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
711
abci/client/mocks/client.go
Normal 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
|
||||
}
|
||||
515
abci/client/socket_client.go
Normal file
515
abci/client/socket_client.go
Normal 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)
|
||||
}
|
||||
}
|
||||
239
abci/client/socket_client_test.go
Normal file
239
abci/client/socket_client_test.go
Normal 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)
|
||||
}
|
||||
797
abci/cmd/abci-cli/abci-cli.go
Normal file
797
abci/cmd/abci-cli/abci-cli.go
Normal 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
14
abci/cmd/abci-cli/main.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := Execute()
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
17
abci/example/kvstore/README.md
Normal file
17
abci/example/kvstore/README.md
Normal 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.
|
||||
10
abci/example/kvstore/code.go
Normal file
10
abci/example/kvstore/code.go
Normal 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
|
||||
)
|
||||
80
abci/example/kvstore/helpers.go
Normal file
80
abci/example/kvstore/helpers.go
Normal 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))
|
||||
}
|
||||
553
abci/example/kvstore/kvstore.go
Normal file
553
abci/example/kvstore/kvstore.go
Normal 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...)
|
||||
}
|
||||
337
abci/example/kvstore/kvstore_test.go
Normal file
337
abci/example/kvstore/kvstore_test.go
Normal 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)
|
||||
}
|
||||
76
abci/server/grpc_server.go
Normal file
76
abci/server/grpc_server.go
Normal 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
31
abci/server/server.go
Normal 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
|
||||
}
|
||||
335
abci/server/socket_server.go
Normal file
335
abci/server/socket_server.go
Normal 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++
|
||||
}
|
||||
}
|
||||
1
abci/tests/benchmarks/blank.go
Normal file
1
abci/tests/benchmarks/blank.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package benchmarks
|
||||
55
abci/tests/benchmarks/parallel/parallel.go
Normal file
55
abci/tests/benchmarks/parallel/parallel.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
abci/tests/benchmarks/simple/simple.go
Normal file
69
abci/tests/benchmarks/simple/simple.go
Normal 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
|
||||
}
|
||||
39
abci/tests/client_server_test.go
Normal file
39
abci/tests/client_server_test.go
Normal 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
114
abci/tests/server/client.go
Normal 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
|
||||
}
|
||||
13
abci/tests/test_cli/ex1.abci
Normal file
13
abci/tests/test_cli/ex1.abci
Normal 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"
|
||||
|
||||
62
abci/tests/test_cli/ex1.abci.out
Normal file
62
abci/tests/test_cli/ex1.abci.out
Normal 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
|
||||
|
||||
9
abci/tests/test_cli/ex2.abci
Normal file
9
abci/tests/test_cli/ex2.abci
Normal 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
|
||||
35
abci/tests/test_cli/ex2.abci.out
Normal file
35
abci/tests/test_cli/ex2.abci.out
Normal 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
71
abci/tests/test_cli/test.sh
Executable 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"
|
||||
30
abci/tests/test_cli/testHelp.out
Normal file
30
abci/tests/test_cli/testHelp.out
Normal 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
1
abci/tests/tests.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package tests
|
||||
119
abci/types/application.go
Normal file
119
abci/types/application.go
Normal 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
229
abci/types/messages.go
Normal 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
114
abci/types/messages_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
449
abci/types/mocks/application.go
Normal file
449
abci/types/mocks/application.go
Normal 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
44
abci/types/pubkey.go
Normal 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
170
abci/types/types.go
Normal 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
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
74
abci/types/types_test.go
Normal 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
31
abci/types/util.go
Normal 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
9
abci/version/version.go
Normal 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
53
blocksync/errors.go
Normal 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
Loading…
Add table
Reference in a new issue