diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5fb219e24..0a006f9b9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,17 +39,17 @@ jobs: platforms: all - name: Set up Docker Build - uses: docker/setup-buildx-action@v1.6.0 + uses: docker/setup-buildx-action@v2.0.0 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} - uses: docker/login-action@v1.14.1 + uses: docker/login-action@v2.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v2.10.0 + uses: docker/build-push-action@v3.0.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/docs-deployment.yml b/.github/workflows/docs-deployment.yml new file mode 100644 index 000000000..082484dd5 --- /dev/null +++ b/.github/workflows/docs-deployment.yml @@ -0,0 +1,62 @@ +# Build and deploy the docs.tendermint.com website content. +# The static content is published to GitHub Pages. +# +# For documentation build info, see docs/DOCS_README.md. +name: Build static documentation site +on: + workflow_dispatch: # allow manual updates + push: + branches: + - master + paths: + - docs/** + - spec/** + +jobs: + # This is split into two jobs so that the build, which runs npm, does not + # have write access to anything. The deploy requires write access to publish + # to the branch used by GitHub Pages, however, so we can't just make the + # whole workflow read-only. + build: + name: VuePress build + runs-on: ubuntu-latest + container: + image: alpine:latest + permissions: + contents: read + steps: + - name: Install generator dependencies + run: | + apk add --no-cache make bash git npm + - uses: actions/checkout@v3 + with: + # We need to fetch full history so the backport branches for previous + # versions will be available for the build. + fetch-depth: 0 + - name: Build documentation + run: | + git config --global --add safe.directory "$PWD" + make build-docs + - uses: actions/upload-artifact@v3 + with: + name: build-output + path: ~/output/ + + deploy: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + needs: build + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: build-output + path: ~/output + - name: Deploy to GitHub Pages + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: 'docs-tendermint-com' + folder: ~/output + single-commit: true diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index d143fd905..e2ba80861 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: gaurav-nelson/github-action-markdown-link-check@1.0.14 + - uses: creachadair/github-action-markdown-link-check@master with: folder-path: "docs" diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 8d0da6798..b1fbeab9d 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.3.1 + - uses: bufbuild/buf-setup-action@v1.4.0 - uses: bufbuild/buf-lint-action@v1 with: input: 'proto' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7ee964d21..6ce8a4d34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,7 +69,7 @@ jobs: - run: | cat ./*profile.out | grep -v "mode: set" >> coverage.txt if: env.GIT_DIFF - - uses: codecov/codecov-action@v3.0.0 + - uses: codecov/codecov-action@v3.1.0 with: file: ./coverage.txt if: env.GIT_DIFF diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6bfc948f8..65ab5ee3b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -47,6 +47,7 @@ Special thanks to external contributors on this release: - [libs/service] \#7288 Remove SetLogger method on `service.Service` interface. (@tychoish) - [abci/client] \#7607 Simplify client interface (removes most "async" methods). (@creachadair) - [libs/json] \#7673 Remove the libs/json (tmjson) library. (@creachadair) + - [crypto] \#8412 \#8432 Remove `crypto/tmhash` package in favor of small functions in `crypto` package and cleanup of unused functions. (@tychoish) - Blockchain Protocol diff --git a/Makefile b/Makefile index ba8850164..cd9380768 100644 --- a/Makefile +++ b/Makefile @@ -117,12 +117,6 @@ proto-check-breaking: check-proto-deps @buf breaking --against ".git" .PHONY: proto-check-breaking -# TODO: Should be removed when work on ABCI++ is complete. -# For more information, see https://github.com/tendermint/tendermint/issues/8066 -abci-proto-gen: - ./scripts/abci-gen.sh -.PHONY: abci-proto-gen - ############################################################################### ### Build ABCI ### ############################################################################### @@ -232,7 +226,8 @@ DESTINATION = ./index.html.md build-docs: @cd docs && \ while read -r branch path_prefix; do \ - (git checkout $${branch} && npm ci && VUEPRESS_BASE="/$${path_prefix}/" npm run build) ; \ + ( git checkout $${branch} && npm ci --quiet && \ + VUEPRESS_BASE="/$${path_prefix}/" npm run build --quiet ) ; \ mkdir -p ~/output/$${path_prefix} ; \ cp -r .vuepress/dist/* ~/output/$${path_prefix}/ ; \ cp ~/output/$${path_prefix}/index.html ~/output ; \ @@ -256,6 +251,21 @@ mockery: go generate -run="./scripts/mockery_generate.sh" ./... .PHONY: mockery +############################################################################### +### Metrics ### +############################################################################### + +metrics: testdata-metrics + go generate -run="scripts/metricsgen" ./... +.PHONY: metrics + + # By convention, the go tool ignores subdirectories of directories named + # 'testdata'. This command invokes the generate command on the folder directly + # to avoid this. +testdata-metrics: + ls ./scripts/metricsgen/testdata | xargs -I{} go generate -run="scripts/metricsgen" ./scripts/metricsgen/testdata/{} +.PHONY: testdata-metrics + ############################################################################### ### Local testnet using docker ### ############################################################################### diff --git a/UPGRADING.md b/UPGRADING.md index a67d9dfb2..93cd6c20f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -145,6 +145,27 @@ subcommands: `blockchain`, `peers`, `unsafe-signer` and `unsafe-all`. - `tendermint reset unsafe-all`: A summation of the other three commands. This will delete the entire `data` directory which may include application data as well. +### Go API Changes + +#### `crypto` Package Cleanup + +The `github.com/tendermint/tendermint/crypto/tmhash` package was removed +to improve clarity. Users are encouraged to use the standard library +`crypto/sha256` package directly. However, as a convenience, some constants +and one function have moved to the Tendermint `crypto` package: + +- The `crypto.Checksum` function returns the sha256 checksum of a + byteslice. This is a wrapper around `sha256.Sum265` from the + standard libary, but provided as a function to ease type + requirements (the library function returns a `[32]byte` rather than + a `[]byte`). +- `tmhash.TruncatedSize` is now `crypto.AddressSize` which was + previously an alias for the same value. +- `tmhash.Size` and `tmhash.BlockSize` are now `crypto.HashSize` and + `crypto.HashSize`. +- `tmhash.SumTruncated` is now available via `crypto.AddressHash` or by + `crypto.Checksum(<...>)[:crypto.AddressSize]` + ## v0.35 ### ABCI Changes @@ -191,22 +212,25 @@ subcommands: `blockchain`, `peers`, `unsafe-signer` and `unsafe-all`. The format of all tendermint on-disk database keys changes in 0.35. Upgrading nodes must either re-sync all data or run a migration -script provided in this release. The script located in -`github.com/tendermint/tendermint/scripts/keymigrate/migrate.go` -provides the function `Migrate(context.Context, db.DB)` which you can -operationalize as makes sense for your deployment. +script provided in this release. + +The script located in +`github.com/tendermint/tendermint/scripts/keymigrate/migrate.go` provides the +function `Migrate(context.Context, db.DB)` which you can operationalize as +makes sense for your deployment. For ease of use the `tendermint` command includes a CLI version of the migration script, which you can invoke, as in: tendermint key-migrate -This reads the configuration file as normal and allows the -`--db-backend` and `--db-dir` flags to change database operations as -needed. +This reads the configuration file as normal and allows the `--db-backend` and +`--db-dir` flags to override the database location as needed. -The migration operation is idempotent and can be run more than once, -if needed. +The migration operation is intended to be idempotent, and should be safe to +rerun on the same database multiple times. As a safety measure, however, we +recommend that operators test out the migration on a copy of the database +first, if it is practical to do so, before applying it to the production data. ### CLI Changes @@ -259,11 +283,13 @@ the full RPC interface provided as direct function calls. Import the the node service as in the following: ```go - node := node.NewDefault() //construct the node object - // start and set up the node service +logger := log.NewNopLogger() - client := local.New(node.(local.NodeService)) - // use client object to interact with the node +// Construct and start up a node with default settings. +node := node.NewDefault(logger) + +// Construct a local (in-memory) RPC client to the node. +client := local.New(logger, node.(local.NodeService)) ``` ### gRPC Support diff --git a/abci/client/client.go b/abci/client/client.go index de72a0606..4508dcbc8 100644 --- a/abci/client/client.go +++ b/abci/client/client.go @@ -17,35 +17,18 @@ const ( //go:generate ../../scripts/mockery_generate.sh Client -// Client defines an interface for an ABCI client. -// -// All methods return the appropriate protobuf ResponseXxx struct and -// an error. +// 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 logs. +// and (potentially) error response. type Client interface { service.Service + types.Application Error() error - Flush(context.Context) error - Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) - Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error) - CheckTx(context.Context, types.RequestCheckTx) (*types.ResponseCheckTx, error) - Query(context.Context, types.RequestQuery) (*types.ResponseQuery, error) - Commit(context.Context) (*types.ResponseCommit, error) - InitChain(context.Context, types.RequestInitChain) (*types.ResponseInitChain, error) - PrepareProposal(context.Context, types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) - ProcessProposal(context.Context, types.RequestProcessProposal) (*types.ResponseProcessProposal, error) - ExtendVote(context.Context, types.RequestExtendVote) (*types.ResponseExtendVote, error) - VerifyVoteExtension(context.Context, types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) - FinalizeBlock(context.Context, types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) - ListSnapshots(context.Context, types.RequestListSnapshots) (*types.ResponseListSnapshots, error) - OfferSnapshot(context.Context, types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) - LoadSnapshotChunk(context.Context, types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) - ApplySnapshotChunk(context.Context, types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) + Echo(context.Context, string) (*types.ResponseEcho, error) } //---------------------------------------- diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 251939253..1e163056d 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -52,6 +52,9 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) { } func (cli *grpcClient) OnStart(ctx context.Context) error { + timer := time.NewTimer(0) + defer timer.Stop() + RETRY_LOOP: for { conn, err := grpc.Dial(cli.addr, @@ -63,8 +66,13 @@ RETRY_LOOP: 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 + timer.Reset(time.Second * dialRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue RETRY_LOOP + } } cli.logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr) @@ -82,7 +90,13 @@ RETRY_LOOP: } cli.logger.Error("Echo failed", "err", err) - time.Sleep(time.Second * echoRetryIntervalSeconds) + timer.Reset(time.Second * echoRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue ENSURE_CONNECTED + } } cli.client = client @@ -114,15 +128,15 @@ func (cli *grpcClient) Echo(ctx context.Context, msg string) (*types.ResponseEch return cli.client.Echo(ctx, types.ToRequestEcho(msg).GetEcho(), grpc.WaitForReady(true)) } -func (cli *grpcClient) Info(ctx context.Context, params types.RequestInfo) (*types.ResponseInfo, error) { +func (cli *grpcClient) Info(ctx context.Context, params *types.RequestInfo) (*types.ResponseInfo, error) { return cli.client.Info(ctx, types.ToRequestInfo(params).GetInfo(), grpc.WaitForReady(true)) } -func (cli *grpcClient) CheckTx(ctx context.Context, params types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (cli *grpcClient) CheckTx(ctx context.Context, params *types.RequestCheckTx) (*types.ResponseCheckTx, error) { return cli.client.CheckTx(ctx, types.ToRequestCheckTx(params).GetCheckTx(), grpc.WaitForReady(true)) } -func (cli *grpcClient) Query(ctx context.Context, params types.RequestQuery) (*types.ResponseQuery, error) { +func (cli *grpcClient) Query(ctx context.Context, params *types.RequestQuery) (*types.ResponseQuery, error) { return cli.client.Query(ctx, types.ToRequestQuery(params).GetQuery(), grpc.WaitForReady(true)) } @@ -130,42 +144,42 @@ func (cli *grpcClient) Commit(ctx context.Context) (*types.ResponseCommit, error return cli.client.Commit(ctx, types.ToRequestCommit().GetCommit(), grpc.WaitForReady(true)) } -func (cli *grpcClient) InitChain(ctx context.Context, params types.RequestInitChain) (*types.ResponseInitChain, error) { +func (cli *grpcClient) InitChain(ctx context.Context, params *types.RequestInitChain) (*types.ResponseInitChain, error) { return cli.client.InitChain(ctx, types.ToRequestInitChain(params).GetInitChain(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ListSnapshots(ctx context.Context, params types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (cli *grpcClient) ListSnapshots(ctx context.Context, params *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { return cli.client.ListSnapshots(ctx, types.ToRequestListSnapshots(params).GetListSnapshots(), grpc.WaitForReady(true)) } -func (cli *grpcClient) OfferSnapshot(ctx context.Context, params types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (cli *grpcClient) OfferSnapshot(ctx context.Context, params *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { return cli.client.OfferSnapshot(ctx, types.ToRequestOfferSnapshot(params).GetOfferSnapshot(), grpc.WaitForReady(true)) } -func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, params types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (cli *grpcClient) LoadSnapshotChunk(ctx context.Context, params *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { return cli.client.LoadSnapshotChunk(ctx, types.ToRequestLoadSnapshotChunk(params).GetLoadSnapshotChunk(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, params types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (cli *grpcClient) ApplySnapshotChunk(ctx context.Context, params *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { return cli.client.ApplySnapshotChunk(ctx, types.ToRequestApplySnapshotChunk(params).GetApplySnapshotChunk(), grpc.WaitForReady(true)) } -func (cli *grpcClient) PrepareProposal(ctx context.Context, params types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (cli *grpcClient) PrepareProposal(ctx context.Context, params *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { return cli.client.PrepareProposal(ctx, types.ToRequestPrepareProposal(params).GetPrepareProposal(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ProcessProposal(ctx context.Context, params types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (cli *grpcClient) ProcessProposal(ctx context.Context, params *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { return cli.client.ProcessProposal(ctx, types.ToRequestProcessProposal(params).GetProcessProposal(), grpc.WaitForReady(true)) } -func (cli *grpcClient) ExtendVote(ctx context.Context, params types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (cli *grpcClient) ExtendVote(ctx context.Context, params *types.RequestExtendVote) (*types.ResponseExtendVote, error) { return cli.client.ExtendVote(ctx, types.ToRequestExtendVote(params).GetExtendVote(), grpc.WaitForReady(true)) } -func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, params types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (cli *grpcClient) VerifyVoteExtension(ctx context.Context, params *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { return cli.client.VerifyVoteExtension(ctx, types.ToRequestVerifyVoteExtension(params).GetVerifyVoteExtension(), grpc.WaitForReady(true)) } -func (cli *grpcClient) FinalizeBlock(ctx context.Context, params types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (cli *grpcClient) FinalizeBlock(ctx context.Context, params *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { return cli.client.FinalizeBlock(ctx, types.ToRequestFinalizeBlock(params).GetFinalizeBlock(), grpc.WaitForReady(true)) } diff --git a/abci/client/local_client.go b/abci/client/local_client.go index 938ed2ab6..1002c64e8 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -34,81 +34,7 @@ func NewLocalClient(logger log.Logger, app types.Application) Client { func (*localClient) OnStart(context.Context) error { return nil } func (*localClient) OnStop() {} func (*localClient) Error() error { return nil } - -//------------------------------------------------------- - -func (*localClient) Flush(context.Context) error { return nil } - -func (app *localClient) Echo(_ context.Context, msg string) (*types.ResponseEcho, error) { +func (*localClient) Flush(context.Context) error { return nil } +func (*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) { - res := app.Application.Info(req) - return &res, nil -} - -func (app *localClient) CheckTx(_ context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { - res := app.Application.CheckTx(req) - return &res, nil -} - -func (app *localClient) Query(_ context.Context, req types.RequestQuery) (*types.ResponseQuery, error) { - res := app.Application.Query(req) - return &res, nil -} - -func (app *localClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { - res := app.Application.Commit() - return &res, nil -} - -func (app *localClient) InitChain(_ context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { - res := app.Application.InitChain(req) - return &res, nil -} - -func (app *localClient) ListSnapshots(_ context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { - res := app.Application.ListSnapshots(req) - return &res, nil -} - -func (app *localClient) OfferSnapshot(_ context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { - res := app.Application.OfferSnapshot(req) - return &res, nil -} - -func (app *localClient) LoadSnapshotChunk(_ context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { - res := app.Application.LoadSnapshotChunk(req) - return &res, nil -} - -func (app *localClient) ApplySnapshotChunk(_ context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { - res := app.Application.ApplySnapshotChunk(req) - return &res, nil -} - -func (app *localClient) PrepareProposal(_ context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { - res := app.Application.PrepareProposal(req) - return &res, nil -} - -func (app *localClient) ProcessProposal(_ context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { - res := app.Application.ProcessProposal(req) - return &res, nil -} - -func (app *localClient) ExtendVote(_ context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { - res := app.Application.ExtendVote(req) - return &res, nil -} - -func (app *localClient) VerifyVoteExtension(_ context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { - res := app.Application.VerifyVoteExtension(req) - return &res, nil -} - -func (app *localClient) FinalizeBlock(_ context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { - res := app.Application.FinalizeBlock(req) - return &res, nil -} diff --git a/abci/client/mocks/client.go b/abci/client/mocks/client.go index 37df53979..e5f2898f3 100644 --- a/abci/client/mocks/client.go +++ b/abci/client/mocks/client.go @@ -4,8 +4,10 @@ package mocks import ( context "context" + testing "testing" mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/abci/types" ) @@ -15,11 +17,11 @@ type Client struct { } // ApplySnapshotChunk provides a mock function with given fields: _a0, _a1 -func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseApplySnapshotChunk - if rf, ok := ret.Get(0).(func(context.Context, types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -28,7 +30,7 @@ func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApply } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestApplySnapshotChunk) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestApplySnapshotChunk) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -38,11 +40,11 @@ func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 types.RequestApply } // CheckTx provides a mock function with given fields: _a0, _a1 -func (_m *Client) CheckTx(_a0 context.Context, _a1 types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (_m *Client) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(context.Context, types.RequestCheckTx) *types.ResponseCheckTx); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -51,7 +53,7 @@ func (_m *Client) CheckTx(_a0 context.Context, _a1 types.RequestCheckTx) (*types } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestCheckTx) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCheckTx) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -83,13 +85,13 @@ func (_m *Client) Commit(_a0 context.Context) (*types.ResponseCommit, error) { return r0, r1 } -// Echo provides a mock function with given fields: ctx, msg -func (_m *Client) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { - ret := _m.Called(ctx, msg) +// Echo provides a mock function with given fields: _a0, _a1 +func (_m *Client) Echo(_a0 context.Context, _a1 string) (*types.ResponseEcho, error) { + ret := _m.Called(_a0, _a1) var r0 *types.ResponseEcho if rf, ok := ret.Get(0).(func(context.Context, string) *types.ResponseEcho); ok { - r0 = rf(ctx, msg) + r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*types.ResponseEcho) @@ -98,7 +100,7 @@ func (_m *Client) Echo(ctx context.Context, msg string) (*types.ResponseEcho, er var r1 error if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, msg) + r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) } @@ -121,11 +123,11 @@ func (_m *Client) Error() error { } // ExtendVote provides a mock function with given fields: _a0, _a1 -func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (_m *Client) ExtendVote(_a0 context.Context, _a1 *types.RequestExtendVote) (*types.ResponseExtendVote, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseExtendVote - if rf, ok := ret.Get(0).(func(context.Context, types.RequestExtendVote) *types.ResponseExtendVote); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -134,7 +136,7 @@ func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) ( } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestExtendVote) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -144,11 +146,11 @@ func (_m *Client) ExtendVote(_a0 context.Context, _a1 types.RequestExtendVote) ( } // FinalizeBlock provides a mock function with given fields: _a0, _a1 -func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseFinalizeBlock - if rf, ok := ret.Get(0).(func(context.Context, types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -157,7 +159,7 @@ func (_m *Client) FinalizeBlock(_a0 context.Context, _a1 types.RequestFinalizeBl } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestFinalizeBlock) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestFinalizeBlock) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -181,11 +183,11 @@ func (_m *Client) Flush(_a0 context.Context) error { } // Info provides a mock function with given fields: _a0, _a1 -func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.ResponseInfo, error) { +func (_m *Client) Info(_a0 context.Context, _a1 *types.RequestInfo) (*types.ResponseInfo, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseInfo - if rf, ok := ret.Get(0).(func(context.Context, types.RequestInfo) *types.ResponseInfo); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -194,7 +196,7 @@ func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.Respo } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestInfo) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -204,11 +206,11 @@ func (_m *Client) Info(_a0 context.Context, _a1 types.RequestInfo) (*types.Respo } // InitChain provides a mock function with given fields: _a0, _a1 -func (_m *Client) InitChain(_a0 context.Context, _a1 types.RequestInitChain) (*types.ResponseInitChain, error) { +func (_m *Client) InitChain(_a0 context.Context, _a1 *types.RequestInitChain) (*types.ResponseInitChain, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseInitChain - if rf, ok := ret.Get(0).(func(context.Context, types.RequestInitChain) *types.ResponseInitChain); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -217,7 +219,7 @@ func (_m *Client) InitChain(_a0 context.Context, _a1 types.RequestInitChain) (*t } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestInitChain) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -241,11 +243,11 @@ func (_m *Client) IsRunning() bool { } // ListSnapshots provides a mock function with given fields: _a0, _a1 -func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (_m *Client) ListSnapshots(_a0 context.Context, _a1 *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseListSnapshots - if rf, ok := ret.Get(0).(func(context.Context, types.RequestListSnapshots) *types.ResponseListSnapshots); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) *types.ResponseListSnapshots); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -254,7 +256,7 @@ func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapsh } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestListSnapshots) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestListSnapshots) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -264,11 +266,11 @@ func (_m *Client) ListSnapshots(_a0 context.Context, _a1 types.RequestListSnapsh } // LoadSnapshotChunk provides a mock function with given fields: _a0, _a1 -func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseLoadSnapshotChunk - if rf, ok := ret.Get(0).(func(context.Context, types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -277,7 +279,7 @@ func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSn } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestLoadSnapshotChunk) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestLoadSnapshotChunk) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -287,11 +289,11 @@ func (_m *Client) LoadSnapshotChunk(_a0 context.Context, _a1 types.RequestLoadSn } // OfferSnapshot provides a mock function with given fields: _a0, _a1 -func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseOfferSnapshot - if rf, ok := ret.Get(0).(func(context.Context, types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -300,7 +302,7 @@ func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnaps } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestOfferSnapshot) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestOfferSnapshot) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -310,11 +312,11 @@ func (_m *Client) OfferSnapshot(_a0 context.Context, _a1 types.RequestOfferSnaps } // PrepareProposal provides a mock function with given fields: _a0, _a1 -func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (_m *Client) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponsePrepareProposal - if rf, ok := ret.Get(0).(func(context.Context, types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -323,7 +325,7 @@ func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareP } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestPrepareProposal) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestPrepareProposal) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -333,11 +335,11 @@ func (_m *Client) PrepareProposal(_a0 context.Context, _a1 types.RequestPrepareP } // ProcessProposal provides a mock function with given fields: _a0, _a1 -func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (_m *Client) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseProcessProposal - if rf, ok := ret.Get(0).(func(context.Context, types.RequestProcessProposal) *types.ResponseProcessProposal); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) *types.ResponseProcessProposal); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -346,7 +348,7 @@ func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessP } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestProcessProposal) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestProcessProposal) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -356,11 +358,11 @@ func (_m *Client) ProcessProposal(_a0 context.Context, _a1 types.RequestProcessP } // Query provides a mock function with given fields: _a0, _a1 -func (_m *Client) Query(_a0 context.Context, _a1 types.RequestQuery) (*types.ResponseQuery, error) { +func (_m *Client) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseQuery - if rf, ok := ret.Get(0).(func(context.Context, types.RequestQuery) *types.ResponseQuery); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) *types.ResponseQuery); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -369,7 +371,7 @@ func (_m *Client) Query(_a0 context.Context, _a1 types.RequestQuery) (*types.Res } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestQuery) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestQuery) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -393,11 +395,11 @@ func (_m *Client) Start(_a0 context.Context) error { } // VerifyVoteExtension provides a mock function with given fields: _a0, _a1 -func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { ret := _m.Called(_a0, _a1) var r0 *types.ResponseVerifyVoteExtension - if rf, ok := ret.Get(0).(func(context.Context, types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -406,7 +408,7 @@ func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVeri } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, types.RequestVerifyVoteExtension) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -419,3 +421,13 @@ func (_m *Client) VerifyVoteExtension(_a0 context.Context, _a1 types.RequestVeri func (_m *Client) Wait() { _m.Called() } + +// NewClient creates a new instance of Client. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t testing.TB) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index dcf5fa519..aa4fdcbe9 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -64,6 +64,8 @@ func (cli *socketClient) OnStart(ctx context.Context) error { err error conn net.Conn ) + timer := time.NewTimer(0) + defer timer.Stop() for { conn, err = tmnet.Connect(cli.addr) @@ -73,8 +75,15 @@ func (cli *socketClient) OnStart(ctx context.Context) error { } 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 + + timer.Reset(time.Second * dialRetryIntervalSeconds) + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue + } + } cli.conn = conn @@ -90,11 +99,7 @@ func (cli *socketClient) OnStop() { if cli.conn != nil { cli.conn.Close() } - - // this timeout is arbitrary. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - cli.drainQueue(ctx) + cli.drainQueue() } // Error returns an error if the client was stopped abruptly. @@ -113,12 +118,6 @@ func (cli *socketClient) sendRequestsRoutine(ctx context.Context, conn io.Writer case <-ctx.Done(): return case reqres := <-cli.reqQueue: - if ctx.Err() != nil { - return - } - - cli.willSendReq(reqres) - if err := types.WriteMessage(reqres.Request, bw); err != nil { cli.stopForError(fmt.Errorf("write to buffer: %w", err)) return @@ -162,6 +161,11 @@ func (cli *socketClient) recvResponseRoutine(ctx context.Context, conn io.Reader func (cli *socketClient) willSendReq(reqres *requestAndResponse) { cli.mtx.Lock() defer cli.mtx.Unlock() + + if !cli.IsRunning() { + return + } + cli.reqSent.PushBack(reqres) } @@ -189,138 +193,13 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error { //---------------------------------------- -func (cli *socketClient) Flush(ctx context.Context) error { - _, err := cli.doRequest(ctx, types.ToRequestFlush()) - if err != nil { - return err - } - return nil -} - -func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { - res, err := cli.doRequest(ctx, types.ToRequestEcho(msg)) - if err != nil { - return nil, err - } - return res.GetEcho(), nil -} - -func (cli *socketClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { - res, err := cli.doRequest(ctx, types.ToRequestInfo(req)) - if err != nil { - return nil, err - } - return res.GetInfo(), nil -} - -func (cli *socketClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { - res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req)) - if err != nil { - return nil, err - } - return res.GetCheckTx(), nil -} - -func (cli *socketClient) Query(ctx context.Context, req types.RequestQuery) (*types.ResponseQuery, error) { - res, err := cli.doRequest(ctx, types.ToRequestQuery(req)) - if err != nil { - return nil, err - } - return res.GetQuery(), nil -} - -func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { - res, err := cli.doRequest(ctx, types.ToRequestCommit()) - if err != nil { - return nil, err - } - return res.GetCommit(), nil -} - -func (cli *socketClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { - res, err := cli.doRequest(ctx, types.ToRequestInitChain(req)) - if err != nil { - return nil, err - } - return res.GetInitChain(), nil -} - -func (cli *socketClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { - res, err := cli.doRequest(ctx, types.ToRequestListSnapshots(req)) - if err != nil { - return nil, err - } - return res.GetListSnapshots(), nil -} - -func (cli *socketClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { - res, err := cli.doRequest(ctx, types.ToRequestOfferSnapshot(req)) - if err != nil { - return nil, err - } - return res.GetOfferSnapshot(), nil -} - -func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { - res, err := cli.doRequest(ctx, types.ToRequestLoadSnapshotChunk(req)) - if err != nil { - return nil, err - } - return res.GetLoadSnapshotChunk(), nil -} - -func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { - res, err := cli.doRequest(ctx, types.ToRequestApplySnapshotChunk(req)) - if err != nil { - return nil, err - } - return res.GetApplySnapshotChunk(), nil -} - -func (cli *socketClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { - res, err := cli.doRequest(ctx, types.ToRequestPrepareProposal(req)) - if err != nil { - return nil, err - } - return res.GetPrepareProposal(), nil -} - -func (cli *socketClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { - res, err := cli.doRequest(ctx, types.ToRequestProcessProposal(req)) - if err != nil { - return nil, err - } - return res.GetProcessProposal(), nil -} - -func (cli *socketClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { - res, err := cli.doRequest(ctx, types.ToRequestExtendVote(req)) - if err != nil { - return nil, err - } - return res.GetExtendVote(), nil -} - -func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { - res, err := cli.doRequest(ctx, types.ToRequestVerifyVoteExtension(req)) - if err != nil { - return nil, err - } - return res.GetVerifyVoteExtension(), nil -} - -func (cli *socketClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { - res, err := cli.doRequest(ctx, types.ToRequestFinalizeBlock(req)) - if err != nil { - return nil, err - } - return res.GetFinalizeBlock(), nil -} - -//---------------------------------------- - func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*types.Response, error) { + if !cli.IsRunning() { + return nil, errors.New("client has stopped") + } + reqres := makeReqRes(req) + cli.willSendReq(reqres) select { case cli.reqQueue <- reqres: @@ -342,7 +221,7 @@ func (cli *socketClient) doRequest(ctx context.Context, req *types.Request) (*ty // drainQueue marks as complete and discards all remaining pending requests // from the queue. -func (cli *socketClient) drainQueue(ctx context.Context) { +func (cli *socketClient) drainQueue() { cli.mtx.Lock() defer cli.mtx.Unlock() @@ -351,22 +230,136 @@ func (cli *socketClient) drainQueue(ctx context.Context) { reqres := req.Value.(*requestAndResponse) reqres.markDone() } +} - // Mark all queued messages as resolved. - // - // TODO(creachadair): We can't simply range the channel, because it is never - // closed, and the writer continues to add work. - // See https://github.com/tendermint/tendermint/issues/6996. - for { - select { - case <-ctx.Done(): - return - case reqres := <-cli.reqQueue: - reqres.markDone() - default: - return - } +//---------------------------------------- + +func (cli *socketClient) Flush(ctx context.Context) error { + _, err := cli.doRequest(ctx, types.ToRequestFlush()) + if err != nil { + return err } + return nil +} + +func (cli *socketClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { + res, err := cli.doRequest(ctx, types.ToRequestEcho(msg)) + if err != nil { + return nil, err + } + return res.GetEcho(), nil +} + +func (cli *socketClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { + res, err := cli.doRequest(ctx, types.ToRequestInfo(req)) + if err != nil { + return nil, err + } + return res.GetInfo(), nil +} + +func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req)) + if err != nil { + return nil, err + } + return res.GetCheckTx(), nil +} + +func (cli *socketClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { + res, err := cli.doRequest(ctx, types.ToRequestQuery(req)) + if err != nil { + return nil, err + } + return res.GetQuery(), nil +} + +func (cli *socketClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { + res, err := cli.doRequest(ctx, types.ToRequestCommit()) + if err != nil { + return nil, err + } + return res.GetCommit(), nil +} + +func (cli *socketClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { + res, err := cli.doRequest(ctx, types.ToRequestInitChain(req)) + if err != nil { + return nil, err + } + return res.GetInitChain(), nil +} + +func (cli *socketClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { + res, err := cli.doRequest(ctx, types.ToRequestListSnapshots(req)) + if err != nil { + return nil, err + } + return res.GetListSnapshots(), nil +} + +func (cli *socketClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { + res, err := cli.doRequest(ctx, types.ToRequestOfferSnapshot(req)) + if err != nil { + return nil, err + } + return res.GetOfferSnapshot(), nil +} + +func (cli *socketClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { + res, err := cli.doRequest(ctx, types.ToRequestLoadSnapshotChunk(req)) + if err != nil { + return nil, err + } + return res.GetLoadSnapshotChunk(), nil +} + +func (cli *socketClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { + res, err := cli.doRequest(ctx, types.ToRequestApplySnapshotChunk(req)) + if err != nil { + return nil, err + } + return res.GetApplySnapshotChunk(), nil +} + +func (cli *socketClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { + res, err := cli.doRequest(ctx, types.ToRequestPrepareProposal(req)) + if err != nil { + return nil, err + } + return res.GetPrepareProposal(), nil +} + +func (cli *socketClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { + res, err := cli.doRequest(ctx, types.ToRequestProcessProposal(req)) + if err != nil { + return nil, err + } + return res.GetProcessProposal(), nil +} + +func (cli *socketClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) { + res, err := cli.doRequest(ctx, types.ToRequestExtendVote(req)) + if err != nil { + return nil, err + } + return res.GetExtendVote(), nil +} + +func (cli *socketClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { + res, err := cli.doRequest(ctx, types.ToRequestVerifyVoteExtension(req)) + if err != nil { + return nil, err + } + return res.GetVerifyVoteExtension(), nil +} + +func (cli *socketClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { + res, err := cli.doRequest(ctx, types.ToRequestFinalizeBlock(req)) + if err != nil { + return nil, err + } + return res.GetFinalizeBlock(), nil } //---------------------------------------- diff --git a/abci/cmd/abci-cli/abci-cli.go b/abci/cmd/abci-cli/abci-cli.go index 7bfea4a4c..cb1603be3 100644 --- a/abci/cmd/abci-cli/abci-cli.go +++ b/abci/cmd/abci-cli/abci-cli.go @@ -482,7 +482,7 @@ func cmdInfo(cmd *cobra.Command, args []string) error { if len(args) == 1 { version = args[0] } - res, err := client.Info(cmd.Context(), types.RequestInfo{Version: version}) + res, err := client.Info(cmd.Context(), &types.RequestInfo{Version: version}) if err != nil { return err } @@ -511,7 +511,7 @@ func cmdFinalizeBlock(cmd *cobra.Command, args []string) error { } txs[i] = txBytes } - res, err := client.FinalizeBlock(cmd.Context(), types.RequestFinalizeBlock{Txs: txs}) + res, err := client.FinalizeBlock(cmd.Context(), &types.RequestFinalizeBlock{Txs: txs}) if err != nil { return err } @@ -539,7 +539,7 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - res, err := client.CheckTx(cmd.Context(), types.RequestCheckTx{Tx: txBytes}) + res, err := client.CheckTx(cmd.Context(), &types.RequestCheckTx{Tx: txBytes}) if err != nil { return err } @@ -579,7 +579,7 @@ func cmdQuery(cmd *cobra.Command, args []string) error { return err } - resQuery, err := client.Query(cmd.Context(), types.RequestQuery{ + resQuery, err := client.Query(cmd.Context(), &types.RequestQuery{ Data: queryBytes, Path: flagPath, Height: int64(flagHeight), diff --git a/abci/example/example_test.go b/abci/example/example_test.go index 9d9d1548f..066d4071d 100644 --- a/abci/example/example_test.go +++ b/abci/example/example_test.go @@ -53,7 +53,7 @@ func TestGRPC(t *testing.T) { logger := log.NewNopLogger() t.Log("### Testing GRPC") - testGRPCSync(ctx, t, logger, types.NewGRPCApplication(types.NewBaseApplication())) + testGRPCSync(ctx, t, logger, types.NewBaseApplication()) } func testBulk(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) { @@ -77,7 +77,7 @@ func testBulk(ctx context.Context, t *testing.T, logger log.Logger, app types.Ap require.NoError(t, err) // Construct request - rfb := types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)} + rfb := &types.RequestFinalizeBlock{Txs: make([][]byte, numDeliverTxs)} for counter := 0; counter < numDeliverTxs; counter++ { rfb.Txs[counter] = []byte("test") } @@ -101,7 +101,7 @@ func dialerFunc(ctx context.Context, addr string) (net.Conn, error) { return tmnet.Connect(addr) } -func testGRPCSync(ctx context.Context, t *testing.T, logger log.Logger, app types.ABCIApplicationServer) { +func testGRPCSync(ctx context.Context, t *testing.T, logger log.Logger, app types.Application) { t.Helper() numDeliverTxs := 680000 socketFile := fmt.Sprintf("/tmp/test-%08x.sock", rand.Int31n(1<<30)) diff --git a/abci/example/kvstore/helpers.go b/abci/example/kvstore/helpers.go index 38bb42ea8..8a2a8a35b 100644 --- a/abci/example/kvstore/helpers.go +++ b/abci/example/kvstore/helpers.go @@ -1,6 +1,7 @@ package kvstore import ( + "context" mrand "math/rand" "github.com/tendermint/tendermint/abci/types" @@ -32,8 +33,9 @@ func RandVals(cnt int) []types.ValidatorUpdate { // InitKVStore initializes the kvstore app with some data, // which allows tests to pass and is fine as long as you // don't make any tx that modify the validator state -func InitKVStore(app *PersistentKVStoreApplication) { - app.InitChain(types.RequestInitChain{ +func InitKVStore(ctx context.Context, app *PersistentKVStoreApplication) error { + _, err := app.InitChain(ctx, &types.RequestInitChain{ Validators: RandVals(1), }) + return err } diff --git a/abci/example/kvstore/kvstore.go b/abci/example/kvstore/kvstore.go index 045e872d4..61d129239 100644 --- a/abci/example/kvstore/kvstore.go +++ b/abci/example/kvstore/kvstore.go @@ -2,6 +2,7 @@ package kvstore import ( "bytes" + "context" "encoding/base64" "encoding/binary" "encoding/json" @@ -90,7 +91,7 @@ func NewApplication() *Application { } } -func (app *Application) InitChain(req types.RequestInitChain) types.ResponseInitChain { +func (app *Application) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { app.mu.Lock() defer app.mu.Unlock() @@ -101,19 +102,19 @@ func (app *Application) InitChain(req types.RequestInitChain) types.ResponseInit panic("problem updating validators") } } - return types.ResponseInitChain{} + return &types.ResponseInitChain{}, nil } -func (app *Application) Info(req types.RequestInfo) types.ResponseInfo { +func (app *Application) Info(_ context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { app.mu.Lock() defer app.mu.Unlock() - return types.ResponseInfo{ + return &types.ResponseInfo{ Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), Version: version.ABCIVersion, AppVersion: ProtocolVersion, LastBlockHeight: app.state.Height, LastBlockAppHash: app.state.AppHash, - } + }, nil } // tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes @@ -166,7 +167,7 @@ func (app *Application) Close() error { return app.state.db.Close() } -func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.ResponseFinalizeBlock { +func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { app.mu.Lock() defer app.mu.Unlock() @@ -195,14 +196,14 @@ func (app *Application) FinalizeBlock(req types.RequestFinalizeBlock) types.Resp respTxs[i] = app.handleTx(tx) } - return types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates} + return &types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates}, nil } -func (*Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { - return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} +func (*Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + return &types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil } -func (app *Application) Commit() types.ResponseCommit { +func (app *Application) Commit(_ context.Context) (*types.ResponseCommit, error) { app.mu.Lock() defer app.mu.Unlock() @@ -213,15 +214,15 @@ func (app *Application) Commit() types.ResponseCommit { app.state.Height++ saveState(app.state) - resp := types.ResponseCommit{Data: appHash} + resp := &types.ResponseCommit{Data: appHash} if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks { resp.RetainHeight = app.state.Height - app.RetainBlocks + 1 } - return resp + return resp, nil } // Returns an associated value or nil if missing. -func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { +func (app *Application) Query(_ context.Context, reqQuery *types.RequestQuery) (*types.ResponseQuery, error) { app.mu.Lock() defer app.mu.Unlock() @@ -232,10 +233,10 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { panic(err) } - return types.ResponseQuery{ + return &types.ResponseQuery{ Key: reqQuery.Data, Value: value, - } + }, nil } if reqQuery.Prove { @@ -257,7 +258,7 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { resQuery.Log = "exists" } - return resQuery + return &resQuery, nil } value, err := app.state.db.Get(prefixKey(reqQuery.Data)) @@ -277,25 +278,25 @@ func (app *Application) Query(reqQuery types.RequestQuery) types.ResponseQuery { resQuery.Log = "exists" } - return resQuery + return &resQuery, nil } -func (app *Application) PrepareProposal(req types.RequestPrepareProposal) types.ResponsePrepareProposal { +func (app *Application) PrepareProposal(_ context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { app.mu.Lock() defer app.mu.Unlock() - return types.ResponsePrepareProposal{ + return &types.ResponsePrepareProposal{ TxRecords: app.substPrepareTx(req.Txs, req.MaxTxBytes), - } + }, nil } -func (*Application) ProcessProposal(req types.RequestProcessProposal) types.ResponseProcessProposal { +func (*Application) ProcessProposal(_ context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { for _, tx := range req.Txs { if len(tx) == 0 { - return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT} + return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil } } - return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT} + return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}, nil } //--------------------------------------------- diff --git a/abci/example/kvstore/kvstore_test.go b/abci/example/kvstore/kvstore_test.go index 0b62bbc61..9631bc1ab 100644 --- a/abci/example/kvstore/kvstore_test.go +++ b/abci/example/kvstore/kvstore_test.go @@ -23,37 +23,43 @@ const ( testValue = "def" ) -func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) { - req := types.RequestFinalizeBlock{Txs: [][]byte{tx}} - ar := app.FinalizeBlock(req) +func testKVStore(ctx context.Context, t *testing.T, app types.Application, tx []byte, key, value string) { + req := &types.RequestFinalizeBlock{Txs: [][]byte{tx}} + ar, err := app.FinalizeBlock(ctx, req) + require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) // repeating tx doesn't raise error - ar = app.FinalizeBlock(req) + 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 - app.Commit() + _, err = app.Commit(ctx) + require.NoError(t, err) - info := app.Info(types.RequestInfo{}) + info, err := app.Info(ctx, &types.RequestInfo{}) + require.NoError(t, err) require.NotZero(t, info.LastBlockHeight) // make sure query is fine - resQuery := app.Query(types.RequestQuery{ + resQuery, err := app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), }) + require.NoError(t, err) require.Equal(t, code.CodeTypeOK, resQuery.Code) require.Equal(t, key, string(resQuery.Key)) require.Equal(t, value, string(resQuery.Value)) require.EqualValues(t, info.LastBlockHeight, resQuery.Height) // make sure proof is fine - resQuery = app.Query(types.RequestQuery{ + resQuery, err = app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), Prove: true, }) + require.NoError(t, err) require.EqualValues(t, code.CodeTypeOK, resQuery.Code) require.Equal(t, key, string(resQuery.Key)) require.Equal(t, value, string(resQuery.Value)) @@ -61,18 +67,24 @@ func testKVStore(t *testing.T, app types.Application, tx []byte, key, value stri } func TestKVStoreKV(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + kvstore := NewApplication() key := testKey value := key tx := []byte(key) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) value = testValue tx = []byte(key + "=" + value) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) } func TestPersistentKVStoreKV(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + dir := t.TempDir() logger := log.NewNopLogger() @@ -80,22 +92,30 @@ func TestPersistentKVStoreKV(t *testing.T) { key := testKey value := key tx := []byte(key) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) value = testValue tx = []byte(key + "=" + value) - testKVStore(t, kvstore, tx, key, value) + testKVStore(ctx, t, kvstore, tx, key, value) } func TestPersistentKVStoreInfo(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() dir := t.TempDir() logger := log.NewNopLogger() kvstore := NewPersistentKVStoreApplication(logger, dir) - InitKVStore(kvstore) + if err := InitKVStore(ctx, kvstore); err != nil { + t.Fatal(err) + } height := int64(0) - resInfo := kvstore.Info(types.RequestInfo{}) + resInfo, err := kvstore.Info(ctx, &types.RequestInfo{}) + if err != nil { + t.Fatal(err) + } + if resInfo.LastBlockHeight != height { t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) } @@ -103,10 +123,19 @@ func TestPersistentKVStoreInfo(t *testing.T) { // make and apply block height = int64(1) hash := []byte("foo") - kvstore.FinalizeBlock(types.RequestFinalizeBlock{Hash: hash, Height: height}) - kvstore.Commit() + if _, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Hash: hash, Height: height}); err != nil { + t.Fatal(err) + } - resInfo = kvstore.Info(types.RequestInfo{}) + if _, err := kvstore.Commit(ctx); err != nil { + t.Fatal(err) + + } + + resInfo, err = kvstore.Info(ctx, &types.RequestInfo{}) + if err != nil { + t.Fatal(err) + } if resInfo.LastBlockHeight != height { t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) } @@ -115,6 +144,9 @@ func TestPersistentKVStoreInfo(t *testing.T) { // add a validator, remove a validator, update a validator func TestValUpdates(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + kvstore := NewApplication() // init with some validators @@ -122,9 +154,12 @@ func TestValUpdates(t *testing.T) { nInit := 5 vals := RandVals(total) // initialize with the first nInit - kvstore.InitChain(types.RequestInitChain{ + _, err := kvstore.InitChain(ctx, &types.RequestInitChain{ Validators: vals[:nInit], }) + if err != nil { + t.Fatal(err) + } vals1, vals2 := vals[:nInit], kvstore.Validators() valsEqual(t, vals1, vals2) @@ -137,7 +172,7 @@ func TestValUpdates(t *testing.T) { tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power) tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power) - makeApplyBlock(t, kvstore, 1, diff, tx1, tx2) + makeApplyBlock(ctx, t, kvstore, 1, diff, tx1, tx2) vals1, vals2 = vals[:nInit+2], kvstore.Validators() valsEqual(t, vals1, vals2) @@ -152,7 +187,7 @@ func TestValUpdates(t *testing.T) { tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power) tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power) - makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3) + makeApplyBlock(ctx, t, kvstore, 2, diff, tx1, tx2, tx3) vals1 = append(vals[:nInit-2], vals[nInit+1]) // nolint: gocritic vals2 = kvstore.Validators() @@ -168,7 +203,7 @@ func TestValUpdates(t *testing.T) { diff = []types.ValidatorUpdate{v1} tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) - makeApplyBlock(t, kvstore, 3, diff, tx1) + makeApplyBlock(ctx, t, kvstore, 3, diff, tx1) vals1 = append([]types.ValidatorUpdate{v1}, vals1[1:]...) vals2 = kvstore.Validators() @@ -176,22 +211,23 @@ func TestValUpdates(t *testing.T) { } -func makeApplyBlock( - t *testing.T, - kvstore types.Application, - heightInt int, - diff []types.ValidatorUpdate, - txs ...[]byte) { +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 := kvstore.FinalizeBlock(types.RequestFinalizeBlock{ + resFinalizeBlock, err := kvstore.FinalizeBlock(ctx, &types.RequestFinalizeBlock{ Hash: hash, Height: height, Txs: txs, }) + if err != nil { + t.Fatal(err) + } - kvstore.Commit() + _, err = kvstore.Commit(ctx) + if err != nil { + t.Fatal(err) + } valsEqual(t, diff, resFinalizeBlock.ValidatorUpdates) @@ -260,8 +296,7 @@ func makeGRPCClientServer( // Start the listener socket := fmt.Sprintf("unix://%s.sock", name) - gapp := types.NewGRPCApplication(app) - server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, gapp) + server := abciserver.NewGRPCServer(logger.With("module", "abci-server"), socket, app) if err := server.Start(ctx); err != nil { cancel() @@ -315,12 +350,12 @@ func runClientTests(ctx context.Context, t *testing.T, client abciclient.Client) } func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []byte, key, value string) { - ar, err := app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}}) + ar, err := app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}}) require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) // repeating FinalizeBlock doesn't raise error - ar, err = app.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: [][]byte{tx}}) + ar, err = app.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: [][]byte{tx}}) require.NoError(t, err) require.Equal(t, 1, len(ar.TxResults)) require.False(t, ar.TxResults[0].IsErr()) @@ -328,12 +363,12 @@ func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []b _, err = app.Commit(ctx) require.NoError(t, err) - info, err := app.Info(ctx, types.RequestInfo{}) + 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{ + resQuery, err := app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), }) @@ -344,7 +379,7 @@ func testClient(ctx context.Context, t *testing.T, app abciclient.Client, tx []b require.EqualValues(t, info.LastBlockHeight, resQuery.Height) // make sure proof is fine - resQuery, err = app.Query(ctx, types.RequestQuery{ + resQuery, err = app.Query(ctx, &types.RequestQuery{ Path: "/store", Data: []byte(key), Prove: true, diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index 2a6e8aa19..bede5d517 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -1,14 +1,13 @@ package kvstore import ( - "bytes" + "context" dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" - ptypes "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -38,41 +37,10 @@ func NewPersistentKVStoreApplication(logger log.Logger, dbDir string) *Persisten } } -func (app *PersistentKVStoreApplication) OfferSnapshot(req types.RequestOfferSnapshot) types.ResponseOfferSnapshot { - return types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT} +func (app *PersistentKVStoreApplication) OfferSnapshot(_ context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { + return &types.ResponseOfferSnapshot{Result: types.ResponseOfferSnapshot_ABORT}, nil } -func (app *PersistentKVStoreApplication) ApplySnapshotChunk(req types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { - return types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT} -} - -func (app *PersistentKVStoreApplication) ExtendVote(req types.RequestExtendVote) types.ResponseExtendVote { - return types.ResponseExtendVote{VoteExtension: ConstructVoteExtension(req.Vote.ValidatorAddress)} -} - -func (app *PersistentKVStoreApplication) VerifyVoteExtension(req types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { - return types.RespondVerifyVoteExtension(app.verifyExtension(req.Vote.ValidatorAddress, req.Vote.VoteExtension)) -} - -// ----------------------------- - -func ConstructVoteExtension(valAddr []byte) *ptypes.VoteExtension { - return &ptypes.VoteExtension{ - AppDataToSign: valAddr, - AppDataSelfAuthenticating: valAddr, - } -} - -func (app *PersistentKVStoreApplication) verifyExtension(valAddr []byte, ext *ptypes.VoteExtension) bool { - if ext == nil { - return false - } - canonical := ConstructVoteExtension(valAddr) - if !bytes.Equal(canonical.AppDataToSign, ext.AppDataToSign) { - return false - } - if !bytes.Equal(canonical.AppDataSelfAuthenticating, ext.AppDataSelfAuthenticating) { - return false - } - return true +func (app *PersistentKVStoreApplication) ApplySnapshotChunk(_ context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { + return &types.ResponseApplySnapshotChunk{Result: types.ResponseApplySnapshotChunk_ABORT}, nil } diff --git a/abci/server/grpc_server.go b/abci/server/grpc_server.go index c1446d17a..0dfee8169 100644 --- a/abci/server/grpc_server.go +++ b/abci/server/grpc_server.go @@ -20,11 +20,11 @@ type GRPCServer struct { addr string server *grpc.Server - app types.ABCIApplicationServer + app types.Application } // NewGRPCServer returns a new gRPC ABCI server -func NewGRPCServer(logger log.Logger, protoAddr string, app types.ABCIApplicationServer) service.Service { +func NewGRPCServer(logger log.Logger, protoAddr string, app types.Application) service.Service { proto, addr := tmnet.ProtocolAndAddress(protoAddr) s := &GRPCServer{ logger: logger, @@ -44,7 +44,7 @@ func (s *GRPCServer) OnStart(ctx context.Context) error { } s.server = grpc.NewServer() - types.RegisterABCIApplicationServer(s.server, s.app) + types.RegisterABCIApplicationServer(s.server, &gRPCApplication{Application: s.app}) s.logger.Info("Listening", "proto", s.proto, "addr", s.addr) go func() { @@ -62,3 +62,22 @@ func (s *GRPCServer) OnStart(ctx context.Context) error { // OnStop stops the gRPC server. func (s *GRPCServer) OnStop() { s.server.Stop() } + +//------------------------------------------------------- + +// gRPCApplication is a gRPC shim for Application +type gRPCApplication struct { + types.Application +} + +func (app *gRPCApplication) Echo(_ context.Context, req *types.RequestEcho) (*types.ResponseEcho, error) { + return &types.ResponseEcho{Message: req.Message}, nil +} + +func (app *gRPCApplication) Flush(_ context.Context, req *types.RequestFlush) (*types.ResponseFlush, error) { + return &types.ResponseFlush{}, nil +} + +func (app *gRPCApplication) Commit(ctx context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) { + return app.Application.Commit(ctx) +} diff --git a/abci/server/server.go b/abci/server/server.go index 2a6d50fd2..0e0173ca6 100644 --- a/abci/server/server.go +++ b/abci/server/server.go @@ -23,7 +23,7 @@ func NewServer(logger log.Logger, protoAddr, transport string, app types.Applica case "socket": s = NewSocketServer(logger, protoAddr, app) case "grpc": - s = NewGRPCServer(logger, protoAddr, types.NewGRPCApplication(app)) + s = NewGRPCServer(logger, protoAddr, app) default: err = fmt.Errorf("unknown server type %s", transport) } diff --git a/abci/server/socket_server.go b/abci/server/socket_server.go index 72dd1221c..570ecfb4e 100644 --- a/abci/server/socket_server.go +++ b/abci/server/socket_server.go @@ -159,12 +159,7 @@ func (s *SocketServer) acceptConnectionsRoutine(ctx context.Context) { } // Read requests from conn and deal with them -func (s *SocketServer) handleRequests( - ctx context.Context, - closer func(error), - conn io.Reader, - responses chan<- *types.Response, -) { +func (s *SocketServer) handleRequests(ctx context.Context, closer func(error), conn io.Reader, responses chan<- *types.Response) { var bufReader = bufio.NewReader(conn) defer func() { @@ -184,7 +179,12 @@ func (s *SocketServer) handleRequests( return } - resp := s.processRequest(req) + resp, err := s.processRequest(ctx, req) + if err != nil { + closer(err) + return + } + select { case <-ctx.Done(): closer(ctx.Err()) @@ -194,42 +194,99 @@ func (s *SocketServer) handleRequests( } } -func (s *SocketServer) processRequest(req *types.Request) *types.Response { +func (s *SocketServer) processRequest(ctx context.Context, req *types.Request) (*types.Response, error) { switch r := req.Value.(type) { case *types.Request_Echo: - return types.ToResponseEcho(r.Echo.Message) + return types.ToResponseEcho(r.Echo.Message), nil case *types.Request_Flush: - return types.ToResponseFlush() + return types.ToResponseFlush(), nil case *types.Request_Info: - return types.ToResponseInfo(s.app.Info(*r.Info)) + res, err := s.app.Info(ctx, r.Info) + if err != nil { + return nil, err + } + + return types.ToResponseInfo(res), nil case *types.Request_CheckTx: - return types.ToResponseCheckTx(s.app.CheckTx(*r.CheckTx)) + res, err := s.app.CheckTx(ctx, r.CheckTx) + if err != nil { + return nil, err + } + return types.ToResponseCheckTx(res), nil case *types.Request_Commit: - return types.ToResponseCommit(s.app.Commit()) + res, err := s.app.Commit(ctx) + if err != nil { + return nil, err + } + return types.ToResponseCommit(res), nil case *types.Request_Query: - return types.ToResponseQuery(s.app.Query(*r.Query)) + res, err := s.app.Query(ctx, r.Query) + if err != nil { + return nil, err + } + return types.ToResponseQuery(res), nil case *types.Request_InitChain: - return types.ToResponseInitChain(s.app.InitChain(*r.InitChain)) + res, err := s.app.InitChain(ctx, r.InitChain) + if err != nil { + return nil, err + } + return types.ToResponseInitChain(res), nil case *types.Request_ListSnapshots: - return types.ToResponseListSnapshots(s.app.ListSnapshots(*r.ListSnapshots)) + res, err := s.app.ListSnapshots(ctx, r.ListSnapshots) + if err != nil { + return nil, err + } + return types.ToResponseListSnapshots(res), nil case *types.Request_OfferSnapshot: - return types.ToResponseOfferSnapshot(s.app.OfferSnapshot(*r.OfferSnapshot)) + res, err := s.app.OfferSnapshot(ctx, r.OfferSnapshot) + if err != nil { + return nil, err + } + return types.ToResponseOfferSnapshot(res), nil case *types.Request_PrepareProposal: - return types.ToResponsePrepareProposal(s.app.PrepareProposal(*r.PrepareProposal)) + res, err := s.app.PrepareProposal(ctx, r.PrepareProposal) + if err != nil { + return nil, err + } + return types.ToResponsePrepareProposal(res), nil case *types.Request_ProcessProposal: - return types.ToResponseProcessProposal(s.app.ProcessProposal(*r.ProcessProposal)) + res, err := s.app.ProcessProposal(ctx, r.ProcessProposal) + if err != nil { + return nil, err + } + return types.ToResponseProcessProposal(res), nil case *types.Request_LoadSnapshotChunk: - return types.ToResponseLoadSnapshotChunk(s.app.LoadSnapshotChunk(*r.LoadSnapshotChunk)) + res, err := s.app.LoadSnapshotChunk(ctx, r.LoadSnapshotChunk) + if err != nil { + return nil, err + } + return types.ToResponseLoadSnapshotChunk(res), nil case *types.Request_ApplySnapshotChunk: - return types.ToResponseApplySnapshotChunk(s.app.ApplySnapshotChunk(*r.ApplySnapshotChunk)) + res, err := s.app.ApplySnapshotChunk(ctx, r.ApplySnapshotChunk) + if err != nil { + return nil, err + } + return types.ToResponseApplySnapshotChunk(res), nil case *types.Request_ExtendVote: - return types.ToResponseExtendVote(s.app.ExtendVote(*r.ExtendVote)) + res, err := s.app.ExtendVote(ctx, r.ExtendVote) + if err != nil { + return nil, err + } + return types.ToResponseExtendVote(res), nil case *types.Request_VerifyVoteExtension: - return types.ToResponseVerifyVoteExtension(s.app.VerifyVoteExtension(*r.VerifyVoteExtension)) + res, err := s.app.VerifyVoteExtension(ctx, r.VerifyVoteExtension) + if err != nil { + return nil, err + } + return types.ToResponseVerifyVoteExtension(res), nil case *types.Request_FinalizeBlock: - return types.ToResponseFinalizeBlock(s.app.FinalizeBlock(*r.FinalizeBlock)) + res, err := s.app.FinalizeBlock(ctx, r.FinalizeBlock) + if err != nil { + return nil, err + } + return types.ToResponseFinalizeBlock(res), nil default: - return types.ToResponseException("Unknown request") + return types.ToResponseException("Unknown request"), errors.New("unknown request type") } } diff --git a/abci/tests/server/client.go b/abci/tests/server/client.go index 9273e8046..b02fd0b14 100644 --- a/abci/tests/server/client.go +++ b/abci/tests/server/client.go @@ -21,7 +21,7 @@ func InitChain(ctx context.Context, client abciclient.Client) error { power := mrand.Int() vals[i] = types.UpdateValidator(pubkey, int64(power), "") } - _, err := client.InitChain(ctx, types.RequestInitChain{ + _, err := client.InitChain(ctx, &types.RequestInitChain{ Validators: vals, }) if err != nil { @@ -50,7 +50,7 @@ func Commit(ctx context.Context, client abciclient.Client, hashExp []byte) error } func FinalizeBlock(ctx context.Context, client abciclient.Client, txBytes [][]byte, codeExp []uint32, dataExp []byte) error { - res, _ := client.FinalizeBlock(ctx, types.RequestFinalizeBlock{Txs: txBytes}) + res, _ := client.FinalizeBlock(ctx, &types.RequestFinalizeBlock{Txs: txBytes}) for i, tx := range res.TxResults { code, data, log := tx.Code, tx.Data, tx.Log if code != codeExp[i] { @@ -71,7 +71,7 @@ func FinalizeBlock(ctx context.Context, client abciclient.Client, txBytes [][]by } func CheckTx(ctx context.Context, client abciclient.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { - res, _ := client.CheckTx(ctx, types.RequestCheckTx{Tx: txBytes}) + 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") diff --git a/abci/types/application.go b/abci/types/application.go index 959311e3b..e74b87743 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -1,40 +1,36 @@ package types -import ( - "context" -) +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. -// All methods take a RequestXxx argument and return a ResponseXxx argument, -// except CheckTx/DeliverTx, which take `tx []byte`, and `Commit`, which takes nothing. type Application interface { // Info/Query Connection - Info(RequestInfo) ResponseInfo // Return application info - Query(RequestQuery) ResponseQuery // Query for state + Info(context.Context, *RequestInfo) (*ResponseInfo, error) // Return application info + Query(context.Context, *RequestQuery) (*ResponseQuery, error) // Query for state // Mempool Connection - CheckTx(RequestCheckTx) ResponseCheckTx // Validate a tx for the mempool + CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTx, error) // Validate a tx for the mempool // Consensus Connection - InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain w validators/other info from TendermintCore - PrepareProposal(RequestPrepareProposal) ResponsePrepareProposal - ProcessProposal(RequestProcessProposal) ResponseProcessProposal + InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore + PrepareProposal(context.Context, *RequestPrepareProposal) (*ResponsePrepareProposal, error) + ProcessProposal(context.Context, *RequestProcessProposal) (*ResponseProcessProposal, error) // Commit the state and return the application Merkle root hash - Commit() ResponseCommit + Commit(context.Context) (*ResponseCommit, error) // Create application specific vote extension - ExtendVote(RequestExtendVote) ResponseExtendVote + ExtendVote(context.Context, *RequestExtendVote) (*ResponseExtendVote, error) // Verify application's vote extension data - VerifyVoteExtension(RequestVerifyVoteExtension) ResponseVerifyVoteExtension + VerifyVoteExtension(context.Context, *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) // Deliver the decided block with its txs to the Application - FinalizeBlock(RequestFinalizeBlock) ResponseFinalizeBlock + FinalizeBlock(context.Context, *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) // State Sync Connection - ListSnapshots(RequestListSnapshots) ResponseListSnapshots // List available snapshots - OfferSnapshot(RequestOfferSnapshot) ResponseOfferSnapshot // Offer a snapshot to the application - LoadSnapshotChunk(RequestLoadSnapshotChunk) ResponseLoadSnapshotChunk // Load a snapshot chunk - ApplySnapshotChunk(RequestApplySnapshotChunk) ResponseApplySnapshotChunk // Apply a shapshot chunk + 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 } //------------------------------------------------------- @@ -48,53 +44,53 @@ func NewBaseApplication() *BaseApplication { return &BaseApplication{} } -func (BaseApplication) Info(req RequestInfo) ResponseInfo { - return ResponseInfo{} +func (BaseApplication) Info(_ context.Context, req *RequestInfo) (*ResponseInfo, error) { + return &ResponseInfo{}, nil } -func (BaseApplication) CheckTx(req RequestCheckTx) ResponseCheckTx { - return ResponseCheckTx{Code: CodeTypeOK} +func (BaseApplication) CheckTx(_ context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { + return &ResponseCheckTx{Code: CodeTypeOK}, nil } -func (BaseApplication) Commit() ResponseCommit { - return ResponseCommit{} +func (BaseApplication) Commit(_ context.Context) (*ResponseCommit, error) { + return &ResponseCommit{}, nil } -func (BaseApplication) ExtendVote(req RequestExtendVote) ResponseExtendVote { - return ResponseExtendVote{} +func (BaseApplication) ExtendVote(_ context.Context, req *RequestExtendVote) (*ResponseExtendVote, error) { + return &ResponseExtendVote{}, nil } -func (BaseApplication) VerifyVoteExtension(req RequestVerifyVoteExtension) ResponseVerifyVoteExtension { - return ResponseVerifyVoteExtension{ +func (BaseApplication) VerifyVoteExtension(_ context.Context, req *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) { + return &ResponseVerifyVoteExtension{ Status: ResponseVerifyVoteExtension_ACCEPT, - } + }, nil } -func (BaseApplication) Query(req RequestQuery) ResponseQuery { - return ResponseQuery{Code: CodeTypeOK} +func (BaseApplication) Query(_ context.Context, req *RequestQuery) (*ResponseQuery, error) { + return &ResponseQuery{Code: CodeTypeOK}, nil } -func (BaseApplication) InitChain(req RequestInitChain) ResponseInitChain { - return ResponseInitChain{} +func (BaseApplication) InitChain(_ context.Context, req *RequestInitChain) (*ResponseInitChain, error) { + return &ResponseInitChain{}, nil } -func (BaseApplication) ListSnapshots(req RequestListSnapshots) ResponseListSnapshots { - return ResponseListSnapshots{} +func (BaseApplication) ListSnapshots(_ context.Context, req *RequestListSnapshots) (*ResponseListSnapshots, error) { + return &ResponseListSnapshots{}, nil } -func (BaseApplication) OfferSnapshot(req RequestOfferSnapshot) ResponseOfferSnapshot { - return ResponseOfferSnapshot{} +func (BaseApplication) OfferSnapshot(_ context.Context, req *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) { + return &ResponseOfferSnapshot{}, nil } -func (BaseApplication) LoadSnapshotChunk(req RequestLoadSnapshotChunk) ResponseLoadSnapshotChunk { - return ResponseLoadSnapshotChunk{} +func (BaseApplication) LoadSnapshotChunk(_ context.Context, _ *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) { + return &ResponseLoadSnapshotChunk{}, nil } -func (BaseApplication) ApplySnapshotChunk(req RequestApplySnapshotChunk) ResponseApplySnapshotChunk { - return ResponseApplySnapshotChunk{} +func (BaseApplication) ApplySnapshotChunk(_ context.Context, req *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) { + return &ResponseApplySnapshotChunk{}, nil } -func (BaseApplication) PrepareProposal(req RequestPrepareProposal) ResponsePrepareProposal { +func (BaseApplication) PrepareProposal(_ context.Context, req *RequestPrepareProposal) (*ResponsePrepareProposal, error) { trs := make([]*TxRecord, 0, len(req.Txs)) var totalBytes int64 for _, tx := range req.Txs { @@ -107,117 +103,19 @@ func (BaseApplication) PrepareProposal(req RequestPrepareProposal) ResponsePrepa Tx: tx, }) } - return ResponsePrepareProposal{TxRecords: trs} + return &ResponsePrepareProposal{TxRecords: trs}, nil } -func (BaseApplication) ProcessProposal(req RequestProcessProposal) ResponseProcessProposal { - return ResponseProcessProposal{Status: ResponseProcessProposal_ACCEPT} +func (BaseApplication) ProcessProposal(_ context.Context, req *RequestProcessProposal) (*ResponseProcessProposal, error) { + return &ResponseProcessProposal{Status: ResponseProcessProposal_ACCEPT}, nil } -func (BaseApplication) FinalizeBlock(req RequestFinalizeBlock) ResponseFinalizeBlock { +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{ + return &ResponseFinalizeBlock{ TxResults: txs, - } -} - -//------------------------------------------------------- - -// GRPCApplication is a GRPC wrapper for Application -type GRPCApplication struct { - app Application -} - -func NewGRPCApplication(app Application) *GRPCApplication { - return &GRPCApplication{app} -} - -func (app *GRPCApplication) Echo(ctx context.Context, req *RequestEcho) (*ResponseEcho, error) { - return &ResponseEcho{Message: req.Message}, nil -} - -func (app *GRPCApplication) Flush(ctx context.Context, req *RequestFlush) (*ResponseFlush, error) { - return &ResponseFlush{}, nil -} - -func (app *GRPCApplication) Info(ctx context.Context, req *RequestInfo) (*ResponseInfo, error) { - res := app.app.Info(*req) - return &res, nil -} - -func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { - res := app.app.CheckTx(*req) - return &res, nil -} - -func (app *GRPCApplication) Query(ctx context.Context, req *RequestQuery) (*ResponseQuery, error) { - res := app.app.Query(*req) - return &res, nil -} - -func (app *GRPCApplication) Commit(ctx context.Context, req *RequestCommit) (*ResponseCommit, error) { - res := app.app.Commit() - return &res, nil -} - -func (app *GRPCApplication) InitChain(ctx context.Context, req *RequestInitChain) (*ResponseInitChain, error) { - res := app.app.InitChain(*req) - return &res, nil -} - -func (app *GRPCApplication) ListSnapshots( - ctx context.Context, req *RequestListSnapshots) (*ResponseListSnapshots, error) { - res := app.app.ListSnapshots(*req) - return &res, nil -} - -func (app *GRPCApplication) OfferSnapshot( - ctx context.Context, req *RequestOfferSnapshot) (*ResponseOfferSnapshot, error) { - res := app.app.OfferSnapshot(*req) - return &res, nil -} - -func (app *GRPCApplication) LoadSnapshotChunk( - ctx context.Context, req *RequestLoadSnapshotChunk) (*ResponseLoadSnapshotChunk, error) { - res := app.app.LoadSnapshotChunk(*req) - return &res, nil -} - -func (app *GRPCApplication) ApplySnapshotChunk( - ctx context.Context, req *RequestApplySnapshotChunk) (*ResponseApplySnapshotChunk, error) { - res := app.app.ApplySnapshotChunk(*req) - return &res, nil -} - -func (app *GRPCApplication) ExtendVote( - ctx context.Context, req *RequestExtendVote) (*ResponseExtendVote, error) { - res := app.app.ExtendVote(*req) - return &res, nil -} - -func (app *GRPCApplication) VerifyVoteExtension( - ctx context.Context, req *RequestVerifyVoteExtension) (*ResponseVerifyVoteExtension, error) { - res := app.app.VerifyVoteExtension(*req) - return &res, nil -} - -func (app *GRPCApplication) PrepareProposal( - ctx context.Context, req *RequestPrepareProposal) (*ResponsePrepareProposal, error) { - res := app.app.PrepareProposal(*req) - return &res, nil -} - -func (app *GRPCApplication) ProcessProposal( - ctx context.Context, req *RequestProcessProposal) (*ResponseProcessProposal, error) { - res := app.app.ProcessProposal(*req) - return &res, nil -} - -func (app *GRPCApplication) FinalizeBlock( - ctx context.Context, req *RequestFinalizeBlock) (*ResponseFinalizeBlock, error) { - res := app.app.FinalizeBlock(*req) - return &res, nil + }, nil } diff --git a/abci/types/messages.go b/abci/types/messages.go index 74d9b9d1a..80ab19525 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -39,15 +39,15 @@ func ToRequestFlush() *Request { } } -func ToRequestInfo(req RequestInfo) *Request { +func ToRequestInfo(req *RequestInfo) *Request { return &Request{ - Value: &Request_Info{&req}, + Value: &Request_Info{req}, } } -func ToRequestCheckTx(req RequestCheckTx) *Request { +func ToRequestCheckTx(req *RequestCheckTx) *Request { return &Request{ - Value: &Request_CheckTx{&req}, + Value: &Request_CheckTx{req}, } } @@ -57,69 +57,69 @@ func ToRequestCommit() *Request { } } -func ToRequestQuery(req RequestQuery) *Request { +func ToRequestQuery(req *RequestQuery) *Request { return &Request{ - Value: &Request_Query{&req}, + Value: &Request_Query{req}, } } -func ToRequestInitChain(req RequestInitChain) *Request { +func ToRequestInitChain(req *RequestInitChain) *Request { return &Request{ - Value: &Request_InitChain{&req}, + Value: &Request_InitChain{req}, } } -func ToRequestListSnapshots(req RequestListSnapshots) *Request { +func ToRequestListSnapshots(req *RequestListSnapshots) *Request { return &Request{ - Value: &Request_ListSnapshots{&req}, + Value: &Request_ListSnapshots{req}, } } -func ToRequestOfferSnapshot(req RequestOfferSnapshot) *Request { +func ToRequestOfferSnapshot(req *RequestOfferSnapshot) *Request { return &Request{ - Value: &Request_OfferSnapshot{&req}, + Value: &Request_OfferSnapshot{req}, } } -func ToRequestLoadSnapshotChunk(req RequestLoadSnapshotChunk) *Request { +func ToRequestLoadSnapshotChunk(req *RequestLoadSnapshotChunk) *Request { return &Request{ - Value: &Request_LoadSnapshotChunk{&req}, + Value: &Request_LoadSnapshotChunk{req}, } } -func ToRequestApplySnapshotChunk(req RequestApplySnapshotChunk) *Request { +func ToRequestApplySnapshotChunk(req *RequestApplySnapshotChunk) *Request { return &Request{ - Value: &Request_ApplySnapshotChunk{&req}, + Value: &Request_ApplySnapshotChunk{req}, } } -func ToRequestExtendVote(req RequestExtendVote) *Request { +func ToRequestExtendVote(req *RequestExtendVote) *Request { return &Request{ - Value: &Request_ExtendVote{&req}, + Value: &Request_ExtendVote{req}, } } -func ToRequestVerifyVoteExtension(req RequestVerifyVoteExtension) *Request { +func ToRequestVerifyVoteExtension(req *RequestVerifyVoteExtension) *Request { return &Request{ - Value: &Request_VerifyVoteExtension{&req}, + Value: &Request_VerifyVoteExtension{req}, } } -func ToRequestPrepareProposal(req RequestPrepareProposal) *Request { +func ToRequestPrepareProposal(req *RequestPrepareProposal) *Request { return &Request{ - Value: &Request_PrepareProposal{&req}, + Value: &Request_PrepareProposal{req}, } } -func ToRequestProcessProposal(req RequestProcessProposal) *Request { +func ToRequestProcessProposal(req *RequestProcessProposal) *Request { return &Request{ - Value: &Request_ProcessProposal{&req}, + Value: &Request_ProcessProposal{req}, } } -func ToRequestFinalizeBlock(req RequestFinalizeBlock) *Request { +func ToRequestFinalizeBlock(req *RequestFinalizeBlock) *Request { return &Request{ - Value: &Request_FinalizeBlock{&req}, + Value: &Request_FinalizeBlock{req}, } } @@ -143,86 +143,86 @@ func ToResponseFlush() *Response { } } -func ToResponseInfo(res ResponseInfo) *Response { +func ToResponseInfo(res *ResponseInfo) *Response { return &Response{ - Value: &Response_Info{&res}, + Value: &Response_Info{res}, } } -func ToResponseCheckTx(res ResponseCheckTx) *Response { +func ToResponseCheckTx(res *ResponseCheckTx) *Response { return &Response{ - Value: &Response_CheckTx{&res}, + Value: &Response_CheckTx{res}, } } -func ToResponseCommit(res ResponseCommit) *Response { +func ToResponseCommit(res *ResponseCommit) *Response { return &Response{ - Value: &Response_Commit{&res}, + Value: &Response_Commit{res}, } } -func ToResponseQuery(res ResponseQuery) *Response { +func ToResponseQuery(res *ResponseQuery) *Response { return &Response{ - Value: &Response_Query{&res}, + Value: &Response_Query{res}, } } -func ToResponseInitChain(res ResponseInitChain) *Response { +func ToResponseInitChain(res *ResponseInitChain) *Response { return &Response{ - Value: &Response_InitChain{&res}, + Value: &Response_InitChain{res}, } } -func ToResponseListSnapshots(res ResponseListSnapshots) *Response { +func ToResponseListSnapshots(res *ResponseListSnapshots) *Response { return &Response{ - Value: &Response_ListSnapshots{&res}, + Value: &Response_ListSnapshots{res}, } } -func ToResponseOfferSnapshot(res ResponseOfferSnapshot) *Response { +func ToResponseOfferSnapshot(res *ResponseOfferSnapshot) *Response { return &Response{ - Value: &Response_OfferSnapshot{&res}, + Value: &Response_OfferSnapshot{res}, } } -func ToResponseLoadSnapshotChunk(res ResponseLoadSnapshotChunk) *Response { +func ToResponseLoadSnapshotChunk(res *ResponseLoadSnapshotChunk) *Response { return &Response{ - Value: &Response_LoadSnapshotChunk{&res}, + Value: &Response_LoadSnapshotChunk{res}, } } -func ToResponseApplySnapshotChunk(res ResponseApplySnapshotChunk) *Response { +func ToResponseApplySnapshotChunk(res *ResponseApplySnapshotChunk) *Response { return &Response{ - Value: &Response_ApplySnapshotChunk{&res}, + Value: &Response_ApplySnapshotChunk{res}, } } -func ToResponseExtendVote(res ResponseExtendVote) *Response { +func ToResponseExtendVote(res *ResponseExtendVote) *Response { return &Response{ - Value: &Response_ExtendVote{&res}, + Value: &Response_ExtendVote{res}, } } -func ToResponseVerifyVoteExtension(res ResponseVerifyVoteExtension) *Response { +func ToResponseVerifyVoteExtension(res *ResponseVerifyVoteExtension) *Response { return &Response{ - Value: &Response_VerifyVoteExtension{&res}, + Value: &Response_VerifyVoteExtension{res}, } } -func ToResponsePrepareProposal(res ResponsePrepareProposal) *Response { +func ToResponsePrepareProposal(res *ResponsePrepareProposal) *Response { return &Response{ - Value: &Response_PrepareProposal{&res}, + Value: &Response_PrepareProposal{res}, } } -func ToResponseProcessProposal(res ResponseProcessProposal) *Response { +func ToResponseProcessProposal(res *ResponseProcessProposal) *Response { return &Response{ - Value: &Response_ProcessProposal{&res}, + Value: &Response_ProcessProposal{res}, } } -func ToResponseFinalizeBlock(res ResponseFinalizeBlock) *Response { +func ToResponseFinalizeBlock(res *ResponseFinalizeBlock) *Response { return &Response{ - Value: &Response_FinalizeBlock{&res}, + Value: &Response_FinalizeBlock{res}, } } diff --git a/abci/types/mocks/application.go b/abci/types/mocks/application.go index 30bf0f84c..2d35c481f 100644 --- a/abci/types/mocks/application.go +++ b/abci/types/mocks/application.go @@ -3,7 +3,11 @@ package mocks import ( + context "context" + testing "testing" + mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/abci/types" ) @@ -12,198 +16,334 @@ type Application struct { mock.Mock } -// ApplySnapshotChunk provides a mock function with given fields: _a0 -func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { +// 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) + + var r0 *types.ResponseApplySnapshotChunk + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestApplySnapshotChunk) *types.ResponseApplySnapshotChunk); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseApplySnapshotChunk) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestApplySnapshotChunk) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CheckTx provides a mock function with given fields: _a0, _a1 +func (_m *Application) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseCheckTx + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseCheckTx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestCheckTx) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Commit provides a mock function with given fields: _a0 +func (_m *Application) Commit(_a0 context.Context) (*types.ResponseCommit, error) { ret := _m.Called(_a0) - var r0 types.ResponseApplySnapshotChunk - if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok { + var r0 *types.ResponseCommit + if rf, ok := ret.Get(0).(func(context.Context) *types.ResponseCommit); ok { r0 = rf(_a0) } else { - r0 = ret.Get(0).(types.ResponseApplySnapshotChunk) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseCommit) + } } - return r0 -} - -// CheckTx provides a mock function with given fields: _a0 -func (_m *Application) CheckTx(_a0 types.RequestCheckTx) types.ResponseCheckTx { - ret := _m.Called(_a0) - - var r0 types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(types.RequestCheckTx) types.ResponseCheckTx); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) } else { - r0 = ret.Get(0).(types.ResponseCheckTx) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// Commit provides a mock function with given fields: -func (_m *Application) Commit() types.ResponseCommit { - ret := _m.Called() +// 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) - var r0 types.ResponseCommit - if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok { - r0 = rf() + var r0 *types.ResponseExtendVote + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestExtendVote) *types.ResponseExtendVote); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseCommit) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseExtendVote) + } } - return r0 -} - -// ExtendVote provides a mock function with given fields: _a0 -func (_m *Application) ExtendVote(_a0 types.RequestExtendVote) types.ResponseExtendVote { - ret := _m.Called(_a0) - - var r0 types.ResponseExtendVote - if rf, ok := ret.Get(0).(func(types.RequestExtendVote) types.ResponseExtendVote); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestExtendVote) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseExtendVote) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// FinalizeBlock provides a mock function with given fields: _a0 -func (_m *Application) FinalizeBlock(_a0 types.RequestFinalizeBlock) types.ResponseFinalizeBlock { - ret := _m.Called(_a0) +// 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) - var r0 types.ResponseFinalizeBlock - if rf, ok := ret.Get(0).(func(types.RequestFinalizeBlock) types.ResponseFinalizeBlock); ok { - r0 = rf(_a0) + var r0 *types.ResponseFinalizeBlock + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestFinalizeBlock) *types.ResponseFinalizeBlock); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseFinalizeBlock) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseFinalizeBlock) + } } - return r0 -} - -// Info provides a mock function with given fields: _a0 -func (_m *Application) Info(_a0 types.RequestInfo) types.ResponseInfo { - ret := _m.Called(_a0) - - var r0 types.ResponseInfo - if rf, ok := ret.Get(0).(func(types.RequestInfo) types.ResponseInfo); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestFinalizeBlock) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseInfo) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// InitChain provides a mock function with given fields: _a0 -func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain { - ret := _m.Called(_a0) +// 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) - var r0 types.ResponseInitChain - if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok { - r0 = rf(_a0) + var r0 *types.ResponseInfo + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInfo) *types.ResponseInfo); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseInitChain) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseInfo) + } } - return r0 -} - -// ListSnapshots provides a mock function with given fields: _a0 -func (_m *Application) ListSnapshots(_a0 types.RequestListSnapshots) types.ResponseListSnapshots { - ret := _m.Called(_a0) - - var r0 types.ResponseListSnapshots - if rf, ok := ret.Get(0).(func(types.RequestListSnapshots) types.ResponseListSnapshots); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInfo) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseListSnapshots) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// LoadSnapshotChunk provides a mock function with given fields: _a0 -func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { - ret := _m.Called(_a0) +// 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) - var r0 types.ResponseLoadSnapshotChunk - if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok { - r0 = rf(_a0) + var r0 *types.ResponseInitChain + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestInitChain) *types.ResponseInitChain); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseInitChain) + } } - return r0 -} - -// OfferSnapshot provides a mock function with given fields: _a0 -func (_m *Application) OfferSnapshot(_a0 types.RequestOfferSnapshot) types.ResponseOfferSnapshot { - ret := _m.Called(_a0) - - var r0 types.ResponseOfferSnapshot - if rf, ok := ret.Get(0).(func(types.RequestOfferSnapshot) types.ResponseOfferSnapshot); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestInitChain) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseOfferSnapshot) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// PrepareProposal provides a mock function with given fields: _a0 -func (_m *Application) PrepareProposal(_a0 types.RequestPrepareProposal) types.ResponsePrepareProposal { - ret := _m.Called(_a0) +// 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) - var r0 types.ResponsePrepareProposal - if rf, ok := ret.Get(0).(func(types.RequestPrepareProposal) types.ResponsePrepareProposal); ok { - r0 = rf(_a0) + var r0 *types.ResponseListSnapshots + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestListSnapshots) *types.ResponseListSnapshots); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponsePrepareProposal) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseListSnapshots) + } } - return r0 -} - -// ProcessProposal provides a mock function with given fields: _a0 -func (_m *Application) ProcessProposal(_a0 types.RequestProcessProposal) types.ResponseProcessProposal { - ret := _m.Called(_a0) - - var r0 types.ResponseProcessProposal - if rf, ok := ret.Get(0).(func(types.RequestProcessProposal) types.ResponseProcessProposal); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestListSnapshots) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseProcessProposal) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// Query provides a mock function with given fields: _a0 -func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery { - ret := _m.Called(_a0) +// 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) - var r0 types.ResponseQuery - if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok { - r0 = rf(_a0) + var r0 *types.ResponseLoadSnapshotChunk + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestLoadSnapshotChunk) *types.ResponseLoadSnapshotChunk); ok { + r0 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseQuery) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseLoadSnapshotChunk) + } } - return r0 -} - -// VerifyVoteExtension provides a mock function with given fields: _a0 -func (_m *Application) VerifyVoteExtension(_a0 types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension { - ret := _m.Called(_a0) - - var r0 types.ResponseVerifyVoteExtension - if rf, ok := ret.Get(0).(func(types.RequestVerifyVoteExtension) types.ResponseVerifyVoteExtension); ok { - r0 = rf(_a0) + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestLoadSnapshotChunk) error); ok { + r1 = rf(_a0, _a1) } else { - r0 = ret.Get(0).(types.ResponseVerifyVoteExtension) + r1 = ret.Error(1) } - return r0 + 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) + + var r0 *types.ResponseOfferSnapshot + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestOfferSnapshot) *types.ResponseOfferSnapshot); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseOfferSnapshot) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestOfferSnapshot) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PrepareProposal provides a mock function with given fields: _a0, _a1 +func (_m *Application) PrepareProposal(_a0 context.Context, _a1 *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponsePrepareProposal + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestPrepareProposal) *types.ResponsePrepareProposal); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponsePrepareProposal) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestPrepareProposal) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProcessProposal provides a mock function with given fields: _a0, _a1 +func (_m *Application) ProcessProposal(_a0 context.Context, _a1 *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseProcessProposal + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestProcessProposal) *types.ResponseProcessProposal); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseProcessProposal) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestProcessProposal) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: _a0, _a1 +func (_m *Application) Query(_a0 context.Context, _a1 *types.RequestQuery) (*types.ResponseQuery, error) { + ret := _m.Called(_a0, _a1) + + var r0 *types.ResponseQuery + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestQuery) *types.ResponseQuery); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseQuery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// 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) + + var r0 *types.ResponseVerifyVoteExtension + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestVerifyVoteExtension) *types.ResponseVerifyVoteExtension); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.ResponseVerifyVoteExtension) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *types.RequestVerifyVoteExtension) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewApplication creates a new instance of Application. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewApplication(t testing.TB) *Application { + mock := &Application{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock } diff --git a/abci/types/mocks/base.go b/abci/types/mocks/base.go deleted file mode 100644 index fe0516e26..000000000 --- a/abci/types/mocks/base.go +++ /dev/null @@ -1,175 +0,0 @@ -package mocks - -import ( - types "github.com/tendermint/tendermint/abci/types" -) - -// BaseMock provides a wrapper around the generated Application mock and a BaseApplication. -// BaseMock first tries to use the mock's implementation of the method. -// If no functionality was provided for the mock by the user, BaseMock dispatches -// to the BaseApplication and uses its functionality. -// BaseMock allows users to provide mocked functionality for only the methods that matter -// for their test while avoiding a panic if the code calls Application methods that are -// not relevant to the test. -type BaseMock struct { - base *types.BaseApplication - *Application -} - -func NewBaseMock() BaseMock { - return BaseMock{ - base: types.NewBaseApplication(), - Application: new(Application), - } -} - -// Info/Query Connection -// Return application info -func (m BaseMock) Info(input types.RequestInfo) (ret types.ResponseInfo) { - defer func() { - if r := recover(); r != nil { - ret = m.base.Info(input) - } - }() - ret = m.Application.Info(input) - return ret -} - -func (m BaseMock) Query(input types.RequestQuery) (ret types.ResponseQuery) { - defer func() { - if r := recover(); r != nil { - ret = m.base.Query(input) - } - }() - ret = m.Application.Query(input) - return ret -} - -// Mempool Connection -// Validate a tx for the mempool -func (m BaseMock) CheckTx(input types.RequestCheckTx) (ret types.ResponseCheckTx) { - defer func() { - if r := recover(); r != nil { - ret = m.base.CheckTx(input) - } - }() - ret = m.Application.CheckTx(input) - return ret -} - -// Consensus Connection -// Initialize blockchain w validators/other info from TendermintCore -func (m BaseMock) InitChain(input types.RequestInitChain) (ret types.ResponseInitChain) { - defer func() { - if r := recover(); r != nil { - ret = m.base.InitChain(input) - } - }() - ret = m.Application.InitChain(input) - return ret -} - -func (m BaseMock) PrepareProposal(input types.RequestPrepareProposal) (ret types.ResponsePrepareProposal) { - defer func() { - if r := recover(); r != nil { - ret = m.base.PrepareProposal(input) - } - }() - ret = m.Application.PrepareProposal(input) - return ret -} - -func (m BaseMock) ProcessProposal(input types.RequestProcessProposal) (ret types.ResponseProcessProposal) { - defer func() { - if r := recover(); r != nil { - ret = m.base.ProcessProposal(input) - } - }() - ret = m.Application.ProcessProposal(input) - return ret -} - -// Commit the state and return the application Merkle root hash -func (m BaseMock) Commit() (ret types.ResponseCommit) { - defer func() { - if r := recover(); r != nil { - ret = m.base.Commit() - } - }() - ret = m.Application.Commit() - return ret -} - -// Create application specific vote extension -func (m BaseMock) ExtendVote(input types.RequestExtendVote) (ret types.ResponseExtendVote) { - defer func() { - if r := recover(); r != nil { - ret = m.base.ExtendVote(input) - } - }() - ret = m.Application.ExtendVote(input) - return ret -} - -// Verify application's vote extension data -func (m BaseMock) VerifyVoteExtension(input types.RequestVerifyVoteExtension) (ret types.ResponseVerifyVoteExtension) { - defer func() { - if r := recover(); r != nil { - ret = m.base.VerifyVoteExtension(input) - } - }() - ret = m.Application.VerifyVoteExtension(input) - return ret -} - -// State Sync Connection -// List available snapshots -func (m BaseMock) ListSnapshots(input types.RequestListSnapshots) (ret types.ResponseListSnapshots) { - defer func() { - if r := recover(); r != nil { - ret = m.base.ListSnapshots(input) - } - }() - ret = m.Application.ListSnapshots(input) - return ret -} - -func (m BaseMock) OfferSnapshot(input types.RequestOfferSnapshot) (ret types.ResponseOfferSnapshot) { - defer func() { - if r := recover(); r != nil { - ret = m.base.OfferSnapshot(input) - } - }() - ret = m.Application.OfferSnapshot(input) - return ret -} - -func (m BaseMock) LoadSnapshotChunk(input types.RequestLoadSnapshotChunk) (ret types.ResponseLoadSnapshotChunk) { - defer func() { - if r := recover(); r != nil { - ret = m.base.LoadSnapshotChunk(input) - } - }() - ret = m.Application.LoadSnapshotChunk(input) - return ret -} - -func (m BaseMock) ApplySnapshotChunk(input types.RequestApplySnapshotChunk) (ret types.ResponseApplySnapshotChunk) { - defer func() { - if r := recover(); r != nil { - ret = m.base.ApplySnapshotChunk(input) - } - }() - ret = m.Application.ApplySnapshotChunk(input) - return ret -} - -func (m BaseMock) FinalizeBlock(input types.RequestFinalizeBlock) (ret types.ResponseFinalizeBlock) { - defer func() { - if r := recover(); r != nil { - ret = m.base.FinalizeBlock(input) - } - }() - ret = m.Application.FinalizeBlock(input) - return ret -} diff --git a/abci/types/types.go b/abci/types/types.go index d74a2289a..d13947d1a 100644 --- a/abci/types/types.go +++ b/abci/types/types.go @@ -5,8 +5,6 @@ import ( "encoding/json" "github.com/gogo/protobuf/jsonpb" - - types "github.com/tendermint/tendermint/proto/tendermint/types" ) const ( @@ -157,15 +155,6 @@ var _ jsonRoundTripper = (*EventAttribute)(nil) // ----------------------------------------------- // construct Result data -func RespondExtendVote(appDataToSign, appDataSelfAuthenticating []byte) ResponseExtendVote { - return ResponseExtendVote{ - VoteExtension: &types.VoteExtension{ - AppDataToSign: appDataToSign, - AppDataSelfAuthenticating: appDataSelfAuthenticating, - }, - } -} - func RespondVerifyVoteExtension(ok bool) ResponseVerifyVoteExtension { status := ResponseVerifyVoteExtension_REJECT if ok { diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 9e7955462..dd1308628 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -1521,7 +1521,8 @@ func (m *RequestProcessProposal) GetProposerAddress() []byte { // Extends a vote with application-side injection type RequestExtendVote struct { - Vote *types1.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote,omitempty"` + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` } func (m *RequestExtendVote) Reset() { *m = RequestExtendVote{} } @@ -1557,16 +1558,26 @@ func (m *RequestExtendVote) XXX_DiscardUnknown() { var xxx_messageInfo_RequestExtendVote proto.InternalMessageInfo -func (m *RequestExtendVote) GetVote() *types1.Vote { +func (m *RequestExtendVote) GetHash() []byte { if m != nil { - return m.Vote + return m.Hash } return nil } +func (m *RequestExtendVote) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + // Verify the vote extension type RequestVerifyVoteExtension struct { - Vote *types1.Vote `protobuf:"bytes,1,opt,name=vote,proto3" json:"vote,omitempty"` + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + VoteExtension []byte `protobuf:"bytes,4,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` } func (m *RequestVerifyVoteExtension) Reset() { *m = RequestVerifyVoteExtension{} } @@ -1602,9 +1613,30 @@ func (m *RequestVerifyVoteExtension) XXX_DiscardUnknown() { var xxx_messageInfo_RequestVerifyVoteExtension proto.InternalMessageInfo -func (m *RequestVerifyVoteExtension) GetVote() *types1.Vote { +func (m *RequestVerifyVoteExtension) GetHash() []byte { if m != nil { - return m.Vote + return m.Hash + } + return nil +} + +func (m *RequestVerifyVoteExtension) GetValidatorAddress() []byte { + if m != nil { + return m.ValidatorAddress + } + return nil +} + +func (m *RequestVerifyVoteExtension) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RequestVerifyVoteExtension) GetVoteExtension() []byte { + if m != nil { + return m.VoteExtension } return nil } @@ -3131,7 +3163,7 @@ func (m *ResponseProcessProposal) GetConsensusParamUpdates() *types1.ConsensusPa } type ResponseExtendVote struct { - VoteExtension *types1.VoteExtension `protobuf:"bytes,1,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + VoteExtension []byte `protobuf:"bytes,1,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` } func (m *ResponseExtendVote) Reset() { *m = ResponseExtendVote{} } @@ -3167,7 +3199,7 @@ func (m *ResponseExtendVote) XXX_DiscardUnknown() { var xxx_messageInfo_ResponseExtendVote proto.InternalMessageInfo -func (m *ResponseExtendVote) GetVoteExtension() *types1.VoteExtension { +func (m *ResponseExtendVote) GetVoteExtension() []byte { if m != nil { return m.VoteExtension } @@ -3354,8 +3386,14 @@ func (m *CommitInfo) GetVotes() []VoteInfo { return nil } +// ExtendedCommitInfo is similar to CommitInfo except that it is only used in +// the PrepareProposal request such that Tendermint can provide vote extensions +// to the application. type ExtendedCommitInfo struct { - Round int32 `protobuf:"varint,1,opt,name=round,proto3" json:"round,omitempty"` + // The round at which the block proposer decided in the previous height. + Round int32 `protobuf:"varint,1,opt,name=round,proto3" json:"round,omitempty"` + // List of validators' addresses in the last validator set with their voting + // information, including vote extensions. Votes []ExtendedVoteInfo `protobuf:"bytes,2,rep,name=votes,proto3" json:"votes"` } @@ -3908,10 +3946,14 @@ func (m *VoteInfo) GetSignedLastBlock() bool { return false } +// ExtendedVoteInfo type ExtendedVoteInfo struct { - Validator Validator `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator"` - SignedLastBlock bool `protobuf:"varint,2,opt,name=signed_last_block,json=signedLastBlock,proto3" json:"signed_last_block,omitempty"` - VoteExtension []byte `protobuf:"bytes,3,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + // The validator that sent the vote. + Validator Validator `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator"` + // Indicates whether the validator signed the last block, allowing for rewards based on validator availability. + SignedLastBlock bool `protobuf:"varint,2,opt,name=signed_last_block,json=signedLastBlock,proto3" json:"signed_last_block,omitempty"` + // Non-deterministic extension provided by the sending validator's application. + VoteExtension []byte `protobuf:"bytes,3,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` } func (m *ExtendedVoteInfo) Reset() { *m = ExtendedVoteInfo{} } @@ -4194,222 +4236,222 @@ func init() { proto.RegisterFile("tendermint/abci/types.proto", fileDescriptor_2 var fileDescriptor_252557cfdd89a31a = []byte{ // 3451 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0x4b, 0x73, 0x23, 0xd5, - 0xf5, 0xd7, 0xfb, 0x71, 0x64, 0x3d, 0x7c, 0x6d, 0x06, 0x8d, 0x98, 0xb1, 0x87, 0x9e, 0x02, 0x66, - 0x06, 0xf0, 0xf0, 0x9f, 0xf9, 0x0f, 0x0c, 0x01, 0x42, 0xd9, 0xb2, 0x8c, 0x3c, 0xe3, 0xb1, 0x4d, - 0x5b, 0x36, 0x45, 0x42, 0xa6, 0x69, 0xa9, 0xaf, 0xad, 0x66, 0x24, 0x75, 0xd3, 0xdd, 0x32, 0x32, - 0xcb, 0x50, 0x6c, 0xa8, 0x54, 0x85, 0x4d, 0x2a, 0xc9, 0x82, 0x5d, 0x52, 0x95, 0x7c, 0x83, 0xac, - 0xb2, 0xca, 0x82, 0x45, 0x16, 0xac, 0x92, 0x54, 0x16, 0x24, 0x05, 0xbb, 0x7c, 0x81, 0xec, 0x92, - 0xd4, 0x7d, 0xf4, 0x53, 0x6a, 0xa9, 0xc5, 0x00, 0x55, 0xa9, 0xb0, 0xd3, 0x3d, 0x7d, 0xce, 0xe9, - 0xbe, 0xf7, 0x9e, 0x7b, 0x1e, 0xbf, 0x73, 0x05, 0x8f, 0x59, 0x78, 0xa0, 0x60, 0xa3, 0xaf, 0x0e, - 0xac, 0xeb, 0x72, 0xbb, 0xa3, 0x5e, 0xb7, 0xce, 0x74, 0x6c, 0xae, 0xe9, 0x86, 0x66, 0x69, 0xa8, - 0xec, 0x3e, 0x5c, 0x23, 0x0f, 0x6b, 0x17, 0x3d, 0xdc, 0x1d, 0xe3, 0x4c, 0xb7, 0xb4, 0xeb, 0xba, - 0xa1, 0x69, 0xc7, 0x8c, 0xbf, 0x76, 0xc1, 0xf3, 0x98, 0xea, 0xf1, 0x6a, 0xf3, 0x3d, 0xe5, 0xc2, - 0x0f, 0xf0, 0x99, 0xfd, 0xf4, 0xe2, 0x98, 0xac, 0x2e, 0x1b, 0x72, 0xdf, 0x7e, 0xbc, 0x7a, 0xa2, - 0x69, 0x27, 0x3d, 0x7c, 0x9d, 0x8e, 0xda, 0xc3, 0xe3, 0xeb, 0x96, 0xda, 0xc7, 0xa6, 0x25, 0xf7, - 0x75, 0xce, 0xb0, 0x7c, 0xa2, 0x9d, 0x68, 0xf4, 0xe7, 0x75, 0xf2, 0x8b, 0x51, 0x85, 0x7f, 0x03, - 0x64, 0x45, 0xfc, 0xee, 0x10, 0x9b, 0x16, 0xba, 0x01, 0x29, 0xdc, 0xe9, 0x6a, 0xd5, 0xf8, 0xa5, - 0xf8, 0x95, 0xc2, 0x8d, 0x0b, 0x6b, 0x81, 0xc9, 0xad, 0x71, 0xbe, 0x46, 0xa7, 0xab, 0x35, 0x63, - 0x22, 0xe5, 0x45, 0xb7, 0x20, 0x7d, 0xdc, 0x1b, 0x9a, 0xdd, 0x6a, 0x82, 0x0a, 0x5d, 0x0c, 0x13, - 0xda, 0x22, 0x4c, 0xcd, 0x98, 0xc8, 0xb8, 0xc9, 0xab, 0xd4, 0xc1, 0xb1, 0x56, 0x4d, 0x4e, 0x7f, - 0xd5, 0xf6, 0xe0, 0x98, 0xbe, 0x8a, 0xf0, 0xa2, 0x0d, 0x00, 0x75, 0xa0, 0x5a, 0x52, 0xa7, 0x2b, - 0xab, 0x83, 0x6a, 0x8a, 0x4a, 0x3e, 0x1e, 0x2e, 0xa9, 0x5a, 0x75, 0xc2, 0xd8, 0x8c, 0x89, 0x79, - 0xd5, 0x1e, 0x90, 0xcf, 0x7d, 0x77, 0x88, 0x8d, 0xb3, 0x6a, 0x7a, 0xfa, 0xe7, 0xbe, 0x4e, 0x98, - 0xc8, 0xe7, 0x52, 0x6e, 0xb4, 0x0d, 0x85, 0x36, 0x3e, 0x51, 0x07, 0x52, 0xbb, 0xa7, 0x75, 0x1e, - 0x54, 0x33, 0x54, 0x58, 0x08, 0x13, 0xde, 0x20, 0xac, 0x1b, 0x84, 0x73, 0x23, 0x51, 0x8d, 0x37, - 0x63, 0x22, 0xb4, 0x1d, 0x0a, 0x7a, 0x19, 0x72, 0x9d, 0x2e, 0xee, 0x3c, 0x90, 0xac, 0x51, 0x35, - 0x4b, 0xf5, 0xac, 0x86, 0xe9, 0xa9, 0x13, 0xbe, 0xd6, 0xa8, 0x19, 0x13, 0xb3, 0x1d, 0xf6, 0x13, - 0x6d, 0x01, 0x28, 0xb8, 0xa7, 0x9e, 0x62, 0x83, 0xc8, 0xe7, 0xa6, 0xaf, 0xc1, 0x26, 0xe3, 0x6c, - 0x8d, 0xf8, 0x67, 0xe4, 0x15, 0x9b, 0x80, 0xea, 0x90, 0xc7, 0x03, 0x85, 0x4f, 0x27, 0x4f, 0xd5, - 0x5c, 0x0a, 0xdd, 0xef, 0x81, 0xe2, 0x9d, 0x4c, 0x0e, 0xf3, 0x31, 0xba, 0x0d, 0x99, 0x8e, 0xd6, - 0xef, 0xab, 0x56, 0x15, 0xa8, 0x86, 0x95, 0xd0, 0x89, 0x50, 0xae, 0x66, 0x4c, 0xe4, 0xfc, 0x68, - 0x17, 0x4a, 0x3d, 0xd5, 0xb4, 0x24, 0x73, 0x20, 0xeb, 0x66, 0x57, 0xb3, 0xcc, 0x6a, 0x81, 0x6a, - 0x78, 0x22, 0x4c, 0xc3, 0x8e, 0x6a, 0x5a, 0x07, 0x36, 0x73, 0x33, 0x26, 0x16, 0x7b, 0x5e, 0x02, - 0xd1, 0xa7, 0x1d, 0x1f, 0x63, 0xc3, 0x51, 0x58, 0x5d, 0x98, 0xae, 0x6f, 0x8f, 0x70, 0xdb, 0xf2, - 0x44, 0x9f, 0xe6, 0x25, 0xa0, 0x1f, 0xc2, 0x52, 0x4f, 0x93, 0x15, 0x47, 0x9d, 0xd4, 0xe9, 0x0e, - 0x07, 0x0f, 0xaa, 0x45, 0xaa, 0xf4, 0x6a, 0xe8, 0x47, 0x6a, 0xb2, 0x62, 0xab, 0xa8, 0x13, 0x81, - 0x66, 0x4c, 0x5c, 0xec, 0x05, 0x89, 0xe8, 0x3e, 0x2c, 0xcb, 0xba, 0xde, 0x3b, 0x0b, 0x6a, 0x2f, - 0x51, 0xed, 0xd7, 0xc2, 0xb4, 0xaf, 0x13, 0x99, 0xa0, 0x7a, 0x24, 0x8f, 0x51, 0x51, 0x0b, 0x2a, - 0xba, 0x81, 0x75, 0xd9, 0xc0, 0x92, 0x6e, 0x68, 0xba, 0x66, 0xca, 0xbd, 0x6a, 0x99, 0xea, 0x7e, - 0x2a, 0x4c, 0xf7, 0x3e, 0xe3, 0xdf, 0xe7, 0xec, 0xcd, 0x98, 0x58, 0xd6, 0xfd, 0x24, 0xa6, 0x55, - 0xeb, 0x60, 0xd3, 0x74, 0xb5, 0x56, 0x66, 0x69, 0xa5, 0xfc, 0x7e, 0xad, 0x3e, 0x12, 0x6a, 0x40, - 0x01, 0x8f, 0x88, 0xb8, 0x74, 0xaa, 0x59, 0xb8, 0xba, 0x38, 0xfd, 0x60, 0x35, 0x28, 0xeb, 0x91, - 0x66, 0x61, 0x72, 0xa8, 0xb0, 0x33, 0x42, 0x32, 0x3c, 0x72, 0x8a, 0x0d, 0xf5, 0xf8, 0x8c, 0xaa, - 0x91, 0xe8, 0x13, 0x53, 0xd5, 0x06, 0x55, 0x44, 0x15, 0x3e, 0x1d, 0xa6, 0xf0, 0x88, 0x0a, 0x11, - 0x15, 0x0d, 0x5b, 0xa4, 0x19, 0x13, 0x97, 0x4e, 0xc7, 0xc9, 0xc4, 0xc4, 0x8e, 0xd5, 0x81, 0xdc, - 0x53, 0xdf, 0xc7, 0xfc, 0xd8, 0x2c, 0x4d, 0x37, 0xb1, 0x2d, 0xce, 0x4d, 0xcf, 0x0a, 0x31, 0xb1, - 0x63, 0x2f, 0x61, 0x23, 0x0b, 0xe9, 0x53, 0xb9, 0x37, 0xc4, 0xc2, 0x53, 0x50, 0xf0, 0x38, 0x56, - 0x54, 0x85, 0x6c, 0x1f, 0x9b, 0xa6, 0x7c, 0x82, 0xa9, 0x1f, 0xce, 0x8b, 0xf6, 0x50, 0x28, 0xc1, - 0x82, 0xd7, 0x99, 0x0a, 0x1f, 0xc7, 0x1d, 0x49, 0xe2, 0x27, 0x89, 0xe4, 0x29, 0x36, 0xe8, 0xb4, - 0xb9, 0x24, 0x1f, 0xa2, 0xcb, 0x50, 0xa4, 0x9f, 0x2c, 0xd9, 0xcf, 0x89, 0xb3, 0x4e, 0x89, 0x0b, - 0x94, 0x78, 0xc4, 0x99, 0x56, 0xa1, 0xa0, 0xdf, 0xd0, 0x1d, 0x96, 0x24, 0x65, 0x01, 0xfd, 0x86, - 0x6e, 0x33, 0x3c, 0x0e, 0x0b, 0x64, 0x7e, 0x0e, 0x47, 0x8a, 0xbe, 0xa4, 0x40, 0x68, 0x9c, 0x45, - 0xf8, 0x63, 0x02, 0x2a, 0x41, 0x07, 0x8c, 0x6e, 0x43, 0x8a, 0xc4, 0x22, 0x1e, 0x56, 0x6a, 0x6b, - 0x2c, 0x50, 0xad, 0xd9, 0x81, 0x6a, 0xad, 0x65, 0x07, 0xaa, 0x8d, 0xdc, 0xa7, 0x9f, 0xaf, 0xc6, - 0x3e, 0xfe, 0xdb, 0x6a, 0x5c, 0xa4, 0x12, 0xe8, 0x3c, 0xf1, 0x95, 0xb2, 0x3a, 0x90, 0x54, 0x85, - 0x7e, 0x72, 0x9e, 0x38, 0x42, 0x59, 0x1d, 0x6c, 0x2b, 0x68, 0x07, 0x2a, 0x1d, 0x6d, 0x60, 0xe2, - 0x81, 0x39, 0x34, 0x25, 0x16, 0x08, 0x79, 0x30, 0xf1, 0xb9, 0x43, 0x16, 0x5e, 0xeb, 0x36, 0xe7, - 0x3e, 0x65, 0x14, 0xcb, 0x1d, 0x3f, 0x81, 0xb8, 0xd5, 0x53, 0xb9, 0xa7, 0x2a, 0xb2, 0xa5, 0x19, - 0x66, 0x35, 0x75, 0x29, 0x39, 0xd1, 0x1f, 0x1e, 0xd9, 0x2c, 0x87, 0xba, 0x22, 0x5b, 0x78, 0x23, - 0x45, 0x3e, 0x57, 0xf4, 0x48, 0xa2, 0x27, 0xa1, 0x2c, 0xeb, 0xba, 0x64, 0x5a, 0xb2, 0x85, 0xa5, - 0xf6, 0x99, 0x85, 0x4d, 0x1a, 0x68, 0x16, 0xc4, 0xa2, 0xac, 0xeb, 0x07, 0x84, 0xba, 0x41, 0x88, - 0xe8, 0x09, 0x28, 0x91, 0x98, 0xa4, 0xca, 0x3d, 0xa9, 0x8b, 0xd5, 0x93, 0xae, 0x45, 0x43, 0x4a, - 0x52, 0x2c, 0x72, 0x6a, 0x93, 0x12, 0x05, 0xc5, 0xd9, 0x71, 0x1a, 0x8f, 0x10, 0x82, 0x94, 0x22, - 0x5b, 0x32, 0x5d, 0xc9, 0x05, 0x91, 0xfe, 0x26, 0x34, 0x5d, 0xb6, 0xba, 0x7c, 0x7d, 0xe8, 0x6f, - 0x74, 0x0e, 0x32, 0x5c, 0x6d, 0x92, 0xaa, 0xe5, 0x23, 0xb4, 0x0c, 0x69, 0xdd, 0xd0, 0x4e, 0x31, - 0xdd, 0xba, 0x9c, 0xc8, 0x06, 0xc2, 0x07, 0x09, 0x58, 0x1c, 0x8b, 0x5c, 0x44, 0x6f, 0x57, 0x36, - 0xbb, 0xf6, 0xbb, 0xc8, 0x6f, 0xf4, 0x3c, 0xd1, 0x2b, 0x2b, 0xd8, 0xe0, 0xd1, 0xbe, 0x3a, 0xbe, - 0xd4, 0x4d, 0xfa, 0x9c, 0x2f, 0x0d, 0xe7, 0x46, 0x77, 0xa1, 0xd2, 0x93, 0x4d, 0x4b, 0x62, 0xde, - 0x5f, 0xf2, 0x44, 0xfe, 0xc7, 0xc6, 0x16, 0x99, 0xc5, 0x0a, 0x62, 0xd0, 0x5c, 0x49, 0x89, 0x88, - 0xba, 0x54, 0x74, 0x08, 0xcb, 0xed, 0xb3, 0xf7, 0xe5, 0x81, 0xa5, 0x0e, 0xb0, 0x34, 0xb6, 0x6b, - 0xe3, 0xa9, 0xc4, 0x3d, 0xd5, 0x6c, 0xe3, 0xae, 0x7c, 0xaa, 0x6a, 0xf6, 0x67, 0x2d, 0x39, 0xf2, - 0xce, 0x8e, 0x9a, 0x82, 0x08, 0x25, 0x7f, 0xd8, 0x45, 0x25, 0x48, 0x58, 0x23, 0x3e, 0xff, 0x84, - 0x35, 0x42, 0xcf, 0x41, 0x8a, 0xcc, 0x91, 0xce, 0xbd, 0x34, 0xe1, 0x45, 0x5c, 0xae, 0x75, 0xa6, - 0x63, 0x91, 0x72, 0x0a, 0x82, 0x73, 0x1a, 0x9c, 0x50, 0x1c, 0xd4, 0x2a, 0x5c, 0x85, 0x72, 0x20, - 0xce, 0x7a, 0xb6, 0x2f, 0xee, 0xdd, 0x3e, 0xa1, 0x0c, 0x45, 0x5f, 0x40, 0x15, 0xce, 0xc1, 0xf2, - 0xa4, 0xf8, 0x28, 0x74, 0x1d, 0xba, 0x2f, 0xce, 0xa1, 0x5b, 0x90, 0x73, 0x02, 0x24, 0x3b, 0x8d, - 0xe7, 0xc7, 0x66, 0x61, 0x33, 0x8b, 0x0e, 0x2b, 0x39, 0x86, 0xc4, 0xaa, 0xa9, 0x39, 0x24, 0xe8, - 0x87, 0x67, 0x65, 0x5d, 0x6f, 0xca, 0x66, 0x57, 0x78, 0x1b, 0xaa, 0x61, 0xc1, 0x2f, 0x30, 0x8d, - 0x94, 0x63, 0x85, 0xe7, 0x20, 0x73, 0xac, 0x19, 0x7d, 0xd9, 0xa2, 0xca, 0x8a, 0x22, 0x1f, 0x11, - 0xeb, 0x64, 0x81, 0x30, 0x49, 0xc9, 0x6c, 0x20, 0x48, 0x70, 0x3e, 0x34, 0x00, 0x12, 0x11, 0x75, - 0xa0, 0x60, 0xb6, 0x9e, 0x45, 0x91, 0x0d, 0x5c, 0x45, 0xec, 0x63, 0xd9, 0x80, 0xbc, 0xd6, 0xa4, - 0x73, 0xa5, 0xfa, 0xf3, 0x22, 0x1f, 0x09, 0xbf, 0x4d, 0xc2, 0xb9, 0xc9, 0x61, 0x10, 0x5d, 0x82, - 0x85, 0xbe, 0x3c, 0x92, 0xac, 0x11, 0x3f, 0xcb, 0x6c, 0x3b, 0xa0, 0x2f, 0x8f, 0x5a, 0x23, 0x76, - 0x90, 0x2b, 0x90, 0xb4, 0x46, 0x66, 0x35, 0x71, 0x29, 0x79, 0x65, 0x41, 0x24, 0x3f, 0xd1, 0x21, - 0x2c, 0xf6, 0xb4, 0x8e, 0xdc, 0x93, 0x3c, 0x16, 0xcf, 0x8d, 0xfd, 0xf2, 0xd8, 0x62, 0xb3, 0x80, - 0x86, 0x95, 0x31, 0xa3, 0x2f, 0x53, 0x1d, 0x3b, 0x8e, 0xe5, 0x7f, 0x43, 0x56, 0xef, 0xd9, 0xa3, - 0xb4, 0xcf, 0x53, 0xd8, 0x3e, 0x3b, 0x33, 0xb7, 0xcf, 0x7e, 0x0e, 0x96, 0x07, 0x78, 0x64, 0x79, - 0xbe, 0x91, 0x19, 0x4e, 0x96, 0xee, 0x05, 0x22, 0xcf, 0xdc, 0xf7, 0x13, 0x1b, 0x42, 0x57, 0x69, - 0x66, 0xa1, 0x6b, 0x26, 0x36, 0x24, 0x59, 0x51, 0x0c, 0x6c, 0x9a, 0x34, 0xb3, 0x5d, 0xa0, 0xe9, - 0x02, 0xa5, 0xaf, 0x33, 0xb2, 0xf0, 0x4b, 0xef, 0x5e, 0xf9, 0x33, 0x09, 0xbe, 0x13, 0x71, 0x77, - 0x27, 0x0e, 0x60, 0x99, 0xcb, 0x2b, 0xbe, 0xcd, 0x48, 0x44, 0xf5, 0x3c, 0xc8, 0x16, 0x8f, 0xb0, - 0x0f, 0xc9, 0x87, 0xdb, 0x07, 0xdb, 0xdb, 0xa6, 0x3c, 0xde, 0xf6, 0xbf, 0x6c, 0x6f, 0x5e, 0x75, - 0xa2, 0x88, 0x9b, 0xa6, 0xa1, 0x6b, 0x90, 0xa2, 0x89, 0x1d, 0xf3, 0x36, 0xe7, 0xc6, 0xe3, 0x05, - 0xe1, 0x12, 0x29, 0x8f, 0xd0, 0x84, 0x5a, 0x78, 0x5a, 0x36, 0x97, 0xa6, 0x9f, 0x25, 0x1d, 0x07, - 0xe8, 0xcb, 0xc2, 0x26, 0x18, 0xc9, 0xeb, 0xb0, 0xa4, 0xe0, 0x8e, 0xaa, 0x7c, 0x55, 0x1b, 0x59, - 0xe4, 0xd2, 0xdf, 0x99, 0x48, 0x04, 0x13, 0xf9, 0x73, 0x01, 0x72, 0x22, 0x36, 0x75, 0x92, 0x7d, - 0xa1, 0x0d, 0xc8, 0xe3, 0x51, 0x07, 0xeb, 0x96, 0x9d, 0xb0, 0x4e, 0x4e, 0xfc, 0x19, 0x77, 0xc3, - 0xe6, 0x24, 0x65, 0xac, 0x23, 0x86, 0x6e, 0x72, 0xc4, 0x22, 0x1c, 0x7c, 0xe0, 0xe2, 0x5e, 0xc8, - 0xe2, 0x79, 0x1b, 0xb2, 0x48, 0x86, 0x56, 0xad, 0x4c, 0x2a, 0x80, 0x59, 0xdc, 0xe4, 0x98, 0x45, - 0x6a, 0xc6, 0xcb, 0x7c, 0xa0, 0x45, 0xdd, 0x07, 0x5a, 0xa4, 0x67, 0x4c, 0x33, 0x04, 0xb5, 0x78, - 0xde, 0x46, 0x2d, 0x32, 0x33, 0xbe, 0x38, 0x00, 0x5b, 0xdc, 0xf1, 0xc3, 0x16, 0xd9, 0x90, 0x28, - 0x64, 0x4b, 0x4f, 0xc5, 0x2d, 0x5e, 0xf1, 0xe0, 0x16, 0xb9, 0x50, 0xc0, 0x80, 0x29, 0x9a, 0x00, - 0x5c, 0xbc, 0xe6, 0x03, 0x2e, 0xf2, 0x33, 0xd6, 0x61, 0x0a, 0x72, 0xb1, 0xe9, 0x45, 0x2e, 0x20, - 0x14, 0x00, 0xe1, 0xfb, 0x1e, 0x06, 0x5d, 0xbc, 0xe8, 0x40, 0x17, 0x85, 0x50, 0x0c, 0x86, 0xcf, - 0x25, 0x88, 0x5d, 0xec, 0x8d, 0x61, 0x17, 0x0c, 0x6b, 0x78, 0x32, 0x54, 0xc5, 0x0c, 0xf0, 0x62, - 0x6f, 0x0c, 0xbc, 0x28, 0xce, 0x50, 0x38, 0x03, 0xbd, 0x78, 0x6b, 0x32, 0x7a, 0x11, 0x8e, 0x2f, - 0xf0, 0xcf, 0x8c, 0x06, 0x5f, 0x48, 0x21, 0xf0, 0x45, 0x39, 0xb4, 0xd4, 0x66, 0xea, 0x23, 0xe3, - 0x17, 0x87, 0x13, 0xf0, 0x0b, 0x86, 0x34, 0x5c, 0x09, 0x55, 0x1e, 0x01, 0xc0, 0x38, 0x9c, 0x00, - 0x60, 0x2c, 0xce, 0x54, 0x3b, 0x13, 0xc1, 0xd8, 0xf2, 0x23, 0x18, 0x68, 0xc6, 0x19, 0x0b, 0x85, - 0x30, 0xda, 0x61, 0x10, 0x06, 0x83, 0x19, 0x9e, 0x09, 0xd5, 0x38, 0x07, 0x86, 0xb1, 0x37, 0x86, - 0x61, 0x2c, 0xcf, 0xb0, 0xb4, 0xa8, 0x20, 0xc6, 0x55, 0x12, 0xfc, 0x03, 0xae, 0x9a, 0xe4, 0xe1, - 0xd8, 0x30, 0x34, 0x83, 0xc3, 0x11, 0x6c, 0x20, 0x5c, 0x21, 0x45, 0xad, 0xeb, 0x96, 0xa7, 0x00, - 0x1e, 0xb4, 0xde, 0xf1, 0xb8, 0x62, 0xe1, 0x77, 0x71, 0x57, 0x96, 0xd6, 0x82, 0xde, 0x82, 0x38, - 0xcf, 0x0b, 0x62, 0x0f, 0x0c, 0x92, 0xf0, 0xc3, 0x20, 0xab, 0x50, 0x20, 0x75, 0x4c, 0x00, 0xe1, - 0x90, 0x75, 0x07, 0xe1, 0xb8, 0x06, 0x8b, 0x34, 0x09, 0x60, 0x60, 0x09, 0x8f, 0xac, 0x29, 0x1a, - 0x59, 0xcb, 0xe4, 0x01, 0x5b, 0x05, 0x16, 0x62, 0x9f, 0x85, 0x25, 0x0f, 0xaf, 0x53, 0x1f, 0xb1, - 0x72, 0xbf, 0xe2, 0x70, 0xaf, 0xf3, 0x42, 0xe9, 0x0f, 0x71, 0x77, 0x85, 0x5c, 0x68, 0x64, 0x12, - 0x8a, 0x11, 0xff, 0x9a, 0x50, 0x8c, 0xc4, 0x57, 0x46, 0x31, 0xbc, 0xf5, 0x5e, 0xd2, 0x5f, 0xef, - 0xfd, 0x33, 0xee, 0xee, 0x89, 0x83, 0x49, 0x74, 0x34, 0x05, 0xf3, 0x0a, 0x8c, 0xfe, 0x26, 0x69, - 0x56, 0x4f, 0x3b, 0xe1, 0x75, 0x16, 0xf9, 0x49, 0xb8, 0x9c, 0xd8, 0x99, 0xe7, 0xa1, 0xd1, 0x29, - 0xde, 0x58, 0xee, 0xc2, 0x8b, 0xb7, 0x0a, 0x24, 0x1f, 0x60, 0x16, 0xe9, 0x16, 0x44, 0xf2, 0x93, - 0xf0, 0x51, 0x23, 0xe3, 0x39, 0x08, 0x1b, 0xa0, 0xdb, 0x90, 0xa7, 0x9d, 0x15, 0x49, 0xd3, 0x4d, - 0x1e, 0x90, 0x7c, 0xe9, 0x1a, 0x6b, 0xa0, 0xac, 0xed, 0x13, 0x9e, 0x3d, 0xdd, 0x14, 0x73, 0x3a, - 0xff, 0xe5, 0x49, 0x9a, 0xf2, 0xbe, 0xa4, 0xe9, 0x02, 0xe4, 0xc9, 0xd7, 0x9b, 0xba, 0xdc, 0xc1, - 0x34, 0xb2, 0xe4, 0x45, 0x97, 0x20, 0xdc, 0x07, 0x34, 0x1e, 0x27, 0x51, 0x13, 0x32, 0xf8, 0x14, - 0x0f, 0x2c, 0x96, 0x53, 0x06, 0xf2, 0x52, 0x56, 0xe2, 0x91, 0xc7, 0x1b, 0x55, 0xb2, 0xc8, 0xff, - 0xf8, 0x7c, 0xb5, 0xc2, 0xb8, 0x9f, 0xd1, 0xfa, 0xaa, 0x85, 0xfb, 0xba, 0x75, 0x26, 0x72, 0x79, - 0xe1, 0xaf, 0x09, 0x28, 0x07, 0xe2, 0xe7, 0xc4, 0xb5, 0xb5, 0x4d, 0x3e, 0xe1, 0xc1, 0x80, 0xa2, - 0xad, 0xf7, 0x45, 0x80, 0x13, 0xd9, 0x94, 0xde, 0x93, 0x07, 0x16, 0x56, 0xf8, 0xa2, 0xe7, 0x4f, - 0x64, 0xf3, 0x0d, 0x4a, 0x20, 0xbb, 0x4e, 0x1e, 0x0f, 0x4d, 0xac, 0x70, 0x34, 0x2a, 0x7b, 0x22, - 0x9b, 0x87, 0x26, 0x56, 0x3c, 0xb3, 0xcc, 0x3e, 0xdc, 0x2c, 0xfd, 0x6b, 0x9c, 0x0b, 0xac, 0xb1, - 0xa7, 0x44, 0xcf, 0x7b, 0x4b, 0x74, 0x54, 0x83, 0x9c, 0x6e, 0xa8, 0x9a, 0xa1, 0x5a, 0x67, 0x74, - 0x63, 0x92, 0xa2, 0x33, 0x46, 0x97, 0xa1, 0xd8, 0xc7, 0x7d, 0x5d, 0xd3, 0x7a, 0x12, 0x73, 0x36, - 0x05, 0x2a, 0xba, 0xc0, 0x89, 0x0d, 0xea, 0x73, 0x3e, 0x4c, 0xb8, 0xa7, 0xcf, 0x85, 0x62, 0xbe, - 0xde, 0xe5, 0x5d, 0x99, 0xb0, 0xbc, 0x1e, 0x0a, 0x99, 0x44, 0x60, 0x7d, 0x9d, 0xf1, 0xb7, 0xb5, - 0xc0, 0xc2, 0x4f, 0x28, 0x3e, 0xeb, 0xcf, 0x8d, 0xd0, 0x01, 0x2c, 0x3a, 0x87, 0x5f, 0x1a, 0x52, - 0xa7, 0x60, 0x9b, 0x73, 0x54, 0xef, 0x51, 0x39, 0xf5, 0x93, 0x4d, 0xf4, 0x26, 0x3c, 0x1a, 0xf0, - 0x6c, 0x8e, 0xea, 0x44, 0x54, 0x07, 0xf7, 0x88, 0xdf, 0xc1, 0xd9, 0xaa, 0xdd, 0xc5, 0x4a, 0x3e, - 0xe4, 0x99, 0xdb, 0x86, 0x92, 0x3f, 0xcd, 0x9b, 0xb8, 0xfd, 0x97, 0xa1, 0x68, 0x60, 0x4b, 0x56, - 0x07, 0x92, 0x0f, 0x54, 0x5d, 0x60, 0x44, 0x0e, 0xd5, 0xee, 0xc3, 0x23, 0x13, 0xd3, 0x3d, 0xf4, - 0x02, 0xe4, 0xdd, 0x4c, 0x91, 0xad, 0xea, 0x14, 0xd0, 0xcd, 0xe5, 0x15, 0x7e, 0x1f, 0x77, 0x55, - 0xfa, 0x61, 0xbc, 0x06, 0x64, 0x0c, 0x6c, 0x0e, 0x7b, 0x0c, 0x58, 0x2b, 0xdd, 0x78, 0x36, 0x5a, - 0xa2, 0x48, 0xa8, 0xc3, 0x9e, 0x25, 0x72, 0x61, 0xe1, 0x3e, 0x64, 0x18, 0x05, 0x15, 0x20, 0x7b, - 0xb8, 0x7b, 0x77, 0x77, 0xef, 0x8d, 0xdd, 0x4a, 0x0c, 0x01, 0x64, 0xd6, 0xeb, 0xf5, 0xc6, 0x7e, - 0xab, 0x12, 0x47, 0x79, 0x48, 0xaf, 0x6f, 0xec, 0x89, 0xad, 0x4a, 0x82, 0x90, 0xc5, 0xc6, 0x9d, - 0x46, 0xbd, 0x55, 0x49, 0xa2, 0x45, 0x28, 0xb2, 0xdf, 0xd2, 0xd6, 0x9e, 0x78, 0x6f, 0xbd, 0x55, - 0x49, 0x79, 0x48, 0x07, 0x8d, 0xdd, 0xcd, 0x86, 0x58, 0x49, 0x0b, 0xff, 0x07, 0xe7, 0x43, 0x53, - 0x4b, 0x17, 0xa3, 0x8b, 0x7b, 0x30, 0x3a, 0xe1, 0x17, 0x09, 0xa8, 0x85, 0xe7, 0x8b, 0xe8, 0x4e, - 0x60, 0xe2, 0x37, 0xe6, 0x48, 0x36, 0x03, 0xb3, 0x47, 0x4f, 0x40, 0xc9, 0xc0, 0xc7, 0xd8, 0xea, - 0x74, 0x59, 0xfe, 0xca, 0x02, 0x66, 0x51, 0x2c, 0x72, 0x2a, 0x15, 0x32, 0x19, 0xdb, 0x3b, 0xb8, - 0x63, 0x49, 0xcc, 0x17, 0x31, 0xa3, 0xcb, 0x13, 0x36, 0x42, 0x3d, 0x60, 0x44, 0xe1, 0xed, 0xb9, - 0xd6, 0x32, 0x0f, 0x69, 0xb1, 0xd1, 0x12, 0xdf, 0xac, 0x24, 0x11, 0x82, 0x12, 0xfd, 0x29, 0x1d, - 0xec, 0xae, 0xef, 0x1f, 0x34, 0xf7, 0xc8, 0x5a, 0x2e, 0x41, 0xd9, 0x5e, 0x4b, 0x9b, 0x98, 0x16, - 0xfe, 0x94, 0x80, 0x47, 0x43, 0xb2, 0x5d, 0x74, 0x1b, 0xc0, 0x1a, 0x49, 0x06, 0xee, 0x68, 0x86, - 0x12, 0x6e, 0x64, 0xad, 0x91, 0x48, 0x39, 0xc4, 0xbc, 0xc5, 0x7f, 0x99, 0x53, 0xa0, 0x5d, 0xf4, - 0x32, 0x57, 0x4a, 0x66, 0x65, 0x1f, 0xb5, 0x8b, 0x13, 0x10, 0x4c, 0xdc, 0x21, 0x8a, 0xe9, 0xda, - 0x52, 0xc5, 0x94, 0x1f, 0xdd, 0x9b, 0xe4, 0x54, 0x22, 0x36, 0x56, 0xe6, 0x73, 0x27, 0xe9, 0x87, - 0x73, 0x27, 0xc2, 0xaf, 0x92, 0xde, 0x85, 0xf5, 0x27, 0xf7, 0x7b, 0x90, 0x31, 0x2d, 0xd9, 0x1a, - 0x9a, 0xdc, 0xe0, 0x5e, 0x88, 0x5a, 0x29, 0xac, 0xd9, 0x3f, 0x0e, 0xa8, 0xb8, 0xc8, 0xd5, 0x7c, - 0xb7, 0xde, 0xa6, 0x70, 0x0b, 0x4a, 0xfe, 0xc5, 0x09, 0x3f, 0x32, 0xae, 0xcf, 0x49, 0x08, 0x6f, - 0xb9, 0xf9, 0x97, 0x07, 0x5f, 0xdc, 0x82, 0x52, 0xa0, 0x5c, 0x8a, 0x8f, 0xd7, 0xf3, 0x2e, 0x3e, - 0xe8, 0x94, 0x42, 0x62, 0xf1, 0xd4, 0x3b, 0x14, 0x7e, 0x1d, 0x87, 0xc7, 0xa6, 0x14, 0x54, 0xe8, - 0xf5, 0x80, 0x21, 0xbc, 0x38, 0x4f, 0x39, 0xb6, 0xc6, 0x68, 0x7e, 0x53, 0x10, 0x6e, 0xc2, 0x82, - 0x97, 0x1e, 0x6d, 0x15, 0x7e, 0x9a, 0x74, 0x83, 0x82, 0x1f, 0xda, 0xfc, 0xda, 0x32, 0xd1, 0x80, - 0x21, 0x26, 0xe6, 0x34, 0xc4, 0x89, 0xd9, 0x44, 0xf2, 0x9b, 0xcb, 0x26, 0x52, 0x0f, 0x99, 0x4d, - 0x78, 0x4f, 0x64, 0xda, 0x7f, 0x22, 0xc7, 0x02, 0x7f, 0x66, 0x42, 0xe0, 0x7f, 0x13, 0xc0, 0xd3, - 0x9c, 0x5c, 0x86, 0xb4, 0xa1, 0x0d, 0x07, 0x0a, 0x35, 0x93, 0xb4, 0xc8, 0x06, 0xe8, 0x16, 0xa4, - 0x89, 0xb9, 0xd9, 0x8b, 0x39, 0xee, 0x9a, 0x89, 0xb9, 0x78, 0x40, 0x65, 0xc6, 0x2d, 0xa8, 0x80, - 0xc6, 0x1b, 0x44, 0x21, 0xaf, 0x78, 0xc5, 0xff, 0x8a, 0xc7, 0x43, 0x5b, 0x4d, 0x93, 0x5f, 0xf5, - 0x3e, 0xa4, 0xa9, 0x79, 0x90, 0x04, 0x88, 0x36, 0x39, 0x79, 0x45, 0x4d, 0x7e, 0xa3, 0x1f, 0x01, - 0xc8, 0x96, 0x65, 0xa8, 0xed, 0xa1, 0xfb, 0x82, 0xd5, 0xc9, 0xe6, 0xb5, 0x6e, 0xf3, 0x6d, 0x5c, - 0xe0, 0x76, 0xb6, 0xec, 0x8a, 0x7a, 0x6c, 0xcd, 0xa3, 0x50, 0xd8, 0x85, 0x92, 0x5f, 0xd6, 0xae, - 0x01, 0xd9, 0x37, 0xf8, 0x6b, 0x40, 0x56, 0xd2, 0xf3, 0x1a, 0xd0, 0xa9, 0x20, 0x93, 0xac, 0x9f, - 0x4d, 0x07, 0xc2, 0xbf, 0xe2, 0xb0, 0xe0, 0xb5, 0xce, 0xff, 0xb5, 0x32, 0x4a, 0xf8, 0x30, 0x0e, - 0x39, 0x67, 0xf2, 0x21, 0xcd, 0x64, 0x77, 0xed, 0x12, 0xde, 0xd6, 0x29, 0xeb, 0x4e, 0x27, 0x9d, - 0x9e, 0xf7, 0x4b, 0x4e, 0xc6, 0x15, 0x86, 0x7a, 0x7b, 0x57, 0xda, 0x6e, 0xfb, 0xf3, 0x04, 0xf3, - 0xe7, 0xfc, 0x3b, 0x48, 0xaa, 0x81, 0xbe, 0x07, 0x19, 0xb9, 0xe3, 0x60, 0xfd, 0xa5, 0x09, 0xe0, - 0xaf, 0xcd, 0xba, 0xd6, 0x1a, 0xad, 0x53, 0x4e, 0x91, 0x4b, 0xf0, 0xaf, 0x4a, 0x38, 0x3d, 0xf3, - 0x57, 0x89, 0x5e, 0xc6, 0xe3, 0x77, 0x9b, 0x25, 0x80, 0xc3, 0xdd, 0x7b, 0x7b, 0x9b, 0xdb, 0x5b, - 0xdb, 0x8d, 0x4d, 0x9e, 0x73, 0x6d, 0x6e, 0x36, 0x36, 0x2b, 0x09, 0xc2, 0x27, 0x36, 0xee, 0xed, - 0x1d, 0x35, 0x36, 0x2b, 0x49, 0xe1, 0x25, 0xc8, 0x3b, 0xae, 0x07, 0x55, 0x21, 0x6b, 0xf7, 0x2d, - 0xe2, 0xdc, 0x01, 0xb0, 0x21, 0xbd, 0x2f, 0xa1, 0xbd, 0xc7, 0x3b, 0xc6, 0x49, 0x91, 0x0d, 0x04, - 0x05, 0xca, 0x01, 0xbf, 0x85, 0x5e, 0x82, 0xac, 0x3e, 0x6c, 0x4b, 0xb6, 0xd1, 0x06, 0xba, 0x3c, - 0x36, 0x14, 0x31, 0x6c, 0xf7, 0xd4, 0xce, 0x5d, 0x7c, 0x66, 0x2f, 0x93, 0x3e, 0x6c, 0xdf, 0x65, - 0xb6, 0xcd, 0xde, 0x92, 0xf0, 0xbe, 0xe5, 0xc7, 0x71, 0xc8, 0xd9, 0x67, 0x15, 0x7d, 0x1f, 0xf2, - 0x8e, 0x4f, 0x74, 0xee, 0xd1, 0x84, 0x3a, 0x53, 0xae, 0xdf, 0x15, 0x41, 0xd7, 0x60, 0xd1, 0x54, - 0x4f, 0x06, 0x76, 0x93, 0x8b, 0x61, 0x7f, 0x09, 0x7a, 0x68, 0xca, 0xec, 0xc1, 0x8e, 0x0d, 0x58, - 0xdd, 0x49, 0xe5, 0x92, 0x95, 0xd4, 0x9d, 0x54, 0x2e, 0x55, 0x49, 0x93, 0xb0, 0x58, 0x09, 0x3a, - 0x8e, 0x6f, 0xf3, 0x63, 0x48, 0xfa, 0x1d, 0x88, 0xef, 0xcc, 0x36, 0x03, 0xe1, 0xfb, 0x83, 0x04, - 0x14, 0x3c, 0x6d, 0x34, 0xf4, 0xff, 0x1e, 0x2f, 0x56, 0x9a, 0x10, 0x77, 0x3c, 0xbc, 0xee, 0x75, - 0x0d, 0xff, 0xc4, 0x12, 0xf3, 0x4f, 0x2c, 0xec, 0xda, 0x8d, 0xdd, 0x8d, 0x4b, 0xcd, 0xdd, 0x8d, - 0x7b, 0x06, 0x90, 0xa5, 0x59, 0x72, 0x4f, 0x3a, 0xd5, 0x2c, 0x75, 0x70, 0x22, 0x31, 0x3b, 0x61, - 0x3e, 0xa7, 0x42, 0x9f, 0x1c, 0xd1, 0x07, 0xfb, 0x8e, 0xc9, 0x38, 0x45, 0xe2, 0xbc, 0xb7, 0x2f, - 0xce, 0x41, 0x86, 0xd7, 0x41, 0xec, 0xfa, 0x05, 0x1f, 0x4d, 0x6c, 0x3b, 0xd6, 0x20, 0xd7, 0xc7, - 0x96, 0x4c, 0x1d, 0x28, 0x8b, 0x99, 0xce, 0xf8, 0xda, 0x8b, 0x50, 0xf0, 0x5c, 0x84, 0x21, 0x3e, - 0x75, 0xb7, 0xf1, 0x46, 0x25, 0x56, 0xcb, 0x7e, 0xf4, 0xc9, 0xa5, 0xe4, 0x2e, 0x7e, 0x8f, 0x1c, - 0x37, 0xb1, 0x51, 0x6f, 0x36, 0xea, 0x77, 0x2b, 0xf1, 0x5a, 0xe1, 0xa3, 0x4f, 0x2e, 0x65, 0x45, - 0x4c, 0xbb, 0x44, 0xd7, 0xee, 0x42, 0x39, 0xb0, 0x31, 0xfe, 0xd3, 0x8d, 0xa0, 0xb4, 0x79, 0xb8, - 0xbf, 0xb3, 0x5d, 0x5f, 0x6f, 0x35, 0xa4, 0xa3, 0xbd, 0x56, 0xa3, 0x12, 0x47, 0x8f, 0xc2, 0xd2, - 0xce, 0xf6, 0x6b, 0xcd, 0x96, 0x54, 0xdf, 0xd9, 0x6e, 0xec, 0xb6, 0xa4, 0xf5, 0x56, 0x6b, 0xbd, - 0x7e, 0xb7, 0x92, 0xb8, 0xf1, 0x9b, 0x02, 0x94, 0xd7, 0x37, 0xea, 0xdb, 0xa4, 0x12, 0x54, 0x3b, - 0x32, 0xf5, 0x15, 0x75, 0x48, 0x51, 0xc8, 0x79, 0xea, 0xd5, 0xe6, 0xda, 0xf4, 0x36, 0x22, 0xda, - 0x82, 0x34, 0x45, 0xa3, 0xd1, 0xf4, 0xbb, 0xce, 0xb5, 0x19, 0x7d, 0x45, 0xf2, 0x31, 0xf4, 0x38, - 0x4d, 0xbd, 0xfc, 0x5c, 0x9b, 0xde, 0x66, 0x44, 0x3b, 0x90, 0xb5, 0xc1, 0xc2, 0x59, 0xd7, 0x88, - 0x6b, 0x33, 0xfb, 0x75, 0x64, 0x6a, 0x0c, 0xd4, 0x9d, 0x7e, 0x2f, 0xba, 0x36, 0xa3, 0x01, 0x89, - 0xb6, 0x21, 0xc3, 0xf1, 0x94, 0x19, 0x57, 0x82, 0x6b, 0xb3, 0xfa, 0x6e, 0x48, 0x84, 0xbc, 0x0b, - 0x97, 0xcf, 0xbe, 0xed, 0x5d, 0x8b, 0xd0, 0x5b, 0x45, 0xf7, 0xa1, 0xe8, 0xc7, 0x68, 0xa2, 0x5d, - 0x3b, 0xae, 0x45, 0xec, 0xf0, 0x11, 0xfd, 0x7e, 0xc0, 0x26, 0xda, 0x35, 0xe4, 0x5a, 0xc4, 0x86, - 0x1f, 0x7a, 0x07, 0x16, 0xc7, 0x01, 0x95, 0xe8, 0xb7, 0x92, 0x6b, 0x73, 0xb4, 0x00, 0x51, 0x1f, - 0xd0, 0x04, 0x20, 0x66, 0x8e, 0x4b, 0xca, 0xb5, 0x79, 0x3a, 0x82, 0x48, 0x81, 0x72, 0x10, 0xdc, - 0x88, 0x7a, 0x69, 0xb9, 0x16, 0xb9, 0x3b, 0xc8, 0xde, 0xe2, 0xaf, 0xf4, 0xa3, 0x5e, 0x62, 0xae, - 0x45, 0x6e, 0x16, 0xa2, 0x43, 0x00, 0x4f, 0xa5, 0x1a, 0xe1, 0x52, 0x73, 0x2d, 0x4a, 0xdb, 0x10, - 0xe9, 0xb0, 0x34, 0xa9, 0x42, 0x9d, 0xe7, 0x8e, 0x73, 0x6d, 0xae, 0x6e, 0x22, 0xb1, 0x67, 0x7f, - 0xad, 0x19, 0xed, 0xce, 0x73, 0x2d, 0x62, 0x5b, 0x71, 0xa3, 0xf1, 0xe9, 0x17, 0x2b, 0xf1, 0xcf, - 0xbe, 0x58, 0x89, 0xff, 0xfd, 0x8b, 0x95, 0xf8, 0xc7, 0x5f, 0xae, 0xc4, 0x3e, 0xfb, 0x72, 0x25, - 0xf6, 0x97, 0x2f, 0x57, 0x62, 0x3f, 0x78, 0xfa, 0x44, 0xb5, 0xba, 0xc3, 0xf6, 0x5a, 0x47, 0xeb, - 0x5f, 0xf7, 0xfe, 0xfd, 0x65, 0xd2, 0x5f, 0x72, 0xda, 0x19, 0x1a, 0x50, 0x6f, 0xfe, 0x27, 0x00, - 0x00, 0xff, 0xff, 0xc7, 0x45, 0xe7, 0x5f, 0xb2, 0x33, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcb, 0x73, 0x23, 0xe5, + 0xb5, 0x97, 0x5a, 0xef, 0x23, 0xeb, 0xe1, 0xcf, 0x66, 0xd0, 0x88, 0x19, 0x7b, 0xe8, 0xa9, 0x81, + 0x99, 0x01, 0x3c, 0x5c, 0xcf, 0x1d, 0x18, 0xee, 0xc0, 0xa5, 0x6c, 0x59, 0x83, 0xcc, 0x78, 0x6c, + 0xd3, 0x96, 0x4d, 0x71, 0x6f, 0x32, 0x4d, 0x4b, 0xfd, 0xd9, 0x6a, 0x46, 0x52, 0x37, 0xdd, 0x2d, + 0x23, 0xb3, 0x0c, 0xc5, 0x86, 0x4a, 0x55, 0xd8, 0xa4, 0x92, 0x54, 0x85, 0x5d, 0x52, 0x95, 0xfc, + 0x07, 0x59, 0x65, 0x95, 0x05, 0x8b, 0x2c, 0x58, 0x25, 0xa9, 0x2c, 0x48, 0x0a, 0x76, 0xf9, 0x07, + 0xb2, 0x4b, 0x52, 0xdf, 0xa3, 0x5f, 0x52, 0xb7, 0x1e, 0x0c, 0x50, 0x95, 0x0a, 0x3b, 0xf5, 0xe9, + 0x73, 0x4e, 0x7f, 0x8f, 0xf3, 0x9d, 0xc7, 0xef, 0x7c, 0x82, 0x27, 0x6c, 0xdc, 0x57, 0xb1, 0xd9, + 0xd3, 0xfa, 0xf6, 0x0d, 0xa5, 0xd5, 0xd6, 0x6e, 0xd8, 0x67, 0x06, 0xb6, 0xd6, 0x0c, 0x53, 0xb7, + 0x75, 0x54, 0xf2, 0x5e, 0xae, 0x91, 0x97, 0xd5, 0x8b, 0x3e, 0xee, 0xb6, 0x79, 0x66, 0xd8, 0xfa, + 0x0d, 0xc3, 0xd4, 0xf5, 0x63, 0xc6, 0x5f, 0xbd, 0xe0, 0x7b, 0x4d, 0xf5, 0xf8, 0xb5, 0x05, 0xde, + 0x72, 0xe1, 0x87, 0xf8, 0xcc, 0x79, 0x7b, 0x71, 0x4c, 0xd6, 0x50, 0x4c, 0xa5, 0xe7, 0xbc, 0x5e, + 0x3d, 0xd1, 0xf5, 0x93, 0x2e, 0xbe, 0x41, 0x9f, 0x5a, 0x83, 0xe3, 0x1b, 0xb6, 0xd6, 0xc3, 0x96, + 0xad, 0xf4, 0x0c, 0xce, 0xb0, 0x7c, 0xa2, 0x9f, 0xe8, 0xf4, 0xe7, 0x0d, 0xf2, 0x8b, 0x51, 0xc5, + 0x7f, 0x02, 0x64, 0x24, 0xfc, 0xee, 0x00, 0x5b, 0x36, 0x5a, 0x87, 0x24, 0x6e, 0x77, 0xf4, 0x4a, + 0xfc, 0x52, 0xfc, 0x6a, 0x7e, 0xfd, 0xc2, 0xda, 0xc8, 0xe4, 0xd6, 0x38, 0x5f, 0xbd, 0xdd, 0xd1, + 0x1b, 0x31, 0x89, 0xf2, 0xa2, 0x5b, 0x90, 0x3a, 0xee, 0x0e, 0xac, 0x4e, 0x45, 0xa0, 0x42, 0x17, + 0xa3, 0x84, 0xee, 0x12, 0xa6, 0x46, 0x4c, 0x62, 0xdc, 0xe4, 0x53, 0x5a, 0xff, 0x58, 0xaf, 0x24, + 0x26, 0x7f, 0x6a, 0xbb, 0x7f, 0x4c, 0x3f, 0x45, 0x78, 0xd1, 0x26, 0x80, 0xd6, 0xd7, 0x6c, 0xb9, + 0xdd, 0x51, 0xb4, 0x7e, 0x25, 0x49, 0x25, 0x9f, 0x8c, 0x96, 0xd4, 0xec, 0x1a, 0x61, 0x6c, 0xc4, + 0xa4, 0x9c, 0xe6, 0x3c, 0x90, 0xe1, 0xbe, 0x3b, 0xc0, 0xe6, 0x59, 0x25, 0x35, 0x79, 0xb8, 0x6f, + 0x10, 0x26, 0x32, 0x5c, 0xca, 0x8d, 0xb6, 0x21, 0xdf, 0xc2, 0x27, 0x5a, 0x5f, 0x6e, 0x75, 0xf5, + 0xf6, 0xc3, 0x4a, 0x9a, 0x0a, 0x8b, 0x51, 0xc2, 0x9b, 0x84, 0x75, 0x93, 0x70, 0x6e, 0x0a, 0x95, + 0x78, 0x23, 0x26, 0x41, 0xcb, 0xa5, 0xa0, 0x97, 0x21, 0xdb, 0xee, 0xe0, 0xf6, 0x43, 0xd9, 0x1e, + 0x56, 0x32, 0x54, 0xcf, 0x6a, 0x94, 0x9e, 0x1a, 0xe1, 0x6b, 0x0e, 0x1b, 0x31, 0x29, 0xd3, 0x66, + 0x3f, 0xd1, 0x5d, 0x00, 0x15, 0x77, 0xb5, 0x53, 0x6c, 0x12, 0xf9, 0xec, 0xe4, 0x35, 0xd8, 0x62, + 0x9c, 0xcd, 0x21, 0x1f, 0x46, 0x4e, 0x75, 0x08, 0xa8, 0x06, 0x39, 0xdc, 0x57, 0xf9, 0x74, 0x72, + 0x54, 0xcd, 0xa5, 0xc8, 0xfd, 0xee, 0xab, 0xfe, 0xc9, 0x64, 0x31, 0x7f, 0x46, 0xb7, 0x21, 0xdd, + 0xd6, 0x7b, 0x3d, 0xcd, 0xae, 0x00, 0xd5, 0xb0, 0x12, 0x39, 0x11, 0xca, 0xd5, 0x88, 0x49, 0x9c, + 0x1f, 0xed, 0x42, 0xb1, 0xab, 0x59, 0xb6, 0x6c, 0xf5, 0x15, 0xc3, 0xea, 0xe8, 0xb6, 0x55, 0xc9, + 0x53, 0x0d, 0x57, 0xa2, 0x34, 0xec, 0x68, 0x96, 0x7d, 0xe0, 0x30, 0x37, 0x62, 0x52, 0xa1, 0xeb, + 0x27, 0x10, 0x7d, 0xfa, 0xf1, 0x31, 0x36, 0x5d, 0x85, 0x95, 0x85, 0xc9, 0xfa, 0xf6, 0x08, 0xb7, + 0x23, 0x4f, 0xf4, 0xe9, 0x7e, 0x02, 0xfa, 0x7f, 0x58, 0xea, 0xea, 0x8a, 0xea, 0xaa, 0x93, 0xdb, + 0x9d, 0x41, 0xff, 0x61, 0xa5, 0x40, 0x95, 0x5e, 0x8b, 0x1c, 0xa4, 0xae, 0xa8, 0x8e, 0x8a, 0x1a, + 0x11, 0x68, 0xc4, 0xa4, 0xc5, 0xee, 0x28, 0x11, 0x3d, 0x80, 0x65, 0xc5, 0x30, 0xba, 0x67, 0xa3, + 0xda, 0x8b, 0x54, 0xfb, 0xf5, 0x28, 0xed, 0x1b, 0x44, 0x66, 0x54, 0x3d, 0x52, 0xc6, 0xa8, 0xa8, + 0x09, 0x65, 0xc3, 0xc4, 0x86, 0x62, 0x62, 0xd9, 0x30, 0x75, 0x43, 0xb7, 0x94, 0x6e, 0xa5, 0x44, + 0x75, 0x3f, 0x1d, 0xa5, 0x7b, 0x9f, 0xf1, 0xef, 0x73, 0xf6, 0x46, 0x4c, 0x2a, 0x19, 0x41, 0x12, + 0xd3, 0xaa, 0xb7, 0xb1, 0x65, 0x79, 0x5a, 0xcb, 0xd3, 0xb4, 0x52, 0xfe, 0xa0, 0xd6, 0x00, 0x09, + 0xd5, 0x21, 0x8f, 0x87, 0x44, 0x5c, 0x3e, 0xd5, 0x6d, 0x5c, 0x59, 0x9c, 0x7c, 0xb0, 0xea, 0x94, + 0xf5, 0x48, 0xb7, 0x31, 0x39, 0x54, 0xd8, 0x7d, 0x42, 0x0a, 0x3c, 0x76, 0x8a, 0x4d, 0xed, 0xf8, + 0x8c, 0xaa, 0x91, 0xe9, 0x1b, 0x4b, 0xd3, 0xfb, 0x15, 0x44, 0x15, 0x3e, 0x13, 0xa5, 0xf0, 0x88, + 0x0a, 0x11, 0x15, 0x75, 0x47, 0xa4, 0x11, 0x93, 0x96, 0x4e, 0xc7, 0xc9, 0xc4, 0xc4, 0x8e, 0xb5, + 0xbe, 0xd2, 0xd5, 0xde, 0xc7, 0xfc, 0xd8, 0x2c, 0x4d, 0x36, 0xb1, 0xbb, 0x9c, 0x9b, 0x9e, 0x15, + 0x62, 0x62, 0xc7, 0x7e, 0xc2, 0x66, 0x06, 0x52, 0xa7, 0x4a, 0x77, 0x80, 0xc5, 0xa7, 0x21, 0xef, + 0x73, 0xac, 0xa8, 0x02, 0x99, 0x1e, 0xb6, 0x2c, 0xe5, 0x04, 0x53, 0x3f, 0x9c, 0x93, 0x9c, 0x47, + 0xb1, 0x08, 0x0b, 0x7e, 0x67, 0x2a, 0x7e, 0x1c, 0x77, 0x25, 0x89, 0x9f, 0x24, 0x92, 0xa7, 0xd8, + 0xa4, 0xd3, 0xe6, 0x92, 0xfc, 0x11, 0x5d, 0x86, 0x02, 0x1d, 0xb2, 0xec, 0xbc, 0x27, 0xce, 0x3a, + 0x29, 0x2d, 0x50, 0xe2, 0x11, 0x67, 0x5a, 0x85, 0xbc, 0xb1, 0x6e, 0xb8, 0x2c, 0x09, 0xca, 0x02, + 0xc6, 0xba, 0xe1, 0x30, 0x3c, 0x09, 0x0b, 0x64, 0x7e, 0x2e, 0x47, 0x92, 0x7e, 0x24, 0x4f, 0x68, + 0x9c, 0x45, 0xfc, 0xbd, 0x00, 0xe5, 0x51, 0x07, 0x8c, 0x6e, 0x43, 0x92, 0xc4, 0x22, 0x1e, 0x56, + 0xaa, 0x6b, 0x2c, 0x50, 0xad, 0x39, 0x81, 0x6a, 0xad, 0xe9, 0x04, 0xaa, 0xcd, 0xec, 0xa7, 0x9f, + 0xaf, 0xc6, 0x3e, 0xfe, 0xcb, 0x6a, 0x5c, 0xa2, 0x12, 0xe8, 0x3c, 0xf1, 0x95, 0x8a, 0xd6, 0x97, + 0x35, 0x95, 0x0e, 0x39, 0x47, 0x1c, 0xa1, 0xa2, 0xf5, 0xb7, 0x55, 0xb4, 0x03, 0xe5, 0xb6, 0xde, + 0xb7, 0x70, 0xdf, 0x1a, 0x58, 0x32, 0x0b, 0x84, 0x3c, 0x98, 0x04, 0xdc, 0x21, 0x0b, 0xaf, 0x35, + 0x87, 0x73, 0x9f, 0x32, 0x4a, 0xa5, 0x76, 0x90, 0x40, 0xdc, 0xea, 0xa9, 0xd2, 0xd5, 0x54, 0xc5, + 0xd6, 0x4d, 0xab, 0x92, 0xbc, 0x94, 0x08, 0xf5, 0x87, 0x47, 0x0e, 0xcb, 0xa1, 0xa1, 0x2a, 0x36, + 0xde, 0x4c, 0x92, 0xe1, 0x4a, 0x3e, 0x49, 0xf4, 0x14, 0x94, 0x14, 0xc3, 0x90, 0x2d, 0x5b, 0xb1, + 0xb1, 0xdc, 0x3a, 0xb3, 0xb1, 0x45, 0x03, 0xcd, 0x82, 0x54, 0x50, 0x0c, 0xe3, 0x80, 0x50, 0x37, + 0x09, 0x11, 0x5d, 0x81, 0x22, 0x89, 0x49, 0x9a, 0xd2, 0x95, 0x3b, 0x58, 0x3b, 0xe9, 0xd8, 0x34, + 0xa4, 0x24, 0xa4, 0x02, 0xa7, 0x36, 0x28, 0x51, 0x54, 0xdd, 0x1d, 0xa7, 0xf1, 0x08, 0x21, 0x48, + 0xaa, 0x8a, 0xad, 0xd0, 0x95, 0x5c, 0x90, 0xe8, 0x6f, 0x42, 0x33, 0x14, 0xbb, 0xc3, 0xd7, 0x87, + 0xfe, 0x46, 0xe7, 0x20, 0xcd, 0xd5, 0x26, 0xa8, 0x5a, 0xfe, 0x84, 0x96, 0x21, 0x65, 0x98, 0xfa, + 0x29, 0xa6, 0x5b, 0x97, 0x95, 0xd8, 0x83, 0xf8, 0x81, 0x00, 0x8b, 0x63, 0x91, 0x8b, 0xe8, 0xed, + 0x28, 0x56, 0xc7, 0xf9, 0x16, 0xf9, 0x8d, 0x5e, 0x20, 0x7a, 0x15, 0x15, 0x9b, 0x3c, 0xda, 0x57, + 0xc6, 0x97, 0xba, 0x41, 0xdf, 0xf3, 0xa5, 0xe1, 0xdc, 0xe8, 0x1e, 0x94, 0xbb, 0x8a, 0x65, 0xcb, + 0xcc, 0xfb, 0xcb, 0xbe, 0xc8, 0xff, 0xc4, 0xd8, 0x22, 0xb3, 0x58, 0x41, 0x0c, 0x9a, 0x2b, 0x29, + 0x12, 0x51, 0x8f, 0x8a, 0x0e, 0x61, 0xb9, 0x75, 0xf6, 0xbe, 0xd2, 0xb7, 0xb5, 0x3e, 0x96, 0xc7, + 0x76, 0x6d, 0x3c, 0x95, 0xb8, 0xaf, 0x59, 0x2d, 0xdc, 0x51, 0x4e, 0x35, 0xdd, 0x19, 0xd6, 0x92, + 0x2b, 0xef, 0xee, 0xa8, 0x25, 0x4a, 0x50, 0x0c, 0x86, 0x5d, 0x54, 0x04, 0xc1, 0x1e, 0xf2, 0xf9, + 0x0b, 0xf6, 0x10, 0x3d, 0x0f, 0x49, 0x32, 0x47, 0x3a, 0xf7, 0x62, 0xc8, 0x87, 0xb8, 0x5c, 0xf3, + 0xcc, 0xc0, 0x12, 0xe5, 0x14, 0x45, 0xf7, 0x34, 0xb8, 0xa1, 0x78, 0x54, 0xab, 0x78, 0x0d, 0x4a, + 0x23, 0x71, 0xd6, 0xb7, 0x7d, 0x71, 0xff, 0xf6, 0x89, 0x25, 0x28, 0x04, 0x02, 0xaa, 0x78, 0x0e, + 0x96, 0xc3, 0xe2, 0xa3, 0xd8, 0x71, 0xe9, 0x81, 0x38, 0x87, 0x6e, 0x41, 0xd6, 0x0d, 0x90, 0xec, + 0x34, 0x9e, 0x1f, 0x9b, 0x85, 0xc3, 0x2c, 0xb9, 0xac, 0xe4, 0x18, 0x12, 0xab, 0xa6, 0xe6, 0x20, + 0xd0, 0x81, 0x67, 0x14, 0xc3, 0x68, 0x28, 0x56, 0x47, 0x7c, 0x1b, 0x2a, 0x51, 0xc1, 0x6f, 0x64, + 0x1a, 0x49, 0xd7, 0x0a, 0xcf, 0x41, 0xfa, 0x58, 0x37, 0x7b, 0x8a, 0x4d, 0x95, 0x15, 0x24, 0xfe, + 0x44, 0xac, 0x93, 0x05, 0xc2, 0x04, 0x25, 0xb3, 0x07, 0x51, 0x86, 0xf3, 0x91, 0x01, 0x90, 0x88, + 0x68, 0x7d, 0x15, 0xb3, 0xf5, 0x2c, 0x48, 0xec, 0xc1, 0x53, 0xc4, 0x06, 0xcb, 0x1e, 0xc8, 0x67, + 0x2d, 0x3a, 0x57, 0xaa, 0x3f, 0x27, 0xf1, 0x27, 0xf1, 0xd7, 0x09, 0x38, 0x17, 0x1e, 0x06, 0xd1, + 0x25, 0x58, 0xe8, 0x29, 0x43, 0xd9, 0x1e, 0xf2, 0xb3, 0xcc, 0xb6, 0x03, 0x7a, 0xca, 0xb0, 0x39, + 0x64, 0x07, 0xb9, 0x0c, 0x09, 0x7b, 0x68, 0x55, 0x84, 0x4b, 0x89, 0xab, 0x0b, 0x12, 0xf9, 0x89, + 0x0e, 0x61, 0xb1, 0xab, 0xb7, 0x95, 0xae, 0xec, 0xb3, 0x78, 0x6e, 0xec, 0x97, 0xc7, 0x16, 0x9b, + 0x05, 0x34, 0xac, 0x8e, 0x19, 0x7d, 0x89, 0xea, 0xd8, 0x71, 0x2d, 0xff, 0x1b, 0xb2, 0x7a, 0xdf, + 0x1e, 0xa5, 0x02, 0x9e, 0xc2, 0xf1, 0xd9, 0xe9, 0xb9, 0x7d, 0xf6, 0xf3, 0xb0, 0xdc, 0xc7, 0x43, + 0xdb, 0x37, 0x46, 0x66, 0x38, 0x19, 0xba, 0x17, 0x88, 0xbc, 0xf3, 0xbe, 0x4f, 0x6c, 0x08, 0x5d, + 0xa3, 0x99, 0x85, 0xa1, 0x5b, 0xd8, 0x94, 0x15, 0x55, 0x35, 0xb1, 0x65, 0xd1, 0xcc, 0x76, 0x81, + 0xa6, 0x0b, 0x94, 0xbe, 0xc1, 0xc8, 0xe2, 0xcf, 0xfc, 0x7b, 0x15, 0xcc, 0x24, 0xf8, 0x4e, 0xc4, + 0xbd, 0x9d, 0x38, 0x80, 0x65, 0x2e, 0xaf, 0x06, 0x36, 0x43, 0x98, 0xd5, 0xf3, 0x20, 0x47, 0x7c, + 0x86, 0x7d, 0x48, 0x3c, 0xda, 0x3e, 0x38, 0xde, 0x36, 0xe9, 0xf3, 0xb6, 0xff, 0x66, 0x7b, 0xf3, + 0xaa, 0x1b, 0x45, 0xbc, 0x34, 0x2d, 0x34, 0x8a, 0x78, 0xf3, 0x12, 0x02, 0xee, 0xed, 0xe7, 0x71, + 0xa8, 0x46, 0xe7, 0x65, 0xa1, 0xaa, 0x9e, 0x81, 0x45, 0x77, 0x2e, 0xee, 0xf8, 0xd8, 0xa9, 0x2f, + 0xbb, 0x2f, 0xf8, 0x00, 0x23, 0xa3, 0xe2, 0x15, 0x28, 0x8e, 0x64, 0x8d, 0x6c, 0x17, 0x0a, 0xa7, + 0xfe, 0xef, 0x8b, 0x3f, 0x4e, 0xb8, 0x5e, 0x35, 0x90, 0xda, 0x85, 0x58, 0xde, 0x1b, 0xb0, 0xa4, + 0xe2, 0xb6, 0xa6, 0x7e, 0x55, 0xc3, 0x5b, 0xe4, 0xd2, 0xdf, 0xd9, 0xdd, 0x0c, 0x76, 0xf7, 0xc7, + 0x3c, 0x64, 0x25, 0x6c, 0x19, 0x24, 0xa5, 0x43, 0x9b, 0x90, 0xc3, 0xc3, 0x36, 0x36, 0x6c, 0x27, + 0x0b, 0x0e, 0xaf, 0x26, 0x18, 0x77, 0xdd, 0xe1, 0x24, 0xb5, 0xb1, 0x2b, 0x86, 0x6e, 0x72, 0x18, + 0x24, 0x1a, 0xd1, 0xe0, 0xe2, 0x7e, 0x1c, 0xe4, 0x05, 0x07, 0x07, 0x49, 0x44, 0x96, 0xc2, 0x4c, + 0x6a, 0x04, 0x08, 0xb9, 0xc9, 0x81, 0x90, 0xe4, 0x94, 0x8f, 0x05, 0x90, 0x90, 0x5a, 0x00, 0x09, + 0x49, 0x4d, 0x99, 0x66, 0x04, 0x14, 0xf2, 0x82, 0x03, 0x85, 0xa4, 0xa7, 0x8c, 0x78, 0x04, 0x0b, + 0x79, 0x3d, 0x88, 0x85, 0x64, 0x22, 0x42, 0x9b, 0x23, 0x3d, 0x11, 0x0c, 0x79, 0xc5, 0x07, 0x86, + 0x64, 0x23, 0x51, 0x08, 0xa6, 0x28, 0x04, 0x0d, 0x79, 0x2d, 0x80, 0x86, 0xe4, 0xa6, 0xac, 0xc3, + 0x04, 0x38, 0x64, 0xcb, 0x0f, 0x87, 0x40, 0x24, 0xaa, 0xc2, 0xf7, 0x3d, 0x0a, 0x0f, 0x79, 0xc9, + 0xc5, 0x43, 0xf2, 0x91, 0xc0, 0x0e, 0x9f, 0xcb, 0x28, 0x20, 0xb2, 0x37, 0x06, 0x88, 0x30, 0x00, + 0xe3, 0xa9, 0x48, 0x15, 0x53, 0x10, 0x91, 0xbd, 0x31, 0x44, 0xa4, 0x30, 0x45, 0xe1, 0x14, 0x48, + 0xe4, 0x7b, 0xe1, 0x90, 0x48, 0x34, 0x68, 0xc1, 0x87, 0x39, 0x1b, 0x26, 0x22, 0x47, 0x60, 0x22, + 0xa5, 0xc8, 0xfa, 0x9d, 0xa9, 0x9f, 0x19, 0x14, 0x39, 0x0c, 0x01, 0x45, 0x18, 0x7c, 0x71, 0x35, + 0x52, 0xf9, 0x0c, 0xa8, 0xc8, 0x61, 0x08, 0x2a, 0xb2, 0x38, 0x55, 0xed, 0x54, 0x58, 0xe4, 0x6e, + 0x10, 0x16, 0x41, 0x53, 0xce, 0x58, 0x24, 0x2e, 0xd2, 0x8a, 0xc2, 0x45, 0x18, 0x76, 0xf1, 0x6c, + 0xa4, 0xc6, 0x39, 0x80, 0x91, 0xbd, 0x31, 0x60, 0x64, 0x79, 0x8a, 0xa5, 0xcd, 0x8a, 0x8c, 0x5c, + 0x23, 0x19, 0xc5, 0x88, 0xab, 0x26, 0xc9, 0x3d, 0x36, 0x4d, 0xdd, 0xe4, 0x18, 0x07, 0x7b, 0x10, + 0xaf, 0x92, 0x4a, 0xd9, 0x73, 0xcb, 0x13, 0x50, 0x14, 0x5a, 0x44, 0xf9, 0x5c, 0xb1, 0xf8, 0x9b, + 0xb8, 0x27, 0x4b, 0x0b, 0x4c, 0x7f, 0x95, 0x9d, 0xe3, 0x55, 0xb6, 0x0f, 0x5b, 0x11, 0x82, 0xd8, + 0xca, 0x2a, 0xe4, 0x49, 0x71, 0x34, 0x02, 0x9b, 0x28, 0x86, 0x0b, 0x9b, 0x5c, 0x87, 0x45, 0x9a, + 0x04, 0x30, 0x04, 0x86, 0x47, 0xd6, 0x24, 0x8d, 0xac, 0x25, 0xf2, 0x82, 0xad, 0x02, 0x0b, 0xb1, + 0xcf, 0xc1, 0x92, 0x8f, 0xd7, 0x2d, 0xba, 0x18, 0x86, 0x50, 0x76, 0xb9, 0x37, 0x78, 0xf5, 0xf5, + 0xbb, 0xb8, 0xb7, 0x42, 0x1e, 0xde, 0x12, 0x06, 0x8d, 0xc4, 0xbf, 0x26, 0x68, 0x44, 0xf8, 0xca, + 0xd0, 0x88, 0xbf, 0x88, 0x4c, 0x04, 0x8b, 0xc8, 0xbf, 0xc7, 0xbd, 0x3d, 0x71, 0x81, 0x8e, 0xb6, + 0xae, 0x62, 0x5e, 0xd6, 0xd1, 0xdf, 0x24, 0xcd, 0xea, 0xea, 0x27, 0xbc, 0x78, 0x23, 0x3f, 0x09, + 0x97, 0x1b, 0x3b, 0x73, 0x3c, 0x34, 0xba, 0x15, 0x21, 0xcb, 0x5d, 0x78, 0x45, 0x58, 0x86, 0xc4, + 0x43, 0xcc, 0x22, 0xdd, 0x82, 0x44, 0x7e, 0x12, 0x3e, 0x6a, 0x64, 0x3c, 0x07, 0x61, 0x0f, 0xe8, + 0x36, 0xe4, 0x68, 0xbb, 0x46, 0xd6, 0x0d, 0x8b, 0x07, 0xa4, 0x40, 0xba, 0xc6, 0xba, 0x32, 0x6b, + 0xfb, 0x84, 0x67, 0xcf, 0xb0, 0xa4, 0xac, 0xc1, 0x7f, 0xf9, 0x92, 0xa6, 0x5c, 0x20, 0x69, 0xba, + 0x00, 0x39, 0x32, 0x7a, 0xcb, 0x50, 0xda, 0x98, 0x46, 0x96, 0x9c, 0xe4, 0x11, 0xc4, 0x07, 0x80, + 0xc6, 0xe3, 0x24, 0x6a, 0x40, 0x1a, 0x9f, 0xe2, 0xbe, 0xcd, 0x72, 0xca, 0xfc, 0xfa, 0xb9, 0xf1, + 0xba, 0x91, 0xbc, 0xde, 0xac, 0x90, 0x45, 0xfe, 0xdb, 0xe7, 0xab, 0x65, 0xc6, 0xfd, 0xac, 0xde, + 0xd3, 0x6c, 0xdc, 0x33, 0xec, 0x33, 0x89, 0xcb, 0x8b, 0x7f, 0x16, 0xa0, 0x34, 0x12, 0x3f, 0x43, + 0xd7, 0xd6, 0x31, 0x79, 0xc1, 0x07, 0x2c, 0xcd, 0xb6, 0xde, 0x17, 0x01, 0x4e, 0x14, 0x4b, 0x7e, + 0x4f, 0xe9, 0xdb, 0x58, 0xe5, 0x8b, 0x9e, 0x3b, 0x51, 0xac, 0x37, 0x29, 0x81, 0xec, 0x3a, 0x79, + 0x3d, 0xb0, 0xb0, 0xca, 0x21, 0xae, 0xcc, 0x89, 0x62, 0x1d, 0x5a, 0x58, 0xf5, 0xcd, 0x32, 0xf3, + 0x68, 0xb3, 0x0c, 0xae, 0x71, 0x76, 0x64, 0x8d, 0x7d, 0x75, 0x7f, 0xce, 0x5f, 0xf7, 0xa3, 0x2a, + 0x64, 0x0d, 0x53, 0xd3, 0x4d, 0xcd, 0x3e, 0xa3, 0x1b, 0x93, 0x90, 0xdc, 0x67, 0x74, 0x19, 0x0a, + 0x3d, 0xdc, 0x33, 0x74, 0xbd, 0x2b, 0x33, 0x67, 0x93, 0xa7, 0xa2, 0x0b, 0x9c, 0x58, 0xa7, 0x3e, + 0xe7, 0x43, 0xc1, 0x3b, 0x7d, 0x1e, 0xbe, 0xf3, 0xf5, 0x2e, 0xef, 0x4a, 0xc8, 0xf2, 0xfa, 0x28, + 0x64, 0x12, 0x23, 0xeb, 0xeb, 0x3e, 0x7f, 0x5b, 0x0b, 0x2c, 0xfe, 0x90, 0x82, 0xbe, 0xc1, 0xdc, + 0x08, 0x1d, 0xf8, 0x2b, 0xb3, 0x01, 0x75, 0x0a, 0x8e, 0x39, 0xcf, 0xea, 0x3d, 0xbc, 0x0a, 0x8e, + 0x91, 0x2d, 0xf4, 0x16, 0x3c, 0x3e, 0xe2, 0xd9, 0x5c, 0xd5, 0xc2, 0xac, 0x0e, 0xee, 0xb1, 0xa0, + 0x83, 0x73, 0x54, 0x7b, 0x8b, 0x95, 0x78, 0xc4, 0x33, 0xb7, 0x0d, 0xc5, 0x60, 0x9a, 0x17, 0xba, + 0xfd, 0x97, 0xa1, 0x60, 0x62, 0x5b, 0xd1, 0xfa, 0x72, 0xa0, 0x26, 0x5d, 0x60, 0x44, 0x8e, 0xff, + 0xee, 0xc3, 0x63, 0xa1, 0xe9, 0x1e, 0x7a, 0x11, 0x72, 0x5e, 0xa6, 0xc8, 0x56, 0x75, 0x02, 0x92, + 0xe7, 0xf1, 0x8a, 0xbf, 0x8d, 0x7b, 0x2a, 0x83, 0xd8, 0x60, 0x1d, 0xd2, 0x26, 0xb6, 0x06, 0x5d, + 0x86, 0xd6, 0x15, 0xd7, 0x9f, 0x9b, 0x2d, 0x51, 0x24, 0xd4, 0x41, 0xd7, 0x96, 0xb8, 0xb0, 0xf8, + 0x00, 0xd2, 0x8c, 0x82, 0xf2, 0x90, 0x39, 0xdc, 0xbd, 0xb7, 0xbb, 0xf7, 0xe6, 0x6e, 0x39, 0x86, + 0x00, 0xd2, 0x1b, 0xb5, 0x5a, 0x7d, 0xbf, 0x59, 0x8e, 0xa3, 0x1c, 0xa4, 0x36, 0x36, 0xf7, 0xa4, + 0x66, 0x59, 0x20, 0x64, 0xa9, 0xfe, 0x7a, 0xbd, 0xd6, 0x2c, 0x27, 0xd0, 0x22, 0x14, 0xd8, 0x6f, + 0xf9, 0xee, 0x9e, 0x74, 0x7f, 0xa3, 0x59, 0x4e, 0xfa, 0x48, 0x07, 0xf5, 0xdd, 0xad, 0xba, 0x54, + 0x4e, 0x89, 0xff, 0x05, 0xe7, 0x23, 0x53, 0x4b, 0x0f, 0xf8, 0x8b, 0xfb, 0x80, 0x3f, 0xf1, 0xa7, + 0x02, 0x54, 0xa3, 0xf3, 0x45, 0xf4, 0xfa, 0xc8, 0xc4, 0xd7, 0xe7, 0x48, 0x36, 0x47, 0x66, 0x8f, + 0xae, 0x40, 0xd1, 0xc4, 0xc7, 0xd8, 0x6e, 0x77, 0x58, 0xfe, 0xca, 0x02, 0x66, 0x41, 0x2a, 0x70, + 0x2a, 0x15, 0xb2, 0x18, 0xdb, 0x3b, 0xb8, 0x6d, 0xcb, 0xcc, 0x17, 0x31, 0xa3, 0xcb, 0x11, 0x36, + 0x42, 0x3d, 0x60, 0x44, 0xf1, 0xed, 0xb9, 0xd6, 0x32, 0x07, 0x29, 0xa9, 0xde, 0x94, 0xde, 0x2a, + 0x27, 0x10, 0x82, 0x22, 0xfd, 0x29, 0x1f, 0xec, 0x6e, 0xec, 0x1f, 0x34, 0xf6, 0xc8, 0x5a, 0x2e, + 0x41, 0xc9, 0x59, 0x4b, 0x87, 0x98, 0x12, 0xff, 0x20, 0xc0, 0xe3, 0x11, 0xd9, 0x2e, 0xba, 0x0d, + 0x60, 0x0f, 0x65, 0x13, 0xb7, 0x75, 0x53, 0x8d, 0x36, 0xb2, 0xe6, 0x50, 0xa2, 0x1c, 0x52, 0xce, + 0xe6, 0xbf, 0xac, 0x09, 0x78, 0x31, 0x7a, 0x99, 0x2b, 0x25, 0xb3, 0x72, 0x8e, 0xda, 0xc5, 0x10, + 0x58, 0x14, 0xb7, 0x89, 0x62, 0xba, 0xb6, 0x54, 0x31, 0xe5, 0x47, 0xf7, 0xc3, 0x9c, 0xca, 0x8c, + 0xdd, 0x9a, 0xf9, 0xdc, 0x49, 0xea, 0xd1, 0xdc, 0x89, 0xf8, 0x8b, 0x84, 0x7f, 0x61, 0x83, 0xc9, + 0xfd, 0x1e, 0xa4, 0x2d, 0x5b, 0xb1, 0x07, 0x16, 0x37, 0xb8, 0x17, 0x67, 0xad, 0x14, 0xd6, 0x9c, + 0x1f, 0x07, 0x54, 0x5c, 0xe2, 0x6a, 0xbe, 0x5b, 0x6f, 0x4b, 0xbc, 0x05, 0xc5, 0xe0, 0xe2, 0x44, + 0x1f, 0x19, 0xcf, 0xe7, 0x08, 0xe2, 0x1d, 0x2f, 0xff, 0xf2, 0x81, 0x96, 0xe3, 0x80, 0x60, 0x3c, + 0x0c, 0x10, 0xfc, 0x65, 0x1c, 0x9e, 0x98, 0x50, 0x2f, 0xa1, 0x37, 0x46, 0xf6, 0xf9, 0xa5, 0x79, + 0xaa, 0xad, 0x35, 0x46, 0x0b, 0xee, 0xb4, 0x78, 0x13, 0x16, 0xfc, 0xf4, 0xd9, 0x26, 0xf9, 0xa3, + 0x84, 0xe7, 0xf3, 0x83, 0xc8, 0xe5, 0xd7, 0x96, 0x68, 0x8e, 0xd8, 0x99, 0x30, 0xa7, 0x9d, 0x85, + 0x26, 0x0b, 0x89, 0x6f, 0x2e, 0x59, 0x48, 0x3e, 0x62, 0xb2, 0xe0, 0x3f, 0x70, 0xa9, 0xe0, 0x81, + 0x1b, 0x8b, 0xeb, 0xe9, 0x90, 0xb8, 0xfe, 0x16, 0x80, 0xaf, 0xa1, 0xb9, 0x0c, 0x29, 0x53, 0x1f, + 0xf4, 0x55, 0x6a, 0x26, 0x29, 0x89, 0x3d, 0xa0, 0x5b, 0x90, 0x22, 0xe6, 0xe6, 0x2c, 0xe6, 0xb8, + 0xe7, 0x25, 0xe6, 0xe2, 0xc3, 0x8c, 0x19, 0xb7, 0xa8, 0x01, 0x1a, 0x6f, 0x2a, 0x45, 0x7c, 0xe2, + 0x95, 0xe0, 0x27, 0x9e, 0x8c, 0x6c, 0x4f, 0x85, 0x7f, 0xea, 0x7d, 0x48, 0x51, 0xf3, 0x20, 0xf9, + 0x0d, 0x6d, 0x8c, 0xf2, 0x82, 0x99, 0xfc, 0x46, 0xdf, 0x07, 0x50, 0x6c, 0xdb, 0xd4, 0x5a, 0x03, + 0xef, 0x03, 0xab, 0xe1, 0xe6, 0xb5, 0xe1, 0xf0, 0x6d, 0x5e, 0xe0, 0x76, 0xb6, 0xec, 0x89, 0xfa, + 0x6c, 0xcd, 0xa7, 0x50, 0xdc, 0x85, 0x62, 0x50, 0xd6, 0x29, 0xf1, 0xd8, 0x18, 0x82, 0x25, 0x1e, + 0xab, 0xd8, 0x79, 0x89, 0xe7, 0x16, 0x88, 0x09, 0xd6, 0x03, 0xa7, 0x0f, 0xe2, 0x3f, 0xe2, 0xb0, + 0xe0, 0xb7, 0xce, 0xff, 0xb4, 0x2a, 0x49, 0xfc, 0x30, 0x0e, 0x59, 0x77, 0xf2, 0x11, 0x0d, 0x68, + 0x6f, 0xed, 0x04, 0x7f, 0xbb, 0x95, 0x75, 0xb4, 0x13, 0x6e, 0x9f, 0xfc, 0x8e, 0x9b, 0x50, 0x45, + 0x81, 0xda, 0xfe, 0x95, 0x76, 0xae, 0x0a, 0xf0, 0xfc, 0xf1, 0x27, 0x7c, 0x1c, 0x24, 0x93, 0x40, + 0xff, 0x03, 0x69, 0xa5, 0xed, 0x42, 0xf9, 0xc5, 0x10, 0x6c, 0xd7, 0x61, 0x5d, 0x6b, 0x0e, 0x37, + 0x28, 0xa7, 0xc4, 0x25, 0xf8, 0xa8, 0x04, 0xb7, 0xcf, 0xfe, 0x2a, 0xd1, 0xcb, 0x78, 0x82, 0x6e, + 0xb3, 0x08, 0x70, 0xb8, 0x7b, 0x7f, 0x6f, 0x6b, 0xfb, 0xee, 0x76, 0x7d, 0x8b, 0xa7, 0x54, 0x5b, + 0x5b, 0xf5, 0xad, 0xb2, 0x40, 0xf8, 0xa4, 0xfa, 0xfd, 0xbd, 0xa3, 0xfa, 0x56, 0x39, 0x21, 0xde, + 0x81, 0x9c, 0xeb, 0x7a, 0x50, 0x05, 0x32, 0x4e, 0x5b, 0x22, 0xce, 0x1d, 0x00, 0xef, 0x32, 0x2d, + 0x43, 0xca, 0xd0, 0xdf, 0xe3, 0x5d, 0xe6, 0x84, 0xc4, 0x1e, 0x44, 0x15, 0x4a, 0x23, 0x7e, 0x0b, + 0xdd, 0x81, 0x8c, 0x31, 0x68, 0xc9, 0x8e, 0xd1, 0x8e, 0x34, 0x71, 0x1c, 0xa4, 0x61, 0xd0, 0xea, + 0x6a, 0xed, 0x7b, 0xf8, 0xcc, 0x59, 0x26, 0x63, 0xd0, 0xba, 0xc7, 0x6c, 0x9b, 0x7d, 0x45, 0xf0, + 0x7f, 0xe5, 0x14, 0xb2, 0xce, 0x51, 0x45, 0xff, 0x0b, 0x39, 0xd7, 0x25, 0xba, 0x57, 0x6f, 0x22, + 0x7d, 0x29, 0x57, 0xef, 0x89, 0xa0, 0xeb, 0xb0, 0x68, 0x69, 0x27, 0x7d, 0xa7, 0x85, 0xc5, 0x90, + 0x3d, 0x81, 0x9e, 0x99, 0x12, 0x7b, 0xb1, 0xe3, 0xc0, 0x51, 0x24, 0x12, 0x96, 0x47, 0x7d, 0xc5, + 0xb7, 0x39, 0x80, 0x90, 0x88, 0x9d, 0x08, 0x8b, 0xd8, 0x1f, 0x08, 0x90, 0xf7, 0x35, 0xc6, 0xd0, + 0x7f, 0xfb, 0x1c, 0x57, 0x31, 0x24, 0xd4, 0xf8, 0x78, 0xbd, 0x5b, 0x1d, 0xc1, 0x89, 0x09, 0xf3, + 0x4f, 0x2c, 0xaa, 0x0f, 0xe9, 0xf4, 0xd7, 0x92, 0x73, 0xf7, 0xd7, 0x9e, 0x05, 0x64, 0xeb, 0xb6, + 0xd2, 0x95, 0x4f, 0x75, 0x5b, 0xeb, 0x9f, 0xc8, 0xcc, 0x34, 0x98, 0x9b, 0x29, 0xd3, 0x37, 0x47, + 0xf4, 0xc5, 0x3e, 0xb5, 0x92, 0x1f, 0xc4, 0x21, 0xeb, 0x96, 0x7d, 0xf3, 0x5e, 0xd2, 0x38, 0x07, + 0x69, 0x5e, 0xd9, 0xb0, 0x5b, 0x1a, 0xfc, 0x29, 0xb4, 0x91, 0x58, 0x85, 0x6c, 0x0f, 0xdb, 0x0a, + 0xf5, 0x99, 0x2c, 0x4c, 0xba, 0xcf, 0xd7, 0x5f, 0x82, 0xbc, 0xef, 0xbe, 0x0c, 0x71, 0xa3, 0xbb, + 0xf5, 0x37, 0xcb, 0xb1, 0x6a, 0xe6, 0xa3, 0x4f, 0x2e, 0x25, 0x76, 0xf1, 0x7b, 0xe4, 0x84, 0x49, + 0xf5, 0x5a, 0xa3, 0x5e, 0xbb, 0x57, 0x8e, 0x57, 0xf3, 0x1f, 0x7d, 0x72, 0x29, 0x23, 0x61, 0xda, + 0xf7, 0xb9, 0x7e, 0x0f, 0x4a, 0x23, 0x1b, 0x13, 0x3c, 0xd0, 0x08, 0x8a, 0x5b, 0x87, 0xfb, 0x3b, + 0xdb, 0xb5, 0x8d, 0x66, 0x5d, 0x3e, 0xda, 0x6b, 0xd6, 0xcb, 0x71, 0xf4, 0x38, 0x2c, 0xed, 0x6c, + 0xbf, 0xd6, 0x68, 0xca, 0xb5, 0x9d, 0xed, 0xfa, 0x6e, 0x53, 0xde, 0x68, 0x36, 0x37, 0x6a, 0xf7, + 0xca, 0xc2, 0xfa, 0xaf, 0xf2, 0x50, 0xda, 0xd8, 0xac, 0x6d, 0x93, 0xda, 0x4e, 0x6b, 0x2b, 0xd4, + 0x3d, 0xd4, 0x20, 0x49, 0x41, 0xe4, 0x89, 0x37, 0xa0, 0xab, 0x93, 0x1b, 0x83, 0xe8, 0x2e, 0xa4, + 0x28, 0xbe, 0x8c, 0x26, 0x5f, 0x89, 0xae, 0x4e, 0xe9, 0x14, 0x92, 0xc1, 0xd0, 0xe3, 0x34, 0xf1, + 0x8e, 0x74, 0x75, 0x72, 0xe3, 0x10, 0xed, 0x40, 0xc6, 0x81, 0xff, 0xa6, 0xdd, 0x36, 0xae, 0x4e, + 0xed, 0xc0, 0x91, 0xa9, 0x31, 0x98, 0x76, 0xf2, 0xf5, 0xe9, 0xea, 0x94, 0x96, 0x22, 0xda, 0x86, + 0x34, 0x47, 0x48, 0xa6, 0xdc, 0x1c, 0xae, 0x4e, 0xeb, 0xa4, 0x21, 0x09, 0x72, 0x1e, 0x00, 0x3e, + 0xfd, 0x52, 0x78, 0x75, 0x86, 0x6e, 0x29, 0x7a, 0x00, 0x85, 0x20, 0xea, 0x32, 0xdb, 0xed, 0xe4, + 0xea, 0x8c, 0x3d, 0x3b, 0xa2, 0x3f, 0x08, 0xc1, 0xcc, 0x76, 0x5b, 0xb9, 0x3a, 0x63, 0x0b, 0x0f, + 0xbd, 0x03, 0x8b, 0xe3, 0x10, 0xc9, 0xec, 0x97, 0x97, 0xab, 0x73, 0x34, 0xf5, 0x50, 0x0f, 0x50, + 0x08, 0xb4, 0x32, 0xc7, 0x5d, 0xe6, 0xea, 0x3c, 0x3d, 0x3e, 0xa4, 0x42, 0x69, 0x14, 0xae, 0x98, + 0xf5, 0x6e, 0x73, 0x75, 0xe6, 0x7e, 0x1f, 0xfb, 0x4a, 0xb0, 0x76, 0x9f, 0xf5, 0xae, 0x73, 0x75, + 0xe6, 0xf6, 0x1f, 0x3a, 0x04, 0xf0, 0xd5, 0x9e, 0x33, 0xdc, 0x7d, 0xae, 0xce, 0xd2, 0x08, 0x44, + 0x06, 0x2c, 0x85, 0x15, 0xa5, 0xf3, 0x5c, 0x85, 0xae, 0xce, 0xd5, 0x1f, 0x24, 0xf6, 0x1c, 0x2c, + 0x2f, 0x67, 0xbb, 0x1a, 0x5d, 0x9d, 0xb1, 0x51, 0xb8, 0x59, 0xff, 0xf4, 0x8b, 0x95, 0xf8, 0x67, + 0x5f, 0xac, 0xc4, 0xff, 0xfa, 0xc5, 0x4a, 0xfc, 0xe3, 0x2f, 0x57, 0x62, 0x9f, 0x7d, 0xb9, 0x12, + 0xfb, 0xd3, 0x97, 0x2b, 0xb1, 0xff, 0x7b, 0xe6, 0x44, 0xb3, 0x3b, 0x83, 0xd6, 0x5a, 0x5b, 0xef, + 0xdd, 0xf0, 0xff, 0x4b, 0x26, 0xec, 0x9f, 0x3b, 0xad, 0x34, 0x0d, 0xa8, 0x37, 0xff, 0x15, 0x00, + 0x00, 0xff, 0xff, 0xa5, 0x05, 0x49, 0xaf, 0xd9, 0x33, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -6222,15 +6264,15 @@ func (m *RequestExtendVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Vote != nil { - { - size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0xa } @@ -6257,15 +6299,29 @@ func (m *RequestVerifyVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, err _ = i var l int _ = l - if m.Vote != nil { - { - size, err := m.Vote.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if len(m.VoteExtension) > 0 { + i -= len(m.VoteExtension) + copy(dAtA[i:], m.VoteExtension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.VoteExtension))) + i-- + dAtA[i] = 0x22 + } + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x18 + } + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0xa } @@ -6306,12 +6362,12 @@ func (m *RequestFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x3a } - n31, err31 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err31 != nil { - return 0, err31 + n29, err29 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err29 != nil { + return 0, err29 } - i -= n31 - i = encodeVarintTypes(dAtA, i, uint64(n31)) + i -= n29 + i = encodeVarintTypes(dAtA, i, uint64(n29)) i-- dAtA[i] = 0x32 if m.Height != 0 { @@ -7541,20 +7597,20 @@ func (m *ResponseApplySnapshotChunk) MarshalToSizedBuffer(dAtA []byte) (int, err } } if len(m.RefetchChunks) > 0 { - dAtA57 := make([]byte, len(m.RefetchChunks)*10) - var j56 int + dAtA55 := make([]byte, len(m.RefetchChunks)*10) + var j54 int for _, num := range m.RefetchChunks { for num >= 1<<7 { - dAtA57[j56] = uint8(uint64(num)&0x7f | 0x80) + dAtA55[j54] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j56++ + j54++ } - dAtA57[j56] = uint8(num) - j56++ + dAtA55[j54] = uint8(num) + j54++ } - i -= j56 - copy(dAtA[i:], dAtA57[:j56]) - i = encodeVarintTypes(dAtA, i, uint64(j56)) + i -= j54 + copy(dAtA[i:], dAtA55[:j54]) + i = encodeVarintTypes(dAtA, i, uint64(j54)) i-- dAtA[i] = 0x12 } @@ -7745,15 +7801,10 @@ func (m *ResponseExtendVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if len(m.VoteExtension) > 0 { + i -= len(m.VoteExtension) + copy(dAtA[i:], m.VoteExtension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.VoteExtension))) i-- dAtA[i] = 0xa } @@ -8408,12 +8459,12 @@ func (m *Misbehavior) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - n66, err66 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err66 != nil { - return 0, err66 + n63, err63 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err63 != nil { + return 0, err63 } - i -= n66 - i = encodeVarintTypes(dAtA, i, uint64(n66)) + i -= n63 + i = encodeVarintTypes(dAtA, i, uint64(n63)) i-- dAtA[i] = 0x22 if m.Height != 0 { @@ -9064,10 +9115,13 @@ func (m *RequestExtendVote) Size() (n int) { } var l int _ = l - if m.Vote != nil { - l = m.Vote.Size() + l = len(m.Hash) + if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } return n } @@ -9077,8 +9131,19 @@ func (m *RequestVerifyVoteExtension) Size() (n int) { } var l int _ = l - if m.Vote != nil { - l = m.Vote.Size() + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + l = len(m.VoteExtension) + if l > 0 { n += 1 + l + sovTypes(uint64(l)) } return n @@ -9788,8 +9853,8 @@ func (m *ResponseExtendVote) Size() (n int) { } var l int _ = l - if m.VoteExtension != nil { - l = m.VoteExtension.Size() + l = len(m.VoteExtension) + if l > 0 { n += 1 + l + sovTypes(uint64(l)) } return n @@ -13029,9 +13094,9 @@ func (m *RequestExtendVote) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTypes @@ -13041,28 +13106,45 @@ func (m *RequestExtendVote) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTypes } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTypes } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Vote == nil { - m.Vote = &types1.Vote{} - } - if err := m.Vote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} } iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -13115,9 +13197,9 @@ func (m *RequestVerifyVoteExtension) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Vote", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTypes @@ -13127,26 +13209,111 @@ func (m *RequestVerifyVoteExtension) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTypes } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTypes } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Vote == nil { - m.Vote = &types1.Vote{} + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} } - if err := m.Vote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = append(m.ValidatorAddress[:0], dAtA[iNdEx:postIndex]...) + if m.ValidatorAddress == nil { + m.ValidatorAddress = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.VoteExtension = append(m.VoteExtension[:0], dAtA[iNdEx:postIndex]...) + if m.VoteExtension == nil { + m.VoteExtension = []byte{} } iNdEx = postIndex default: @@ -16926,7 +17093,7 @@ func (m *ResponseExtendVote) Unmarshal(dAtA []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTypes @@ -16936,26 +17103,24 @@ func (m *ResponseExtendVote) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthTypes } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthTypes } if postIndex > l { return io.ErrUnexpectedEOF } + m.VoteExtension = append(m.VoteExtension[:0], dAtA[iNdEx:postIndex]...) if m.VoteExtension == nil { - m.VoteExtension = &types1.VoteExtension{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.VoteExtension = []byte{} } iNdEx = postIndex default: diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index e34236acc..901422145 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -113,7 +113,7 @@ func main() { // add prometheus metrics for unary RPC calls opts = append(opts, grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor)) - ss := grpcprivval.NewSignerServer(*chainID, pv, logger) + ss := grpcprivval.NewSignerServer(logger, *chainID, pv) protocol, address := tmnet.ProtocolAndAddress(*addr) diff --git a/config/toml.go b/config/toml.go index 7f49249e9..578718ca5 100644 --- a/config/toml.go +++ b/config/toml.go @@ -12,8 +12,8 @@ import ( tmrand "github.com/tendermint/tendermint/libs/rand" ) -// DefaultDirPerm is the default permissions used when creating directories. -const DefaultDirPerm = 0700 +// defaultDirPerm is the default permissions used when creating directories. +const defaultDirPerm = 0700 var configTemplate *template.Template @@ -32,13 +32,13 @@ func init() { // EnsureRoot creates the root, config, and data directories if they don't exist, // and panics if it fails. func EnsureRoot(rootDir string) { - if err := tmos.EnsureDir(rootDir, DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(rootDir, defaultDirPerm); err != nil { panic(err.Error()) } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), defaultDirPerm); err != nil { panic(err.Error()) } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), defaultDirPerm); err != nil { panic(err.Error()) } } @@ -566,10 +566,10 @@ func ResetTestRootWithChainID(dir, testName string, chainID string) (*Config, er return nil, err } // ensure config and data subdirs are created - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultConfigDir), defaultDirPerm); err != nil { return nil, err } - if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), DefaultDirPerm); err != nil { + if err := tmos.EnsureDir(filepath.Join(rootDir, defaultDataDir), defaultDirPerm); err != nil { return nil, err } @@ -612,7 +612,7 @@ func writeFile(filePath string, contents []byte, mode os.FileMode) error { return nil } -var testGenesisFmt = `{ +const testGenesisFmt = `{ "genesis_time": "2018-10-10T08:20:13.695936996Z", "chain_id": "%s", "initial_height": "1", @@ -659,7 +659,7 @@ var testGenesisFmt = `{ "app_hash": "" }` -var testPrivValidatorKey = `{ +const testPrivValidatorKey = `{ "address": "A3258DCBF45DCA0DF052981870F2D1441A36D145", "pub_key": { "type": "tendermint/PubKeyEd25519", @@ -671,7 +671,7 @@ var testPrivValidatorKey = `{ } }` -var testPrivValidatorState = `{ +const testPrivValidatorState = `{ "height": "0", "round": 0, "step": 0 diff --git a/crypto/crypto.go b/crypto/crypto.go index 4f0dc05e7..ea24af243 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,14 +1,18 @@ package crypto import ( - "github.com/tendermint/tendermint/crypto/tmhash" + "crypto/sha256" + "github.com/tendermint/tendermint/internal/jsontypes" "github.com/tendermint/tendermint/libs/bytes" ) const ( + // HashSize is the size in bytes of an AddressHash. + HashSize = sha256.Size + // AddressSize is the size of a pubkey address. - AddressSize = tmhash.TruncatedSize + AddressSize = 20 ) // An address is a []byte, but hex-encoded even in JSON. @@ -16,8 +20,19 @@ const ( // Use an alias so Unmarshal methods (with ptr receivers) are available too. type Address = bytes.HexBytes +// AddressHash computes a truncated SHA-256 hash of bz for use as +// a peer address. +// +// See: https://docs.tendermint.com/master/spec/core/data_structures.html#address func AddressHash(bz []byte) Address { - return Address(tmhash.SumTruncated(bz)) + h := sha256.Sum256(bz) + return Address(h[:AddressSize]) +} + +// Checksum returns the SHA256 of the bz. +func Checksum(bz []byte) []byte { + h := sha256.Sum256(bz) + return h[:] } type PubKey interface { diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index ffd4a3ed1..ca425d111 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -2,6 +2,8 @@ package ed25519 import ( "bytes" + "crypto/rand" + "crypto/sha256" "crypto/subtle" "errors" "fmt" @@ -11,7 +13,6 @@ import ( "github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/cache" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/jsontypes" ) @@ -124,7 +125,7 @@ func (privKey PrivKey) Type() string { // It uses OS randomness in conjunction with the current global random seed // in tendermint/libs/common to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } // genPrivKey generates a new ed25519 private key using the provided reader. @@ -142,9 +143,8 @@ func genPrivKey(rand io.Reader) PrivKey { // NOTE: secret should be the output of a KDF like bcrypt, // if it's derived from user input. func GenPrivKeyFromSecret(secret []byte) PrivKey { - seed := crypto.Sha256(secret) // Not Ripemd160 because we want 32 bytes. - - return PrivKey(ed25519.NewKeyFromSeed(seed)) + seed := sha256.Sum256(secret) + return PrivKey(ed25519.NewKeyFromSeed(seed[:])) } //------------------------------------- @@ -162,7 +162,7 @@ func (pubKey PubKey) Address() crypto.Address { if len(pubKey) != PubKeySize { panic("pubkey is incorrect size") } - return crypto.Address(tmhash.SumTruncated(pubKey)) + return crypto.AddressHash(pubKey) } // Bytes returns the PubKey byte format. @@ -229,5 +229,5 @@ func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error { } func (b *BatchVerifier) Verify() (bool, []bool) { - return b.BatchVerifier.Verify(crypto.CReader()) + return b.BatchVerifier.Verify(rand.Reader) } diff --git a/crypto/example_test.go b/crypto/example_test.go deleted file mode 100644 index f1d0013d4..000000000 --- a/crypto/example_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 Tendermint. All Rights Reserved. -// -// 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. - -package crypto_test - -import ( - "fmt" - - "github.com/tendermint/tendermint/crypto" -) - -func ExampleSha256() { - sum := crypto.Sha256([]byte("This is Tendermint")) - fmt.Printf("%x\n", sum) - // Output: - // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e -} diff --git a/crypto/hash.go b/crypto/hash.go deleted file mode 100644 index e1d22523f..000000000 --- a/crypto/hash.go +++ /dev/null @@ -1,11 +0,0 @@ -package crypto - -import ( - "crypto/sha256" -) - -func Sha256(bytes []byte) []byte { - hasher := sha256.New() - hasher.Write(bytes) - return hasher.Sum(nil) -} diff --git a/crypto/merkle/hash.go b/crypto/merkle/hash.go index 9c6df1786..0bb5448d7 100644 --- a/crypto/merkle/hash.go +++ b/crypto/merkle/hash.go @@ -3,7 +3,7 @@ package merkle import ( "hash" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) // TODO: make these have a large predefined capacity @@ -14,12 +14,12 @@ var ( // returns tmhash() func emptyHash() []byte { - return tmhash.Sum([]byte{}) + return crypto.Checksum([]byte{}) } // returns tmhash(0x00 || leaf) func leafHash(leaf []byte) []byte { - return tmhash.Sum(append(leafPrefix, leaf...)) + return crypto.Checksum(append(leafPrefix, leaf...)) } // returns tmhash(0x00 || leaf) @@ -36,7 +36,7 @@ func innerHash(left []byte, right []byte) []byte { n := copy(data, innerPrefix) n += copy(data[n:], left) copy(data[n:], right) - return tmhash.Sum(data) + return crypto.Checksum(data)[:] } func innerHashOpt(s hash.Hash, left []byte, right []byte) []byte { diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 4f09e4414..8b98d1b21 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) @@ -102,15 +102,15 @@ func (sp *Proof) ValidateBasic() error { if sp.Index < 0 { return errors.New("negative Index") } - if len(sp.LeafHash) != tmhash.Size { - return fmt.Errorf("expected LeafHash size to be %d, got %d", tmhash.Size, len(sp.LeafHash)) + if len(sp.LeafHash) != crypto.HashSize { + return fmt.Errorf("expected LeafHash size to be %d, got %d", crypto.HashSize, len(sp.LeafHash)) } if len(sp.Aunts) > MaxAunts { return fmt.Errorf("expected no more than %d aunts, got %d", MaxAunts, len(sp.Aunts)) } for i, auntHash := range sp.Aunts { - if len(auntHash) != tmhash.Size { - return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, tmhash.Size, len(auntHash)) + if len(auntHash) != crypto.HashSize { + return fmt.Errorf("expected Aunts#%d size to be %d, got %d", i, crypto.HashSize, len(auntHash)) } } return nil diff --git a/crypto/merkle/proof_value.go b/crypto/merkle/proof_value.go index 842dc8201..0f4f2eb3d 100644 --- a/crypto/merkle/proof_value.go +++ b/crypto/merkle/proof_value.go @@ -2,9 +2,9 @@ package merkle import ( "bytes" + "crypto/sha256" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) @@ -79,14 +79,13 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { return nil, fmt.Errorf("expected 1 arg, got %v", len(args)) } value := args[0] - hasher := tmhash.New() - hasher.Write(value) - vhash := hasher.Sum(nil) + + vhash := sha256.Sum256(value) bz := new(bytes.Buffer) // Wrap to hash the KVPair. - encodeByteSlice(bz, op.key) //nolint: errcheck // does not error - encodeByteSlice(bz, vhash) //nolint: errcheck // does not error + encodeByteSlice(bz, op.key) //nolint: errcheck // does not error + encodeByteSlice(bz, vhash[:]) //nolint: errcheck // does not error kvhash := leafHash(bz.Bytes()) if !bytes.Equal(kvhash, op.Proof.LeafHash) { diff --git a/crypto/merkle/rfc6962_test.go b/crypto/merkle/rfc6962_test.go index c762cda56..7a70dbb91 100644 --- a/crypto/merkle/rfc6962_test.go +++ b/crypto/merkle/rfc6962_test.go @@ -20,7 +20,7 @@ import ( "encoding/hex" "testing" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) func TestRFC6962Hasher(t *testing.T) { @@ -39,7 +39,7 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n '' | sha256sum { desc: "RFC6962 Empty Tree", - want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:tmhash.Size*2], + want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"[:crypto.HashSize*2], got: emptyTreeHash, }, @@ -47,19 +47,19 @@ func TestRFC6962Hasher(t *testing.T) { // echo -n 00 | xxd -r -p | sha256sum { desc: "RFC6962 Empty Leaf", - want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:tmhash.Size*2], + want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:crypto.HashSize*2], got: emptyLeafHash, }, // echo -n 004C313233343536 | xxd -r -p | sha256sum { desc: "RFC6962 Leaf", - want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:tmhash.Size*2], + want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:crypto.HashSize*2], got: leafHash, }, // echo -n 014E3132334E343536 | xxd -r -p | sha256sum { desc: "RFC6962 Node", - want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:tmhash.Size*2], + want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:crypto.HashSize*2], got: innerHash([]byte("N123"), []byte("N456")), }, } { diff --git a/crypto/merkle/tree_test.go b/crypto/merkle/tree_test.go index 641c46b76..72b260178 100644 --- a/crypto/merkle/tree_test.go +++ b/crypto/merkle/tree_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ctest "github.com/tendermint/tendermint/internal/libs/test" tmrand "github.com/tendermint/tendermint/libs/rand" ) @@ -53,7 +53,7 @@ func TestProof(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } rootHash = HashFromByteSlices(items) @@ -106,7 +106,7 @@ func TestHashAlternatives(t *testing.T) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } rootHash1 := HashFromByteSlicesIterative(items) @@ -119,7 +119,7 @@ func BenchmarkHashAlternatives(b *testing.B) { items := make([][]byte, total) for i := 0; i < total; i++ { - items[i] = testItem(tmrand.Bytes(tmhash.Size)) + items[i] = testItem(tmrand.Bytes(crypto.HashSize)) } b.ResetTimer() diff --git a/crypto/random.go b/crypto/random.go index 275fb1044..d3e66801c 100644 --- a/crypto/random.go +++ b/crypto/random.go @@ -1,35 +1,15 @@ package crypto import ( - crand "crypto/rand" - "encoding/hex" - "io" + "crypto/rand" ) // This only uses the OS's randomness -func randBytes(numBytes int) []byte { +func CRandBytes(numBytes int) []byte { b := make([]byte, numBytes) - _, err := crand.Read(b) + _, err := rand.Read(b) if err != nil { panic(err) } return b } - -// This only uses the OS's randomness -func CRandBytes(numBytes int) []byte { - return randBytes(numBytes) -} - -// CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long. -// -// Note: CRandHex(24) gives 96 bits of randomness that -// are usually strong enough for most purposes. -func CRandHex(numDigits int) string { - return hex.EncodeToString(CRandBytes(numDigits / 2)) -} - -// Returns a crand.Reader. -func CReader() io.Reader { - return crand.Reader -} diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index 7892cfbb1..d0626456c 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -2,6 +2,7 @@ package secp256k1 import ( "bytes" + "crypto/rand" "crypto/sha256" "crypto/subtle" "fmt" @@ -70,7 +71,7 @@ func (privKey PrivKey) Type() string { // GenPrivKey generates a new ECDSA private key on curve secp256k1 private key. // It uses OS randomness to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } // genPrivKey generates a new secp256k1 private key using the provided reader. @@ -190,8 +191,8 @@ var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1) // The returned signature will be of the form R || S (in lower-S form). func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey) - - sig, err := priv.Sign(crypto.Sha256(msg)) + seed := sha256.Sum256(msg) + sig, err := priv.Sign(seed[:]) if err != nil { return nil, err } @@ -220,7 +221,8 @@ func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool { return false } - return signature.Verify(crypto.Sha256(msg), pub) + seed := sha256.Sum256(msg) + return signature.Verify(seed[:], pub) } // Read Signature struct from R || S. Caller needs to ensure diff --git a/crypto/sr25519/batch.go b/crypto/sr25519/batch.go index 462728598..3e959fbbf 100644 --- a/crypto/sr25519/batch.go +++ b/crypto/sr25519/batch.go @@ -1,6 +1,7 @@ package sr25519 import ( + "crypto/rand" "fmt" "github.com/oasisprotocol/curve25519-voi/primitives/sr25519" @@ -42,5 +43,5 @@ func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error { } func (b *BatchVerifier) Verify() (bool, []bool) { - return b.BatchVerifier.Verify(crypto.CReader()) + return b.BatchVerifier.Verify(rand.Reader) } diff --git a/crypto/sr25519/privkey.go b/crypto/sr25519/privkey.go index 4e9cc995f..c8da94d5d 100644 --- a/crypto/sr25519/privkey.go +++ b/crypto/sr25519/privkey.go @@ -1,6 +1,8 @@ package sr25519 import ( + "crypto/rand" + "crypto/sha256" "encoding/json" "fmt" "io" @@ -48,7 +50,7 @@ func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { st := signingCtx.NewTranscriptBytes(msg) - sig, err := privKey.kp.Sign(crypto.CReader(), st) + sig, err := privKey.kp.Sign(rand.Reader, st) if err != nil { return nil, fmt.Errorf("sr25519: failed to sign message: %w", err) } @@ -132,7 +134,7 @@ func (privKey *PrivKey) UnmarshalJSON(data []byte) error { // It uses OS randomness in conjunction with the current global random seed // in tendermint/libs/common to generate the private key. func GenPrivKey() PrivKey { - return genPrivKey(crypto.CReader()) + return genPrivKey(rand.Reader) } func genPrivKey(rng io.Reader) PrivKey { @@ -154,10 +156,9 @@ func genPrivKey(rng io.Reader) PrivKey { // NOTE: secret should be the output of a KDF like bcrypt, // if it's derived from user input. func GenPrivKeyFromSecret(secret []byte) PrivKey { - seed := crypto.Sha256(secret) // Not Ripemd160 because we want 32 bytes. - + seed := sha256.Sum256(secret) var privKey PrivKey - if err := privKey.msk.UnmarshalBinary(seed); err != nil { + if err := privKey.msk.UnmarshalBinary(seed[:]); err != nil { panic("sr25519: failed to deserialize MiniSecretKey: " + err.Error()) } diff --git a/crypto/sr25519/pubkey.go b/crypto/sr25519/pubkey.go index 717f25c8c..a2c6bb920 100644 --- a/crypto/sr25519/pubkey.go +++ b/crypto/sr25519/pubkey.go @@ -7,7 +7,6 @@ import ( "github.com/oasisprotocol/curve25519-voi/primitives/sr25519" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" ) var _ crypto.PubKey = PubKey{} @@ -31,7 +30,7 @@ func (pubKey PubKey) Address() crypto.Address { if len(pubKey) != PubKeySize { panic("pubkey is incorrect size") } - return crypto.Address(tmhash.SumTruncated(pubKey)) + return crypto.AddressHash(pubKey) } // Bytes returns the PubKey byte format. diff --git a/crypto/tmhash/hash.go b/crypto/tmhash/hash.go deleted file mode 100644 index f9b958242..000000000 --- a/crypto/tmhash/hash.go +++ /dev/null @@ -1,65 +0,0 @@ -package tmhash - -import ( - "crypto/sha256" - "hash" -) - -const ( - Size = sha256.Size - BlockSize = sha256.BlockSize -) - -// New returns a new hash.Hash. -func New() hash.Hash { - return sha256.New() -} - -// Sum returns the SHA256 of the bz. -func Sum(bz []byte) []byte { - h := sha256.Sum256(bz) - return h[:] -} - -//------------------------------------------------------------- - -const ( - TruncatedSize = 20 -) - -type sha256trunc struct { - sha256 hash.Hash -} - -func (h sha256trunc) Write(p []byte) (n int, err error) { - return h.sha256.Write(p) -} -func (h sha256trunc) Sum(b []byte) []byte { - shasum := h.sha256.Sum(b) - return shasum[:TruncatedSize] -} - -func (h sha256trunc) Reset() { - h.sha256.Reset() -} - -func (h sha256trunc) Size() int { - return TruncatedSize -} - -func (h sha256trunc) BlockSize() int { - return h.sha256.BlockSize() -} - -// NewTruncated returns a new hash.Hash. -func NewTruncated() hash.Hash { - return sha256trunc{ - sha256: sha256.New(), - } -} - -// SumTruncated returns the first 20 bytes of SHA256 of the bz. -func SumTruncated(bz []byte) []byte { - hash := sha256.Sum256(bz) - return hash[:TruncatedSize] -} diff --git a/crypto/tmhash/hash_test.go b/crypto/tmhash/hash_test.go deleted file mode 100644 index cf9991b3b..000000000 --- a/crypto/tmhash/hash_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package tmhash_test - -import ( - "crypto/sha256" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/crypto/tmhash" -) - -func TestHash(t *testing.T) { - testVector := []byte("abc") - hasher := tmhash.New() - _, err := hasher.Write(testVector) - require.NoError(t, err) - bz := hasher.Sum(nil) - - bz2 := tmhash.Sum(testVector) - - hasher = sha256.New() - _, err = hasher.Write(testVector) - require.NoError(t, err) - bz3 := hasher.Sum(nil) - - assert.Equal(t, bz, bz2) - assert.Equal(t, bz, bz3) -} - -func TestHashTruncated(t *testing.T) { - testVector := []byte("abc") - hasher := tmhash.NewTruncated() - _, err := hasher.Write(testVector) - require.NoError(t, err) - bz := hasher.Sum(nil) - - bz2 := tmhash.SumTruncated(testVector) - - hasher = sha256.New() - _, err = hasher.Write(testVector) - require.NoError(t, err) - bz3 := hasher.Sum(nil) - bz3 = bz3[:tmhash.TruncatedSize] - - assert.Equal(t, bz, bz2) - assert.Equal(t, bz, bz3) -} diff --git a/crypto/version.go b/crypto/version.go deleted file mode 100644 index 77c0bed8a..000000000 --- a/crypto/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package crypto - -const Version = "0.9.0-dev" diff --git a/docs/nodes/metrics.md b/docs/nodes/metrics.md index 1b2e9f007..7b0622519 100644 --- a/docs/nodes/metrics.md +++ b/docs/nodes/metrics.md @@ -18,40 +18,53 @@ Listen address can be changed in the config file (see The following metrics are available: -| **Name** | **Type** | **Tags** | **Description** | -| -------------------------------------- | --------- | ------------- | ---------------------------------------------------------------------- | -| abci_connection_method_timing | Histogram | method, type | Timings for each of the ABCI methods | -| consensus_height | Gauge | | Height of the chain | -| consensus_validators | Gauge | | Number of validators | -| consensus_validators_power | Gauge | | Total voting power of all validators | -| consensus_validator_power | Gauge | | Voting power of the node if in the validator set | -| consensus_validator_last_signed_height | Gauge | | Last height the node signed a block, if the node is a validator | -| consensus_validator_missed_blocks | Gauge | | Total amount of blocks missed for the node, if the node is a validator | -| consensus_missing_validators | Gauge | | Number of validators who did not sign | -| consensus_missing_validators_power | Gauge | | Total voting power of the missing validators | -| consensus_byzantine_validators | Gauge | | Number of validators who tried to double sign | -| consensus_byzantine_validators_power | Gauge | | Total voting power of the byzantine validators | -| consensus_block_interval_seconds | Histogram | | Time between this and last block (Block.Header.Time) in seconds | -| consensus_rounds | Gauge | | Number of rounds | -| consensus_num_txs | Gauge | | Number of transactions | -| consensus_total_txs | Gauge | | Total number of transactions committed | -| consensus_block_parts | counter | peer_id | number of blockparts transmitted by peer | -| consensus_latest_block_height | gauge | | /status sync_info number | -| consensus_fast_syncing | gauge | | either 0 (not fast syncing) or 1 (syncing) | -| consensus_state_syncing | gauge | | either 0 (not state syncing) or 1 (syncing) | -| consensus_block_size_bytes | Gauge | | Block size in bytes | -| evidence_pool_num_evidence | Gauge | | Number of evidence in the evidence pool -| p2p_peers | Gauge | | Number of peers node's connected to | -| p2p_peer_receive_bytes_total | counter | peer_id, chID | number of bytes per channel received from a given peer | -| p2p_peer_send_bytes_total | counter | peer_id, chID | number of bytes per channel sent to a given peer | -| p2p_peer_pending_send_bytes | gauge | peer_id | number of pending bytes to be sent to a given peer | -| p2p_num_txs | gauge | peer_id | number of transactions submitted by each peer_id | -| p2p_pending_send_bytes | gauge | peer_id | amount of data pending to be sent to peer | -| mempool_size | Gauge | | Number of uncommitted transactions | -| mempool_tx_size_bytes | histogram | | transaction sizes in bytes | -| mempool_failed_txs | counter | | number of failed transactions | -| mempool_recheck_times | counter | | number of transactions rechecked in the mempool | -| state_block_processing_time | histogram | | time between BeginBlock and EndBlock in ms | +| **Name** | **Type** | **Tags** | **Description** | +|-----------------------------------------|-----------|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| abci_connection_method_timing | Histogram | method, type | Timings for each of the ABCI methods | +| consensus_height | Gauge | | Height of the chain | +| consensus_validators | Gauge | | Number of validators | +| consensus_validators_power | Gauge | | Total voting power of all validators | +| consensus_validator_power | Gauge | | Voting power of the node if in the validator set | +| consensus_validator_last_signed_height | Gauge | | Last height the node signed a block, if the node is a validator | +| consensus_validator_missed_blocks | Gauge | | Total amount of blocks missed for the node, if the node is a validator | +| consensus_missing_validators | Gauge | | Number of validators who did not sign | +| consensus_missing_validators_power | Gauge | | Total voting power of the missing validators | +| consensus_byzantine_validators | Gauge | | Number of validators who tried to double sign | +| consensus_byzantine_validators_power | Gauge | | Total voting power of the byzantine validators | +| consensus_block_interval_seconds | Histogram | | Time between this and last block (Block.Header.Time) in seconds | +| consensus_rounds | Gauge | | Number of rounds | +| consensus_num_txs | Gauge | | Number of transactions | +| consensus_total_txs | Gauge | | Total number of transactions committed | +| consensus_block_parts | Counter | peer_id | number of blockparts transmitted by peer | +| consensus_latest_block_height | gauge | | /status sync_info number | +| consensus_fast_syncing | gauge | | either 0 (not fast syncing) or 1 (syncing) | +| consensus_state_syncing | gauge | | either 0 (not state syncing) or 1 (syncing) | +| consensus_block_size_bytes | Gauge | | Block size in bytes | +| consensus_step_duration | Histogram | step | Histogram of durations for each step in the consensus protocol | +| consensus_block_gossip_receive_latency | Histogram | | Histogram of time taken to receive a block in seconds, measure between when a new block is first discovered to when the block is completed | +| consensus_block_gossip_parts_received | Counter | matches_current | Number of block parts received by the node | +| consensus_quorum_prevote_delay | Gauge | | Interval in seconds between the proposal timestamp and the timestamp of the earliest prevote that achieved a quorum | +| consensus_full_prevote_delay | Gauge | | Interval in seconds between the proposal timestamp and the timestamp of the latest prevote in a round where all validators voted | +| consensus_proposal_timestamp_difference | Histogram | | Difference between the timestamp in the proposal message and the local time of the validator at the time it received the message | +| consensus_vote_extension_receive_count | Counter | status | Number of vote extensions received | +| consensus_proposal_receive_count | Counter | status | Total number of proposals received by the node since process start | +| consensus_proposal_create_count | Counter | | Total number of proposals created by the node since process start | +| consensus_round_voting_power_percent | Gauge | vote_type | A value between 0 and 1.0 representing the percentage of the total voting power per vote type received within a round | +| consensus_late_votes | Counter | vote_type | Number of votes received by the node since process start that correspond to earlier heights and rounds than this node is currently in. | +| evidence_pool_num_evidence | Gauge | | Number of evidence in the evidence pool | +| p2p_peers | Gauge | | Number of peers node's connected to | +| p2p_peer_receive_bytes_total | Counter | peer_id, chID | number of bytes per channel received from a given peer | +| p2p_peer_send_bytes_total | Counter | peer_id, chID | number of bytes per channel sent to a given peer | +| p2p_peer_pending_send_bytes | Gauge | peer_id | number of pending bytes to be sent to a given peer | +| p2p_num_txs | Gauge | peer_id | number of transactions submitted by each peer_id | +| p2p_pending_send_bytes | Gauge | peer_id | amount of data pending to be sent to peer | +| mempool_size | Gauge | | Number of uncommitted transactions | +| mempool_tx_size_bytes | Histogram | | transaction sizes in bytes | +| mempool_failed_txs | Counter | | number of failed transactions | +| mempool_recheck_times | Counter | | number of transactions rechecked in the mempool | +| state_block_processing_time | Histogram | | time between BeginBlock and EndBlock in ms | +| state_consensus_param_updates | Counter | | number of consensus parameter updates returned by the application since process start | +| state_validator_set_updates | Counter | | number of validator set updates returned by the application since process start | ## Useful queries diff --git a/docs/package.json b/docs/package.json index 4b42527c7..3200a5222 100644 --- a/docs/package.json +++ b/docs/package.json @@ -15,7 +15,7 @@ "serve": "trap 'exit 0' SIGINT; vuepress dev --no-cache", "postserve": "./post.sh", "prebuild": "./pre.sh", - "build": "trap 'exit 0' SIGINT; vuepress build --no-cache", + "build": "trap 'exit 0' SIGINT; vuepress build --no-cache --silent", "postbuild": "./post.sh" }, "author": "", diff --git a/docs/rfc/README.md b/docs/rfc/README.md index b5c42ea85..d944e72e7 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -53,5 +53,9 @@ sections. - [RFC-013: ABCI++](./rfc-013-abci++.md) - [RFC-014: Semantic Versioning](./rfc-014-semantic-versioning.md) - [RFC-015: ABCI++ Tx Mutation](./rfc-015-abci++-tx-mutation.md) +- [RFC-016: Node Architecture](./rfc-016-node-architecture.md) +- [RFC-017: ABCI++ Vote Extension Propagation](./rfc-017-abci++-vote-extension-propag.md) +- [RFC-018: BLS Signature Aggregation Exploration](./rfc-018-bls-agg-exploration.md) +- [RFC-019: Configuration File Versioning](./rfc-019-config-version.md) diff --git a/docs/rfc/images/node-dependency-tree.svg b/docs/rfc/images/node-dependency-tree.svg new file mode 100644 index 000000000..6d95e0e15 --- /dev/null +++ b/docs/rfc/images/node-dependency-tree.svg @@ -0,0 +1,3 @@ + + +
Node
Node
Statesync
Statesync
Blocksync
Blocksync
Consensus
Consensus
Mempool
Mempool
Evidence
Evidence
Block Executor
Block Executor
Blockchain
Blockchain
Evidence
Evidence
PEX
PEX
Peer Store
Peer Store
Peer Networking
Peer Networking
RPC External
RPC External
ABCI Layer
ABCI Layer
Events System
Events System
RPC Internal
RPC Internal
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/rfc/rfc-009-consensus-parameter-upgrades.md b/docs/rfc/rfc-009-consensus-parameter-upgrades.md index 60be878df..d5077840d 100644 --- a/docs/rfc/rfc-009-consensus-parameter-upgrades.md +++ b/docs/rfc/rfc-009-consensus-parameter-upgrades.md @@ -31,12 +31,12 @@ not reference the new parameters. Any nodes joining the network with the newer version of Tendermint will have the new consensus parameters. Tendermint will need to handle this case so that new versions of Tendermint with new consensus parameters can still validate old blocks correctly without having to do anything overly complex -or hacky. +or hacky. ### Allowing Developer-Defined Values and the `EndBlock` Problem When new consensus parameters are added, application developers may wish to set -values for them so that the developer-defined values may be used as soon as the +values for them so that the developer-defined values may be used as soon as the software upgrades. We do not currently have a clean mechanism for handling this. Consensus parameter updates are communicated from the application to Tendermint @@ -51,7 +51,7 @@ can take effect is height `H+1`. As of now, height `H` must run with the default ### Hash Compatibility -This section discusses possible solutions to the problem of maintaining backwards-compatibility +This section discusses possible solutions to the problem of maintaining backwards-compatibility of hashed parameters while adding new parameters. #### Never Hash Defaults diff --git a/docs/rfc/rfc-016-node-architecture.md b/docs/rfc/rfc-016-node-architecture.md new file mode 100644 index 000000000..29098d297 --- /dev/null +++ b/docs/rfc/rfc-016-node-architecture.md @@ -0,0 +1,83 @@ +# RFC 016: Node Architecture + +## Changelog + +- April 8, 2022: Initial draft (@cmwaters) +- April 15, 2022: Incorporation of feedback + +## Abstract + +The `node` package is the entry point into the Tendermint codebase, used both by the command line and programatically to create the nodes that make up a network. The package has suffered the most from the evolution of the codebase, becoming bloated as developers clipped on their bits of code here and there to get whatever feature they wanted working. + +The decisions made at the node level have the biggest impact to simplifying the protocols within them, unlocking better internal designs and making Tendermint more intuitive to use and easier to understand from the outside. Work, in minor increments, has already begun on this section of the codebase. This document exists to spark forth the necessary discourse in a few related areas that will help the team to converge on the long term makeup of the node. + +## Discussion + +The following is a list of points of discussion around the architecture of the node: + +### Dependency Tree + +The node object is currently stuffed with every component that possibly exists within Tendermint. In the constructor, all objects are built and interlaid with one another in some awkward dance. My guiding principle is that the node should only be made up of the components that it wants to have direct control of throughout its life. The node is a service which currently has the purpose of starting other services up in a particular order and stopping them all when commanded to do so. However, there are many services which are not direct dependents i.e. the mempool and evidence services should only be working when the consensus service is running. I propose to form more of a hierarchical structure of dependents which forces us to be clear about the relations that one component has to the other. More concretely, I propose the following dependency tree: + +![node dependency tree](./images/node-dependency-tree.svg) + +Many of the further discussion topics circle back to this representation of the node. + +It's also important to distinguish two dimensions which may require different characteristics of the architecture. There is the starting and stopping of services and their general lifecycle management. What is the correct order of operations to starting a node for example. Then there is the question of the needs of the service during actual operation. Then there is the question of what resources each service needs access to during its operation. Some need to publish events, others need access to data stores, and so forth. + +An alternative model and one that perhaps better suits the latter of these dimensions is the notion of an internal message passing system. Either the events bus or p2p layer could serve as a viable transport. This would essentially allow all services to communicate with any other service and could perhaps provide a solution to the coordination problem (presented below) without a centralized coordinator. The other main advantage is that such a system would be more robust to disruptions and changes to the code which may make a hierarchical structure quickly outdated and suboptimal. The addition of message routing is an added complexity to implement, will increase the degree of asynchronicity in the system and may make it harder to debug problems that are across multiple services. + +### Coordination of State Advancing Mechanisms + +Advancement of state in Tendermint is simply defined in heights: If the node is at height n, how does it get to height n + 1 and so on. Based on this definition we have three components that help a node to advance in height: consensus, statesync and blocksync. The way these components behave currently is very tightly coupled to one another with references passed back and forth. My guiding principle is that each of these should be able to operate completely independently of each other, e.g. a node should be able to run solely blocksync indefinitely. There have been several ideas suggested towards improving this flow. I've been leaning strongly towards a centralized system, whereby an orchestrator (in this case the node) decides what services to start and stop. +In a decentralized message passing system, individual services make their decision based upon a "global" shared state i.e. if my height is less that 10 below the average peer height, I as consensus, should stop (knowing that blocksync has the same condition for starting). As the example illustrates, each mechanism will still need to be aware of the presence of other mechanisms. + +Both centralized and decentralized systems rely on the communication of the nodes current height and a judgement on the height of the head of the chain. The latter, working out the head of the chain, is quite a difficult challenge as their is nothing preventing the node from acting maliciously and providing a different height. Currently both blocksync, consensus (and to a certain degree statesync), have parallel systems where peers communicate their height. This could be streamlined with the consensus (or even the p2p layer), broadcasting peer heights and either the node or the other state advancing mechanisms acting accordingly. + +Currently, when a node starts, it turns on every service that it is attached to. This means that while a node is syncing up by requesting blocks, it is also receiving transactions and votes, as well as snapshot and block requests. This is a needless use of bandwidth. An implementation of an orchestrator, regardless of whether the system is heirachical or not, should look to be able to open and close channels dynamically and effectively broadcast which services it is running. Integrating this with service discovery may also lead to a better serivce to peers. + +The orchestrator allows for some deal of variablity in how a node is constructed. Does it just run blocksync, shadowing the head of the chain and be highly available for querying. Does it rely on state sync at all? An important question that arises from this dynamicism is we ideally want to encourage nodes to provide as much of their resources as possible so that their is a healthy amount of providers to consumers. Do we make all services compulsory or allow for them to be disabled? Arguably it's possible that a user forks the codebase and rips out the blocksync code because they want to reduce bandwidth so this is more a question of how easy do we want to make this for users. + +### Block Executor + +The block executor is an important component that is currently used by both consensus and blocksync to execute transactions and update application state. Principally, I think it should be the only component that can write (and possibly even read) the block and state stores, and we should clean up other direct dependencies on the storage engine if we can. This would mean: + +- The reactors Consensus, BlockSync and StateSync should all import the executor for advancing state ie. `ApplyBlock` and `BootstrapState`. +- Pruning should also be a concern of the block executor as well as `FinalizeBlock` and `Commit`. This can simplify consensus to focus just on the consensus part. + +### The Interprocess communication systems: RPC, P2P, ABCI, and Events + +The schematic supplied above shows the relations between the different services, the node, the block executor, and the storage layer. Represented as colored dots are the components responsible for different roles of interprocess communication (IPC). These components permeate throughout the code base, seeping into most services. What can provide powerful functionality on one hand can also become a twisted vine, creating messy corner cases and convoluting the protocols themselves. A lot of the thinking around +how we want our IPC systens to function has been summarised in this [RFC](./rfc-002-ipc-ecosystem.md). In this section, I'd like to focus the reader on the relation between the IPC and the node structure. An issue that has frequently risen is that the RPC has control of the components where it strikes me as being more logical for the component to dictate the information that is emitted/available and the knobs it wishes to expose. The RPC is also inextricably tied to the node instance and has situations where it is passed pointers directly to the storage engine and other components. + +I am currently convinced of the approach that the p2p layer takes and would like to see other IPC components follow suit. This would mean that the RPC and events system would be constructed in the node yet would pass the adequate methods to register endpoints and topics to the sub components. For example, + +```go +// Methods from the RPC and event bus that would be passed into the constructor of components like "consensus" +// NOTE: This is a hypothetical construction to convey the idea. An actual implementation may differ. +func RegisterRoute(path string, handler func(http.ResponseWriter, *http.Request)) + +func RegisterTopic(name string) EventPublisher + +type EventPublisher func (context.Context, types.EventData, []abci.Event) +``` + +This would give the components control to the information they want to expose and keep all relevant logic within that package. It accomodates more to a dynamic system where services can switch on and off. Each component would also receive access to the logger and metrics system for introspection and debuggability. + +#### IPC Rubric + +I'd like to aim to reach a state where we as a team have either an implicit or explicit rubric which can determine, in the event of some new need to communicate information, what tool it should use for doing this. In the case of inter node communication, this is obviously the p2p stack (with perhaps the exception of the light client). Metrics and logging also have clear usage patterns. RPC and the events system are less clear. The RPC is used for debugging data and fine tuned operator control as it is for general public querying and transaction submission. The RPC is also known to have been plumbed back into the application for historical queries. The events system, similarly, is used for consuming transaction events as it is for the testing of consensus state transitions. + +Principally, I think we should look to change our language away from what the actual transport is and more towards what it's being used for and to whom. We call it a peer to peer layer and not the underlying tcp connection. In the same way, we should look to split RPC into an operator interface (RPC Internal), a public interface (RPC External) and a bidirectional ABCI. + +### Seperation of consumers and suppliers + +When a service such as blocksync is turned on, it automatically begins requesting blocks to verify and apply them as it also tries to serve them to other peers catching up. We should look to distinguish these two aspects: supplying of information and consuming of information in many of these components. More concretely, I'd suggest: + +- The blocksync and statesync service, i.e. supplying information for those trying to catch up should only start running once a node has caught up i.e. after running the blocksync and/or state sync *processes* +- The blocksync and state sync processes have defined termination clauses that inform the orchestrator when they are done and where they finished. + - One way of achieving this would be that every process both passes and returns the `State` object + - In some cases, a node may specify that it wants to run blocksync indefinitely. +- The mempool should also indicate whether it wants to receive transactions or to send them only (one-directional mempool) +- Similarly, the light client itself only requests information whereas the light client service (currently part of state sync) can do both. +- This distinction needs to be communicated in the p2p layer handshake itself but should also be changeable over the lifespan of the connection. diff --git a/docs/rfc/rfc-017-abci++-vote-extension-propag.md b/docs/rfc/rfc-017-abci++-vote-extension-propag.md new file mode 100644 index 000000000..15d08f7ba --- /dev/null +++ b/docs/rfc/rfc-017-abci++-vote-extension-propag.md @@ -0,0 +1,571 @@ +# RFC 017: ABCI++ Vote Extension Propagation + +## Changelog + +- 11-Apr-2022: Initial draft (@sergio-mena). +- 15-Apr-2022: Addressed initial comments. First complete version (@sergio-mena). +- 09-May-2022: Addressed all outstanding comments. + +## Abstract + +According to the +[ABCI++ specification](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/abci%2B%2B/README.md) +(as of 11-Apr-2022), a validator MUST provide a signed vote extension for each non-`nil` precommit vote +of height *h* that it uses to propose a block in height *h+1*. When a validator is up to +date, this is easy to do, but when a validator needs to catch up this is far from trivial as this data +cannot be retrieved from the blockchain. + +This RFC presents and compares the different options to address this problem, which have been proposed +in several discussions by the Tendermint Core team. + +## Document Structure + +The RFC is structured as follows. In the [Background](#background) section, +subsections [Problem Description](#problem-description) and [Cases to Address](#cases-to-address) +explain the problem at hand from a high level perspective, i.e., abstracting away from the current +Tendermint implementation. In contrast, subsection +[Current Catch-up Mechanisms](#current-catch-up-mechanisms) delves into the details of the current +Tendermint code. + +In the [Discussion](#discussion) section, subsection [Solutions Proposed](#solutions-proposed) is also +worded abstracting away from implementation details, whilst subsections +[Feasibility of the Proposed Solutions](#feasibility-of-the-proposed-solutions) and +[Current Limitations and Possible Implementations](#current-limitations-and-possible-implementations) +analize the viability of one of the proposed solutions in the context of Tendermint's architecture +based on reactors. Finally, [Formalization Work](#formalization-work) briefly discusses the work +still needed demonstrate the correctness of the chosen solution. + +The high level subsections are aimed at readers who are familiar with consensus algorithms, in +particular with the one described in the Tendermint (white paper), but who are not necessarily +acquainted with the details of the Tendermint codebase. The other subsections, which go into +implementation details, are best understood by engineers with deep knowledge of the implementation of +Tendermint's blocksync and consensus reactors. + +## Background + +### Basic Definitions + +This document assumes that all validators have equal voting power for the sake of simplicity. This is done +without loss of generality. + +There are two types of votes in Tendermint: *prevotes* and *precommits*. Votes can be `nil` or refer to +a proposed block. This RFC focuses on precommits, +also known as *precommit votes*. In this document we sometimes call them simply *votes*. + +Validators send precommit votes to their peer nodes in *precommit messages*. According to the +[ABCI++ specification](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/abci%2B%2B/README.md), +a precommit message MUST also contain a *vote extension*. +This mandatory vote extension can be empty, but MUST be signed with the same key as the precommit +vote (i.e., the sending validator's). +Nevertheless, the vote extension is signed independently from the vote, so a vote can be separated from +its extension. +The reason for vote extensions to be mandatory in precommit messages is that, otherwise, a (malicious) +node can omit a vote extension while still providing/forwarding/sending the corresponding precommit vote. + +The validator set at height *h* is denoted *valseth*. A *commit* for height *h* consists of more +than *2nh/3* precommit votes voting for a block *b*, where *nh* denotes the size of +*valseth*. A commit does not contain `nil` precommit votes, and all votes in it refer to the +same block. An *extended commit* is a *commit* where every precommit vote has its respective vote extension +attached. + +### Problem Description + +In the version of [ABCI](https://github.com/tendermint/spec/blob/4fb99af/spec/abci/README.md) present up to +Tendermint v0.35, for any height *h*, a validator *v* MUST have the decided block *b* and a commit for +height *h* in order to decide at height *h*. Then, *v* just needs a commit for height *h* to propose at +height *h+1*, in the rounds of *h+1* where *v* is a proposer. + +In [ABCI++](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/abci%2B%2B/README.md), +the information that a validator *v* MUST have to be able to decide in *h* does not change with +respect to pre-existing ABCI: the decided block *b* and a commit for *h*. +In contrast, for proposing in *h+1*, a commit for *h* is not enough: *v* MUST now have an extended +commit. + +When a validator takes an active part in consensus at height *h*, it has all the data it needs in memory, +in its consensus state, to decide on *h* and propose in *h+1*. Things are not so easy in the cases when +*v* cannot take part in consensus because it is late (e.g., it falls behind, it crashes +and recovers, or it just starts after the others). If *v* does not take part, it cannot actively +gather precommit messages (which include vote extensions) in order to decide. +Before ABCI++, this was not a problem: full nodes are supposed to persist past blocks in the block store, +so other nodes would realise that *v* is late and send it the missing decided block at height *h* and +the corresponding commit (kept in block *h+1*) so that *v* can catch up. +However, we cannot apply this catch-up technique for ABCI++, as the vote extensions, which are part +of the needed *extended commit* are not part of the blockchain. + +### Cases to Address + +Before we tackle the description of the possible cases we need to address, let us describe the following +incremental improvement to the ABCI++ logic. Upon decision, a full node persists (e.g., in the block +store) the extended commit that allowed the node to decide. For the moment, let us assume the node only +needs to keep its *most recent* extended commit, and MAY remove any older extended commits from persistent +storage. +This improvement is so obvious that all solutions described in the [Discussion](#discussion) section use +it as a building block. Moreover, it completely addresses by itself some of the cases described in this +subsection. + +We now describe the cases (i.e. possible *runs* of the system) that have been raised in different +discussions and need to be addressed. They are (roughly) ordered from easiest to hardest to deal with. + +- **(a)** *Happy path: all validators advance together, no crash*. + + This case is included for completeness. All validators have taken part in height *h*. + Even if some of them did not manage to send a precommit message for the decided block, they all + receive enough precommit messages to be able to decide. As vote extensions are mandatory in + precommit messages, every validator *v* trivially has all the information, namely the decided block + and the extended commit, needed to propose in height *h+1* for the rounds in which *v* is the + proposer. + + No problem to solve here. + +- **(b)** *All validators advance together, then all crash at the same height*. + + This case has been raised in some discussions, the main concern being whether the vote extensions + for the previous height would be lost across the network. With the improvement described above, + namely persisting the latest extended commit at decision time, this case is solved. + When a crashed validator recovers, it recovers the last extended commit from persistent storage + and handshakes with the Application. + If need be, it also reconstructs messages for the unfinished height + (including all precommits received) from the WAL. + Then, the validator can resume where it was at the time of the crash. Thus, as extensions are + persisted, either in the WAL (in the form of received precommit messages), or in the latest + extended commit, the only way that vote extensions needed to start the next height could be lost + forever would be if all validators crashed and never recovered (e.g. disk corruption). + Since a *correct* node MUST eventually recover, this violates Tendermint's assumption of more than + *2nh/3* correct validators for every height *h*. + + No problem to solve here. + +- **(c)** *Lagging majority*. + + Let us assume the validator set does not change between *h* and *h+1*. + It is not possible by the nature of the Tendermint algorithm, which requires more + than *2nh/3* precommit votes for some round of height *h* in order to make progress. + So, only up to *nh/3* validators can lag behind. + + On the other hand, for the case where there are changes to the validator set between *h* and + *h+1* please see case (d) below, where the extreme case is discussed. + +- **(d)** *Validator set changes completely between* h *and* h+1. + + If sets *valseth* and *valseth+1* are disjoint, + more than *2nh/3* of validators in height *h* should + have actively participated in conensus in *h*. So, as of height *h*, only a minority of validators + in *h* can be lagging behind, although they could all lag behind from *h+1* on, as they are no + longer validators, only full nodes. This situation falls under the assumptions of case (h) below. + + As for validators in *valseth+1*, as they were not validators as of height *h*, they + could all be lagging behind by that time. However, by the time *h* finishes and *h+1* begins, the + chain will halt until more than *2nh+1/3* of them have caught up and started consensus + at height *h+1*. If set *valseth+1* does not change in *h+2* and subsequent + heights, only up to *nh+1/3* validators will be able to lag behind. Thus, we have + converted this case into case (h) below. + +- **(e)** *Enough validators crash to block the rest*. + + In this case, blockchain progress halts, i.e. surviving full nodes keep increasing rounds + indefinitely, until some of the crashed validators are able to recover. + Those validators that recover first will handshake with the Application and recover at the height + they crashed, which is still the same the nodes that did not crash are stuck in, so they don't need + to catch up. + Further, they had persisted the extended commit for the previous height. Nothing to solve. + + For those validators recovering later, we are in case (h) below. + +- **(f)** *Some validators crash, but not enough to block progress*. + + When the correct processes that crashed recover, they handshake with the Application and resume at + the height they were at when they crashed. As the blockchain did not stop making progress, the + recovered processes are likely to have fallen behind with respect to the progressing majority. + + At this point, the recovered processes are in case (h) below. + +- **(g)** *A new full node starts*. + + The reasoning here also applies to the case when more than one full node are starting. + When the full node starts from scratch, it has no state (its current height is 0). Ignoring + statesync for the time being, the node just needs to catch up by applying past blocks one by one + (after verifying them). + + Thus, the node is in case (h) below. + +- **(h)** *Advancing majority, lagging minority* + + In this case, some nodes are late. More precisely, at the present time, a set of full nodes, + denoted *Lhp*, are falling behind + (e.g., temporary disconnection or network partition, memory thrashing, crashes, new nodes) + an arbitrary + number of heights: + between *hs* and *hp*, where *hs < hp*, and + *hp* is the highest height + any correct full node has reached so far. + + The correct full nodes that reached *hp* were able to decide for *hp-1*. + Therefore, less than *nhp-1/3* validators of *hp-1* can be part + of *Lhp*, since enough up-to-date validators needed to actively participate + in consensus for *hp-1*. + + Since, at the present time, + no node in *Lhp* took part in any consensus between + *hs* and *hp-1*, + the reasoning above can be extended to validator set changes between *hs* and + *hp-1*. This results in the following restriction on the full nodes that can be part of *Lhp*. + + - ∀ *h*, where *hs ≤ h < hp*, + | *valseth* ∩ *Lhp* | *< nh/3* + + If this property does not hold for a particular height *h*, where + *hs ≤ h < hp*, Tendermint could not have progressed beyond *h* and + therefore no full node could have reached *hp* (a contradiction). + + These lagging nodes in *Lhp* need to catch up. They have to obtain the + information needed to make + progress from other nodes. For each height *h* between *hs* and *hp-2*, + this includes the decided block for *h*, and the + precommit votes also for *deciding h* (which can be extracted from the block at height *h+1*). + + At a given height *hc* (where possibly *hc << hp*), + a full node in *Lhp* will consider itself *caught up*, based on the + (maybe out of date) information it is getting from its peers. Then, the node needs to be ready to + propose at height *hc+1*, which requires having received the vote extensions for + *hc*. + As the vote extensions are *not* stored in the blocks, and it is difficult to have strong + guarantees on *when* a late node considers itself caught up, providing the late node with the right + vote extensions for the right height poses a problem. + +At this point, we have described and compared all cases raised in discussions leading up to this +RFC. The list above aims at being exhaustive. The analysis of each case included above makes all of +them converge into case (h). + +### Current Catch-up Mechanisms + +We now briefly describe the current catch-up mechanisms in the reactors concerned in Tendermint. + +#### Statesync + +Full nodes optionally run statesync just after starting, when they start from scratch. +If statesync succeeds, an Application snapshot is installed, and Tendermint jumps from height 0 directly +to the height the Application snapshop represents, without applying the block of any previous height. +Some light blocks are received and stored in the block store for running light-client verification of +all the skipped blocks. Light blocks are incomplete blocks, typically containing the header and the +canonical commit but, e.g., no transactions. They are stored in the block store as "signed headers". + +The statesync reactor is not really relevant for solving the problem discussed in this RFC. We will +nevertheless mention it when needed; in particular, to understand some corner cases. + +#### Blocksync + +The blocksync reactor kicks in after start up or recovery (and, optionally, after statesync is done) +and sends the following messages to its peers: + +- `StatusRequest` to query the height its peers are currently at, and +- `BlockRequest`, asking for blocks of heights the local node is missing. + +Using `BlockResponse` messages received from peers, the blocksync reactor validates each received +block using the block of the following height, saves the block in the block store, and sends the +block to the Application for execution. + +If blocksync has validated and applied the block for the height *previous* to the highest seen in +a `StatusResponse` message, or if no progress has been made after a timeout, the node considers +itself as caught up and switches to the consensus reactor. + +#### Consensus Reactor + +The consensus reactor runs the full Tendermint algorithm. For a validator this means it has to +propose blocks, and send/receive prevote/precommit messages, as mandated by Tendermint, before it can +decide and move on to the next height. + +If a full node that is running the consensus reactor falls behind at height *h*, when a peer node +realises this it will retrieve the canonical commit of *h+1* from the block store, and *convert* +it into a set of precommit votes and will send those to the late node. + +## Discussion + +### Solutions Proposed + +These are the solutions proposed in discussions leading up to this RFC. + +- **Solution 0.** *Vote extensions are made **best effort** in the specification*. + + This is the simplest solution, considered as a way to provide vote extensions in a simple enough + way so that it can be part of v0.36. + It consists in changing the specification so as to not *require* that precommit votes used upon + `PrepareProposal` contain their corresponding vote extensions. In other words, we render vote + extensions optional. + There are strong implications stemming from such a relaxation of the original specification. + + - As a vote extension is signed *separately* from the vote it is extending, an intermediate node + can now remove (i.e., censor) vote extensions from precommit messages at will. + - Further, there is no point anymore in the spec requiring the Application to accept a vote extension + passed via `VerifyVoteExtension` to consider a precommit message valid in its entirety. Remember + this behavior of `VerifyVoteExtension` is adding a constraint to Tendermint's conditions for + liveness. + In this situation, it is better and simpler to just drop the vote extension rejected by the + Application via `VerifyVoteExtension`, but still consider the precommit vote itself valid as long + as its signature verifies. + +- **Solution 1.** *Include vote extensions in the blockchain*. + + Another obvious solution, which has somehow been considered in the past, is to include the vote + extensions and their signatures in the blockchain. + The blockchain would thus include the extended commit, rather than a regular commit, as the structure + to be canonicalized in the next block. + With this solution, the current mechanisms implemented both in the blocksync and consensus reactors + would still be correct, as all the information a node needs to catch up, and to start proposing when + it considers itself as caught-up, can now be recovered from past blocks saved in the block store. + + This solution has two main drawbacks. + + - As the block format must change, upgrading a chain requires a hard fork. Furthermore, + all existing light client implementations will stop working until they are upgraded to deal with + the new format (e.g., how certain hashes calculated and/or how certain signatures are checked). + For instance, let us consider IBC, which relies on light clients. An IBC connection between + two chains will be broken if only one chain upgrades. + - The extra information (i.e., the vote extensions) that is now kept in the blockchain is not really + needed *at every height* for a late node to catch up. + - This information is only needed to be able to *propose* at the height the validator considers + itself as caught-up. If a validator is indeed late for height *h*, it is useless (although + correct) for it to call `PrepareProposal`, or `ExtendVote`, since the block is already decided. + - Moreover, some use cases require pretty sizeable vote extensions, which would result in an + important waste of space in the blockchain. + +- **Solution 2.** *Skip* propose *step in Tendermint algorithm*. + + This solution consists in modifying the Tendermint algorithm to skip the *send proposal* step in + heights where the node does not have the required vote extensions to populate the call to + `PrepareProposal`. The main idea behind this is that it should only happen when the validator is late + and, therefore, up-to-date validators have already proposed (and decided) for that height. + A small variation of this solution is, rather than skipping the *send proposal* step, the validator + sends a special *empty* or *bottom* (⊥) proposal to signal other nodes that it is not ready to propose + at (any round of) the current height. + + The appeal of this solution is its simplicity. A possible implementation does not need to extend + the data structures, or change the current catch-up mechanisms implemented in the blocksync or + in the consensus reactor. When we lack the needed information (vote extensions), we simply rely + on another correct validator to propose a valid block in other rounds of the current height. + + However, this solution can be attacked by a byzantine node in the network in the following way. + Let us consider the following scenario: + + - all validators in *valseth* send out precommit messages, with vote extensions, + for height *h*, round 0, roughly at the same time, + - all those precommit messages contain non-`nil` precommit votes, which vote for block *b* + - all those precommit messages sent in height *h*, round 0, and all messages sent in + height *h*, round *r > 0* get delayed indefinitely, so, + - all validators in *valseth* keep waiting for enough precommit + messages for height *h*, round 0, needed for deciding in height *h* + - an intermediate (malicious) full node *m* manages to receive block *b*, and gather more than + *2nh/3* precommit messages for height *h*, round 0, + - one way or another, the solution should have either (a) a mechanism for a full node to *tell* + another full node it is late, or (b) a mechanism for a full node to conclude it is late based + on other full nodes' messages; any of these mechanisms should, at the very least, + require the late node receiving the decided block and a commit (not necessarily an extended + commit) for *h*, + - node *m* uses the gathered precommit messages to build a commit for height *h*, round 0, + - in order to convince full nodes that they are late, node *m* either (a) *tells* them they + are late, or (b) shows them it (i.e. *m*) is ahead, by sending them block *b*, along with the + commit for height *h*, round 0, + - all full nodes conclude they are late from *m*'s behavior, and use block *b* and the commit for + height *h*, round 0, to decide on height *h*, and proceed to height *h+1*. + + At this point, *all* full nodes, including all validators in *valseth+1*, have advanced + to height *h+1* believing they are late, and so, expecting the *hypothetical* leading majority of + validators in *valseth+1* to propose for *h+1*. As a result, the blockhain + grinds to a halt. + A (rather complex) ad-hoc mechanism would need to be carried out by node operators to roll + back all validators to the precommit step of height *h*, round *r*, so that they can regenerate + vote extensions (remember vote extensions are non-deterministic) and continue execution. + +- **Solution 3.** *Require extended commits to be available at switching time*. + + This one is more involved than all previous solutions, and builds on an idea present in Solution 2: + vote extensions are actually not needed for Tendermint to make progress as long as the + validator is *certain* it is late. + + We define two modes. The first is denoted *catch-up mode*, and Tendermint only calls + `FinalizeBlock` for each height when in this mode. The second is denoted *consensus mode*, in + which the validator considers itself up to date and fully participates in consensus and calls + `PrepareProposal`/`ProcessProposal`, `ExtendVote`, and `VerifyVoteExtension`, before calling + `FinalizeBlock`. + + The catch-up mode does not need vote extension information to make progress, as all it needs is the + decided block at each height to call `FinalizeBlock` and keep the state-machine replication making + progress. The consensus mode, on the other hand, does need vote extension information when + starting every height. + + Validators are in consensus mode by default. When a validator in consensus mode falls behind + for whatever reason, e.g. cases (b), (d), (e), (f), (g), or (h) above, we introduce the following + key safety property: + + - for every height *hp*, a full node *f* in *hp* refuses to switch to catch-up + mode **until** there exists a height *h'* such that: + - *p* has received and (light-client) verified the blocks of + all heights *h*, where *hp ≤ h ≤ h'* + - it has received an extended commit for *h'* and has verified: + - the precommit vote signatures in the extended commit + - the vote extension signatures in the extended commit: each is signed with the same + key as the precommit vote it extends + + If the condition above holds for *hp*, namely receiving a valid sequence of blocks in + the *f*'s future, and an extended commit corresponding to the last block in the sequence, then + node *f*: + + - switches to catch-up mode, + - applies all blocks between *hp* and *h'* (calling `FinalizeBlock` only), and + - switches back to consensus mode using the extended commit for *h'* to propose in the rounds of + *h' + 1* where it is the proposer. + + This mechanism, together with the invariant it uses, ensures that the node cannot be attacked by + being fed a block without extensions to make it believe it is late, in a similar way as explained + for Solution 2. + +### Feasibility of the Proposed Solutions + +Solution 0, besides the drawbacks described in the previous section, provides guarantees that are +weaker than the rest. The Application does not have the assurance that more than *2nh/3* vote +extensions will *always* be available when calling `PrepareProposal` at height *h+1*. +This level of guarantees is probably not strong enough for vote extensions to be useful for some +important use cases that motivated them in the first place, e.g., encrypted mempool transactions. + +Solution 1, while being simple in that the changes needed in the current Tendermint codebase would +be rather small, is changing the block format, and would therefore require all blockchains using +Tendermint v0.35 or earlier to hard-fork when upgrading to v0.36. + +Since Solution 2 can be attacked, one might prefer Solution 3, even if it is more involved +to implement. Further, we must elaborate on how we can turn Solution 3, described in abstract +terms in the previous section, into a concrete implementation compatible with the current +Tendermint codebase. + +### Current Limitations and Possible Implementations + +The main limitations affecting the current version of Tendermint are the following. + +- The current version of the blocksync reactor does not use the full + [light client verification](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/light-client/README.md) + algorithm to validate blocks coming from other peers. +- The code being structured into the blocksync and consensus reactors, only switching from the + blocksync reactor to the consensus reactor is supported; switching in the opposite direction is + not supported. Alternatively, the consensus reactor could have a mechanism allowing a late node + to catch up by skipping calls to `PrepareProposal`/`ProcessProposal`, and + `ExtendVote`/`VerifyVoteExtension` and only calling `FinalizeBlock` for each height. + Such a mechanism does not exist at the time of writing this RFC. + +The blocksync reactor featuring light client verification is being actively worked on (tentatively +for v0.37). So it is best if this RFC does not try to delve into that problem, but just makes sure +its outcomes are compatible with that effort. + +In subsection [Cases to Address](#cases-to-address), we concluded that we can focus on +solving case (h) in theoretical terms. +However, as the current Tendermint version does not yet support switching back to blocksync once a +node has switched to consensus, we need to split case (h) into two cases. When a full node needs to +catch up... + +- **(h.1)** ... it has not switched yet from the blocksync reactor to the consensus reactor, or + +- **(h.2)** ... it has already switched to the consensus reactor. + +This is important in order to discuss the different possible implementations. + +#### Base Implementation: Persist and Propagate Extended Commit History + +In order to circumvent the fact that we cannot switch from the consensus reactor back to blocksync, +rather than just keeping the few most recent extended commits, nodes will need to keep +and gossip a backlog of extended commits so that the consensus reactor can still propose and decide +in out-of-date heights (even if those proposals will be useless). + +The base implementation - for which an experimental patch exists - consists in the conservative +approach of persisting in the block store *all* extended commits for which we have also stored +the full block. Currently, when statesync is run at startup, it saves light blocks. +This base implementation does not seek +to receive or persist extended commits for those light blocks as they would not be of any use. + +Then, we modify the blocksync reactor so that peers *always* send requested full blocks together +with the corresponding extended commit in the `BlockResponse` messages. This guarantees that the +block store being reconstructed by blocksync has the same information as that of peers that are +up to date (at least starting from the latest snapshot applied by statesync before starting blocksync). +Thus, blocksync has all the data it requires to switch to the consensus reactor, as long as one of +the following exit conditions are met: + +- The node is still at height 0 (where no commit or extended commit is needed) +- The node has processed at least 1 block in blocksync + +The second condition is needed in case the node has installed an Application snapshot during statesync. +If that is the case, at the time blocksync starts, the block store only has the data statesync has saved: +light blocks, and no extended commits. +Hence we need to blocksync at least one block from another node, which will be sent with its corresponding extended commit, before we can switch to consensus. + +As a side note, a chain might be started at a height *hi > 0*, all other heights +*h < hi* being non-existent. In this case, the chain is still considered to be at height 0 before +block *hi* is applied, so the first condition above allows the node to switch to consensus even +if blocksync has not processed any block (which is always the case if all nodes are starting from scratch). + +When a validator falls behind while having already switched to the consensus reactor, a peer node can +simply retrieve the extended commit for the required height from the block store and reconstruct a set of +precommit votes together with their extensions and send them in the form of precommit messages to the +validator falling behind, regardless of whether the peer node holds the extended commit because it +actually participated in that consensus and thus received the precommit messages, or it received the extended commit via a `BlockResponse` message while running blocksync. + +This solution requires a few changes to the consensus reactor: + +- upon saving the block for a given height in the block store at decision time, save the + corresponding extended commit as well +- in the catch-up mechanism, when a node realizes that another peer is more than 2 heights + behind, it uses the extended commit (rather than the canoncial commit as done previously) to + reconstruct the precommit votes with their corresponding extensions + +The changes to the blocksync reactor are more substantial: + +- the `BlockResponse` message is extended to include the extended commit of the same height as + the block included in the response (just as they are stored in the block store) +- structure `bpRequester` is likewise extended to hold the received extended commits coming in + `BlockResponse` messages +- method `PeekTwoBlocks` is modified to also return the extended commit corresponding to the first block +- when successfully verifying a received block, the reactor saves its corresponding extended commit in + the block store + +The two main drawbacks of this base implementation are: + +- the increased size taken by the block store, in particular with big extensions +- the increased bandwith taken by the new format of `BlockResponse` + +#### Possible Optimization: Pruning the Extended Commit History + +If we cannot switch from the consensus reactor back to the blocksync reactor we cannot prune the extended commit backlog in the block store without sacrificing the implementation's correctness. The asynchronous +nature of our distributed system model allows a process to fall behing an arbitrary number of +heights, and thus all extended commits need to be kept *just in case* a node that late had +previously switched to the consensus reactor. + +However, there is a possibility to optimize the base implementation. Every time we enter a new height, +we could prune from the block store all extended commits that are more than *d* heights in the past. +Then, we need to handle two new situations, roughly equivalent to cases (h.1) and (h.2) described above. + +- (h.1) A node starts from scratch or recovers after a crash. In thisy case, we need to modify the + blocksync reactor's base implementation. + - when receiving a `BlockResponse` message, it MUST accept that the extended commit set to `nil`, + - when sending a `BlockResponse` message, if the block store contains the extended commit for that + height, it MUST set it in the message, otherwise it sets it to `nil`, + - the exit conditions used for the base implementation are no longer valid; the only reliable exit + condition now consists in making sure that the last block processed by blocksync was received with + the corresponding commit, and not `nil`; this extended commit will allow the node to switch from + the blocksync reactor to the consensus reactor and immediately act as a proposer if required. +- (h.2) A node already running the consensus reactor falls behind beyond *d* heights. In principle, + the node will be stuck forever as no other node can provide the vote extensions it needs to make + progress (they all have pruned the corresponding extended commit). + However we can manually have the node crash and recover as a workaround. This effectively converts + this case into (h.1). + +### Formalization Work + +A formalization work to show or prove the correctness of the different use cases and solutions +presented here (and any other that may be found) needs to be carried out. +A question that needs a precise answer is how many extended commits (one?, two?) a node needs +to keep in persistent memory when implementing Solution 3 described above without Tendermint's +current limitations. +Another important invariant we need to prove formally is that the set of vote extensions +required to make progress will always be held somewhere in the network. + +## References + +- [ABCI++ specification](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/abci%2B%2B/README.md) +- [ABCI as of v0.35](https://github.com/tendermint/spec/blob/4fb99af/spec/abci/README.md) +- [Vote extensions issue](https://github.com/tendermint/tendermint/issues/8174) +- [Light client verification](https://github.com/tendermint/tendermint/blob/4743a7ad0/spec/light-client/README.md) diff --git a/docs/rfc/rfc-018-bls-agg-exploration.md b/docs/rfc/rfc-018-bls-agg-exploration.md new file mode 100644 index 000000000..70ca171a0 --- /dev/null +++ b/docs/rfc/rfc-018-bls-agg-exploration.md @@ -0,0 +1,555 @@ +# RFC 018: BLS Signature Aggregation Exploration + +## Changelog + +- 01-April-2022: Initial draft (@williambanfield). +- 15-April-2022: Draft complete (@williambanfield). + +## Abstract + +## Background + +### Glossary + +The terms that are attached to these types of cryptographic signing systems +become confusing quickly. Different sources appear to use slightly different +meanings of each term and this can certainly add to the confusion. Below is +a brief glossary that may be helpful in understanding the discussion that follows. + +* **Short Signature**: A signature that does not vary in length with the +number of signers. +* **Multi-Signature**: A signature generated over a single message +where, given the message and signature, a verifier is able to determine that +all parties signed the message. May be short or may vary with the number of signers. +* **Aggregated Signature**: A _short_ signature generated over messages with +possibly different content where, given the messages and signature, a verifier +should be able to determine that all the parties signed the designated messages. +* **Threshold Signature**: A _short_ signature generated from multiple signers +where, given a message and the signature, a verifier is able to determine that +a large enough share of the parties signed the message. The identities of the +parties that contributed to the signature are not revealed. +* **BLS Signature**: An elliptic-curve pairing-based signature system that +has some nice properties for short multi-signatures. May stand for +*Boneh-Lynn-Schacham* or *Barreto-Lynn-Scott* depending on the context. A +BLS signature is type of signature scheme that is distinct from other forms +of elliptic-curve signatures such as ECDSA and EdDSA. +* **Interactive**: Cryptographic scheme where parties need to perform one or +more request-response cycles to produce the cryptographic material. For +example, an interactive signature scheme may require the signer and the +verifier to cooperate to create and/or verify the signature, rather than a +signature being created ahead of time. +* **Non-interactive**: Cryptographic scheme where parties do not need to +perform any request-response cycles to produce the cryptographic material. + +### Brief notes on pairing-based elliptic-curve cryptography + +Pairing-based elliptic-curve cryptography is quite complex and relies on several +types of high-level math. Cryptography, in general, relies on being able to find +problems with an asymmetry between the difficulty of calculating the solution +and verifying that a given solution is correct. + +Pairing-based cryptography works by operating on mathematical functions that +satisfy the property of **bilinear mapping**. This property is satisfied for +functions `e` with values `P`, `Q`, `R` and `S` where `e(P, Q + R) = e(P, Q) * e(P, R)` +and `e(P + S, Q) = e(P, Q) * e(S, Q)`. The most familiar example of this is +exponentiation. Written in common notation, `g^P*(Q+R) = g^(P*Q) * g^(P*R)` for +some value `g`. + +Pairing-based elliptic-curve cryptography creates a bilinear mapping using +elliptic curves over a finite field. With some original curve, you can define two groups, +`G1` and `G2` which are points of the original curve _modulo_ different values. +Finally, you define a third group `Gt`, where points from `G1` and `G2` satisfy +the property of bilinearity with `Gt`. In this scheme, the function `e` takes +as inputs points in `G1` and `G2` and outputs values in `Gt`. Succintly, given +some point `P` in `G1` and some point `Q` in `G1`, `e(P, Q) = C` where `C` is in `Gt`. +You can efficiently compute the mapping of points in `G1` and `G2` into `Gt`, +but you cannot efficiently determine what points were summed and paired to +produce the value in `Gt`. + +Functions are then defined to map digital signatures, messages, and keys into +and out of points of `G1` or `G2` and signature verification is the process +of calculating if a set of values representing a message, public key, and digital +signature produce the same value in `Gt` through `e`. + +Signatures can be created as either points in `G1` with public keys being +created as points in `G2` or vice versa. For the case of BLS12-381, the popular +curve used, points in `G1` are represented with 48 bytes and points in `G2` are +represented with 96 bytes. It is up to the implementer of the cryptosystem to +decide which should be larger, the public keys or the signatures. + +BLS signatures rely on pairing-based elliptic-curve cryptography to produce +various types of signatures. For a more in-depth but still high level discussion +pairing-based elliptic-curve cryptography, see Vitalik Buterin's post on +[Exploring Elliptic Curve Pairings][vitalik-pairing-post]. For much more in +depth discussion, see the specific paper on BLS12-381, [Short signatures from + the Weil Pairing][bls-weil-pairing] and +[Compact Multi-Signatures for Smaller Blockchains][multi-signatures-smaller-blockchains]. + +### Adoption + +BLS signatures have already gained traction within several popular projects. + +* Algorand is working on an implementation. +* [Zcash][zcash-adoption] has adopted BLS12-381 into the protocol. +* [Ethereum 2.0][eth-2-adoption] has adopted BLS12-381 into the protocol. +* [Chia Network][chia-adoption] has adopted BLS for signing blocks. +* [Ostracon][line-ostracon-pr], a fork of Tendermint has adopted BLS for signing blocks. + +### What systems may be affected by adding aggregated signatures? + +#### Gossip + +Gossip could be updated to aggregate vote signatures during a consensus round. +This appears to be of frankly little utility. Creating an aggregated signature +incurs overhead, so frequently re-aggregating may incur a significant +overhead. How costly this is is still subject to further investigation and +performance testing. + +Even if vote signatures were aggregated before gossip, each validator would still +need to receive and verify vote extension data from each (individual) peer validator in +order for consensus to proceed. That displaces any advantage gained by aggregating signatures across the vote message in the presence of vote extensions. + +#### Block Creation + +When creating a block, the proposer may create a small set of short +multi-signatures and attach these to the block instead of including one +signature per validator. + +#### Block Verification + +Currently, we verify each validator signature using the public key associated +with that validator. With signature aggregation, verification of blocks would +not verify many signatures individually, but would instead check the (single) +multi-signature using the public keys stored by the validator. This would also +require a mechanism for indicating which validators are included in the +aggregated signature. + +#### IBC Relaying + +IBC would no longer need to transmit a large set of signatures when +updating state. These state updates do not happen for every IBC packet, only +when changing an IBC light client's view of the counterparty chain's state. +General [IBC packets][ibc-packet] only contain enough information to correctly +route the data to the counterparty chain. + +IBC does persist commit signatures to the chain in these `MsgUpdateClient` +message when updating state. This message would no longer need the full set +of unique signatures and would instead only need one signature for all of the +data in the header. + +Adding BLS signatures would create a new signature type that must be +understood by the IBC module and by the relayers. For some operations, such +as state updates, the set of data written into the chain and received by the +IBC module could be slightly smaller. + +## Discussion + +### What are the proposed benefits to aggregated signatures? + +#### Reduce Block Size + +At the moment, a commit contains a 64-byte (512-bit) signature for each validator +that voted for the block. For the Cosmos Hub, which has 175 validators in the +active set, this amounts to about 11 KiB per block. That gives an upper bound of +around 113 GiB over the lifetime of the chain's 10.12M blocks. (Note, the Hub has +increased the number of validators in the active set over time so the total +signature size over the history of the chain is likely somewhat less than that). + +Signature aggregation would only produce two signatures for the entire block. +One for the yeas and one for the nays. Each BLS aggregated signature is 48 +bytes, per the [IETF standard of BLS signatures][bls-ietf-ecdsa-compare]. +Over the lifetime of the same Cosmos Hub chain, that would amount to about 1 +GB, a savings of 112 GB. While that is a large factor of reduction it's worth +bearing in mind that, at [GCP's cost][gcp-storage-pricing] of $.026 USD per GB, +that is a total savings of around $2.50 per month. + +#### Reduce Signature Creation and Verification Time + +From the [IETF draft standard on BLS Signatures][bls-ietf], BLS signatures can be +created in 370 microseconds and verified in 2700 microseconds. Our current +[Ed25519 implementation][voi-ed25519-perf] was benchmarked locally to take +13.9 microseconds to produce a signature and 2.03 milliseconds to batch verify +128 signatures, which is slightly fewer than the 175 in the Hub. blst, a popular +implementation of BLS signature aggregation was benchmarked to perform verification +on 100 signatures in 1.5 milliseconds [when run locally][blst-verify-bench] +on an 8 thread machine and pre-aggregated public keys. It is worth noting that +the `ed25519` library verification time grew steadily with the number of signatures, +whereas the bls library verification time remains constant. This is because the +number of operations used to verify a signature does not grow at all with the +number of signatures included in the aggregate signature (as long as the signers +signed over the same message data as is the case in Tendermint). + +It is worth noting that this would also represent a _degredation_ in signature +verification time for chains with small validator sets. When batch verifying +only 32 signatures, our ed25519 library takes .57 milliseconds, whereas BLS +would still require the same 1.5 milliseconds. + +For massive validator sets, blst dominates, taking the same 1.5 milliseconds to +check an aggregated signature from 1024 validators versus our ed25519 library's +13.066 milliseconds to batch verify a set of that size. + +#### Reduce Light-Client Verification Time + +The light client aims to be a faster and lighter-weight way to verify that a +block was voted on by a Tendermint network. The light client fetches +Tendermint block headers and commit signatures, performing public key +verification to ensure that the associated validator set signed the block. +Reducing the size of the commit signature would allow the light client to fetch +block data more quickly. + +Additionally, the faster signature verification times of BLS signatures mean +that light client verification would proceed more quickly. + +However, verification of an aggregated signature is all-or-nothing. The verifier +cannot check that some singular signer had a signature included in the block. +Instead, the verifier must use all public keys to check if some signature +was included. This does mean that any light client implementation must always +be able to fetch all public keys for any height instead of potentially being +able to check if some singular validator's key signed the block. + +#### Reduce Gossip Bandwidth + +##### Vote Gossip + +It is possible to aggregate subsets of signatures during voting, so that the +network need not gossip all *n* validator signatures to all *n* validators. +Theoretically, subsets of the signatures could be aggregated during consensus +and vote messages could carry those aggregated signatures. Implementing this +would certainly increase the complexity of the gossip layer but could possibly +reduce the total number of signatures required to be verified by each validator. + +##### Block Gossip + +A reduction in the block size as a result of signature aggregation would +naturally lead to a reduction in the bandwidth required to gossip a block. +Each validator would only send and receive the smaller aggregated signatures +instead of the full list of multi-signatures as we have them now. + +### What are the drawbacks to aggregated signatures? + +#### Heterogeneous key types cannot be aggregated + +Aggregation requires a specific signature algorithm, and our legacy signing schemes +cannot be aggregated. In practice, this means that aggregated signatures could +be created for a subset of validators using BLS signatures, and validators +with other key types (such as Ed25519) would still have to be be separately +propagated in blocks and votes. + +#### Many HSMs do not support aggregated signatures + +**Hardware Signing Modules** (HSM) are a popular way to manage private keys. +They provide additional security for key management and should be used when +possible for storing highly sensitive private key material. + +Below is a list of popular HSMs along with their support for BLS signatures. + +* YubiKey + * [No support][yubi-key-bls-support] +* Amazon Cloud HSM + * [No support][cloud-hsm-support] +* Ledger + * [Lists support for the BLS12-381 curve][ledger-bls-announce] + +I cannot find support listed for Google Cloud, although perhaps it exists. + +## Feasibility of implementation + +This section outlines the various hurdles that would exist to implementing BLS +signature aggregation into Tendermint. It aims to demonstrate that we _could_ +implement BLS signatures but that it would incur risk and require breaking changes for a +reasonably unclear benefit. + +### Can aggregated signatures be added as soft-upgrades? + +In my estimation, yes. With the implementation of proposer-based timestamps, +all validators now produce signatures on only one of two messages: + +1. A [CanonicalVote][canonical-vote-proto] where the BlockID is the hash of the block or +2. A `CanonicalVote` where the `BlockID` is nil. + +The block structure can be updated to perform hashing and validation in a new +way as a soft upgrade. This would look like adding a new section to the [Block.Commit][commit-proto] structure +alongside the current `Commit.Signatures` field. This new field, tentatively named +`AggregatedSignature` would contain the following structure: + +```proto +message AggregatedSignature { + // yeas is a BitArray representing which validators in the active validator + // set issued a 'yea' vote for the block. + tendermint.libs.bits.BitArray yeas = 1; + + // absent is a BitArray representing which validators in the active + // validator set did not issue votes for the block. + tendermint.libs.bits.BitArray absent = 2; + + // yea_signature is an aggregated signature produced from all of the vote + // signatures for the block. + repeated bytes yea_signature = 3; + + // nay_signature is an aggregated signature produced from all of the vote + // signatures from votes for 'nil' for this block. + // nay_signature should be made from all of the validators that were both not + // in the 'yeas' BitArray and not in the 'absent' BitArray. + repeated bytes nay_signature = 4; +} +``` + +Adding this new field as a soft upgrade would mean hashing this data structure +into the blockID along with the old `Commit.Signatures` when both are present +as well as ensuring that the voting power represented in the new +`AggregatedSignature` and `Signatures` field was enough to commit the block +during block validation. One can certainly imagine other possible schemes for +implementing this but the above should serve as a simple enough proof of concept. + +### Implementing vote-time and commit-time signature aggregation separately + +Implementing aggregated BLS signatures as part of the block structure can easily be +achieved without implementing any 'vote-time' signature aggregation. +The block proposer would gather all of the votes, complete with signatures, +as it does now, and produce a set of aggregate signatures from all of the +individual vote signatures. + +Implementing 'vote-time' signature aggregation cannot be achieved without +also implementing commit-time signature aggregation. This is because such +signatures cannot be dis-aggregated into their constituent pieces. Therefore, +in order to implement 'vote-time' signature aggregation, we would need to +either first implement 'commit-time' signature aggregation, or implement both +'vote-time' signature aggregation while also updating the block creation and +verification protocols to allow for aggregated signatures. + +### Updating IBC clients + +In order for IBC clients to function, they must be able to perform light-client +verification of blocks on counterparty chains. Because BLS signatures are not +currently part of light-clients, chains that transmit messages over IBC +cannot update to using BLS signatures without their counterparties first +being upgraded to parse and verify BLS. If chains upgrade without their +counterparties first updating, they will lose the ability to interoperate with +non-updated chains. + +### New attack surfaces + +BLS signatures and signature aggregation comes with a new set of attack surfaces. +Additionally, it's not clear that all possible major attacks are currently known +on the BLS aggregation schemes since new ones have been discovered since the ietf +draft standard was written. The known attacks are manageable and are listed below. +Our implementation would need to prevent against these but this does not appear +to present a significant hurdle to implementation. + +#### Rogue key attack prevention + +Generating an aggregated signature requires guarding against what is called +a [rogue key attack][bls-ietf-terms]. A rogue key attack is one in which a +malicious actor can craft an _aggregate_ key that can produce signatures that +appear to include a signature from a private key that the malicious actor +does not actually know. In Tendermint terms, this would look like a Validator +producing a vote signed by both itself and some other validator where the other +validator did not actually produce the vote itself. + +The main mechanisms for preventing this require that each entity prove that it +can can sign data with just their private key. The options involve either +ensuring that each entity sign a _different_ message when producing every +signature _or_ producing a [proof of possession][bls-ietf-pop] (PoP) when announcing +their key to the network. + +A PoP is a message that demonstrates ownership of a private +key. A simple scheme for PoP is one where the entity announcing +its new public key to the network includes a digital signature over the bytes +of the public key generated using the associated private key. Everyone receiving +the public key and associated proof-of-possession can easily verify the +signature and be sure the entity owns the private key. + +This PoP scheme suits the Tendermint use case quite well since +validator keys change infrequently so the associated PoPs would not be onerous +to produce, verify, and store. Using this scheme allows signature verification +to proceed more quickly, since all signatures are over identical data and +can therefore be checked using an aggregated public key instead of one at a +time, public key by public key. + +#### Summing Zero Attacks + +[Summing zero attacks][summing-zero-paper] are attacks that rely on using the '0' point of an +elliptic curve. For BLS signatures, if the point 0 is chosen as the private +key, then the 0 point will also always be the public key and all signatures +produced by the key will also be the 0 point. This is easy enough to +detect when verifying each signature individually. + +However, because BLS signature aggregation creates an aggregated signature and +an aggregated public key, a set of colluding signers can create a pair or set +of signatures that are non-zero but which aggregate ("sum") to 0. The signatures that sum zero along with the +summed public key of the colluding signers will verify any message. This would +allow the colluding signers to sign any block or message with the same signature. +This would be reasonably easy to detect and create evidence for because, in +all other cases, the same signature should not verify more than message. It's +not exactly clear how such an attack would advantage the colluding validators +because the normal mechanisms of evidence gathering would still detect the +double signing, regardless of the signatures on both blocks being identical. + +### Backwards Compatibility + +Backwards compatibility is an important consideration for signature verification. +Specifically, it is important to consider whether chains using current versions +of IBC would be able to interact with chains adopting BLS. + +Because the `Block` shared by IBC and Tendermint is produced and parsed using +protobuf, new structures can be added to the Block without breaking the +ability of legacy users to parse the new structure. Breaking changes between +current users of IBC and new Tendermint blocks only occur if data that is +relied upon by the current users is no longer included in the current fields. + +For the case of BLS aggregated signatures, a new `AggregatedSignature` field +can therefore be added to the `Commit` field without breaking current users. +Current users will be broken when counterparty chains upgrade to the new version +and _begin using_ BLS signatures. Once counterparty chains begin using BLS +signatures, the BlockID hashes will include hashes of the `AggregatedSignature` +data structure that the legacy users will not be able to compute. Additionally, +the legacy software will not be able to parse and verify the signatures to +ensure that a supermajority of validators from the counterparty chain signed +the block. + +### Library Support + +Libraries for BLS signature creation are limited in number, although active +development appears to be ongoing. Cryptographic algorithms are difficult to +implement correctly and correctness issues are extremely serious and dangerous. +No further exploration of BLS should be undertaken without strong assurance of +a well-tested library with continuing support for creating and verifying BLS +signatures. + +At the moment, there is one candidate, `blst`, that appears to be the most +mature and well vetted. While this library is undergoing continuing auditing +and is supported by funds from the Ethereum foundation, adopting a new cryptographic +library presents some serious risks. Namely, if the support for the library were +to be discontinued, Tendermint may become saddled with the requirement of supporting +a very complex piece of software or force a massive ecosystem-wide migration away +from BLS signatures. + +This is one of the more serious reasons to avoid adopting BLS signatures at this +time. There is no gold standard library. Some projects look promising, but no +project has been formally verified with a long term promise of being supported +well into the future. + +#### Go Standard Library + +The Go Standard library has no implementation of BLS signatures. + +#### BLST + +[blst][blst], or 'blast' is an implementation of BLS signatures written in C +that provides bindings into Go as part of the repository. This library is +actively undergoing formal verification by Galois and previously received an +initial audit by NCC group, a firm I'd never heard of. + +`blst` is [targeted for use in prysm][prysm-blst], the golang implementation of Ethereum 2.0. + +#### Gnark-Crypto + +[Gnark-Crypto][gnark] is a Go-native implementation of elliptic-curve pairing-based +cryptography. It is not audited and is documented as 'as-is', although +development appears to be active so formal verification may be forthcoming. + +#### CIRCL + +[CIRCL][circl] is a go-native implementation of several cryptographic primitives, +bls12-381 among them. The library is written and maintained by Cloudflare and +appears to receive frequent contributions. However, it lists itself as experimental +and urges users to take caution before using it in production. + +### Added complexity to light client verification + +Implementing BLS signature aggregation in Tendermint would pose issues for the +light client. The light client currently validates a subset of the signatures +on a block when performing the verification algorithm. This is no longer possible +with an aggregated signature. Aggregated signature verification is all-or-nothing. +The light client could no longer check that a subset of validators from some +set of validators is represented in the signature. Instead, it would need to create +a new aggregated key with all the stated signers for each height it verified where +the validator set changed. + +This means that the speed advantages gained by using BLS cannot be fully realized +by the light client since the client needs to perform the expensive operation +of re-aggregating the public key. Aggregation is _not_ constant time in the +number of keys and instead grows linearly. When [benchmarked locally][blst-verify-bench-agg], +blst public key aggregation of 128 keys took 2.43 milliseconds. This, along with +the 1.5 milliseconds to verify a signature would raise light client signature +verification time to 3.9 milliseconds, a time above the previously mentioned +batch verification time using our ed25519 library of 2.0 milliseconds. + +Schemes to cache aggregated subsets of keys could certainly cut this time down at the +cost of adding complexity to the light client. + +### Added complexity to evidence handling + +Implementing BLS signature aggregation in Tendermint would add complexity to +the evidence handling within Tendermint. Currently, the light client can submit +evidence of a fork attempt to the chain. This evidence consists of the set of +validators that double-signed, including their public keys, with the conflicting +block. + +We can quickly check that the listed validators double signed by verifying +that each of their signatures are in the submitted conflicting block. A BLS +signature scheme would change this by requiring the light client to submit +the public keys of all of the validators that signed the conflicting block so +that the aggregated signature may be checked against the full signature set. +Again, aggregated signature verification is all-or-nothing, so without all of +the public keys, we cannot verify the signature at all. These keys would be +retrievable. Any party that wanted to create a fork would want to convince a +network that its fork is legitimate, so it would need to gossip the public keys. +This does not hamper the feasibility of implementing BLS signature aggregation +into Tendermint, but does represent yet another piece of added complexity to +the associated protocols. + +## Open Questions + +* *Q*: Can you aggregate Ed25519 signatures in Tendermint? + * There is a suggested scheme in github issue [7892][suggested-ed25519-agg], +but additional rigor would be required to fully verify its correctness. + +## Current Consideration + +Adopting a signature aggregation scheme presents some serious risks and costs +to the Tendermint project. It requires multiple backwards-incompatible changes +to the code, namely a change in the structure of the block and a new backwards-incompatible +signature and key type. It risks adding a new signature type for which new attack +types are still being discovered _and_ for which no industry standard, battle-tested +library yet exists. + +The gains boasted by this new signing scheme are modest: Verification time is +marginally faster and block sizes shrink by a few kilobytes. These are relatively +minor gains in exchange for the complexity of the change and the listed risks of the technology. +We should take a wait-and-see approach to BLS signature aggregation, monitoring +the up-and-coming projects and consider implementing it as the libraries and +standards develop. + +### References + +[line-ostracon-repo]: https://github.com/line/ostracon +[line-ostracon-pr]: https://github.com/line/ostracon/pull/117 +[mit-BLS-lecture]: https://youtu.be/BFwc2XA8rSk?t=2521 +[gcp-storage-pricing]: https://cloud.google.com/storage/pricing#north-america_2 +[yubi-key-bls-support]: https://github.com/Yubico/yubihsm-shell/issues/66 +[cloud-hsm-support]: https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-key-types.html +[bls-ietf]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04 +[bls-ietf-terms]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-1.3 +[bls-ietf-pop]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.3 +[multi-signatures-smaller-blockchains]: https://eprint.iacr.org/2018/483.pdf +[ibc-tendermint]: https://github.com/cosmos/ibc/tree/master/spec/client/ics-007-tendermint-client +[zcash-adoption]: https://github.com/zcash/zcash/issues/2502 +[chia-adoption]: https://github.com/Chia-Network/chia-blockchain#chia-blockchain +[bls-ietf-ecdsa-compare]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-1.1 +[voi-ed25519-perf]: https://github.com/williambanfield/curve25519-voi/blob/benchmark/primitives/ed25519/PERFORMANCE.txt#L79 +[blst-verify-bench]: https://github.com/williambanfield/blst/blame/bench/bindings/go/PERFORMANCE.md#L9 +[blst-verify-bench-agg]: https://github.com/williambanfield/blst/blame/bench/bindings/go/PERFORMANCE.md#L23 +[vitalik-pairing-post]: https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627 +[ledger-bls-announce]: https://www.ledger.com/first-ever-firmware-update-coming-to-the-ledger-nano-x +[commit-proto]: https://github.com/tendermint/tendermint/blob/be7cb50bb3432ee652f88a443e8ee7b8ef7122bc/proto/tendermint/types/types.proto#L121 +[canonical-vote-proto]: https://github.com/tendermint/tendermint/blob/be7cb50bb3432ee652f88a443e8ee7b8ef7122bc/spec/core/encoding.md#L283 +[blst]: https://github.com/supranational/blst +[prysm-blst]: https://github.com/prysmaticlabs/prysm/blob/develop/go.mod#L75 +[gnark]: https://github.com/ConsenSys/gnark-crypto/ +[eth-2-adoption]: https://notes.ethereum.org/@GW1ZUbNKR5iRjjKYx6_dJQ/Skxf3tNcg_ +[bls-weil-pairing]: https://www.iacr.org/archive/asiacrypt2001/22480516.pdf +[summing-zero-paper]: https://eprint.iacr.org/2021/323.pdf +[circl]: https://github.com/cloudflare/circl +[light-client-evidence]: https://github.com/tendermint/tendermint/blob/a6fd1fe20116d4b1f7e819cded81cece8e5c1ac7/types/evidence.go#L245 +[suggested-ed25519-agg]: https://github.com/tendermint/tendermint/issues/7892 diff --git a/docs/rfc/rfc-019-config-version.md b/docs/rfc/rfc-019-config-version.md new file mode 100644 index 000000000..3dfd7840b --- /dev/null +++ b/docs/rfc/rfc-019-config-version.md @@ -0,0 +1,400 @@ +# RFC 019: Configuration File Versioning + +## Changelog + +- 19-Apr-2022: Initial draft (@creachadair) +- 20-Apr-2022: Updates from review feedback (@creachadair) + +## Abstract + +Updating configuration settings is an essential part of upgrading an existing +node to a new version of the Tendermint software. Unfortunately, it is also +currently a very manual process. This document discusses some of the history of +changes to the config format, actions we've taken to improve the tooling for +configuration upgrades, and additional steps we may want to consider. + +## Background + +A Tendermint node reads configuration settings at startup from a TOML formatted +text file, typically named `config.toml`. The contents of this file are defined +by the [`github.com/tendermint/tendermint/config`][config-pkg]. + +Although many settings in this file remain valid from one version of Tendermint +to the next, new versions of Tendermint often add, update, and remove settings. +These changes often require manual intervention by operators who are upgrading +their nodes. + +I propose we should provide better tools and documentation to help operators +make configuration changes correctly during version upgrades. Ideally, as much +as possible of any configuration file update should be automated, and where +that is not possible or practical, we should provide clear, explicit directions +for what steps need to be taken manually. Moreover, when the node discovers +incorrect or invalid configuration, we should improve the diagnostics it emits +so that the operator can quickly and easily find the relevant documentation, +without having to grep through source code. + +## Discussion + +By convention, we are supposed to document required changes to the config file +in the `UPGRADING.md` file for the release that introduces them. Although we +have mostly done this, the level of detail in the upgrading instructions is +often insufficient for an operator to correctly update their file. + +The updates vary widely in complexity: Operators may need to add new required +settings, update obsolete values for existing settings, move or rename existing +settings within the file, or remove obsolete settings (which are thus invalid). +Here are a few examples of each of these cases: + +- **New required settings:** Tendermint v0.35 added a new top-level `mode` + setting that determines whether a node runs as a validator, a full node, or a + seed node. The default value is `"full"`, which means the operator of a + validator must manually add `mode = "validator"` (or set the `--mode` flag on + the command line) for their node to come up in the correct mode. + +- **Updated obsolete values:** Tendermint v0.35 removed support for versions + `"v1"` and `"v2"` of the blocksync (formerly "fastsync") protocol, requiring + any node using either of those values to update to `"v0"`. + +- **Moved/renamed settings:** Version v0.34 moved the top-level `pprof_laddr` + setting under the `[rpc]` section. + + Version v0.35 renamed every setting in the file from `snake_case` to + `kebab-case`, moved the top-level `fast_sync` setting into the `[blocksync]` + section as (itself renamed from `[fastsync]`), and moved all the top-level + `priv-validator-*` settings under a new `[priv-validator]` section with their + prefix trimmed off. + +- **Removed obsolete settings:** Version v0.34 removed the `index_all_keys` and + `index_keys` settings from the `[tx_index]` section; version v0.35 removed + the `wal-dir` setting from the `[mempool]` section, and version v0.36 removed + the `[blocksync]` section entirely. + +While many of these changes are mentioned in the config section of the upgrade +instructions, some are not mentioned at all, or are hidden in other parts of +the doc. For instance, the v0.34 `pprof_laddr` change was documented only as an +RPC flag change. (A savvy reader might realize that the flag `--rpc.pprof_laddr` +implies a corresponding config section, but it omits the related detail that +there was a top-level setting that's been renamed). The lesson here is not +that the docs are bad, but to point out that prose is not the most efficient +format to convey detailed changes like this. The upgrading instructions are +still valuable for the human reader to understand what to expect. + +### Concrete Steps + +As part of the v0.36 development cycle, we spent some time reverse-engineering +the configuration changes since the v0.34 release and built an experimental +command-line tool called [`confix`][confix], whose job it is to automatically +update the settings in a `config.toml` file to the latest version. We also +backported a version of this tool into the v0.35.x branch at release v0.35.4. + +This tool should work fine for configuration files created by Tendermint v0.34 +and later, but does not (yet) know how to handle changes from prior versions of +Tendermint. Part of the difficulty for older versions is simply logistical: To +figure out which changes to apply, we need to understand something about the +version that made the file, as well as the version we're converting it to. + +> **Discussion point:** In the future we might want to consider incorporating +> this into the node CLI directly, but we're keeping it separate for now until +> we can get some feedback from operators. + +For the experiment, we handled this by carefully searching the history of +config format changes for shibboleths to bound the version: For example, the +`[fastsync]` section was added in Tendermint v0.32 and renamed `[blocksync]` in +Tendermint v0.35. So if we see a `[fastsync]` section, we have some confidence +that the file was created by v0.32, v0.33, or v0.34. + +But such signals are delicate: The `[blocksync]` section was removed in v0.36, +so if we do not find `[fastsync]`, we cannot conclude from that alone that the +file is from v0.31 or earlier -- we have to look for corroborating details. +While such "sniffing" tactics are fine for an experiment, they aren't as robust +as we might like. + +This is especially relevant for configuration files that may have already been +manually upgraded across several versions by the time we are asked to update +them again. Another related concern is that we'd like to make sure conversion +is idempotent, so that it would be safe to rerun the tool over an +already-converted file without breaking anything. + +### Config Versioning + +One obvious tactic we could use for future releases is add a version marker to +the config file. This would give tools like `confix` (and the node itself) a +way to calibrate their expectations. Rather than being a version for the file +itself, however, this version marker would indicate which version of Tendermint +is needed to read the file. + +Provisionally, this might look something like: + +```toml +# THe minimum version of Tendermint compatible with the contents of +# this configuration file. +config-version = 'v0.35' +``` + +When initializing a new node, Tendermint would populate this field with its own +version (e.g., `v0.36`). When conducting an upgrade, tools like `confix` can +then use this to decide which conversions are valid, and then update the value +accordingly. After converting a file marked `'v0.35'` to`'v0.37'`, the +conversion tool sets the file's `config-version` to reflect its compatibility. + +> **Discussion point:** This example presumes we would keep config files +> compatible within a given release cycle, e.g., all of v0.36.x. We could also +> use patch numbers here, if we think there's some reason to permit changes +> that would require config file edits at that granularity. I don't think we +> should, but that's a design question to consider. + +Upon seeing an up-to-date version marker, the conversion tool can simply exit +with a diagnostic like "this file is already up-to-date", rather than sniffing +the keyspace and potentially introducing errors. In addition, this would let a +tool detect config files that are _newer_ than the one it understands, and +issue a safe diagnostic rather than doing something wrong. Plus, besides +avoiding potentially unsafe conversions, this would also serve as +human-readable documentation that the file is up-to-date for a given version. + +Adding a config version would not address the problem of how to convert files +created by older versions of Tendermint, but it would at least help us build +more robust config tooling going forward. + +### Stability and Change + +In light of the discussion so far, it is natural to examine why we make so many +changes to the configuration file from one version to the next, and whether we +could reduce friction by being more conservative about what we make +configurable, what config changes we make over time, and how we roll them out. + +Some changes, like renaming everything from snake case to kebab case, are +entirely gratuitous. We could safely agree not to make those kinds of changes. +Apart from that obvious case, however, many other configuration settings +provide value to node operators in cases where there is no simple, universal +setting that matches every application. + +Taking a high-level view, there are several broad reasons why we might want to +make changes to configuration settings: + +- **Lessons learned:** Configuration settings are a good way to try things out + in production, before making more invasive changes to the consensus protocol. + + For example, up until Tendermint v0.35, consensus timeouts were specified as + per-node configuration settings (e.g., `timeout-precommit` et al.). This + allowed operators to tune these values for the needs of their network, but + had the downside that individually-misconfigured nodes could stall consensus. + + Based on that experience, these timeouts have been deprecated in Tendermint + v0.36 and converted to consensus parameters, to be consistent across all + nodes in the network. + +- **Migration & experimentation:** Introducing new features and updating old + features can complicate migration for existing users of the software. + Temporary or "experimental" configuration settings can be a valuable way to + mitigate that friction. + + For example, Tendermint v0.36 introduces a new RPC event subscription + endpoint (see [ADR 075][adr075]) that will eventually replace the existing + webwocket-based interface. To give users time to migrate, v0.36 adds an + `experimental-disable-websocket` setting, defaulted to `false`, that allows + operators to selectively disable the websocket API for testing purposes + during the conversion. This setting is designed to be removed in v0.37, when + the old interface is no longer supported. + +- **Ongoing maintenance:** Sometimes configuration settings become obsolete, + and the cost of removing them trades off against the potential risks of + leaving a non-functional or deprecated knob hooked up indefinitely. + + For example, Tendermint v0.35 deprecated two alternate implementations of the + blocksync protocol, one of which was deleted entirely (`v1`) and one of which + was scheduled for removal (`v2`). The `blocksync.version` setting, which had + been added as a migration aid, became obsolete and needed to be updated. + + Despite our best intentions, sometimes engineering designs do not work out. + It's just as important to leave room to back out of changes we have since + reconsidered, as it is to support migrations forward onto new and improved + code. + +- **Clarity and legibility:** Besides configuring the software, another + important purpose of a config file is to document intent for the humans who + operate and maintain the software. Operators need adjust settings to keep the + node running, and developers need to know what options were in use when + something goes wrong so they can diagnose and fix bugs. The legibility of a + config file as a _human_ artifact is also thus important. + + For example, Tendermint v0.35 moved settings related to validator private + keys from the top-level section of the configuration file to their own + designated `[priv-validator]` section. Although this change did not make any + difference to the meaning of those settings, it made the organization of the + file easier to understand, and allowed the names of the individual settings + to be simplified (e.g., `priv-validator-key-file` became simply `key-file` in + the new section). + + Although such changes are "gratuitous" with respect to the software, there is + often value in making things more legible for the humans. While there is no + simple rule to define the line, the Potter Stewart principle can be used with + due care. + +Keeping these examples in mind, we can and should take reasonable steps to +avoid churn in the configuration file across versions where we can. However, we +must also accept that part of the reason for _having_ a config file is to allow +us flexibility elsewhere in the design. On that basis, we should not attempt +to be too dogmatic about config changes either. Unlike changes in the block +protocol, for example, which affect every user of every network that adopts +them, config changes are relatively self-contained. + +There are few guiding principles I think we can use to strike a sensible +balance: + +1. **No gratuitous changes.** Aesthetic changes that do not enhance legibility, + avert confusion, or clarity documentation, should be entirely avoided. + +2. **Prefer mechanical changes.** Whenever it is practical, change settings in + a way that can be updated by a tool without operator judgement. This implies + finding safe, universal defaults for new settings, and not changing the + default values of existing settings. + + Even if that means we have to make multiple changes (e.g., add a new setting + in the current version, deprecate the old one, and remove the old one in the + next version) it's preferable if we can mechanize each step. + +3. **Clearly signal intent.** When adding temporary or experimental settings, + they should be clearly named and documented as such. Use long names and + suggestive prefixes (e.g., `experimental-*`) so that they stand out when + read in the config file or printed in logs. + + Relatedly, using temporary or experimental settings should cause the + software to emit diagnostic logs at runtime. These log messages should be + easy to grep for, and should contain pointers to more complete documentation + (say, issue numbers or URLs) that the operator can read, as well as a hint + about when the setting is expected to become invalid. For example: + + ``` + WARNING: Websocket RPC access is deprecated and will be removed in + Tendermint v0.37. See https://tinyurl.com/adr075 for more information. + ``` + +4. **Consider both directions.** When adding a configuration setting, take some + time during the implementation process to think about how the setting could + be removed, as well as how it will be rolled out. This applies even for + settings we imagine should be permanent. Experience may cause is to rethink + our original design intent more broadly than we expected. + + This does not mean we have to spend a long time picking nits over the design + of every setting; merely that we should convince ourselves we _could_ undo + it without making too big a mess later. Even a little extra effort up front + can sometimes save a lot. + +## References + +- [Tendermint `config` package][config-pkg] +- [`confix` command-line tool][confix] +- [`condiff` command-line tool][condiff] +- [Configuration update plan][plan] +- [ADR 075: RPC Event Subscription Interface][adr075] + +[config-pkg]: https://godoc.org/github.com/tendermint/tendermint/config +[confix]: https://github.com/tendermint/tendermint/blob/master/scripts/confix +[condiff]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/condiff +[plan]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/plan.go +[testdata]: https://github.com/tendermint/tendermint/blob/master/scripts/confix/testdata +[adr075]: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-075-rpc-subscription.md + +## Appendix: Research Notes + +Discovering when various configuration settings were added, updated, and +removed turns out to be surprisingly tedious. To solve this puzzle, we had to +answer the following questions: + +1. What changes were made between v0.x and v0.y? This is further complicated by + cases where we have backported config changes into the middle of an earlier + release cycle (e.g., `psql-conn` from v0.35.x into v0.34.13). + +2. When during the development cycle were those changes made? This allows us to + recognize features that were backported into a previous release. + +3. What were the default values of the changed settings, and did they change at + all during or across the release boundary? + +Each step of the [configuration update plan][plan] is commented with a link to +one or more PRs where that change was made. The sections below discuss how we +found these references. + +### Tracking Changes Across Releases + +To figure out what changed between two releases, we built a tool called +[`condiff`][condiff], which performs a "keyspace" diff of two TOML documents. +This diff respects the structure of the TOML file, but ignores comments, blank +lines, and configuration values, so that we can see what was added and removed. + +To use it, run: + +```shell +go run ./scripts/confix/condiff old.toml new.toml +``` + +This tool works on any TOML documents, but for our purposes we needed +Tendermint `config.toml` files. The easiest way to get these is to build the +node binary for your version of interest, run `tendermint init` on a clean home +directory, and copy the generated config file out. The [`testdata`][testdata] +directory for the `confix` tool has configs generated from the heads of each +release branch from v0.31 through v0.35. + +If you want to reproduce this yourself, it looks something like this: + +```shell +# Example for Tendermint v0.32. +git checkout --track origin/v0.32.x +go get golang.org/x/sys/unix +go mod tidy +make build +rm -fr -- tmhome +./build/tendermint --home=tmhome init +cp tmhome/config/config.toml config-v32.toml +``` + +Be advised that the further back you go, the more idiosyncrasies you will +encounter. For example, Tendermint v0.31 and earlier predate Go modules (v0.31 +used dep), and lack backport branches. And you may need to do some editing of +Makefile rules once you get back into the 20s. + +Note that when diffing config files across the v0.34/v0.35 gap, the swap from +`snake_case` to `kebab-case` makes it look like everything changed. The +`condiff` tool has a `-desnake` flag that normalizes all the keys to kebab case +in both inputs before comparison. + +### Locating Additions and Deletions + +To figure out when a configuration setting was added or removed, your tool of +choice is `git bisect`. The only tricky part is finding the endpoints for the +search. If the transition happened within a release, you can use that +release's backport branch as the endpoint (if it has one, e.g., `v0.35.x`). + +However, the start point can be more problematic. The backport branches are not +ancestors of `master` or of each other, which means you need to find some point +in history _prior_ to the change but still attached to the mainline. For recent +releases there is a dev root (e.g., `v0.35.0-dev`, `v0.34.0-dev1`, etc.). These +are not named consistently, but you can usually grep the output of `git tag` to +find them. + +In the worst case you could try starting from the root commit of the repo, but +that turns out not to work in all cases. We've done some branching shenanigans +over the years that mean the root is not a direct ancestor of all our release +branches. When you find this you will probably swear a lot. I did. + +Once you have a start and end point (say, `v0.35.0-dev` and `master`), you can +bisect in the usual way. I use `git grep` on the `config` directory to check +whether the case I am looking for is present. For example, to find when the +`[fastsync]` section was removed: + +```shell +# Setup: +git checkout master +git bisect start +git bisect bad # it's not present on tip of master. +git bisect good v0.34.0-dev1 # it was present at the start of v0.34. +``` + +```shell +# Now repeat this until it gives you a specific commit: +if git grep -q '\[fastsync\]' config ; then git bisect good ; else git bisect bad ; fi +``` + +The above example finds where a config was removed: To find where a setting was +added, do the same thing except reverse the sense of the test (`if ! git grep -q +...`). diff --git a/docs/tendermint-core/consensus/proposer-based-timestamps.md b/docs/tendermint-core/consensus/proposer-based-timestamps.md index 7f98f10d6..17036a9f2 100644 --- a/docs/tendermint-core/consensus/proposer-based-timestamps.md +++ b/docs/tendermint-core/consensus/proposer-based-timestamps.md @@ -13,14 +13,15 @@ order: 3 The PBTS algorithm defines a way for a Tendermint blockchain to create block timestamps that are within a reasonable bound of the clocks of the validators on the network. This replaces the original BFTTime algorithm for timestamp -assignment that relied on the timestamps included in precommit messages. +assignment that computed a timestamp using the timestamps included in precommit +messages. ## Algorithm Parameters The functionality of the PBTS algorithm is governed by two parameters within Tendermint. These two parameters are [consensus parameters](https://github.com/tendermint/tendermint/blob/master/spec/abci/apps.md#L291), -meaning they are configured by the ABCI application and are expected to be the +meaning they are configured by the ABCI application and are therefore the same same across all nodes on the network. ### `Precision` @@ -51,7 +52,7 @@ useful for the protocols and applications built on top of Tendermint. The following protocols and application features require a reliable source of time: * Tendermint Light Clients [rely on correspondence between their known time](https://github.com/tendermint/tendermint/blob/master/spec/light-client/verification/README.md#definitions-1) and the block time for block verification. -* Tendermint Evidence validity is determined [either in terms of heights or in terms of time](https://github.com/tendermint/tendermint/blob/master/spec/consensus/evidence.md#verification). +* Tendermint Evidence expiration is determined [either in terms of heights or in terms of time](https://github.com/tendermint/tendermint/blob/master/spec/consensus/evidence.md#verification). * Unbonding of staked assets in the Cosmos Hub [occurs after a period of 21 days](https://github.com/cosmos/governance/blob/master/params-change/Staking.md#unbondingtime). * IBC packets can use either a [timestamp or a height to timeout packet diff --git a/go.mod b/go.mod index 92c54d106..6922847a3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/BurntSushi/toml v1.1.0 github.com/adlio/schema v1.3.0 - github.com/btcsuite/btcd v0.22.0-beta + github.com/btcsuite/btcd v0.22.1 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/fortytw2/leaktest v1.3.0 github.com/go-kit/kit v0.12.0 @@ -32,26 +32,34 @@ require ( golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/net v0.0.0-20220412020605-290c469a71a5 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/grpc v1.45.0 + google.golang.org/grpc v1.46.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 ) require ( - github.com/creachadair/atomicfile v0.2.5 + github.com/creachadair/atomicfile v0.2.6 github.com/creachadair/taskgroup v0.3.2 - github.com/golangci/golangci-lint v1.45.2 - github.com/google/go-cmp v0.5.7 - github.com/vektra/mockery/v2 v2.10.6 + github.com/golangci/golangci-lint v1.46.0 + github.com/google/go-cmp v0.5.8 + github.com/vektra/mockery/v2 v2.12.2 gotest.tools v2.2.0+incompatible ) -require github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect +require ( + github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0 // indirect + github.com/firefart/nonamedreturns v1.0.1 // indirect + github.com/lufeee/execinquery v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.0 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect +) require ( 4d63.com/gochecknoglobals v0.1.0 // indirect - github.com/Antonboom/errname v0.1.5 // indirect - github.com/Antonboom/nilnil v0.1.0 // indirect + github.com/Antonboom/errname v0.1.6 // indirect + github.com/Antonboom/nilnil v0.1.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/zstd v1.4.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect @@ -64,18 +72,18 @@ require ( github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.0 // indirect - github.com/blizzy78/varnamelen v0.6.1 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v3 v3.3.0 // indirect - github.com/breml/bidichk v0.2.2 // indirect - github.com/breml/errchkjson v0.2.3 // indirect + github.com/breml/bidichk v0.2.3 // indirect + github.com/breml/errchkjson v0.3.0 // indirect github.com/butuzov/ireturn v0.1.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.9 // indirect - github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect + github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4 // indirect github.com/containerd/continuity v0.2.1 // indirect - github.com/creachadair/tomledit v0.0.16 + github.com/creachadair/tomledit v0.0.19 github.com/daixiang0/gci v0.3.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.4.3 // indirect @@ -92,9 +100,9 @@ require ( github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect - github.com/fzipp/gocyclo v0.4.0 // indirect - github.com/go-critic/go-critic v0.6.2 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fzipp/gocyclo v0.5.1 // indirect + github.com/go-critic/go-critic v0.6.3 // indirect github.com/go-toolsmith/astcast v1.0.0 // indirect github.com/go-toolsmith/astcopy v1.0.0 // indirect github.com/go-toolsmith/astequal v1.0.1 // indirect @@ -108,7 +116,7 @@ require ( github.com/golang/snappy v0.0.3 // indirect github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 // indirect + github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a // indirect github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect @@ -134,10 +142,10 @@ require ( github.com/julz/importas v0.1.0 // indirect github.com/kisielk/errcheck v1.6.0 // indirect github.com/kisielk/gotool v1.0.0 // indirect - github.com/kulti/thelper v0.5.1 // indirect + github.com/kulti/thelper v0.6.2 // indirect github.com/kunwardeep/paralleltest v1.0.3 // indirect github.com/kyoh86/exportloopref v0.1.8 // indirect - github.com/ldez/gomoddirectives v0.2.2 // indirect + github.com/ldez/gomoddirectives v0.2.3 // indirect github.com/ldez/tagliatelle v0.3.1 // indirect github.com/leonklingele/grouper v1.1.0 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -148,19 +156,19 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.1.4 // indirect + github.com/mgechev/revive v1.2.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.2.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/nishanths/exhaustive v0.7.11 // indirect - github.com/nishanths/predeclared v0.2.1 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.0.3 // indirect - github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -168,17 +176,17 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.3.15 // indirect - github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 // indirect + github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a // indirect + github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5 // indirect github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 // indirect github.com/ryancurrah/gomodguard v1.2.3 // indirect github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect - github.com/securego/gosec/v2 v2.10.0 // indirect + github.com/securego/gosec/v2 v2.11.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/sivchari/containedctx v1.0.2 // indirect - github.com/sivchari/tenv v1.4.7 // indirect + github.com/sivchari/tenv v1.5.0 // indirect github.com/sonatard/noctx v0.0.1 // indirect github.com/sourcegraph/go-diff v0.6.1 // indirect github.com/spf13/afero v1.8.2 // indirect @@ -194,28 +202,28 @@ require ( github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tetafro/godot v1.4.11 // indirect github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect - github.com/tomarrell/wrapcheck/v2 v2.5.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.6.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.0 // indirect github.com/ultraware/funlen v0.0.3 // indirect github.com/ultraware/whitespace v0.0.5 // indirect github.com/uudashr/gocognit v1.0.5 // indirect github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 // indirect + github.com/yeya24/promlinter v0.2.0 // indirect gitlab.com/bosi/decorder v0.2.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.10 // indirect + golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - honnef.co/go/tools v0.2.2 // indirect - mvdan.cc/gofumpt v0.3.0 // indirect + honnef.co/go/tools v0.3.1 // indirect + mvdan.cc/gofumpt v0.3.1 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 // indirect diff --git a/go.sum b/go.sum index 60f26f779..96f0e92e2 100644 --- a/go.sum +++ b/go.sum @@ -46,7 +46,6 @@ cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJW cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -62,17 +61,17 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Antonboom/errname v0.1.5 h1:IM+A/gz0pDhKmlt5KSNTVAvfLMb+65RxavBXpRtCUEg= -github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= -github.com/Antonboom/nilnil v0.1.0 h1:DLDavmg0a6G/F4Lt9t7Enrbgb3Oph6LnDE6YVsmTt74= -github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= +github.com/Antonboom/errname v0.1.6 h1:LzIJZlyLOCSu51o3/t2n9Ck7PcoP9wdbrdaW6J8fX24= +github.com/Antonboom/errname v0.1.6/go.mod h1:7lz79JAnuoMNDAWE9MeeIr1/c/VpSUWatBv2FH9NYpI= +github.com/Antonboom/nilnil v0.1.1 h1:PHhrh5ANKFWRBh7TdYmyyq2gyT2lotnvFvvFbylF81Q= +github.com/Antonboom/nilnil v0.1.1/go.mod h1:L1jBqoWM7AOeTD+tSquifKSesRHs4ZdaxvZR+xdJEaI= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -83,6 +82,8 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0 h1:LAPPhJ4KR5Z8aKVZF5S48csJkxL5RMKmE/98fMs1u5M= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0/go.mod h1:LGOGuvEgCfCQsy3JF2tRmpGDpzA53iZfyGEWSPwQ6/4= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -148,17 +149,19 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blizzy78/varnamelen v0.6.1 h1:kttPCLzXFa+0nt++Cw9fb7GrSSM4KkyIAoX/vXsbuqA= -github.com/blizzy78/varnamelen v0.6.1/go.mod h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/breml/bidichk v0.2.2 h1:w7QXnpH0eCBJm55zGCTJveZEkQBt6Fs5zThIdA6qQ9Y= -github.com/breml/bidichk v0.2.2/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= -github.com/breml/errchkjson v0.2.3 h1:97eGTmR/w0paL2SwfRPI1jaAZHaH/fXnxWTw2eEIqE0= -github.com/breml/errchkjson v0.2.3/go.mod h1:jZEATw/jF69cL1iy7//Yih8yp/mXp2CBoBr9GJwCAsY= +github.com/breml/bidichk v0.2.3 h1:qe6ggxpTfA8E75hdjWPZ581sY3a2lnl0IRxLQFelECI= +github.com/breml/bidichk v0.2.3/go.mod h1:8u2C6DnAy0g2cEq+k/A2+tr9O1s+vHGxWn0LTc70T2A= +github.com/breml/errchkjson v0.3.0 h1:YdDqhfqMT+I1vIxPSas44P+9Z9HzJwCeAzjB8PxP1xw= +github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92zSDFcofU= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= @@ -185,8 +188,8 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.9 h1:mPP4ucLrf/rKZiIG/a9IPXHGlh8p4CzgpyTy6EEutYk= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= -github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af h1:spmv8nSH9h5oCQf40jt/ufBCt9j0/58u4G+rkeMqXGI= -github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= +github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4 h1:tFXjAxje9thrTF4h57Ckik+scJjTWdwAtZqZPtOT48M= +github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4/go.mod h1:W8EnPSQ8Nv4fUjc/v1/8tHFqhuOJXnRub0dTfuAQktU= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -225,12 +228,12 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/atomicfile v0.2.5 h1:wkOlpsjyJOvJ3Hd8juHKdirJnCSIPacvtY21/3nYjAo= -github.com/creachadair/atomicfile v0.2.5/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc= +github.com/creachadair/atomicfile v0.2.6 h1:FgYxYvGcqREApTY8Nxg8msM6P/KVKK3ob5h9FaRUTNg= +github.com/creachadair/atomicfile v0.2.6/go.mod h1:BRq8Une6ckFneYXZQ+kO7p1ZZP3I2fzVzf28JxrIkBc= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= -github.com/creachadair/tomledit v0.0.16 h1:PDNxgDjeeiNk1cyFfliIVQmagh1jPbDMabOw9yfSKLk= -github.com/creachadair/tomledit v0.0.16/go.mod h1:gvtfnSZLa+YNQD28vaPq0Nk12bRxEhmUdBzAWn+EGF4= +github.com/creachadair/tomledit v0.0.19 h1:zbpfUtYFYFdpRjwJY9HJlto1iZ4M5YwYB6qqc37F6UM= +github.com/creachadair/tomledit v0.0.19/go.mod h1:gvtfnSZLa+YNQD28vaPq0Nk12bRxEhmUdBzAWn+EGF4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= @@ -275,6 +278,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= @@ -296,6 +300,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/firefart/nonamedreturns v1.0.1 h1:fSvcq6ZpK/uBAgJEGMvzErlzyM4NELLqqdTofVjVNag= +github.com/firefart/nonamedreturns v1.0.1/go.mod h1:D3dpIBojGGNh5UfElmwPu73SwDCm+VKhHYqwlNOk2uQ= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -306,14 +312,15 @@ github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnX github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= -github.com/fzipp/gocyclo v0.4.0 h1:IykTnjwh2YLyYkGa0y92iTTEQcnyAz0r9zOo15EbJ7k= -github.com/fzipp/gocyclo v0.4.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/fzipp/gocyclo v0.5.1 h1:L66amyuYogbxl0j2U+vGqJXusPF2IkduvXLnYD5TFgw= +github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-critic/go-critic v0.6.2 h1:L5SDut1N4ZfsWZY0sH4DCrsHLHnhuuWak2wa165t9gs= -github.com/go-critic/go-critic v0.6.2/go.mod h1:td1s27kfmLpe5G/DPjlnFI7o1UCzePptwU7Az0V5iCM= +github.com/go-critic/go-critic v0.6.3 h1:abibh5XYBTASawfTQ0rA7dVtQT+6KzpGqb/J+DxRDaw= +github.com/go-critic/go-critic v0.6.3/go.mod h1:c6b3ZP1MQ7o6lPR7Rv3lEf7pYQUmAcx8ABHgdZCQt/k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -415,12 +422,12 @@ github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5 github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.45.2 h1:9I3PzkvscJkFAQpTQi5Ga0V4qWdJERajX1UZ7QqkW+I= -github.com/golangci/golangci-lint v1.45.2/go.mod h1:f20dpzMmUTRp+oYnX0OGjV1Au3Jm2JeI9yLqHq1/xsI= +github.com/golangci/golangci-lint v1.46.0 h1:uz9AtEcIP63FH+FIyuAXcQGVQO4vCUavEsMTJpPeD4s= +github.com/golangci/golangci-lint v1.46.0/go.mod h1:IJpcNOUfx/XLRwE95FHQ6QtbhYwwqcm0H5QkwUfF4ZE= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= @@ -448,8 +455,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -651,15 +659,15 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.5.1 h1:Uf4CUekH0OvzQTFPrWkstJvXgm6pnNEtQu3HiqEkpB0= -github.com/kulti/thelper v0.5.1/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= +github.com/kulti/thelper v0.6.2 h1:K4xulKkwOCnT1CDms6Ex3uG1dvSMUUQe9zxgYQgbRXs= +github.com/kulti/thelper v0.6.2/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.3 h1:UdKIkImEAXjR1chUWLn+PNXqWUGs//7tzMeWuP7NhmI= github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M= github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= -github.com/ldez/gomoddirectives v0.2.2 h1:p9/sXuNFArS2RLc+UpYZSI4KQwGMEDWC/LbtF5OPFVg= -github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= +github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= github.com/ldez/tagliatelle v0.3.1 h1:3BqVVlReVUZwafJUwQ+oxbx2BEX2vUG4Yu/NOfMiKiM= github.com/ldez/tagliatelle v0.3.1/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg= @@ -673,7 +681,8 @@ github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufeee/execinquery v1.0.0 h1:1XUTuLIVPDlFvUU3LXmmZwHDsolsxXnY67lzhpeqe0I= +github.com/lufeee/execinquery v1.0.0/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -714,8 +723,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.1.4 h1:sZOjY6GU35Kr9jKa/wsKSHgrFz8eASIB5i3tqWZMp0A= -github.com/mgechev/revive v1.1.4/go.mod h1:ZZq2bmyssGh8MSPz3VVziqRNIMYTJXzP8MUKG90vZ9A= +github.com/mgechev/revive v1.2.1 h1:GjFml7ZsoR0IrQ2E2YIvWFNS5GPDV7xNwvA5GM1HZC4= +github.com/mgechev/revive v1.2.1/go.mod h1:+Ro3wqY4vakcYNtkBWdZC7dBg1xSB6sp054wWwmeFm0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -734,8 +743,9 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -773,8 +783,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nishanths/exhaustive v0.7.11 h1:xV/WU3Vdwh5BUH4N06JNUznb6d5zhRPOnlgCrpNYNKA= github.com/nishanths/exhaustive v0.7.11/go.mod h1:gX+MP7DWMKJmNa1HfMozK+u04hQd3na9i0hyqf3/dOI= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= -github.com/nishanths/predeclared v0.2.1 h1:1TXtjmy4f3YCFjTxRd8zcFHOmoUir+gp0ESzjFzG2sw= -github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -828,10 +838,12 @@ github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.0 h1:P7Bq0SaI8nsexyay5UAyDo+ICWy5MQPgEZ5+l8JQTKo= +github.com/pelletier/go-toml/v2 v2.0.0/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= @@ -888,19 +900,23 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= -github.com/quasilyte/go-ruleguard v0.3.15 h1:iWYzp1z72IlXTioET0+XI6SjQdPfMGfuAiZiKznOt7g= -github.com/quasilyte/go-ruleguard v0.3.15/go.mod h1:NhuWhnlVEM1gT1A4VJHYfy9MuYSxxwHgxWoPsn9llB4= +github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a h1:sWFavxtIctGrVs5SYZ5Ml1CvrDAs8Kf5kx2PI3C41dA= +github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a/go.mod h1:VMX+OnnSw4LicdiEGtRSD/1X8kW7GuEscjYNr4cOIT4= github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.12-0.20220101150716-969a394a9451/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.12/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.17/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.16/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.19/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= -github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3 h1:P4QPNn+TK49zJjXKERt/vyPbv/mCHB/zQ4flDYOMN+M= -github.com/quasilyte/gogrep v0.0.0-20220103110004-ffaa07af02e3/go.mod h1:wSEyW6O61xRV6zb6My3HxrQ5/8ke7NE2OayqCHa3xRM= +github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5 h1:PDWGei+Rf2bBiuZIbZmM20J2ftEy9IeUCHA8HbQqed8= +github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5/go.mod h1:wSEyW6O61xRV6zb6My3HxrQ5/8ke7NE2OayqCHa3xRM= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/go-dbus v0.0.0-20121104212943-b7232d34b1d5/go.mod h1:+u151txRmLpwxBmpYn9z3d1sdJdjRPQpsXuYeY9jNls= +github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls= +github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -921,7 +937,6 @@ github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoL github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= @@ -929,12 +944,12 @@ github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3 github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/securego/gosec/v2 v2.10.0 h1:l6BET4EzWtyUXCpY2v7N92v0DDCas0L7ngg3bpqbr8g= -github.com/securego/gosec/v2 v2.10.0/go.mod h1:PVq8Ewh/nCN8l/kKC6zrGXSr7m2NmEK6ITIAWMtIaA0= +github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI= +github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= +github.com/shirou/gopsutil/v3 v3.22.4/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -946,8 +961,8 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sivchari/containedctx v1.0.2 h1:0hLQKpgC53OVF1VT7CeoFHk9YKstur1XOgfYIc1yrHI= github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= -github.com/sivchari/tenv v1.4.7 h1:FdTpgRlTue5eb5nXIYgS/lyVXSjugU8UUVDwhP1NLU8= -github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= +github.com/sivchari/tenv v1.5.0 h1:wxW0mFpKI6DIb3s6m1jCDYvkWXCskrimXMuGd0K/kSQ= +github.com/sivchari/tenv v1.5.0/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa h1:YJfZp12Z3AFhSBeXOlv4BO55RMwPn2NoQeDsrdWnBtY= @@ -985,13 +1000,14 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= +github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1029,13 +1045,13 @@ github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomarrell/wrapcheck/v2 v2.5.0 h1:g27SGGHNoQdvHz4KZA9o4v09RcWzylR+b1yueE5ECiw= -github.com/tomarrell/wrapcheck/v2 v2.5.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= +github.com/tomarrell/wrapcheck/v2 v2.6.1 h1:Cf4a/iwuMp9s7kKrh74GTgijRVim0wEpKjgAsT7Wctw= +github.com/tomarrell/wrapcheck/v2 v2.6.1/go.mod h1:Eo+Opt6pyMW1b6cNllOcDSSoHO0aTJ+iF6BfCUbHltA= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.5.0 h1:iAj0a8e6+dXSL7Liq0aXPox36FiN1dBbjA6lt9fl65s= github.com/tommy-muehle/go-mnd/v2 v2.5.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= @@ -1055,8 +1071,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektra/mockery/v2 v2.10.6 h1:iLVqC9FozavYx27ZwfXipuizLBN8YzXlh9x5fufk48w= -github.com/vektra/mockery/v2 v2.10.6/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U= +github.com/vektra/mockery/v2 v2.12.2 h1:JbRx9F+XcCJiDTyCm3V5lXYwl56m5ZouV6I9eZa1Dj0= +github.com/vektra/mockery/v2 v2.12.2/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= @@ -1065,8 +1081,8 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1z github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 h1:YAaOqqMTstELMMGblt6yJ/fcOt4owSYuw3IttMnKfAM= -github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= +github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= +github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= @@ -1148,7 +1164,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1164,7 +1180,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= +golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1381,11 +1400,9 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1396,14 +1413,17 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220403020550-483a9cbc67c0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -1431,6 +1451,7 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190228203856-589c23e65e65/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1504,7 +1525,6 @@ golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1524,8 +1544,9 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1698,8 +1719,9 @@ google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1728,7 +1750,6 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= @@ -1760,10 +1781,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk= -honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= -mvdan.cc/gofumpt v0.3.0 h1:kTojdZo9AcEYbQYhGuLf/zszYthRdhDNDUi2JKTxas4= -mvdan.cc/gofumpt v0.3.0/go.mod h1:0+VyGZWleeIj5oostkOex+nDBA0eyavuDnDusAJ8ylo= +honnef.co/go/tools v0.3.1 h1:1kJlrWJLkaGXgcaeosRXViwviqjI7nkBvU2+sZW0AYc= +honnef.co/go/tools v0.3.1/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70= +mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8= +mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= diff --git a/internal/blocksync/pool.go b/internal/blocksync/pool.go index faf3ff84b..c6b4dfd17 100644 --- a/internal/blocksync/pool.go +++ b/internal/blocksync/pool.go @@ -28,7 +28,7 @@ eg, L = latency = 0.1s */ const ( - requestIntervalMS = 2 + requestInterval = 2 * time.Millisecond maxTotalRequesters = 600 maxPeerErrBuffer = 1000 maxPendingRequests = maxTotalRequesters @@ -121,7 +121,6 @@ func NewBlockPool( peers: make(map[types.NodeID]*bpPeer), requesters: make(map[int64]*bpRequester), witnessRequesters: make(map[int64]*witnessRequester), - // verificationRequesters: make(map[int64]*bpRequester), height: start, startHeight: start, numPending: 0, @@ -148,27 +147,23 @@ func (*BlockPool) OnStop() {} // spawns requesters as needed func (pool *BlockPool) makeRequestersRoutine(ctx context.Context) { - for { - if !pool.IsRunning() { - break + for pool.IsRunning() { + if ctx.Err() != nil { + return } _, numPending, lenRequesters := pool.GetStatus() - switch { - case numPending >= maxPendingRequests: - // sleep for a bit. - time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers + if numPending >= maxPendingRequests || lenRequesters >= maxTotalRequesters { + // This is preferable to using a timer because the request interval + // is so small. Larger request intervals may necessitate using a + // timer/ticker. + time.Sleep(requestInterval) pool.removeTimedoutPeers() - case lenRequesters >= maxTotalRequesters: - // sleep for a bit. - time.Sleep(requestIntervalMS * time.Millisecond) - // check for timed out peers - pool.removeTimedoutPeers() - default: - // request for more blocks. - pool.makeNextRequester(ctx) + continue } + + // request for more blocks. + pool.makeNextRequester(ctx) } } @@ -222,16 +217,6 @@ func (pool *BlockPool) IsCaughtUp() bool { return pool.height >= (pool.maxPeerHeight - 1) } -func (pool *BlockPool) PeekBlock() (first *types.Block) { - pool.mtx.RLock() - defer pool.mtx.RUnlock() - - if r := pool.requesters[pool.height]; r != nil { - first = r.getBlock() - } - return -} - // PeekTwoBlocks returns blocks at pool.height and pool.height+1. // We need to see the second block's Commit to validate the first block. // So we peek two blocks at a time. @@ -277,10 +262,6 @@ func (pool *BlockPool) PopRequest() { } else { panic(fmt.Sprintf("Expected requester to pop, got nothing at height %v", pool.height)) } - // if r := pool.verificationRequesters[pool.height]; r != nil { - // r.Stop() - // delete(pool.verificationRequesters, pool.height) - // } } // RedoRequest invalidates the block at pool.height, @@ -856,9 +837,16 @@ OUTER_LOOP: if !bpr.IsRunning() || !bpr.pool.IsRunning() { return } + if ctx.Err() != nil { + return + } + peer = bpr.pool.pickIncrAvailablePeer(bpr.height) if peer == nil { - time.Sleep(requestIntervalMS * time.Millisecond) + // This is preferable to using a timer because the request + // interval is so small. Larger request intervals may + // necessitate using a timer/ticker. + time.Sleep(requestInterval) continue PICK_PEER_LOOP } break PICK_PEER_LOOP diff --git a/internal/blocksync/pool_test.go b/internal/blocksync/pool_test.go index 85221410e..89993b622 100644 --- a/internal/blocksync/pool_test.go +++ b/internal/blocksync/pool_test.go @@ -111,8 +111,8 @@ func TestBlockPoolBasic(t *testing.T) { if !pool.IsRunning() { return } - first := pool.PeekBlock() - if first != nil { + first, second := pool.PeekTwoBlocks() + if first != nil && second != nil { pool.PopRequest() } else { time.Sleep(1 * time.Second) @@ -166,8 +166,8 @@ func TestBlockPoolTimeout(t *testing.T) { if !pool.IsRunning() { return } - first := pool.PeekBlock() - if first != nil { + first, second := pool.PeekTwoBlocks() + if first != nil && second != nil { pool.PopRequest() } else { time.Sleep(1 * time.Second) diff --git a/internal/blocksync/reactor.go b/internal/blocksync/reactor.go index 112c20c68..07903cb2c 100644 --- a/internal/blocksync/reactor.go +++ b/internal/blocksync/reactor.go @@ -214,13 +214,10 @@ func (r *Reactor) sendHeaderToPeer(ctx context.Context, msg *bcproto.HeaderReque func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, peerID types.NodeID, blockSyncCh *p2p.Channel) error { block := r.store.LoadBlockProto(msg.Height) if block != nil { - blockCommit := r.store.LoadBlockCommitProto(msg.Height) - if blockCommit != nil { - return blockSyncCh.Send(ctx, p2p.Envelope{ - To: peerID, - Message: &bcproto.BlockResponse{Block: block}, - }) - } + return blockSyncCh.Send(ctx, p2p.Envelope{ + To: peerID, + Message: &bcproto.BlockResponse{Block: block}, + }) } r.logger.Info("peer requesting a block we do not have", "peer", peerID, "height", msg.Height) @@ -234,7 +231,7 @@ func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope, blockSyncCh *p2p.Channel) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, blockSyncCh *p2p.Channel) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -248,7 +245,7 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "message", envelope.Message, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case BlockSyncChannel: switch msg := envelope.Message.(type) { case *bcproto.HeaderRequest: @@ -292,7 +289,7 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop } default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } return err @@ -307,12 +304,12 @@ func (r *Reactor) processBlockSyncCh(ctx context.Context, blockSyncCh *p2p.Chann iter := blockSyncCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, blockSyncCh.ID, envelope, blockSyncCh); err != nil { + if err := r.handleMessage(ctx, envelope, blockSyncCh); err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return } - r.logger.Error("failed to process message", "ch_id", blockSyncCh.ID, "envelope", envelope, "err", err) + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, @@ -554,7 +551,7 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh newBlockParts, err2 := newBlock.MakePartSet(types.BlockPartSizeBytes) if err2 != nil { - r.logger.Error("failed to make ", + r.logger.Error("failed to make block at ", "height", newBlock.Height, "err", err2.Error()) return @@ -564,11 +561,21 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh newBlockPartSetHeader = newBlockParts.Header() newBlockID = types.BlockID{Hash: newBlock.Hash(), PartSetHeader: newBlockPartSetHeader} ) - + // Finally, verify the first block using the second's commit. + // + // NOTE: We can probably make this more efficient, but note that calling + // first.Hash() doesn't verify the tx contents, so MakePartSet() is + // currently necessary. if r.lastTrustedBlock != nil { err := VerifyNextBlock(newBlock, newBlockID, verifyBlock, r.lastTrustedBlock.block, r.lastTrustedBlock.commit, state.NextValidators) if err != nil { + r.logger.Error( + err.Error(), + "last_commit", verifyBlock.LastCommit, + "block_id", newBlock, + "height", newBlock.Height, + ) switch err.(type) { case ErrBlockIDDiff: case ErrValidationFailed: @@ -579,12 +586,13 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh }); serr != nil { return } + case ErrInvalidVerifyBlock: r.logger.Error( err.Error(), "last_commit", verifyBlock.LastCommit, - "block_id", newBlockID, - "height", r.lastTrustedBlock.block.Height, + "verify_block_id", newBlockID, + "verify_block_height", newBlock.Height, ) peerID := r.pool.RedoRequest(r.lastTrustedBlock.block.Height + 2) if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ @@ -593,10 +601,19 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh }); serr != nil { return } - } + } + continue // was return previously } + // // Finally, verify the first block using the second's commit. + // // + // // NOTE: We can probably make this more efficient, but note that calling + // // first.Hash() doesn't verify the tx contents, so MakePartSet() is + // // currently necessary. + // err = state.Validators.VerifyCommitLight(chainID, firstID, first.Height, second.LastCommit) + r.lastTrustedBlock.block = newBlock + r.lastTrustedBlock.commit = verifyBlock.LastCommit } else { // we need to load the last block we trusted if r.initialState.LastBlockHeight != 0 { @@ -607,36 +624,55 @@ func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh } oldHash := r.initialState.Validators.Hash() if !bytes.Equal(oldHash, newBlock.ValidatorsHash) { - - fmt.Println( - + r.logger.Error("The validator set provided by the new block does not match the expected validator set", "initial hash ", r.initialState.Validators.Hash(), "new hash ", newBlock.ValidatorsHash, ) - return + + peerID := r.pool.RedoRequest(r.lastTrustedBlock.block.Height + 1) + if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ + NodeID: peerID, + Err: ErrValidationFailed{}, + }); serr != nil { + return + } + continue // was return previously } } if err := r.verifyWithWitnesses(newBlock); err != nil { r.logger.Debug("Witness verificatio nfailed") } - if r.lastTrustedBlock == nil { - r.lastTrustedBlock = &BlockResponse{block: newBlock, commit: verifyBlock.LastCommit} - } else { - r.lastTrustedBlock.block = newBlock - r.lastTrustedBlock.commit = verifyBlock.LastCommit - } + var err error + // validate the block before we persist it + err = r.blockExec.ValidateBlock(ctx, state, newBlock) + if err != nil { + r.logger.Error("The validator set provided by the new block does not match the expected validator set", + "initial hash ", r.initialState.Validators.Hash(), + "new hash ", newBlock.ValidatorsHash, + ) + peerID := r.pool.RedoRequest(r.lastTrustedBlock.block.Height + 1) + if serr := blockSyncCh.SendError(ctx, p2p.PeerError{ + NodeID: peerID, + Err: ErrValidationFailed{}, + }); serr != nil { + return + } + continue // was return previously + } r.pool.PopRequest() // TODO: batch saves so we do not persist to disk every block r.store.SaveBlock(newBlock, newBlockParts, verifyBlock.LastCommit) - var err error - // TODO: Same thing for app - but we would need a way to get the hash // without persisting the state. state, err = r.blockExec.ApplyBlock(ctx, state, newBlockID, newBlock) + if err != nil { + panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", newBlock.Height, newBlock.Hash(), err)) + } + if err != nil { // TODO: This is bad, are we zombie? panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", newBlock.Height, newBlock.Hash(), err)) diff --git a/internal/blocksync/reactor_test.go b/internal/blocksync/reactor_test.go index fc7f480a1..4807a41a1 100644 --- a/internal/blocksync/reactor_test.go +++ b/internal/blocksync/reactor_test.go @@ -48,7 +48,7 @@ func setup( ctx context.Context, t *testing.T, genDoc *types.GenesisDoc, - privVal types.PrivValidator, + privValArray []types.PrivValidator, maxBlockHeights []int64, ) *reactorTestSuite { t.Helper() @@ -75,10 +75,14 @@ func setup( chDesc := &p2p.ChannelDescriptor{ID: BlockSyncChannel, MessageType: new(bcproto.Message)} rts.blockSyncChannels = rts.network.MakeChannelsNoCleanup(ctx, t, chDesc) - i := 0 - for nodeID := range rts.network.Nodes { - rts.addNode(ctx, t, nodeID, genDoc, privVal, maxBlockHeights[i]) - i++ + if maxBlockHeights[1] != 0 { + rts.addMultipleNodes(ctx, t, rts.network.NodeIDs(), genDoc, privValArray, maxBlockHeights, 0) + } else { + i := 0 + for nodeID := range rts.network.Nodes { + rts.addNode(ctx, t, nodeID, genDoc, privValArray, maxBlockHeights[i]) + i++ + } } t.Cleanup(func() { @@ -97,12 +101,151 @@ func setup( return rts } +// We add multiple nodes with varying initial heights +// Allows us to test whether block sync works when a node +// has previous state +// maxBlockHeightPerNode - the heights for which the node already has state +// maxBlockHeightIdx - the index of the node with maximum height +func (rts *reactorTestSuite) addMultipleNodes( + ctx context.Context, + t *testing.T, + nodeIDs []types.NodeID, + genDoc *types.GenesisDoc, + privValArray []types.PrivValidator, + maxBlockHeightPerNode []int64, + maxBlockHeightIdx int64, + +) { + t.Helper() + + logger := log.NewNopLogger() + blockDB := make([]*dbm.MemDB, len(nodeIDs)) + stateDB := make([]*dbm.MemDB, len(nodeIDs)) + blockExecutors := make([]*sm.BlockExecutor, len(nodeIDs)) + blockStores := make([]*store.BlockStore, len(nodeIDs)) + stateStores := make([]sm.Store, len(nodeIDs)) + + state, err := sm.MakeGenesisState(genDoc) + require.NoError(t, err) + + for idx, nodeID := range nodeIDs { + rts.nodes = append(rts.nodes, nodeID) + rts.app[nodeID] = proxy.New(abciclient.NewLocalClient(logger, &abci.BaseApplication{}), logger, proxy.NopMetrics()) + require.NoError(t, rts.app[nodeID].Start(ctx)) + stateDB[idx] = dbm.NewMemDB() + stateStores[idx] = sm.NewStore(stateDB[idx]) + + blockDB[idx] = dbm.NewMemDB() + blockStores[idx] = store.NewBlockStore(blockDB[idx]) + + require.NoError(t, stateStores[idx].Save(state)) + mp := &mpmocks.Mempool{} + mp.On("Lock").Return() + mp.On("Unlock").Return() + mp.On("FlushAppConn", mock.Anything).Return(nil) + mp.On("Update", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything).Return(nil) + + eventbus := eventbus.NewDefault(logger) + require.NoError(t, eventbus.Start(ctx)) + + blockExecutors[idx] = sm.NewBlockExecutor(stateStores[idx], + log.NewNopLogger(), + rts.app[nodeID], + mp, + sm.EmptyEvidencePool{}, + blockStores[idx], + eventbus, + sm.NopMetrics(), + ) + } + + for blockHeight := int64(1); blockHeight <= maxBlockHeightPerNode[maxBlockHeightIdx]; blockHeight++ { + lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil) + + if blockHeight > 1 { + lastBlockMeta := blockStores[maxBlockHeightIdx].LoadBlockMeta(blockHeight - 1) + lastBlock := blockStores[maxBlockHeightIdx].LoadBlock(blockHeight - 1) + + commitSigs := make([]types.CommitSig, len(privValArray)) + votes := make([]types.Vote, len(privValArray)) + for i, val := range privValArray { + + vote, err := factory.MakeVote( + ctx, + val, + lastBlock.Header.ChainID, 0, + lastBlock.Header.Height, 0, 2, + lastBlockMeta.BlockID, + time.Now(), + ) + require.NoError(t, err) + votes[i] = *vote + commitSigs[i] = vote.CommitSig() + + } + lastCommit = types.NewCommit( + votes[0].Height, + votes[0].Round, + lastBlockMeta.BlockID, + commitSigs, + ) + + } + thisBlock := sf.MakeBlock(state, blockHeight, lastCommit) + thisParts, err := thisBlock.MakePartSet(types.BlockPartSizeBytes) + require.NoError(t, err) + blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} + + for idx := range nodeIDs { + + if blockHeight <= maxBlockHeightPerNode[idx] { + lastState, err := stateStores[idx].Load() + require.NoError(t, err) + state, err = blockExecutors[idx].ApplyBlock(ctx, lastState, blockID, thisBlock) + require.NoError(t, err) + blockStores[idx].SaveBlock(thisBlock, thisParts, lastCommit) + } + } + } + + for idx, nodeID := range nodeIDs { + rts.peerChans[nodeID] = make(chan p2p.PeerUpdate) + rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], 1) + rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID]) + + chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (*p2p.Channel, error) { + return rts.blockSyncChannels[nodeID], nil + } + rts.reactors[nodeID] = NewReactor( + rts.logger.With("nodeID", nodeID), + stateStores[idx], + blockExecutors[idx], + blockStores[idx], + nil, + chCreator, + func(ctx context.Context) *p2p.PeerUpdates { return rts.peerUpdates[nodeID] }, + rts.blockSync, + consensus.NopMetrics(), + nil, // eventbus, can be nil + ) + + require.NoError(t, rts.reactors[nodeID].Start(ctx)) + require.True(t, rts.reactors[nodeID].IsRunning()) + } +} + func (rts *reactorTestSuite) addNode( ctx context.Context, t *testing.T, nodeID types.NodeID, genDoc *types.GenesisDoc, - privVal types.PrivValidator, + privValArray []types.PrivValidator, maxBlockHeight int64, ) { t.Helper() @@ -154,21 +297,28 @@ func (rts *reactorTestSuite) addNode( lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) lastBlock := blockStore.LoadBlock(blockHeight - 1) - vote, err := factory.MakeVote( - ctx, - privVal, - lastBlock.Header.ChainID, 0, - lastBlock.Header.Height, 0, 2, - lastBlockMeta.BlockID, - time.Now(), - ) - require.NoError(t, err) + commitSigs := make([]types.CommitSig, len(privValArray)) + votes := make([]types.Vote, len(privValArray)) + for i, val := range privValArray { + vote, err := factory.MakeVote( + ctx, + val, + lastBlock.Header.ChainID, 0, + lastBlock.Header.Height, 0, 2, + lastBlockMeta.BlockID, + time.Now(), + ) + require.NoError(t, err) + votes[i] = *vote + commitSigs[i] = vote.CommitSig() + } lastCommit = types.NewCommit( - vote.Height, - vote.Round, + votes[0].Height, + votes[0].Round, lastBlockMeta.BlockID, - []types.CommitSig{vote.CommitSig()}, + commitSigs, ) + } thisBlock := sf.MakeBlock(state, blockHeight, lastCommit) @@ -227,7 +377,7 @@ func TestReactor_AbruptDisconnect(t *testing.T) { genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(64) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) + rts := setup(ctx, t, genDoc, privVals, []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -255,6 +405,51 @@ func TestReactor_AbruptDisconnect(t *testing.T) { rts.network.Nodes[rts.nodes[1]].PeerManager.Disconnected(ctx, rts.nodes[0]) } +//@jmalicevic ToDO +// a) Add tests that verify whether faulty peer is properly detected +// 1. block at H + 1 is faulty +// 2. block at H + 2 is faulty (the validator set does not match) +// b) Add test to verify we replace a peer with a new one if we detect misbehavior +func TestReactor_NonGenesisSync(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cfg, err := config.ResetTestRoot(t.TempDir(), "block_sync_reactor_test") + require.NoError(t, err) + defer os.RemoveAll(cfg.RootDir) + + valSet, privVals := factory.ValidatorSet(ctx, t, 4, 30) + genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) + maxBlockHeight := int64(101) + + rts := setup(ctx, t, genDoc, privVals, []int64{maxBlockHeight, 2, 0}) //50, 4, 0}) + require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) + rts.start(ctx, t) + + require.Eventually( + t, + func() bool { + matching := true + for idx := range rts.nodes { + if idx == 0 { + continue + } + matching = matching && rts.reactors[rts.nodes[idx]].GetRemainingSyncTime() > time.Nanosecond && + rts.reactors[rts.nodes[idx]].pool.getLastSyncRate() > 0.001 + + if !matching { + height, _, _ := rts.reactors[rts.nodes[idx]].pool.GetStatus() + t.Logf("%d %d %s %f", height, idx, rts.reactors[rts.nodes[idx]].GetRemainingSyncTime(), rts.reactors[rts.nodes[idx]].pool.getLastSyncRate()) + } + } + return matching + }, + 10*time.Second, + 10*time.Millisecond, + "expected node to be partially synced", + ) +} + func TestReactor_SyncTime(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -263,18 +458,17 @@ func TestReactor_SyncTime(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(cfg.RootDir) - valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) + valSet, privVals := factory.ValidatorSet(ctx, t, 4, 30) genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(101) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) + rts := setup(ctx, t, genDoc, privVals, []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) rts.start(ctx, t) require.Eventually( t, func() bool { - //t.Logf("%d %d %s", rts.reactors[rts.nodes[1]].pool.height, rts.reactors[rts.nodes[0]].pool.height, rts.reactors[rts.nodes[1]].GetRemainingSyncTime()) return rts.reactors[rts.nodes[1]].GetRemainingSyncTime() > time.Nanosecond && rts.reactors[rts.nodes[1]].pool.getLastSyncRate() > 0.001 }, @@ -296,7 +490,7 @@ func TestReactor_NoBlockResponse(t *testing.T) { genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) maxBlockHeight := int64(65) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}) + rts := setup(ctx, t, genDoc, privVals, []int64{maxBlockHeight, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -315,10 +509,7 @@ func TestReactor_NoBlockResponse(t *testing.T) { secondaryPool := rts.reactors[rts.nodes[1]].pool require.Eventually( t, - func() bool { - t.Logf("%d %d", secondaryPool.MaxPeerHeight(), secondaryPool.height) - return secondaryPool.MaxPeerHeight() > 0 && secondaryPool.IsCaughtUp() - }, + func() bool { return secondaryPool.MaxPeerHeight() > 0 && secondaryPool.IsCaughtUp() }, 10*time.Second, 10*time.Millisecond, "expected node to be fully synced", @@ -351,7 +542,7 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) { valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30) genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, factory.ConsensusParams()) - rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0, 0, 0, 0}) + rts := setup(ctx, t, genDoc, privVals, []int64{maxBlockHeight, 0, 0, 0, 0}) require.Equal(t, maxBlockHeight, rts.reactors[rts.nodes[0]].store.Height()) @@ -389,7 +580,7 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) { MaxPeers: uint16(len(rts.nodes) + 1), MaxConnected: uint16(len(rts.nodes) + 1), }) - rts.addNode(ctx, t, newNode.NodeID, otherGenDoc, otherPrivVals[0], maxBlockHeight) + rts.addNode(ctx, t, newNode.NodeID, otherGenDoc, otherPrivVals, maxBlockHeight) // add a fake peer just so we do not wait for the consensus ticker to timeout rts.reactors[newNode.NodeID].pool.SetPeerRange("00ff", 10, 10) diff --git a/internal/blocksync/verify.go b/internal/blocksync/verify.go index 84ab2eaca..fdfc3c98a 100644 --- a/internal/blocksync/verify.go +++ b/internal/blocksync/verify.go @@ -82,7 +82,8 @@ func (e ErrValidationFailed) Error() string { return "failed to verify next block" } -func VerifyNextBlock(newBlock *types.Block, newBlockID types.BlockID, verifyBlock *types.Block, trustedBlock *types.Block, trustedCommit *types.Commit, validators *types.ValidatorSet) error { +func VerifyNextBlock(newBlock *types.Block, newBlockID types.BlockID, verifyBlock *types.Block, trustedBlock *types.Block, + trustedCommit *types.Commit, validators *types.ValidatorSet) error { // If the blockID in LastCommit of NewBlock does not match the trusted block // we can assume NewBlock is not correct @@ -99,7 +100,8 @@ func VerifyNextBlock(newBlock *types.Block, newBlockID types.BlockID, verifyBloc // Verify NewBlock usign the validator set obtained after applying the last block // Note: VerifyAdjacent in the LightClient relies on a trusting period which is not applicable here // ToDo: We need witness verification here as well and backwards verification from a state where we can trust validators - if err := VerifyAdjacent(&types.SignedHeader{Header: &trustedBlock.Header, Commit: trustedCommit}, &types.SignedHeader{Header: &newBlock.Header, Commit: verifyBlock.LastCommit}, validators); err != nil { + if err := VerifyAdjacent(&types.SignedHeader{Header: &trustedBlock.Header, Commit: trustedCommit}, + &types.SignedHeader{Header: &newBlock.Header, Commit: verifyBlock.LastCommit}, validators); err != nil { return ErrValidationFailed{Reason: err} } diff --git a/internal/consensus/byzantine_test.go b/internal/consensus/byzantine_test.go index 7da5f6ea5..804ebdb18 100644 --- a/internal/consensus/byzantine_test.go +++ b/internal/consensus/byzantine_test.go @@ -68,7 +68,8 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { ensureDir(t, path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) blockDB := dbm.NewMemDB() blockStore := store.NewBlockStore(blockDB) @@ -203,7 +204,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { proposerAddr := lazyNodeState.privValidatorPubKey.Address() block, err := lazyNodeState.blockExec.CreateProposalBlock( - ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, nil) + ctx, lazyNodeState.Height, lazyNodeState.state, commit, proposerAddr, lazyNodeState.LastCommit.GetVotes()) require.NoError(t, err) blockParts, err := block.MakePartSet(types.BlockPartSizeBytes) require.NoError(t, err) diff --git a/internal/consensus/common_test.go b/internal/consensus/common_test.go index c6106c909..ca1db8425 100644 --- a/internal/consensus/common_test.go +++ b/internal/consensus/common_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbm "github.com/tendermint/tm-db" @@ -112,7 +113,8 @@ func (vs *validatorStub) signVote( ctx context.Context, voteType tmproto.SignedMsgType, chainID string, - blockID types.BlockID) (*types.Vote, error) { + blockID types.BlockID, + voteExtension []byte) (*types.Vote, error) { pubKey, err := vs.PrivValidator.GetPubKey(ctx) if err != nil { @@ -120,28 +122,30 @@ func (vs *validatorStub) signVote( } vote := &types.Vote{ - ValidatorIndex: vs.Index, - ValidatorAddress: pubKey.Address(), + Type: voteType, Height: vs.Height, Round: vs.Round, - Timestamp: vs.clock.Now(), - Type: voteType, BlockID: blockID, - VoteExtension: types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(pubKey.Address())), + Timestamp: vs.clock.Now(), + ValidatorAddress: pubKey.Address(), + ValidatorIndex: vs.Index, + Extension: voteExtension, } v := vote.ToProto() - if err := vs.PrivValidator.SignVote(ctx, chainID, v); err != nil { + if err = vs.PrivValidator.SignVote(ctx, chainID, v); err != nil { return nil, fmt.Errorf("sign vote failed: %w", err) } - // ref: signVote in FilePV, the vote should use the privious vote info when the sign data is the same. + // ref: signVote in FilePV, the vote should use the previous vote info when the sign data is the same. if signDataIsEqual(vs.lastVote, v) { v.Signature = vs.lastVote.Signature v.Timestamp = vs.lastVote.Timestamp + v.ExtensionSignature = vs.lastVote.ExtensionSignature } vote.Signature = v.Signature vote.Timestamp = v.Timestamp + vote.ExtensionSignature = v.ExtensionSignature return vote, err } @@ -155,13 +159,13 @@ func signVote( chainID string, blockID types.BlockID) *types.Vote { - v, err := vs.signVote(ctx, voteType, chainID, blockID) + var ext []byte + if voteType == tmproto.PrecommitType { + ext = []byte("extension") + } + v, err := vs.signVote(ctx, voteType, chainID, blockID, ext) require.NoError(t, err, "failed to sign vote") - // TODO: remove hardcoded vote extension. - // currently set for abci/examples/kvstore/persistent_kvstore.go - v.VoteExtension = types.VoteExtensionFromProto(kvstore.ConstructVoteExtension(v.ValidatorAddress)) - vs.lastVote = v return v @@ -351,18 +355,19 @@ func validatePrecommit( require.True(t, bytes.Equal(vote.BlockID.Hash, votedBlockHash), "Expected precommit to be for proposal block") } + rs := cs.GetRoundState() if lockedBlockHash == nil { - require.False(t, cs.LockedRound != lockRound || cs.LockedBlock != nil, + require.False(t, rs.LockedRound != lockRound || rs.LockedBlock != nil, "Expected to be locked on nil at round %d. Got locked at round %d with block %v", lockRound, - cs.LockedRound, - cs.LockedBlock) + rs.LockedRound, + rs.LockedBlock) } else { - require.False(t, cs.LockedRound != lockRound || !bytes.Equal(cs.LockedBlock.Hash(), lockedBlockHash), + require.False(t, rs.LockedRound != lockRound || !bytes.Equal(rs.LockedBlock.Hash(), lockedBlockHash), "Expected block to be locked on round %d, got %d. Got locked block %X, expected %X", lockRound, - cs.LockedRound, - cs.LockedBlock.Hash(), + rs.LockedRound, + rs.LockedBlock.Hash(), lockedBlockHash) } } @@ -730,10 +735,9 @@ func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, msg.Data()) vote := voteEvent.Vote - require.Equal(t, height, vote.Height) - require.Equal(t, round, vote.Round) - - require.Equal(t, voteType, vote.Type) + assert.Equal(t, height, vote.Height, "expected height %d, but got %d", height, vote.Height) + assert.Equal(t, round, vote.Round, "expected round %d, but got %d", round, vote.Round) + assert.Equal(t, voteType, vote.Type, "expected type %s, but got %s", voteType, vote.Type) if hash == nil { require.Nil(t, vote.BlockID.Hash, "Expected prevote to be for nil, got %X", vote.BlockID.Hash) } else { @@ -741,6 +745,7 @@ func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, } } } + func ensureVote(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, round int32, voteType tmproto.SignedMsgType) { t.Helper() msg := ensureMessageBeforeTimeout(t, voteCh, ensureTimeout) @@ -749,10 +754,9 @@ func ensureVote(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, roun msg.Data()) vote := voteEvent.Vote - require.Equal(t, height, vote.Height) - require.Equal(t, round, vote.Round) - - require.Equal(t, voteType, vote.Type) + require.Equal(t, height, vote.Height, "expected height %d, but got %d", height, vote.Height) + require.Equal(t, round, vote.Round, "expected round %d, but got %d", round, vote.Round) + require.Equal(t, voteType, vote.Type, "expected type %s, but got %s", voteType, vote.Type) } func ensureNewEventOnChannel(t *testing.T, ch <-chan tmpubsub.Message) { @@ -821,7 +825,8 @@ func makeConsensusState( closeFuncs = append(closeFuncs, app.Close) vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) l := logger.With("validator", i, "module", "consensus") css[i] = newStateWithConfigAndBlockStore(ctx, t, l, thisConfig, state, privVals[i], app, blockStore) @@ -892,7 +897,8 @@ func randConsensusNetWithPeers( case *kvstore.Application: state.Version.Consensus.App = kvstore.ProtocolVersion } - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) // sm.SaveState(stateDB,state) //height 1's validatorsInfo already saved in LoadStateFromDBOrGenesisDoc above css[i] = newStateWithConfig(ctx, t, logger.With("validator", i, "module", "consensus"), thisConfig, state, privVal, app) @@ -986,5 +992,6 @@ func signDataIsEqual(v1 *types.Vote, v2 *tmproto.Vote) bool { v1.Height == v2.GetHeight() && v1.Round == v2.Round && bytes.Equal(v1.ValidatorAddress.Bytes(), v2.GetValidatorAddress()) && - v1.ValidatorIndex == v2.GetValidatorIndex() + v1.ValidatorIndex == v2.GetValidatorIndex() && + bytes.Equal(v1.Extension, v2.Extension) } diff --git a/internal/consensus/mempool_test.go b/internal/consensus/mempool_test.go index ab7a2baf3..ac04e4027 100644 --- a/internal/consensus/mempool_test.go +++ b/internal/consensus/mempool_test.go @@ -199,10 +199,12 @@ func TestMempoolRmBadTx(t *testing.T) { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(0)) - resFinalize := app.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}}) + resFinalize, err := app.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{txBytes}}) + require.NoError(t, err) assert.False(t, resFinalize.TxResults[0].IsErr(), fmt.Sprintf("expected no error. got %v", resFinalize)) - resCommit := app.Commit() + resCommit, err := app.Commit(ctx) + require.NoError(t, err) assert.True(t, len(resCommit.Data) > 0) emptyMempoolCh := make(chan struct{}) @@ -267,11 +269,11 @@ func NewCounterApplication() *CounterApplication { return &CounterApplication{} } -func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo { - return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)} +func (app *CounterApplication) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { + return &abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}, nil } -func (app *CounterApplication) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *CounterApplication) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { respTxs := make([]*abci.ExecTxResult, len(req.Txs)) for i, tx := range req.Txs { txValue := txAsUint64(tx) @@ -285,18 +287,19 @@ func (app *CounterApplication) FinalizeBlock(req abci.RequestFinalizeBlock) abci app.txCount++ respTxs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK} } - return abci.ResponseFinalizeBlock{TxResults: respTxs} + return &abci.ResponseFinalizeBlock{TxResults: respTxs}, nil } -func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *CounterApplication) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { txValue := txAsUint64(req.Tx) if txValue != uint64(app.mempoolTxCount) { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Code: code.CodeTypeBadNonce, - Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)} + Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue), + }, nil } app.mempoolTxCount++ - return abci.ResponseCheckTx{Code: code.CodeTypeOK} + return &abci.ResponseCheckTx{Code: code.CodeTypeOK}, nil } func txAsUint64(tx []byte) uint64 { @@ -305,19 +308,17 @@ func txAsUint64(tx []byte) uint64 { return binary.BigEndian.Uint64(tx8) } -func (app *CounterApplication) Commit() abci.ResponseCommit { +func (app *CounterApplication) Commit(context.Context) (*abci.ResponseCommit, error) { app.mempoolTxCount = app.txCount if app.txCount == 0 { - return abci.ResponseCommit{} + return &abci.ResponseCommit{}, nil } hash := make([]byte, 8) binary.BigEndian.PutUint64(hash, uint64(app.txCount)) - return abci.ResponseCommit{Data: hash} + return &abci.ResponseCommit{Data: hash}, nil } -func (app *CounterApplication) PrepareProposal( - req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - +func (app *CounterApplication) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { trs := make([]*abci.TxRecord, 0, len(req.Txs)) var totalBytes int64 for _, tx := range req.Txs { @@ -330,10 +331,9 @@ func (app *CounterApplication) PrepareProposal( Tx: tx, }) } - return abci.ResponsePrepareProposal{TxRecords: trs} + return &abci.ResponsePrepareProposal{TxRecords: trs}, nil } -func (app *CounterApplication) ProcessProposal( - req abci.RequestProcessProposal) abci.ResponseProcessProposal { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} +func (app *CounterApplication) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil } diff --git a/internal/consensus/metrics.go b/internal/consensus/metrics.go index ed31ec636..e5c0162f4 100644 --- a/internal/consensus/metrics.go +++ b/internal/consensus/metrics.go @@ -8,6 +8,7 @@ import ( "github.com/go-kit/kit/metrics/discard" cstypes "github.com/tendermint/tendermint/internal/consensus/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" prometheus "github.com/go-kit/kit/metrics/prometheus" @@ -103,6 +104,33 @@ type Metrics struct { // the proposal message and the local time of the validator at the time // that the validator received the message. ProposalTimestampDifference metrics.Histogram + + // VoteExtensionReceiveCount is the number of vote extensions received by this + // node. The metric is annotated by the status of the vote extension from the + // application, either 'accepted' or 'rejected'. + VoteExtensionReceiveCount metrics.Counter + + // ProposalReceiveCount is the total number of proposals received by this node + // since process start. + // The metric is annotated by the status of the proposal from the application, + // either 'accepted' or 'rejected'. + ProposalReceiveCount metrics.Counter + + // ProposalCreationCount is the total number of proposals created by this node + // since process start. + // The metric is annotated by the status of the proposal from the application, + // either 'accepted' or 'rejected'. + ProposalCreateCount metrics.Counter + + // RoundVotingPowerPercent is the percentage of the total voting power received + // with a round. The value begins at 0 for each round and approaches 1.0 as + // additional voting power is observed. The metric is labeled by vote type. + RoundVotingPowerPercent metrics.Gauge + + // LateVotes stores the number of votes that were received by this node that + // correspond to earlier heights and rounds than this node is currently + // in. + LateVotes metrics.Counter } // PrometheusMetrics returns Metrics build using Prometheus client library. @@ -280,6 +308,43 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { "Only calculated when a new block is proposed.", Buckets: []float64{-10, -.5, -.025, 0, .1, .5, 1, 1.5, 2, 10}, }, append(labels, "is_timely")).With(labelsAndValues...), + VoteExtensionReceiveCount: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "vote_extension_receive_count", + Help: "Number of vote extensions received by the node since process start, labeled by " + + "the application's response to VerifyVoteExtension, either accept or reject.", + }, append(labels, "status")).With(labelsAndValues...), + + ProposalReceiveCount: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "proposal_receive_count", + Help: "Number of vote proposals received by the node since process start, labeled by " + + "the application's response to ProcessProposal, either accept or reject.", + }, append(labels, "status")).With(labelsAndValues...), + + ProposalCreateCount: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "proposal_create_count", + Help: "Number of proposals created by the node since process start.", + }, labels).With(labelsAndValues...), + + RoundVotingPowerPercent: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "round_voting_power_percent", + Help: "Percentage of the total voting power received with a round. " + + "The value begins at 0 for each round and approaches 1.0 as additional " + + "voting power is observed.", + }, append(labels, "vote_type")).With(labelsAndValues...), + LateVotes: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "late_votes", + Help: "Number of votes received by the node since process start that correspond to earlier heights and rounds than this node is currently in.", + }, append(labels, "vote_type")).With(labelsAndValues...), } } @@ -317,6 +382,11 @@ func NopMetrics() *Metrics { QuorumPrevoteDelay: discard.NewGauge(), FullPrevoteDelay: discard.NewGauge(), ProposalTimestampDifference: discard.NewHistogram(), + VoteExtensionReceiveCount: discard.NewCounter(), + ProposalReceiveCount: discard.NewCounter(), + ProposalCreateCount: discard.NewCounter(), + RoundVotingPowerPercent: discard.NewGauge(), + LateVotes: discard.NewCounter(), } } @@ -336,10 +406,45 @@ func (m *Metrics) MarkBlockGossipComplete() { m.BlockGossipReceiveLatency.Observe(time.Since(m.blockGossipStart).Seconds()) } +func (m *Metrics) MarkProposalProcessed(accepted bool) { + status := "accepted" + if !accepted { + status = "rejected" + } + m.ProposalReceiveCount.With("status", status).Add(1) +} + +func (m *Metrics) MarkVoteExtensionReceived(accepted bool) { + status := "accepted" + if !accepted { + status = "rejected" + } + m.VoteExtensionReceiveCount.With("status", status).Add(1) +} + +func (m *Metrics) MarkVoteReceived(vt tmproto.SignedMsgType, power, totalPower int64) { + p := float64(power) / float64(totalPower) + n := strings.ToLower(strings.TrimPrefix(vt.String(), "SIGNED_MSG_TYPE_")) + m.RoundVotingPowerPercent.With("vote_type", n).Add(p) +} + func (m *Metrics) MarkRound(r int32, st time.Time) { m.Rounds.Set(float64(r)) roundTime := time.Since(st).Seconds() m.RoundDuration.Observe(roundTime) + + pvt := tmproto.PrevoteType + pvn := strings.ToLower(strings.TrimPrefix(pvt.String(), "SIGNED_MSG_TYPE_")) + m.RoundVotingPowerPercent.With("vote_type", pvn).Set(0) + + pct := tmproto.PrecommitType + pcn := strings.ToLower(strings.TrimPrefix(pct.String(), "SIGNED_MSG_TYPE_")) + m.RoundVotingPowerPercent.With("vote_type", pcn).Set(0) +} + +func (m *Metrics) MarkLateVote(vt tmproto.SignedMsgType) { + n := strings.ToLower(strings.TrimPrefix(vt.String(), "SIGNED_MSG_TYPE_")) + m.LateVotes.With("vote_type", n).Add(1) } func (m *Metrics) MarkStep(s cstypes.RoundStepType) { diff --git a/internal/consensus/mocks/cons_sync_reactor.go b/internal/consensus/mocks/cons_sync_reactor.go index 5ac592f0d..f904e9129 100644 --- a/internal/consensus/mocks/cons_sync_reactor.go +++ b/internal/consensus/mocks/cons_sync_reactor.go @@ -3,6 +3,8 @@ package mocks import ( + testing "testing" + mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" ) @@ -26,3 +28,13 @@ func (_m *ConsSyncReactor) SetStateSyncingMetrics(_a0 float64) { func (_m *ConsSyncReactor) SwitchToConsensus(_a0 state.State, _a1 bool) { _m.Called(_a0, _a1) } + +// NewConsSyncReactor creates a new instance of ConsSyncReactor. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewConsSyncReactor(t testing.TB) *ConsSyncReactor { + mock := &ConsSyncReactor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/consensus/msgs.go b/internal/consensus/msgs.go index 8b19db423..c59c06a41 100644 --- a/internal/consensus/msgs.go +++ b/internal/consensus/msgs.go @@ -220,9 +220,13 @@ type VoteMessage struct { func (*VoteMessage) TypeTag() string { return "tendermint/Vote" } -// ValidateBasic performs basic validation. +// ValidateBasic checks whether the vote within the message is well-formed. func (m *VoteMessage) ValidateBasic() error { - return m.Vote.ValidateBasic() + // Here we validate votes with vote extensions, since we require vote + // extensions to be sent in precommit messages during consensus. Prevote + // messages should never have vote extensions, and this is also validated + // here. + return m.Vote.ValidateWithExtension() } // String returns a string representation. @@ -534,6 +538,8 @@ func MsgFromProto(msg *tmcons.Message) (Message, error) { Part: parts, } case *tmcons.Message_Vote: + // Vote validation will be handled in the vote message ValidateBasic + // call below. vote, err := types.VoteFromProto(msg.Vote.Vote) if err != nil { return nil, fmt.Errorf("vote msg to proto error: %w", err) diff --git a/internal/consensus/msgs_test.go b/internal/consensus/msgs_test.go index e85936820..5a6465294 100644 --- a/internal/consensus/msgs_test.go +++ b/internal/consensus/msgs_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/bits" @@ -357,11 +357,6 @@ func TestConsMsgsVectors(t *testing.T) { } pbProposal := proposal.ToProto() - ext := types.VoteExtension{ - AppDataToSign: []byte("signed"), - AppDataSelfAuthenticating: []byte("auth"), - } - v := &types.Vote{ ValidatorAddress: []byte("add_more_exclamation"), ValidatorIndex: 1, @@ -370,7 +365,7 @@ func TestConsMsgsVectors(t *testing.T) { Timestamp: date, Type: tmproto.PrecommitType, BlockID: bi, - VoteExtension: ext, + Extension: []byte("extension"), } vpb := v.ToProto() @@ -407,7 +402,7 @@ func TestConsMsgsVectors(t *testing.T) { "2a36080110011a3008011204746573741a26080110011a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d"}, {"Vote", &tmcons.Message{Sum: &tmcons.Message_Vote{ Vote: &tmcons.Vote{Vote: vpb}}}, - "3280010a7e0802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e38014a0e0a067369676e6564120461757468"}, + "327b0a790802100122480a206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d1224080112206164645f6d6f72655f6578636c616d6174696f6e5f6d61726b735f636f64652d2a0608c0b89fdc0532146164645f6d6f72655f6578636c616d6174696f6e38014a09657874656e73696f6e"}, {"HasVote", &tmcons.Message{Sum: &tmcons.Message_HasVote{ HasVote: &tmcons.HasVote{Height: 1, Round: 1, Type: tmproto.PrevoteType, Index: 1}}}, "3a080801100118012001"}, @@ -672,7 +667,7 @@ func TestProposalPOLMessageValidateBasic(t *testing.T) { func TestBlockPartMessageValidateBasic(t *testing.T) { testPart := new(types.Part) - testPart.Proof.LeafHash = tmhash.Sum([]byte("leaf")) + testPart.Proof.LeafHash = crypto.Checksum([]byte("leaf")) testCases := []struct { testName string messageHeight int64 diff --git a/internal/consensus/reactor.go b/internal/consensus/reactor.go index 53e0b540d..eea74b5e1 100644 --- a/internal/consensus/reactor.go +++ b/internal/consensus/reactor.go @@ -1266,7 +1266,7 @@ func (r *Reactor) handleVoteSetBitsMessage(ctx context.Context, envelope *p2p.En // the p2p channel. // // NOTE: We block on consensus state for proposals, block parts, and votes. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope, chans channelBundle) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, chans channelBundle) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -1284,18 +1284,19 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop // and because a large part of the core business logic depends on these // domain types opposed to simply working with the Proto types. protoMsg := new(tmcons.Message) - if err := protoMsg.Wrap(envelope.Message); err != nil { + if err = protoMsg.Wrap(envelope.Message); err != nil { return err } - msgI, err := MsgFromProto(protoMsg) + var msgI Message + msgI, err = MsgFromProto(protoMsg) if err != nil { return err } - r.logger.Debug("received message", "ch_id", chID, "message", msgI, "peer", envelope.From) + r.logger.Debug("received message", "ch_id", envelope.ChannelID, "message", msgI, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case StateChannel: err = r.handleStateMessage(ctx, envelope, msgI, chans.votSet) case DataChannel: @@ -1305,7 +1306,7 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop case VoteSetBitsChannel: err = r.handleVoteSetBitsMessage(ctx, envelope, msgI) default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } return err @@ -1320,8 +1321,8 @@ func (r *Reactor) processStateCh(ctx context.Context, chans channelBundle) { iter := chans.state.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, chans.state.ID, envelope, chans); err != nil { - r.logger.Error("failed to process message", "ch_id", chans.state.ID, "envelope", envelope, "err", err) + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := chans.state.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, @@ -1341,8 +1342,8 @@ func (r *Reactor) processDataCh(ctx context.Context, chans channelBundle) { iter := chans.data.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, chans.data.ID, envelope, chans); err != nil { - r.logger.Error("failed to process message", "ch_id", chans.data.ID, "envelope", envelope, "err", err) + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := chans.data.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, @@ -1362,8 +1363,8 @@ func (r *Reactor) processVoteCh(ctx context.Context, chans channelBundle) { iter := chans.vote.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, chans.vote.ID, envelope, chans); err != nil { - r.logger.Error("failed to process message", "ch_id", chans.vote.ID, "envelope", envelope, "err", err) + if err := r.handleMessage(ctx, envelope, chans); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := chans.vote.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, @@ -1384,12 +1385,12 @@ func (r *Reactor) processVoteSetBitsCh(ctx context.Context, chans channelBundle) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, chans.votSet.ID, envelope, chans); err != nil { + if err := r.handleMessage(ctx, envelope, chans); err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return } - r.logger.Error("failed to process message", "ch_id", chans.votSet.ID, "envelope", envelope, "err", err) + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := chans.votSet.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, diff --git a/internal/consensus/reactor_test.go b/internal/consensus/reactor_test.go index 6a92b984f..c6a8869db 100644 --- a/internal/consensus/reactor_test.go +++ b/internal/consensus/reactor_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/rand" "os" "sync" "testing" @@ -466,7 +467,8 @@ func TestReactorWithEvidence(t *testing.T) { app := kvstore.NewApplication() vals := types.TM2PB.ValidatorUpdates(state.Validators) - app.InitChain(abci.RequestInitChain{Validators: vals}) + _, err = app.InitChain(ctx, &abci.RequestInitChain{Validators: vals}) + require.NoError(t, err) pv := privVals[i] blockDB := dbm.NewMemDB() @@ -775,8 +777,8 @@ func TestReactorValidatorSetChanges(t *testing.T) { cfg := configSetup(t) - nPeers := 7 - nVals := 4 + nPeers := 4 + nVals := 2 states, _, _, cleanup := randConsensusNetWithPeers( ctx, t, @@ -869,60 +871,27 @@ func TestReactorValidatorSetChanges(t *testing.T) { // it includes the commit for block 4, which should have the updated validator set waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - updateValidatorPubKey1, err := states[nVals].privValidator.GetPubKey(ctx) - require.NoError(t, err) + for i := 2; i <= 32; i *= 2 { + useState := rand.Intn(nVals) + t.Log(useState) + updateValidatorPubKey1, err := states[useState].privValidator.GetPubKey(ctx) + require.NoError(t, err) - updatePubKey1ABCI, err := encoding.PubKeyToProto(updateValidatorPubKey1) - require.NoError(t, err) + updatePubKey1ABCI, err := encoding.PubKeyToProto(updateValidatorPubKey1) + require.NoError(t, err) - updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25) - previousTotalVotingPower := states[nVals].GetRoundState().LastValidators.TotalVotingPower() + previousTotalVotingPower := states[useState].GetRoundState().LastValidators.TotalVotingPower() + updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, int64(i)) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) + waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) + waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, updateValidatorTx1) + waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) + waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - require.NotEqualf( - t, states[nVals].GetRoundState().LastValidators.TotalVotingPower(), previousTotalVotingPower, - "expected voting power to change (before: %d, after: %d)", - previousTotalVotingPower, states[nVals].GetRoundState().LastValidators.TotalVotingPower(), - ) - - newValidatorPubKey2, err := states[nVals+1].privValidator.GetPubKey(ctx) - require.NoError(t, err) - - newVal2ABCI, err := encoding.PubKeyToProto(newValidatorPubKey2) - require.NoError(t, err) - - newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower) - - newValidatorPubKey3, err := states[nVals+2].privValidator.GetPubKey(ctx) - require.NoError(t, err) - - newVal3ABCI, err := encoding.PubKeyToProto(newValidatorPubKey3) - require.NoError(t, err) - - newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower) - - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, newValidatorTx2, newValidatorTx3) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, newValidatorTx2, newValidatorTx3) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - - activeVals[string(newValidatorPubKey2.Address())] = struct{}{} - activeVals[string(newValidatorPubKey3.Address())] = struct{}{} - - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) - - removeValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, 0) - removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0) - - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states, removeValidatorTx2, removeValidatorTx3) - waitForAndValidateBlockWithTx(ctx, t, nPeers, activeVals, blocksSubs, states, removeValidatorTx2, removeValidatorTx3) - waitForAndValidateBlock(ctx, t, nPeers, activeVals, blocksSubs, states) - - delete(activeVals, string(newValidatorPubKey2.Address())) - delete(activeVals, string(newValidatorPubKey3.Address())) - - waitForBlockWithUpdatedValsAndValidateIt(ctx, t, nPeers, activeVals, blocksSubs, states) + require.NotEqualf( + t, states[useState].GetRoundState().LastValidators.TotalVotingPower(), previousTotalVotingPower, + "expected voting power to change (before: %d, after: %d)", + previousTotalVotingPower, states[useState].GetRoundState().LastValidators.TotalVotingPower(), + ) + } } diff --git a/internal/consensus/replay.go b/internal/consensus/replay.go index 17435d273..fac7c91ac 100644 --- a/internal/consensus/replay.go +++ b/internal/consensus/replay.go @@ -239,7 +239,7 @@ func (h *Handshaker) NBlocks() int { func (h *Handshaker) Handshake(ctx context.Context, appClient abciclient.Client) error { // Handshake is done via ABCI Info on the query conn. - res, err := appClient.Info(ctx, proxy.RequestInfo) + res, err := appClient.Info(ctx, &proxy.RequestInfo) if err != nil { return fmt.Errorf("error calling Info: %w", err) } @@ -307,15 +307,14 @@ func (h *Handshaker) ReplayBlocks( validatorSet := types.NewValidatorSet(validators) nextVals := types.TM2PB.ValidatorUpdates(validatorSet) pbParams := h.genDoc.ConsensusParams.ToProto() - req := abci.RequestInitChain{ + res, err := appClient.InitChain(ctx, &abci.RequestInitChain{ Time: h.genDoc.GenesisTime, ChainId: h.genDoc.ChainID, InitialHeight: h.genDoc.InitialHeight, ConsensusParams: &pbParams, Validators: nextVals, AppStateBytes: h.genDoc.AppState, - } - res, err := appClient.InitChain(ctx, req) + }) if err != nil { return nil, err } diff --git a/internal/consensus/replay_stubs.go b/internal/consensus/replay_stubs.go index e8f5cbc4b..3cd5bdac0 100644 --- a/internal/consensus/replay_stubs.go +++ b/internal/consensus/replay_stubs.go @@ -75,15 +75,15 @@ type mockProxyApp struct { abciResponses *tmstate.ABCIResponses } -func (mock *mockProxyApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (mock *mockProxyApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { r := mock.abciResponses.FinalizeBlock mock.txCount++ if r == nil { - return abci.ResponseFinalizeBlock{} + return &abci.ResponseFinalizeBlock{}, nil } - return *r + return r, nil } -func (mock *mockProxyApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{Data: mock.appHash} +func (mock *mockProxyApp) Commit(context.Context) (*abci.ResponseCommit, error) { + return &abci.ResponseCommit{Data: mock.appHash}, nil } diff --git a/internal/consensus/replay_test.go b/internal/consensus/replay_test.go index 6d9e82a05..f112f23e8 100644 --- a/internal/consensus/replay_test.go +++ b/internal/consensus/replay_test.go @@ -118,9 +118,6 @@ func sendTxs(ctx context.Context, t *testing.T, cs *State) { // TestWALCrash uses crashing WAL to test we can recover from any WAL failure. func TestWALCrash(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - testCases := []struct { name string initFn func(dbm.DB, *State, context.Context) @@ -139,6 +136,9 @@ func TestWALCrash(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + consensusReplayConfig, err := ResetConfig(t.TempDir(), tc.name) require.NoError(t, err) crashWALandCheckLiveness(ctx, t, consensusReplayConfig, tc.initFn, tc.heightToStop) @@ -788,7 +788,7 @@ func testHandshakeReplay( require.NoError(t, err, "Error on abci handshake") // get the latest app hash from the app - res, err := proxyApp.Info(ctx, abci.RequestInfo{Version: ""}) + res, err := proxyApp.Info(ctx, &abci.RequestInfo{Version: ""}) if err != nil { t.Fatal(err) } @@ -856,7 +856,7 @@ func buildAppStateFromChain( state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) - _, err := appClient.InitChain(ctx, abci.RequestInitChain{ + _, err := appClient.InitChain(ctx, &abci.RequestInitChain{ Validators: validators, }) require.NoError(t, err) @@ -910,7 +910,7 @@ func buildTMStateFromChain( state.Version.Consensus.App = kvstore.ProtocolVersion // simulate handshake, receive app version validators := types.TM2PB.ValidatorUpdates(state.Validators) - _, err := proxyApp.InitChain(ctx, abci.RequestInitChain{ + _, err := proxyApp.InitChain(ctx, &abci.RequestInitChain{ Validators: validators, }) require.NoError(t, err) @@ -1017,15 +1017,15 @@ type badApp struct { onlyLastHashIsWrong bool } -func (app *badApp) Commit() abci.ResponseCommit { +func (app *badApp) Commit(context.Context) (*abci.ResponseCommit, error) { app.height++ if app.onlyLastHashIsWrong { if app.height == app.numBlocks { - return abci.ResponseCommit{Data: tmrand.Bytes(8)} + return &abci.ResponseCommit{Data: tmrand.Bytes(8)}, nil } - return abci.ResponseCommit{Data: []byte{app.height}} + return &abci.ResponseCommit{Data: []byte{app.height}}, nil } else if app.allHashesAreWrong { - return abci.ResponseCommit{Data: tmrand.Bytes(8)} + return &abci.ResponseCommit{Data: tmrand.Bytes(8)}, nil } panic("either allHashesAreWrong or onlyLastHashIsWrong must be set") @@ -1275,8 +1275,6 @@ type initChainApp struct { vals []abci.ValidatorUpdate } -func (ica *initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { - return abci.ResponseInitChain{ - Validators: ica.vals, - } +func (ica *initChainApp) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + return &abci.ResponseInitChain{Validators: ica.vals}, nil } diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 7ccc776a6..90efbab77 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -922,7 +922,6 @@ func (cs *State) receiveRoutine(ctx context.Context, maxSteps int) { if err := cs.wal.Write(mi); err != nil { cs.logger.Error("failed writing to WAL", "err", err) } - // handles proposals, block parts, votes // may generate internal events (votes, complete proposals, 2/3 majorities) cs.handleMsg(ctx, mi) @@ -1335,6 +1334,7 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round } else if block == nil { return } + cs.metrics.ProposalCreateCount.Add(1) blockParts, err = block.MakePartSet(types.BlockPartSizeBytes) if err != nil { cs.logger.Error("unable to create proposal block part set", "error", err) @@ -1532,6 +1532,7 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32 if err != nil { panic(fmt.Sprintf("ProcessProposal: %v", err)) } + cs.metrics.MarkProposalProcessed(isAppValid) // Vote nil if the Application rejected the block if !isAppValid { @@ -2298,6 +2299,10 @@ func (cs *State) addVote( "cs_height", cs.Height, ) + if vote.Height < cs.Height || (vote.Height == cs.Height && vote.Round < cs.Round) { + cs.metrics.MarkLateVote(vote.Type) + } + // A precommit for the previous height? // These come in while we wait timeoutCommit if vote.Height+1 == cs.Height && vote.Type == tmproto.PrecommitType { @@ -2338,7 +2343,9 @@ func (cs *State) addVote( // Verify VoteExtension if precommit if vote.Type == tmproto.PrecommitType { - if err = cs.blockExec.VerifyVoteExtension(ctx, vote); err != nil { + err := cs.blockExec.VerifyVoteExtension(ctx, vote) + cs.metrics.MarkVoteExtensionReceived(err == nil) + if err != nil { return false, err } } @@ -2349,6 +2356,11 @@ func (cs *State) addVote( // Either duplicate, or error upon cs.Votes.AddByIndex() return } + if vote.Round == cs.Round { + vals := cs.state.Validators + _, val := vals.GetByIndex(vote.ValidatorIndex) + cs.metrics.MarkVoteReceived(vote.Type, val.VotingPower, vals.TotalVotingPower()) + } if err := cs.eventBus.PublishEventVote(types.EventDataVote{Vote: vote}); err != nil { return added, err @@ -2492,7 +2504,7 @@ func (cs *State) signVote( if err != nil { return nil, err } - vote.VoteExtension = ext + vote.Extension = ext default: timeout = time.Second } @@ -2504,6 +2516,7 @@ func (cs *State) signVote( err := cs.privValidator.SignVote(ctxto, cs.state.ChainID, v) vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature vote.Timestamp = v.Timestamp return vote, err diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index 03bb85cf2..93aa4a49d 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -13,7 +13,7 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" abcimocks "github.com/tendermint/tendermint/abci/types/mocks" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/eventbus" tmpubsub "github.com/tendermint/tendermint/internal/pubsub" @@ -1912,12 +1912,13 @@ func TestProcessProposalAccept(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - m := abcimocks.NewBaseMock() + m := abcimocks.NewApplication(t) status := abci.ResponseProcessProposal_REJECT if testCase.accept { status = abci.ResponseProcessProposal_ACCEPT } - m.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Status: status}) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: status}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Maybe() cs1, _ := makeState(ctx, t, makeStateArgs{config: config, application: m}) height, round := cs1.Height, cs1.Round @@ -1964,12 +1965,18 @@ func TestFinalizeBlockCalled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - m := abcimocks.NewBaseMock() - m.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}) - m.On("VerifyVoteExtension", mock.Anything).Return(abci.ResponseVerifyVoteExtension{ + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{ + Status: abci.ResponseProcessProposal_ACCEPT, + }, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ Status: abci.ResponseVerifyVoteExtension_ACCEPT, - }) - m.On("FinalizeBlock", mock.Anything).Return(abci.ResponseFinalizeBlock{}).Maybe() + }, nil) + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{}, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) height, round := cs1.Height, cs1.Round @@ -2007,14 +2014,254 @@ func TestFinalizeBlockCalled(t *testing.T) { m.AssertExpectations(t) if !testCase.expectCalled { - m.AssertNotCalled(t, "FinalizeBlock", mock.Anything) + m.AssertNotCalled(t, "FinalizeBlock", ctx, mock.Anything) } else { - m.AssertCalled(t, "FinalizeBlock", mock.Anything) + m.AssertCalled(t, "FinalizeBlock", ctx, mock.Anything) } }) } } +// TestExtendVoteCalled tests that the vote extension methods are called at the +// correct point in the consensus algorithm. +func TestExtendVoteCalled(t *testing.T) { + config := configSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, cs1.Height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + m.AssertNotCalled(t, "ExtendVote", mock.Anything, mock.Anything) + + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(t, voteCh, height, round) + + m.AssertCalled(t, "ExtendVote", ctx, &abci.RequestExtendVote{ + Height: height, + Hash: blockID.Hash, + }) + + m.AssertCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[1:]...) + ensureNewRound(t, newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // Only 3 of the vote extensions are seen, as consensus proceeds as soon as the +2/3 threshold + // is observed by the consensus engine. + for _, pv := range vss[:3] { + pv, err := pv.GetPubKey(ctx) + require.NoError(t, err) + addr := pv.Address() + m.AssertCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + } + +} + +// TestVerifyVoteExtensionNotCalledOnAbsentPrecommit tests that the VerifyVoteExtension +// method is not called for a validator's vote that is never delivered. +func TestVerifyVoteExtensionNotCalledOnAbsentPrecommit(t *testing.T) { + config := configSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + m := abcimocks.NewApplication(t) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: []byte("extension"), + }, nil) + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil) + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, cs1.Height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + rs := cs1.GetRoundState() + + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[2:]...) + ensurePrevoteMatch(t, voteCh, height, round, blockID.Hash) + + ensurePrecommit(t, voteCh, height, round) + + m.AssertCalled(t, "ExtendVote", mock.Anything, &abci.RequestExtendVote{ + Height: height, + Hash: blockID.Hash, + }) + + m.AssertCalled(t, "VerifyVoteExtension", mock.Anything, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), blockID, vss[2:]...) + ensureNewRound(t, newRoundCh, height+1, 0) + m.AssertExpectations(t) + + // vss[1] did not issue a precommit for the block, ensure that a vote extension + // for its address was not sent to the application. + pv, err := vss[1].GetPubKey(ctx) + require.NoError(t, err) + addr = pv.Address() + + m.AssertNotCalled(t, "VerifyVoteExtension", ctx, &abci.RequestVerifyVoteExtension{ + Hash: blockID.Hash, + ValidatorAddress: addr, + Height: height, + VoteExtension: []byte("extension"), + }) + +} + +// TestPrepareProposalReceivesVoteExtensions tests that the PrepareProposal method +// is called with the vote extensions from the previous height. The test functions +// be completing a consensus height with a mock application as the proposer. The +// test then proceeds to fail sever rounds of consensus until the mock application +// is the proposer again and ensures that the mock application receives the set of +// vote extensions from the previous consensus instance. +func TestPrepareProposalReceivesVoteExtensions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + config := configSetup(t) + + // create a list of vote extensions, one for each validator. + voteExtensions := [][]byte{ + []byte("extension 0"), + []byte("extension 1"), + []byte("extension 2"), + []byte("extension 3"), + } + + // m := abcimocks.NewApplication(t) + m := &abcimocks.Application{} + m.On("ExtendVote", mock.Anything, mock.Anything).Return(&abci.ResponseExtendVote{ + VoteExtension: voteExtensions[0], + }, nil) + m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) + + // capture the prepare proposal request. + rpp := &abci.RequestPrepareProposal{} + m.On("PrepareProposal", mock.Anything, mock.MatchedBy(func(r *abci.RequestPrepareProposal) bool { + rpp = r + return true + })).Return(&abci.ResponsePrepareProposal{}, nil) + + m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Once() + m.On("VerifyVoteExtension", mock.Anything, mock.Anything).Return(&abci.ResponseVerifyVoteExtension{Status: abci.ResponseVerifyVoteExtension_ACCEPT}, nil) + m.On("Commit", mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() + m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil) + + cs1, vss := makeState(ctx, t, makeStateArgs{config: config, application: m}) + height, round := cs1.Height, cs1.Round + + newRoundCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryNewRound) + proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal) + pv1, err := cs1.privValidator.GetPubKey(ctx) + require.NoError(t, err) + addr := pv1.Address() + voteCh := subscribeToVoter(ctx, t, cs1, addr) + + startTestRound(ctx, cs1, height, round) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + rs := cs1.GetRoundState() + blockID := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartSetHeader: rs.ProposalBlockParts.Header(), + } + signAddVotes(ctx, t, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vss[1:]...) + + // create a precommit for each validator with the associated vote extension. + for i, vs := range vss[1:] { + signAddPrecommitWithExtension(ctx, t, cs1, config.ChainID(), blockID, voteExtensions[i+1], vs) + } + + ensurePrevote(t, voteCh, height, round) + + // ensure that the height is committed. + ensurePrecommitMatch(t, voteCh, height, round, blockID.Hash) + incrementHeight(vss[1:]...) + + height++ + round = 0 + ensureNewRound(t, newRoundCh, height, round) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + incrementRound(vss[1:]...) + round = 3 + + signAddVotes(ctx, t, cs1, tmproto.PrecommitType, config.ChainID(), types.BlockID{}, vss[1:]...) + ensureNewRound(t, newRoundCh, height, round) + ensureNewProposal(t, proposalCh, height, round) + + // ensure that the proposer received the list of vote extensions from the + // previous height. + require.Len(t, rpp.LocalLastCommit.Votes, len(vss)) + for i := range vss { + require.Equal(t, rpp.LocalLastCommit.Votes[i].VoteExtension, voteExtensions[i]) + } +} + // 4 vals, 3 Nil Precommits at P0 // What we want: // P0 waits for timeoutPrecommit before starting next round @@ -2522,7 +2769,7 @@ func TestStateOutputVoteStats(t *testing.T) { peerID, err := types.NewNodeID("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") require.NoError(t, err) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) blockID := types.BlockID{ Hash: randBytes, } @@ -2560,7 +2807,7 @@ func TestSignSameVoteTwice(t *testing.T) { _, vss := makeState(ctx, t, makeStateArgs{config: config, validators: 2}) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) vote := signVote( ctx, @@ -2719,3 +2966,15 @@ func subscribe( }() return ch } + +func signAddPrecommitWithExtension(ctx context.Context, + t *testing.T, + cs *State, + chainID string, + blockID types.BlockID, + extension []byte, + stub *validatorStub) { + v, err := stub.signVote(ctx, tmproto.PrecommitType, chainID, blockID, extension) + require.NoError(t, err, "failed to sign vote") + addVotes(cs, v) +} diff --git a/internal/consensus/types/height_vote_set_test.go b/internal/consensus/types/height_vote_set_test.go index 671c5f214..acffa794c 100644 --- a/internal/consensus/types/height_vote_set_test.go +++ b/internal/consensus/types/height_vote_set_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" @@ -71,7 +71,7 @@ func makeVoteHR( pubKey, err := privVal.GetPubKey(ctx) require.NoError(t, err) - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) vote := &types.Vote{ ValidatorAddress: pubKey.Address(), @@ -88,6 +88,7 @@ func makeVoteHR( require.NoError(t, err, "Error signing vote") vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature return vote } diff --git a/internal/evidence/mocks/block_store.go b/internal/evidence/mocks/block_store.go index ef3346b2a..e45b281b9 100644 --- a/internal/evidence/mocks/block_store.go +++ b/internal/evidence/mocks/block_store.go @@ -3,7 +3,10 @@ package mocks import ( + testing "testing" + mock "github.com/stretchr/testify/mock" + types "github.com/tendermint/tendermint/types" ) @@ -57,3 +60,13 @@ func (_m *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { return r0 } + +// NewBlockStore creates a new instance of BlockStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewBlockStore(t testing.TB) *BlockStore { + mock := &BlockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/evidence/reactor.go b/internal/evidence/reactor.go index 4809df32e..1d952d30e 100644 --- a/internal/evidence/reactor.go +++ b/internal/evidence/reactor.go @@ -133,7 +133,7 @@ func (r *Reactor) handleEvidenceMessage(ctx context.Context, envelope *p2p.Envel // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic in processing message: %v", e) @@ -147,15 +147,14 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "message", envelope.Message, "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case EvidenceChannel: err = r.handleEvidenceMessage(ctx, envelope) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", envelope.ChannelID, envelope) } - return err + return } // processEvidenceCh implements a blocking event loop where we listen for p2p @@ -164,8 +163,8 @@ func (r *Reactor) processEvidenceCh(ctx context.Context, evidenceCh *p2p.Channel iter := evidenceCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, evidenceCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", evidenceCh.ID, "envelope", envelope, "err", err) + if err := r.handleMessage(ctx, envelope); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := evidenceCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, diff --git a/internal/evidence/reactor_test.go b/internal/evidence/reactor_test.go index 5575d7f97..f23195fae 100644 --- a/internal/evidence/reactor_test.go +++ b/internal/evidence/reactor_test.go @@ -16,7 +16,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/evidence/mocks" @@ -523,10 +522,10 @@ func TestEvidenceListSerialization(t *testing.T) { Round: 2, Timestamp: stamp, BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), diff --git a/internal/evidence/verify_test.go b/internal/evidence/verify_test.go index b7f535657..b2056186f 100644 --- a/internal/evidence/verify_test.go +++ b/internal/evidence/verify_test.go @@ -11,7 +11,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/evidence/mocks" @@ -273,7 +272,7 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) { // conflicting header has different next validators hash which should have been correctly derived from // the previous round - ev.ConflictingBlock.Header.NextValidatorsHash = crypto.CRandBytes(tmhash.Size) + ev.ConflictingBlock.Header.NextValidatorsHash = crypto.CRandBytes(crypto.HashSize) assert.Error(t, evidence.VerifyLightClientAttack(ev, trustedSignedHeader, trustedSignedHeader, nil, defaultEvidenceTime.Add(1*time.Minute), 2*time.Hour)) @@ -618,8 +617,8 @@ func makeVote( func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) diff --git a/internal/inspect/rpc/rpc.go b/internal/inspect/rpc/rpc.go index 40f0d5d26..00c3e52ef 100644 --- a/internal/inspect/rpc/rpc.go +++ b/internal/inspect/rpc/rpc.go @@ -38,16 +38,16 @@ func Routes(cfg config.RPCConfig, s state.Store, bs state.BlockStore, es []index Logger: logger, } return core.RoutesMap{ - "blockchain": server.NewRPCFunc(env.BlockchainInfo, "minHeight", "maxHeight"), - "consensus_params": server.NewRPCFunc(env.ConsensusParams, "height"), - "block": server.NewRPCFunc(env.Block, "height"), - "block_by_hash": server.NewRPCFunc(env.BlockByHash, "hash"), - "block_results": server.NewRPCFunc(env.BlockResults, "height"), - "commit": server.NewRPCFunc(env.Commit, "height"), - "validators": server.NewRPCFunc(env.Validators, "height", "page", "per_page"), - "tx": server.NewRPCFunc(env.Tx, "hash", "prove"), - "tx_search": server.NewRPCFunc(env.TxSearch, "query", "prove", "page", "per_page", "order_by"), - "block_search": server.NewRPCFunc(env.BlockSearch, "query", "page", "per_page", "order_by"), + "blockchain": server.NewRPCFunc(env.BlockchainInfo), + "consensus_params": server.NewRPCFunc(env.ConsensusParams), + "block": server.NewRPCFunc(env.Block), + "block_by_hash": server.NewRPCFunc(env.BlockByHash), + "block_results": server.NewRPCFunc(env.BlockResults), + "commit": server.NewRPCFunc(env.Commit), + "validators": server.NewRPCFunc(env.Validators), + "tx": server.NewRPCFunc(env.Tx), + "tx_search": server.NewRPCFunc(env.TxSearch), + "block_search": server.NewRPCFunc(env.BlockSearch), } } diff --git a/internal/libs/protoio/writer_test.go b/internal/libs/protoio/writer_test.go index 69867f733..cf1d0a2a4 100644 --- a/internal/libs/protoio/writer_test.go +++ b/internal/libs/protoio/writer_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" @@ -25,10 +24,10 @@ func aVote(t testing.TB) *types.Vote { Round: 2, Timestamp: stamp, BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go index 6fcfe86c1..629fa0bda 100644 --- a/internal/mempool/mempool.go +++ b/internal/mempool/mempool.go @@ -260,7 +260,7 @@ func (txmp *TxMempool) CheckTx( return types.ErrTxInCache } - res, err := txmp.proxyAppConn.CheckTx(ctx, abci.RequestCheckTx{Tx: tx}) + res, err := txmp.proxyAppConn.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) if err != nil { txmp.cache.Remove(tx) return err @@ -700,7 +700,7 @@ func (txmp *TxMempool) updateReCheckTxs(ctx context.Context) { // Only execute CheckTx if the transaction is not marked as removed which // could happen if the transaction was evicted. if !txmp.txStore.IsTxRemoved(wtx.hash) { - res, err := txmp.proxyAppConn.CheckTx(ctx, abci.RequestCheckTx{ + res, err := txmp.proxyAppConn.CheckTx(ctx, &abci.RequestCheckTx{ Tx: wtx.tx, Type: abci.CheckTxType_Recheck, }) diff --git a/internal/mempool/mempool_test.go b/internal/mempool/mempool_test.go index a20e793c3..946377b1c 100644 --- a/internal/mempool/mempool_test.go +++ b/internal/mempool/mempool_test.go @@ -36,7 +36,7 @@ type testTx struct { priority int64 } -func (app *application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { var ( priority int64 sender string @@ -47,29 +47,29 @@ func (app *application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { if len(parts) == 3 { v, err := strconv.ParseInt(string(parts[2]), 10, 64) if err != nil { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Code: 100, GasWanted: 1, - } + }, nil } priority = v sender = string(parts[0]) } else { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Code: 101, GasWanted: 1, - } + }, nil } - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Priority: priority, Sender: sender, Code: code.CodeTypeOK, GasWanted: 1, - } + }, nil } func setup(t testing.TB, app abciclient.Client, cacheSize int, options ...TxMempoolOption) *TxMempool { diff --git a/internal/mempool/mocks/mempool.go b/internal/mempool/mocks/mempool.go index d4cdfabd7..b82d7d63e 100644 --- a/internal/mempool/mocks/mempool.go +++ b/internal/mempool/mocks/mempool.go @@ -11,6 +11,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -170,3 +172,13 @@ func (_m *Mempool) Update(ctx context.Context, blockHeight int64, blockTxs types return r0 } + +// NewMempool creates a new instance of Mempool. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewMempool(t testing.TB) *Mempool { + mock := &Mempool{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/mempool/reactor.go b/internal/mempool/reactor.go index 8f83f5006..ae578e70a 100644 --- a/internal/mempool/reactor.go +++ b/internal/mempool/reactor.go @@ -169,7 +169,7 @@ func (r *Reactor) handleMempoolMessage(ctx context.Context, envelope *p2p.Envelo // handleMessage handles an Envelope sent from a peer on a specific p2p Channel. // It will handle errors and any possible panics gracefully. A caller can handle // any error returned by sending a PeerError on the respective channel. -func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelope *p2p.Envelope) (err error) { +func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (err error) { defer func() { if e := recover(); e != nil { r.observePanic(e) @@ -184,15 +184,14 @@ func (r *Reactor) handleMessage(ctx context.Context, chID p2p.ChannelID, envelop r.logger.Debug("received message", "peer", envelope.From) - switch chID { + switch envelope.ChannelID { case MempoolChannel: err = r.handleMempoolMessage(ctx, envelope) - default: - err = fmt.Errorf("unknown channel ID (%d) for envelope (%T)", chID, envelope.Message) + err = fmt.Errorf("unknown channel ID (%d) for envelope (%T)", envelope.ChannelID, envelope.Message) } - return err + return } // processMempoolCh implements a blocking event loop where we listen for p2p @@ -201,8 +200,8 @@ func (r *Reactor) processMempoolCh(ctx context.Context, mempoolCh *p2p.Channel) iter := mempoolCh.Receive(ctx) for iter.Next(ctx) { envelope := iter.Envelope() - if err := r.handleMessage(ctx, mempoolCh.ID, envelope); err != nil { - r.logger.Error("failed to process message", "ch_id", mempoolCh.ID, "envelope", envelope, "err", err) + if err := r.handleMessage(ctx, envelope); err != nil { + r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := mempoolCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, diff --git a/internal/p2p/address.go b/internal/p2p/address.go index 7c084216e..0f4066faf 100644 --- a/internal/p2p/address.go +++ b/internal/p2p/address.go @@ -97,7 +97,7 @@ func ParseNodeAddress(urlString string) (NodeAddress, error) { // Resolve resolves a NodeAddress into a set of Endpoints, by expanding // out a DNS hostname to IP addresses. -func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { +func (a NodeAddress) Resolve(ctx context.Context) ([]*Endpoint, error) { if a.Protocol == "" { return nil, errors.New("address has no protocol") } @@ -109,7 +109,7 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { if a.NodeID == "" { return nil, errors.New("local address has no node ID") } - return []Endpoint{{ + return []*Endpoint{{ Protocol: a.Protocol, Path: string(a.NodeID), }}, nil @@ -119,9 +119,9 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) { if err != nil { return nil, err } - endpoints := make([]Endpoint, len(ips)) + endpoints := make([]*Endpoint, len(ips)) for i, ip := range ips { - endpoints[i] = Endpoint{ + endpoints[i] = &Endpoint{ Protocol: a.Protocol, IP: ip, Port: a.Port, diff --git a/internal/p2p/address_test.go b/internal/p2p/address_test.go index d5f9e498e..d4c745d8d 100644 --- a/internal/p2p/address_test.go +++ b/internal/p2p/address_test.go @@ -210,71 +210,71 @@ func TestNodeAddress_Resolve(t *testing.T) { testcases := []struct { address p2p.NodeAddress - expect p2p.Endpoint + expect *p2p.Endpoint ok bool }{ // Valid networked addresses (with hostname). { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "::1"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "8.8.8.8"}, - p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)}, + &p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "2001:0db8::ff00:0042:8329"}, - p2p.Endpoint{Protocol: "tcp", IP: []byte{ + &p2p.Endpoint{Protocol: "tcp", IP: []byte{ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29}}, true, }, { p2p.NodeAddress{Protocol: "tcp", Hostname: "some.missing.host.tendermint.com"}, - p2p.Endpoint{}, + &p2p.Endpoint{}, false, }, // Valid non-networked addresses. { p2p.NodeAddress{Protocol: "memory", NodeID: id}, - p2p.Endpoint{Protocol: "memory", Path: string(id)}, + &p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, { p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: string(id)}, - p2p.Endpoint{Protocol: "memory", Path: string(id)}, + &p2p.Endpoint{Protocol: "memory", Path: string(id)}, true, }, // Invalid addresses. - {p2p.NodeAddress{}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Hostname: "127.0.0.1"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "memory"}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "memory", Path: string(id)}, p2p.Endpoint{}, false}, - {p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, p2p.Endpoint{}, false}, + {p2p.NodeAddress{}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Hostname: "127.0.0.1"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "memory"}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "memory", Path: string(id)}, &p2p.Endpoint{}, false}, + {p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, &p2p.Endpoint{}, false}, } for _, tc := range testcases { tc := tc diff --git a/internal/p2p/channel.go b/internal/p2p/channel.go index 8e6774612..d3d7d104f 100644 --- a/internal/p2p/channel.go +++ b/internal/p2p/channel.go @@ -59,25 +59,17 @@ type Channel struct { outCh chan<- Envelope // outbound messages (reactors to peers) errCh chan<- PeerError // peer error reporting - messageType proto.Message // the channel's message type, used for unmarshaling - name string + name string } // NewChannel creates a new channel. It is primarily for internal and test // use, reactors should use Router.OpenChannel(). -func NewChannel( - id ChannelID, - messageType proto.Message, - inCh <-chan Envelope, - outCh chan<- Envelope, - errCh chan<- PeerError, -) *Channel { +func NewChannel(id ChannelID, inCh <-chan Envelope, outCh chan<- Envelope, errCh chan<- PeerError) *Channel { return &Channel{ - ID: id, - messageType: messageType, - inCh: inCh, - outCh: outCh, - errCh: errCh, + ID: id, + inCh: inCh, + outCh: outCh, + errCh: errCh, } } diff --git a/internal/p2p/conn/conn_go110.go b/internal/p2p/conn/conn_go110.go deleted file mode 100644 index 459c3169b..000000000 --- a/internal/p2p/conn/conn_go110.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build go1.10 -// +build go1.10 - -package conn - -// Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/tendermint/issues/851 - -import "net" - -func NetPipe() (net.Conn, net.Conn) { - return net.Pipe() -} diff --git a/internal/p2p/conn/conn_notgo110.go b/internal/p2p/conn/conn_notgo110.go deleted file mode 100644 index 21dffad2c..000000000 --- a/internal/p2p/conn/conn_notgo110.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !go1.10 -// +build !go1.10 - -package conn - -import ( - "net" - "time" -) - -// Only Go1.10 has a proper net.Conn implementation that -// has the SetDeadline method implemented as per -// https://github.com/golang/go/commit/e2dd8ca946be884bb877e074a21727f1a685a706 -// lest we run into problems like -// https://github.com/tendermint/tendermint/issues/851 -// so for go versions < Go1.10 use our custom net.Conn creator -// that doesn't return an `Unimplemented error` for net.Conn. -// Before https://github.com/tendermint/tendermint/commit/49faa79bdce5663894b3febbf4955fb1d172df04 -// we hadn't cared about errors from SetDeadline so swallow them up anyways. -type pipe struct { - net.Conn -} - -func (p *pipe) SetDeadline(t time.Time) error { - return nil -} - -func NetPipe() (net.Conn, net.Conn) { - p1, p2 := net.Pipe() - return &pipe{p1}, &pipe{p2} -} - -var _ net.Conn = (*pipe)(nil) diff --git a/internal/p2p/conn/connection_test.go b/internal/p2p/conn/connection_test.go index 49c53f6fb..5a604cd23 100644 --- a/internal/p2p/conn/connection_test.go +++ b/internal/p2p/conn/connection_test.go @@ -48,7 +48,7 @@ func createMConnectionWithCallbacks( } func TestMConnectionSendFlushStop(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) @@ -85,7 +85,7 @@ func TestMConnectionSendFlushStop(t *testing.T) { } func TestMConnectionSend(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) @@ -116,7 +116,7 @@ func TestMConnectionSend(t *testing.T) { } func TestMConnectionReceive(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) receivedCh := make(chan []byte) @@ -378,7 +378,7 @@ func TestMConnectionPingPongs(t *testing.T) { } func TestMConnectionStopsAndReturnsError(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) receivedCh := make(chan []byte) @@ -423,7 +423,7 @@ func newClientAndServerConnsForReadErrors( t *testing.T, chOnErr chan struct{}, ) (*MConnection, *MConnection) { - server, client := NetPipe() + server, client := net.Pipe() onReceive := func(context.Context, ChannelID, []byte) {} onError := func(context.Context, interface{}) {} @@ -558,7 +558,7 @@ func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { } func TestMConnectionTrySend(t *testing.T) { - server, client := NetPipe() + server, client := net.Pipe() t.Cleanup(closeAll(t, client, server)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/internal/p2p/mocks/connection.go b/internal/p2p/mocks/connection.go index 576fb2386..73b6cfc3b 100644 --- a/internal/p2p/mocks/connection.go +++ b/internal/p2p/mocks/connection.go @@ -13,6 +13,8 @@ import ( p2p "github.com/tendermint/tendermint/internal/p2p" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -150,3 +152,13 @@ func (_m *Connection) String() string { return r0 } + +// NewConnection creates a new instance of Connection. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewConnection(t testing.TB) *Connection { + mock := &Connection{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/p2p/mocks/transport.go b/internal/p2p/mocks/transport.go index b17290118..34ebec20e 100644 --- a/internal/p2p/mocks/transport.go +++ b/internal/p2p/mocks/transport.go @@ -10,6 +10,8 @@ import ( mock "github.com/stretchr/testify/mock" p2p "github.com/tendermint/tendermint/internal/p2p" + + testing "testing" ) // Transport is an autogenerated mock type for the Transport type @@ -60,11 +62,11 @@ func (_m *Transport) Close() error { } // Dial provides a mock function with given fields: _a0, _a1 -func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection, error) { +func (_m *Transport) Dial(_a0 context.Context, _a1 *p2p.Endpoint) (p2p.Connection, error) { ret := _m.Called(_a0, _a1) var r0 p2p.Connection - if rf, ok := ret.Get(0).(func(context.Context, p2p.Endpoint) p2p.Connection); ok { + if rf, ok := ret.Get(0).(func(context.Context, *p2p.Endpoint) p2p.Connection); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { @@ -73,7 +75,7 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, p2p.Endpoint) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *p2p.Endpoint) error); ok { r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) @@ -82,28 +84,35 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection return r0, r1 } -// Endpoints provides a mock function with given fields: -func (_m *Transport) Endpoints() []p2p.Endpoint { +// Endpoint provides a mock function with given fields: +func (_m *Transport) Endpoint() (*p2p.Endpoint, error) { ret := _m.Called() - var r0 []p2p.Endpoint - if rf, ok := ret.Get(0).(func() []p2p.Endpoint); ok { + var r0 *p2p.Endpoint + if rf, ok := ret.Get(0).(func() *p2p.Endpoint); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]p2p.Endpoint) + r0 = ret.Get(0).(*p2p.Endpoint) } } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Listen provides a mock function with given fields: _a0 -func (_m *Transport) Listen(_a0 p2p.Endpoint) error { +func (_m *Transport) Listen(_a0 *p2p.Endpoint) error { ret := _m.Called(_a0) var r0 error - if rf, ok := ret.Get(0).(func(p2p.Endpoint) error); ok { + if rf, ok := ret.Get(0).(func(*p2p.Endpoint) error); ok { r0 = rf(_a0) } else { r0 = ret.Error(0) @@ -141,3 +150,13 @@ func (_m *Transport) String() string { return r0 } + +// NewTransport creates a new instance of Transport. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewTransport(t testing.TB) *Transport { + mock := &Transport{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/p2p/p2ptest/network.go b/internal/p2p/p2ptest/network.go index cde14e721..85df029d8 100644 --- a/internal/p2p/p2ptest/network.go +++ b/internal/p2p/p2ptest/network.go @@ -247,7 +247,9 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) } transport := n.memoryNetwork.CreateTransport(nodeID) - require.Len(t, transport.Endpoints(), 1, "transport not listening on 1 endpoint") + ep, err := transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, ep, "transport not listening an endpoint") peerManager, err := p2p.NewPeerManager(nodeID, dbm.NewMemDB(), p2p.PeerManagerOptions{ MinRetryTime: 10 * time.Millisecond, @@ -264,8 +266,8 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) privKey, peerManager, func() *types.NodeInfo { return &nodeInfo }, - []p2p.Transport{transport}, - transport.Endpoints(), + transport, + ep, p2p.RouterOptions{DialSleep: func(_ context.Context) {}}, ) @@ -284,7 +286,7 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions) return &Node{ NodeID: nodeID, NodeInfo: nodeInfo, - NodeAddress: transport.Endpoints()[0].NodeAddress(nodeID), + NodeAddress: ep.NodeAddress(nodeID), PrivKey: privKey, Router: router, PeerManager: peerManager, @@ -304,7 +306,6 @@ func (n *Node) MakeChannel( ctx, cancel := context.WithCancel(ctx) channel, err := n.Router.OpenChannel(ctx, chDesc) require.NoError(t, err) - require.Contains(t, n.Router.NodeInfo().Channels, byte(chDesc.ID)) t.Cleanup(func() { RequireEmpty(ctx, t, channel) cancel() diff --git a/internal/p2p/p2ptest/require.go b/internal/p2p/p2ptest/require.go index f9f3ec40e..885e080d4 100644 --- a/internal/p2p/p2ptest/require.go +++ b/internal/p2p/p2ptest/require.go @@ -123,7 +123,7 @@ func RequireError(ctx context.Context, t *testing.T, channel *p2p.Channel, peerE err := channel.SendError(tctx, peerError) switch { case errors.Is(err, context.DeadlineExceeded): - require.Fail(t, "timed out reporting error", "%v on %v", peerError, channel.ID) + require.Fail(t, "timed out reporting error", "%v for %q", peerError, channel.String()) default: require.NoError(t, err, "unexpected error") } diff --git a/internal/p2p/pex/reactor.go b/internal/p2p/pex/reactor.go index 1c80763ee..bd4737326 100644 --- a/internal/p2p/pex/reactor.go +++ b/internal/p2p/pex/reactor.go @@ -193,7 +193,7 @@ func (r *Reactor) processPexCh(ctx context.Context, pexCh *p2p.Channel) { dur, err := r.handlePexMessage(ctx, envelope, pexCh) if err != nil { r.logger.Error("failed to process message", - "ch_id", pexCh.ID, "envelope", envelope, "err", err) + "ch_id", envelope.ChannelID, "envelope", envelope, "err", err) if serr := pexCh.SendError(ctx, p2p.PeerError{ NodeID: envelope.From, Err: err, diff --git a/internal/p2p/pex/reactor_test.go b/internal/p2p/pex/reactor_test.go index f2132fbba..ec2f03d83 100644 --- a/internal/p2p/pex/reactor_test.go +++ b/internal/p2p/pex/reactor_test.go @@ -289,7 +289,6 @@ func setupSingle(ctx context.Context, t *testing.T) *singleTestReactor { pexErrCh := make(chan p2p.PeerError, chBuf) pexCh := p2p.NewChannel( p2p.ChannelID(pex.PexChannel), - new(p2pproto.PexMessage), pexInCh, pexOutCh, pexErrCh, diff --git a/internal/p2p/router.go b/internal/p2p/router.go index ca9536900..459be7975 100644 --- a/internal/p2p/router.go +++ b/internal/p2p/router.go @@ -148,15 +148,14 @@ type Router struct { *service.BaseService logger log.Logger - metrics *Metrics - options RouterOptions - privKey crypto.PrivKey - peerManager *PeerManager - chDescs []*ChannelDescriptor - transports []Transport - endpoints []Endpoint - connTracker connectionTracker - protocolTransports map[Protocol]Transport + metrics *Metrics + options RouterOptions + privKey crypto.PrivKey + peerManager *PeerManager + chDescs []*ChannelDescriptor + transport Transport + endpoint *Endpoint + connTracker connectionTracker peerMtx sync.RWMutex peerQueues map[types.NodeID]queue // outbound messages per peer for all channels @@ -182,8 +181,8 @@ func NewRouter( privKey crypto.PrivKey, peerManager *PeerManager, nodeInfoProducer func() *types.NodeInfo, - transports []Transport, - endpoints []Endpoint, + transport Transport, + endpoint *Endpoint, options RouterOptions, ) (*Router, error) { @@ -200,28 +199,19 @@ func NewRouter( options.MaxIncomingConnectionAttempts, options.IncomingConnectionWindow, ), - chDescs: make([]*ChannelDescriptor, 0), - transports: transports, - endpoints: endpoints, - protocolTransports: map[Protocol]Transport{}, - peerManager: peerManager, - options: options, - channelQueues: map[ChannelID]queue{}, - channelMessages: map[ChannelID]proto.Message{}, - peerQueues: map[types.NodeID]queue{}, - peerChannels: make(map[types.NodeID]ChannelIDSet), + chDescs: make([]*ChannelDescriptor, 0), + transport: transport, + endpoint: endpoint, + peerManager: peerManager, + options: options, + channelQueues: map[ChannelID]queue{}, + channelMessages: map[ChannelID]proto.Message{}, + peerQueues: map[types.NodeID]queue{}, + peerChannels: make(map[types.NodeID]ChannelIDSet), } router.BaseService = service.NewBaseService(logger, "router", router) - for _, transport := range transports { - for _, protocol := range transport.Protocols() { - if _, ok := router.protocolTransports[protocol]; !ok { - router.protocolTransports[protocol] = transport - } - } - } - return router, nil } @@ -272,7 +262,7 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C queue := r.queueFactory(chDesc.RecvBufferCapacity) outCh := make(chan Envelope, chDesc.RecvBufferCapacity) errCh := make(chan PeerError, chDesc.RecvBufferCapacity) - channel := NewChannel(id, messageType, queue.dequeue(), outCh, errCh) + channel := NewChannel(id, queue.dequeue(), outCh, errCh) channel.name = chDesc.Name var wrapper Wrapper @@ -286,9 +276,7 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C // add the channel to the nodeInfo if it's not already there. r.nodeInfoProducer().AddChannel(uint16(chDesc.ID)) - for _, t := range r.transports { - t.AddChannelDescriptors([]*ChannelDescriptor{chDesc}) - } + r.transport.AddChannelDescriptors([]*ChannelDescriptor{chDesc}) go func() { defer func() { @@ -670,12 +658,6 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection, } for _, endpoint := range endpoints { - transport, ok := r.protocolTransports[endpoint.Protocol] - if !ok { - r.logger.Error("no transport found for protocol", "endpoint", endpoint) - continue - } - dialCtx := ctx if r.options.DialTimeout > 0 { var cancel context.CancelFunc @@ -690,7 +672,7 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection, // by the peer's endpoint, since e.g. a peer on 192.168.0.0 can reach us // on a private address on this endpoint, but a peer on the public // Internet can't and needs a different public address. - conn, err := transport.Dial(dialCtx, endpoint) + conn, err := r.transport.Dial(dialCtx, endpoint) if err != nil { r.logger.Error("failed to dial endpoint", "peer", address.NodeID, "endpoint", endpoint, "err", err) } else { @@ -731,7 +713,7 @@ func (r *Router) handshakePeer( return peerInfo, fmt.Errorf("expected to connect with peer %q, got %q", expectID, peerInfo.NodeID) } - if err := r.nodeInfoProducer().CompatibleWith(peerInfo); err != nil { + if err := nodeInfo.CompatibleWith(peerInfo); err != nil { return peerInfo, ErrRejected{ err: err, id: peerInfo.ID(), @@ -929,11 +911,6 @@ func (r *Router) evictPeers(ctx context.Context) { } } -// NodeInfo returns a copy of the current NodeInfo. Used for testing. -func (r *Router) NodeInfo() types.NodeInfo { - return r.nodeInfoProducer().Copy() -} - func (r *Router) setupQueueFactory(ctx context.Context) error { qf, err := r.createQueueFactory(ctx) if err != nil { @@ -950,29 +927,13 @@ func (r *Router) OnStart(ctx context.Context) error { return err } - for _, transport := range r.transports { - for _, endpoint := range r.endpoints { - if err := transport.Listen(endpoint); err != nil { - return err - } - } + if err := r.transport.Listen(r.endpoint); err != nil { + return err } - nodeInfo := r.nodeInfoProducer() - r.logger.Info( - "starting router", - "node_id", nodeInfo.NodeID, - "channels", nodeInfo.Channels, - "listen_addr", nodeInfo.ListenAddr, - "transports", len(r.transports), - ) - go r.dialPeers(ctx) go r.evictPeers(ctx) - - for _, transport := range r.transports { - go r.acceptPeers(ctx, transport) - } + go r.acceptPeers(ctx, r.transport) return nil } @@ -985,10 +946,8 @@ func (r *Router) OnStart(ctx context.Context) error { // sender's responsibility. func (r *Router) OnStop() { // Close transport listeners (unblocks Accept calls). - for _, transport := range r.transports { - if err := transport.Close(); err != nil { - r.logger.Error("failed to close transport", "transport", transport, "err", err) - } + if err := r.transport.Close(); err != nil { + r.logger.Error("failed to close transport", "err", err) } // Collect all remaining queues, and wait for them to close. diff --git a/internal/p2p/router_test.go b/internal/p2p/router_test.go index 7ef77a16d..663e6b81c 100644 --- a/internal/p2p/router_test.go +++ b/internal/p2p/router_test.go @@ -105,14 +105,16 @@ func TestRouter_Channel_Basic(t *testing.T) { peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) require.NoError(t, err) + testnet := p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: 1}) + router, err := p2p.NewRouter( log.NewNopLogger(), p2p.NopMetrics(), selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - nil, - nil, + testnet.RandomNode().Transport, + &p2p.Endpoint{}, p2p.RouterOptions{}, ) require.NoError(t, err) @@ -126,7 +128,6 @@ func TestRouter_Channel_Basic(t *testing.T) { channel, err := router.OpenChannel(chctx, chDesc) require.NoError(t, err) - require.Contains(t, router.NodeInfo().Channels, byte(chDesc.ID)) require.NotNil(t, channel) // Opening the same channel again should fail. @@ -136,9 +137,7 @@ func TestRouter_Channel_Basic(t *testing.T) { // Opening a different channel should work. chDesc2 := &p2p.ChannelDescriptor{ID: 2, MessageType: &p2ptest.Message{}} _, err = router.OpenChannel(ctx, chDesc2) - require.NoError(t, err) - require.Contains(t, router.NodeInfo().Channels, byte(chDesc2.ID)) // Closing the channel, then opening it again should be fine. chcancel() @@ -396,10 +395,10 @@ func TestRouter_AcceptPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil).Maybe() mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -413,7 +412,7 @@ func TestRouter_AcceptPeers(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -453,9 +452,9 @@ func TestRouter_AcceptPeers_Error(t *testing.T) { // the router from calling Accept again. mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Accept", mock.Anything).Once().Return(nil, errors.New("boom")) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -467,7 +466,7 @@ func TestRouter_AcceptPeers_Error(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -490,9 +489,9 @@ func TestRouter_AcceptPeers_ErrorEOF(t *testing.T) { // the router from calling Accept again. mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -504,7 +503,7 @@ func TestRouter_AcceptPeers_ErrorEOF(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -538,12 +537,12 @@ func TestRouter_AcceptPeers_HeadOfLineBlocking(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Times(3).Run(func(_ mock.Arguments) { acceptCh <- true }).Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -555,7 +554,7 @@ func TestRouter_AcceptPeers_HeadOfLineBlocking(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -611,7 +610,7 @@ func TestRouter_DialPeers(t *testing.T) { defer cancel() address := p2p.NodeAddress{Protocol: "mock", NodeID: tc.dialID} - endpoint := p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)} + endpoint := &p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)} // Set up a mock transport that handshakes. connCtx, connCancel := context.WithCancel(context.Background()) @@ -629,8 +628,8 @@ func TestRouter_DialPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil).Maybe() + mockTransport.On("Listen", mock.Anything).Return(nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) if tc.dialErr == nil { mockTransport.On("Dial", mock.Anything, endpoint).Once().Return(mockConnection, nil) @@ -658,7 +657,7 @@ func TestRouter_DialPeers(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -711,11 +710,11 @@ func TestRouter_DialPeers_Parallel(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) + mockTransport.On("Listen", mock.Anything).Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) for _, address := range []p2p.NodeAddress{a, b, c} { - endpoint := p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)} + endpoint := &p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)} mockTransport.On("Dial", mock.Anything, endpoint).Run(func(_ mock.Arguments) { dialCh <- true }).Return(mockConnection, nil) @@ -743,7 +742,7 @@ func TestRouter_DialPeers_Parallel(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{ DialSleep: func(_ context.Context) {}, @@ -800,10 +799,10 @@ func TestRouter_EvictPeers(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -817,7 +816,7 @@ func TestRouter_EvictPeers(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -864,10 +863,10 @@ func TestRouter_ChannelCompatability(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -879,7 +878,7 @@ func TestRouter_ChannelCompatability(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) @@ -917,10 +916,10 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) { mockTransport := &mocks.Transport{} mockTransport.On("AddChannelDescriptors", mock.Anything).Return() mockTransport.On("String").Maybe().Return("mock") - mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"}) mockTransport.On("Close").Return(nil) mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil) mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF) + mockTransport.On("Listen", mock.Anything).Return(nil) // Set up and start the router. peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{}) @@ -934,7 +933,7 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) { selfKey, peerManager, func() *types.NodeInfo { return &selfInfo }, - []p2p.Transport{mockTransport}, + mockTransport, nil, p2p.RouterOptions{}, ) diff --git a/internal/p2p/transport.go b/internal/p2p/transport.go index 041bbda3a..7a965260a 100644 --- a/internal/p2p/transport.go +++ b/internal/p2p/transport.go @@ -24,7 +24,7 @@ type Protocol string // Transport is a connection-oriented mechanism for exchanging data with a peer. type Transport interface { // Listen starts the transport on the specified endpoint. - Listen(Endpoint) error + Listen(*Endpoint) error // Protocols returns the protocols supported by the transport. The Router // uses this to pick a transport for an Endpoint. @@ -34,7 +34,7 @@ type Transport interface { // // How to listen is transport-dependent, e.g. MConnTransport uses Listen() while // MemoryTransport starts listening via MemoryNetwork.CreateTransport(). - Endpoints() []Endpoint + Endpoint() (*Endpoint, error) // Accept waits for the next inbound connection on a listening endpoint, blocking // until either a connection is available or the transport is closed. On closure, @@ -42,7 +42,7 @@ type Transport interface { Accept(context.Context) (Connection, error) // Dial creates an outbound connection to an endpoint. - Dial(context.Context, Endpoint) (Connection, error) + Dial(context.Context, *Endpoint) (Connection, error) // Close stops accepting new connections, but does not close active connections. Close() error @@ -129,13 +129,13 @@ type Endpoint struct { } // NewEndpoint constructs an Endpoint from a types.NetAddress structure. -func NewEndpoint(addr string) (Endpoint, error) { +func NewEndpoint(addr string) (*Endpoint, error) { ip, port, err := types.ParseAddressString(addr) if err != nil { - return Endpoint{}, err + return nil, err } - return Endpoint{ + return &Endpoint{ Protocol: MConnProtocol, IP: ip, Port: port, diff --git a/internal/p2p/transport_mconn.go b/internal/p2p/transport_mconn.go index 6e6728f11..7bf17d1a0 100644 --- a/internal/p2p/transport_mconn.go +++ b/internal/p2p/transport_mconn.go @@ -78,25 +78,25 @@ func (m *MConnTransport) Protocols() []Protocol { return []Protocol{MConnProtocol, TCPProtocol} } -// Endpoints implements Transport. -func (m *MConnTransport) Endpoints() []Endpoint { +// Endpoint implements Transport. +func (m *MConnTransport) Endpoint() (*Endpoint, error) { if m.listener == nil { - return []Endpoint{} + return nil, errors.New("listenter not defined") } select { case <-m.doneCh: - return []Endpoint{} + return nil, errors.New("transport closed") default: } - endpoint := Endpoint{ + endpoint := &Endpoint{ Protocol: MConnProtocol, } if addr, ok := m.listener.Addr().(*net.TCPAddr); ok { endpoint.IP = addr.IP endpoint.Port = uint16(addr.Port) } - return []Endpoint{endpoint} + return endpoint, nil } // Listen asynchronously listens for inbound connections on the given endpoint. @@ -106,7 +106,7 @@ func (m *MConnTransport) Endpoints() []Endpoint { // FIXME: Listen currently only supports listening on a single endpoint, it // might be useful to support listening on multiple addresses (e.g. IPv4 and // IPv6, or a private and public address) via multiple Listen() calls. -func (m *MConnTransport) Listen(endpoint Endpoint) error { +func (m *MConnTransport) Listen(endpoint *Endpoint) error { if m.listener != nil { return errors.New("transport is already listening") } @@ -170,7 +170,7 @@ func (m *MConnTransport) Accept(ctx context.Context) (Connection, error) { } // Dial implements Transport. -func (m *MConnTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) { +func (m *MConnTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) { if err := m.validateEndpoint(endpoint); err != nil { return nil, err } @@ -217,7 +217,7 @@ func (m *MConnTransport) AddChannelDescriptors(channelDesc []*ChannelDescriptor) } // validateEndpoint validates an endpoint. -func (m *MConnTransport) validateEndpoint(endpoint Endpoint) error { +func (m *MConnTransport) validateEndpoint(endpoint *Endpoint) error { if err := endpoint.Validate(); err != nil { return err } diff --git a/internal/p2p/transport_mconn_test.go b/internal/p2p/transport_mconn_test.go index 423f4e930..c478dbe1d 100644 --- a/internal/p2p/transport_mconn_test.go +++ b/internal/p2p/transport_mconn_test.go @@ -25,7 +25,7 @@ func init() { []*p2p.ChannelDescriptor{{ID: chID, Priority: 1}}, p2p.MConnTransportOptions{}, ) - err := transport.Listen(p2p.Endpoint{ + err := transport.Listen(&p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), Port: 0, // assign a random port @@ -73,13 +73,14 @@ func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) { t.Cleanup(func() { _ = transport.Close() }) - err := transport.Listen(p2p.Endpoint{ + err := transport.Listen(&p2p.Endpoint{ Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1), }) require.NoError(t, err) - require.NotEmpty(t, transport.Endpoints()) - endpoint := transport.Endpoints()[0] + endpoint, err := transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) // Start a goroutine to just accept any connections. acceptCh := make(chan p2p.Connection, 10) @@ -132,20 +133,20 @@ func TestMConnTransport_Listen(t *testing.T) { defer cancel() testcases := []struct { - endpoint p2p.Endpoint + endpoint *p2p.Endpoint ok bool }{ // Valid v4 and v6 addresses, with mconn and tcp protocols. - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true}, - {p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true}, + {&p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true}, // Invalid endpoints. - {p2p.Endpoint{}, false}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false}, - {p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false}, + {&p2p.Endpoint{}, false}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false}, + {&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false}, } for _, tc := range testcases { tc := tc @@ -160,10 +161,12 @@ func TestMConnTransport_Listen(t *testing.T) { ) // Transport should not listen on any endpoints yet. - require.Empty(t, transport.Endpoints()) + endpoint, err := transport.Endpoint() + require.Error(t, err) + require.Nil(t, endpoint) // Start listening, and check any expected errors. - err := transport.Listen(tc.endpoint) + err = transport.Listen(tc.endpoint) if !tc.ok { require.Error(t, err) return @@ -171,9 +174,9 @@ func TestMConnTransport_Listen(t *testing.T) { require.NoError(t, err) // Check the endpoint. - endpoints := transport.Endpoints() - require.Len(t, endpoints, 1) - endpoint := endpoints[0] + endpoint, err = transport.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) require.Equal(t, p2p.MConnProtocol, endpoint.Protocol) if tc.endpoint.IP.IsUnspecified() { diff --git a/internal/p2p/transport_memory.go b/internal/p2p/transport_memory.go index 528ce4bb6..3eb4c5b51 100644 --- a/internal/p2p/transport_memory.go +++ b/internal/p2p/transport_memory.go @@ -119,7 +119,7 @@ func (t *MemoryTransport) String() string { return string(MemoryProtocol) } -func (*MemoryTransport) Listen(Endpoint) error { return nil } +func (*MemoryTransport) Listen(*Endpoint) error { return nil } func (t *MemoryTransport) AddChannelDescriptors([]*ChannelDescriptor) {} @@ -129,19 +129,19 @@ func (t *MemoryTransport) Protocols() []Protocol { } // Endpoints implements Transport. -func (t *MemoryTransport) Endpoints() []Endpoint { +func (t *MemoryTransport) Endpoint() (*Endpoint, error) { if n := t.network.GetTransport(t.nodeID); n == nil { - return []Endpoint{} + return nil, errors.New("node not defined") } - return []Endpoint{{ + return &Endpoint{ Protocol: MemoryProtocol, Path: string(t.nodeID), // An arbitrary IP and port is used in order for the pex // reactor to be able to send addresses to one another. IP: net.IPv4zero, Port: 0, - }} + }, nil } // Accept implements Transport. @@ -158,7 +158,7 @@ func (t *MemoryTransport) Accept(ctx context.Context) (Connection, error) { } // Dial implements Transport. -func (t *MemoryTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) { +func (t *MemoryTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) { if endpoint.Protocol != MemoryProtocol { return nil, fmt.Errorf("invalid protocol %q", endpoint.Protocol) } diff --git a/internal/p2p/transport_test.go b/internal/p2p/transport_test.go index 8f4f90483..b4edf9bc9 100644 --- a/internal/p2p/transport_test.go +++ b/internal/p2p/transport_test.go @@ -87,9 +87,9 @@ func TestTransport_DialEndpoints(t *testing.T) { withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) { a := makeTransport(t) - endpoints := a.Endpoints() - require.NotEmpty(t, endpoints) - endpoint := endpoints[0] + endpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint) // Spawn a goroutine to simply accept any connections until closed. go func() { @@ -108,19 +108,19 @@ func TestTransport_DialEndpoints(t *testing.T) { require.NoError(t, conn.Close()) // Dialing empty endpoint should error. - _, err = a.Dial(ctx, p2p.Endpoint{}) + _, err = a.Dial(ctx, &p2p.Endpoint{}) require.Error(t, err) // Dialing without protocol should error. - noProtocol := endpoint + noProtocol := *endpoint noProtocol.Protocol = "" - _, err = a.Dial(ctx, noProtocol) + _, err = a.Dial(ctx, &noProtocol) require.Error(t, err) // Dialing with invalid protocol should error. - fooProtocol := endpoint + fooProtocol := *endpoint fooProtocol.Protocol = "foo" - _, err = a.Dial(ctx, fooProtocol) + _, err = a.Dial(ctx, &fooProtocol) require.Error(t, err) // Tests for networked endpoints (with IP). @@ -129,11 +129,12 @@ func TestTransport_DialEndpoints(t *testing.T) { tc := tc t.Run(tc.ip.String(), func(t *testing.T) { e := endpoint + require.NotNil(t, e) e.IP = tc.ip conn, err := a.Dial(ctx, e) if tc.ok { - require.NoError(t, conn.Close()) require.NoError(t, err) + require.NoError(t, conn.Close()) } else { require.Error(t, err, "endpoint=%s", e) } @@ -167,16 +168,18 @@ func TestTransport_Dial(t *testing.T) { a := makeTransport(t) b := makeTransport(t) - require.NotEmpty(t, a.Endpoints()) - require.NotEmpty(t, b.Endpoints()) - aEndpoint := a.Endpoints()[0] - bEndpoint := b.Endpoints()[0] + aEndpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, aEndpoint) + bEndpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, bEndpoint) // Context cancellation should error. We can't test timeouts since we'd // need a non-responsive endpoint. cancelCtx, cancel := context.WithCancel(ctx) cancel() - _, err := a.Dial(cancelCtx, bEndpoint) + _, err = a.Dial(cancelCtx, bEndpoint) require.Error(t, err) // Unavailable endpoint should error. @@ -210,21 +213,26 @@ func TestTransport_Endpoints(t *testing.T) { b := makeTransport(t) // Both transports return valid and different endpoints. - aEndpoints := a.Endpoints() - bEndpoints := b.Endpoints() - require.NotEmpty(t, aEndpoints) - require.NotEmpty(t, bEndpoints) - require.NotEqual(t, aEndpoints, bEndpoints) - for _, endpoint := range append(aEndpoints, bEndpoints...) { + aEndpoint, err := a.Endpoint() + require.NoError(t, err) + require.NotNil(t, aEndpoint) + bEndpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, bEndpoint) + require.NotEqual(t, aEndpoint, bEndpoint) + for _, endpoint := range []*p2p.Endpoint{aEndpoint, bEndpoint} { err := endpoint.Validate() require.NoError(t, err, "invalid endpoint %q", endpoint) } // When closed, the transport should no longer return any endpoints. - err := a.Close() + require.NoError(t, a.Close()) + aEndpoint, err = a.Endpoint() + require.Error(t, err) + require.Nil(t, aEndpoint) + bEndpoint, err = b.Endpoint() require.NoError(t, err) - require.Empty(t, a.Endpoints()) - require.NotEmpty(t, b.Endpoints()) + require.NotNil(t, bEndpoint) }) } @@ -235,13 +243,12 @@ func TestTransport_Protocols(t *testing.T) { withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) { a := makeTransport(t) protocols := a.Protocols() - endpoints := a.Endpoints() + endpoint, err := a.Endpoint() + require.NoError(t, err) require.NotEmpty(t, protocols) - require.NotEmpty(t, endpoints) + require.NotNil(t, endpoint) - for _, endpoint := range endpoints { - require.Contains(t, protocols, endpoint.Protocol) - } + require.Contains(t, protocols, endpoint.Protocol) }) } @@ -595,8 +602,9 @@ func TestEndpoint_Validate(t *testing.T) { func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Connection, p2p.Connection) { t.Helper() - endpoints := b.Endpoints() - require.NotEmpty(t, endpoints, "peer not listening on any endpoints") + endpoint, err := b.Endpoint() + require.NoError(t, err) + require.NotNil(t, endpoint, "peer not listening on any endpoints") ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -609,7 +617,7 @@ func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Conn acceptCh <- conn }() - dialConn, err := a.Dial(ctx, endpoints[0]) + dialConn, err := a.Dial(ctx, endpoint) require.NoError(t, err) acceptConn := <-acceptCh diff --git a/internal/proxy/client.go b/internal/proxy/client.go index 2af1b1021..735c99656 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -124,32 +124,32 @@ func kill() error { return p.Signal(syscall.SIGABRT) } -func (app *proxyClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { +func (app *proxyClient) InitChain(ctx context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "init_chain", "type", "sync"))() return app.client.InitChain(ctx, req) } -func (app *proxyClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { +func (app *proxyClient) PrepareProposal(ctx context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "prepare_proposal", "type", "sync"))() return app.client.PrepareProposal(ctx, req) } -func (app *proxyClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { +func (app *proxyClient) ProcessProposal(ctx context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "process_proposal", "type", "sync"))() return app.client.ProcessProposal(ctx, req) } -func (app *proxyClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { +func (app *proxyClient) ExtendVote(ctx context.Context, req *types.RequestExtendVote) (*types.ResponseExtendVote, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "extend_vote", "type", "sync"))() return app.client.ExtendVote(ctx, req) } -func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { +func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req *types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "verify_vote_extension", "type", "sync"))() return app.client.VerifyVoteExtension(ctx, req) } -func (app *proxyClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { +func (app *proxyClient) FinalizeBlock(ctx context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "finalize_block", "type", "sync"))() return app.client.FinalizeBlock(ctx, req) } @@ -164,7 +164,7 @@ func (app *proxyClient) Flush(ctx context.Context) error { return app.client.Flush(ctx) } -func (app *proxyClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (app *proxyClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))() return app.client.CheckTx(ctx, req) } @@ -174,32 +174,32 @@ func (app *proxyClient) Echo(ctx context.Context, msg string) (*types.ResponseEc return app.client.Echo(ctx, msg) } -func (app *proxyClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { +func (app *proxyClient) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "info", "type", "sync"))() return app.client.Info(ctx, req) } -func (app *proxyClient) Query(ctx context.Context, reqQuery types.RequestQuery) (*types.ResponseQuery, error) { +func (app *proxyClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "query", "type", "sync"))() - return app.client.Query(ctx, reqQuery) + return app.client.Query(ctx, req) } -func (app *proxyClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { +func (app *proxyClient) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "list_snapshots", "type", "sync"))() return app.client.ListSnapshots(ctx, req) } -func (app *proxyClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { +func (app *proxyClient) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "offer_snapshot", "type", "sync"))() return app.client.OfferSnapshot(ctx, req) } -func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { +func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "load_snapshot_chunk", "type", "sync"))() return app.client.LoadSnapshotChunk(ctx, req) } -func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { +func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "apply_snapshot_chunk", "type", "sync"))() return app.client.ApplySnapshotChunk(ctx, req) } diff --git a/internal/proxy/client_test.go b/internal/proxy/client_test.go index 057177a60..09ac3f2c8 100644 --- a/internal/proxy/client_test.go +++ b/internal/proxy/client_test.go @@ -30,7 +30,7 @@ import ( type appConnTestI interface { Echo(context.Context, string) (*types.ResponseEcho, error) Flush(context.Context) error - Info(context.Context, types.RequestInfo) (*types.ResponseInfo, error) + Info(context.Context, *types.RequestInfo) (*types.ResponseInfo, error) } type appConnTest struct { @@ -49,7 +49,7 @@ func (app *appConnTest) Flush(ctx context.Context) error { return app.appConn.Flush(ctx) } -func (app *appConnTest) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { +func (app *appConnTest) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { return app.appConn.Info(ctx, req) } @@ -164,7 +164,7 @@ func TestInfo(t *testing.T) { proxy := newAppConnTest(client) t.Log("Connected") - resInfo, err := proxy.Info(ctx, RequestInfo) + resInfo, err := proxy.Info(ctx, &RequestInfo) require.NoError(t, err) if resInfo.Data != "{\"size\":0}" { diff --git a/internal/rpc/core/abci.go b/internal/rpc/core/abci.go index 8f5e61d55..fa45c6b45 100644 --- a/internal/rpc/core/abci.go +++ b/internal/rpc/core/abci.go @@ -5,24 +5,17 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/internal/proxy" - "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/rpc/coretypes" ) // ABCIQuery queries the application for some information. // More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_query -func (env *Environment) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, - height int64, - prove bool, -) (*coretypes.ResultABCIQuery, error) { - resQuery, err := env.ProxyApp.Query(ctx, abci.RequestQuery{ - Path: path, - Data: data, - Height: height, - Prove: prove, +func (env *Environment) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) { + resQuery, err := env.ProxyApp.Query(ctx, &abci.RequestQuery{ + Path: req.Path, + Data: req.Data, + Height: int64(req.Height), + Prove: req.Prove, }) if err != nil { return nil, err @@ -34,7 +27,7 @@ func (env *Environment) ABCIQuery( // ABCIInfo gets some info about the application. // More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_info func (env *Environment) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - resInfo, err := env.ProxyApp.Info(ctx, proxy.RequestInfo) + resInfo, err := env.ProxyApp.Info(ctx, &proxy.RequestInfo) if err != nil { return nil, err } diff --git a/internal/rpc/core/blocks.go b/internal/rpc/core/blocks.go index 26044aef7..239344002 100644 --- a/internal/rpc/core/blocks.go +++ b/internal/rpc/core/blocks.go @@ -7,7 +7,6 @@ import ( tmquery "github.com/tendermint/tendermint/internal/pubsub/query" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" "github.com/tendermint/tendermint/types" @@ -23,17 +22,15 @@ import ( // order (highest first). // // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain -func (env *Environment) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - - const limit int64 = 20 - - var err error - minHeight, maxHeight, err = filterMinMax( +func (env *Environment) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) { + const limit = 20 + minHeight, maxHeight, err := filterMinMax( env.BlockStore.Base(), env.BlockStore.Height(), - minHeight, - maxHeight, - limit) + int64(req.MinHeight), + int64(req.MaxHeight), + limit, + ) if err != nil { return nil, err } @@ -90,8 +87,8 @@ func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) { // Block gets block at a given height. // If no height is provided, it will fetch the latest block. // More: https://docs.tendermint.com/master/rpc/#/Info/block -func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -107,12 +104,8 @@ func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes // BlockByHash gets block by hash. // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash -func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - - block := env.BlockStore.LoadBlockByHash(hash) +func (env *Environment) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) { + block := env.BlockStore.LoadBlockByHash(req.Hash) if block == nil { return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil } @@ -124,8 +117,8 @@ func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (* // Header gets block header at a given height. // If no height is provided, it will fetch the latest header. // More: https://docs.tendermint.com/master/rpc/#/Info/header -func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -140,12 +133,8 @@ func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretype // HeaderByHash gets header by hash. // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash -func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - - blockMeta := env.BlockStore.LoadBlockMetaByHash(hash) +func (env *Environment) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) { + blockMeta := env.BlockStore.LoadBlockMetaByHash(req.Hash) if blockMeta == nil { return &coretypes.ResultHeader{}, nil } @@ -156,8 +145,8 @@ func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) ( // Commit gets block commit at a given height. // If no height is provided, it will fetch the commit for the latest block. // More: https://docs.tendermint.com/master/rpc/#/Info/commit -func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -192,8 +181,8 @@ func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretype // // Results are for the height of the block containing the txs. // More: https://docs.tendermint.com/master/rpc/#/Info/block_results -func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) { - height, err := env.getHeight(env.BlockStore.Height(), heightPtr) +func (env *Environment) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) { + height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -218,20 +207,13 @@ func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*co }, nil } -// BlockSearch searches for a paginated set of blocks matching the provided -// query. -func (env *Environment) BlockSearch( - ctx context.Context, - query string, - pagePtr, perPagePtr *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { - +// BlockSearch searches for a paginated set of blocks matching the provided query. +func (env *Environment) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) { if !indexer.KVSinkEnabled(env.EventSinks) { return nil, fmt.Errorf("block searching is disabled due to no kvEventSink") } - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, err } @@ -249,7 +231,7 @@ func (env *Environment) BlockSearch( } // sort results (must be done before pagination) - switch orderBy { + switch req.OrderBy { case "desc", "": sort.Slice(results, func(i, j int) bool { return results[i] > results[j] }) @@ -262,9 +244,9 @@ func (env *Environment) BlockSearch( // paginate results totalCount := len(results) - perPage := env.validatePerPage(perPagePtr) + perPage := env.validatePerPage(req.PerPage.IntPtr()) - page, err := validatePage(pagePtr, perPage, totalCount) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } diff --git a/internal/rpc/core/blocks_test.go b/internal/rpc/core/blocks_test.go index c48ac4c48..d95338332 100644 --- a/internal/rpc/core/blocks_test.go +++ b/internal/rpc/core/blocks_test.go @@ -109,7 +109,9 @@ func TestBlockResults(t *testing.T) { ctx := context.Background() for _, tc := range testCases { - res, err := env.BlockResults(ctx, &tc.height) + res, err := env.BlockResults(ctx, &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(&tc.height), + }) if tc.wantErr { assert.Error(t, err) } else { diff --git a/internal/rpc/core/consensus.go b/internal/rpc/core/consensus.go index f10f37ebc..46e220f05 100644 --- a/internal/rpc/core/consensus.go +++ b/internal/rpc/core/consensus.go @@ -14,10 +14,9 @@ import ( // for the validators in the set as used in computing their Merkle root. // // More: https://docs.tendermint.com/master/rpc/#/Info/validators -func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) { - +func (env *Environment) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) { // The latest validator that we know is the NextValidator of the last block. - height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr) + height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height)) if err != nil { return nil, err } @@ -28,8 +27,8 @@ func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePt } totalCount := len(validators.Validators) - perPage := env.validatePerPage(perPagePtr) - page, err := validatePage(pagePtr, perPage, totalCount) + perPage := env.validatePerPage(req.PerPage.IntPtr()) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -42,7 +41,8 @@ func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePt BlockHeight: height, Validators: v, Count: len(v), - Total: totalCount}, nil + Total: totalCount, + }, nil } // DumpConsensusState dumps consensus state. @@ -99,11 +99,10 @@ func (env *Environment) GetConsensusState(ctx context.Context) (*coretypes.Resul // ConsensusParams gets the consensus parameters at the given block height. // If no height is provided, it will fetch the latest consensus params. // More: https://docs.tendermint.com/master/rpc/#/Info/consensus_params -func (env *Environment) ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error) { - - // The latest consensus params that we know is the consensus params after the - // last block. - height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr) +func (env *Environment) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) { + // The latest consensus params that we know is the consensus params after + // the last block. + height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height)) if err != nil { return nil, err } diff --git a/internal/rpc/core/events.go b/internal/rpc/core/events.go index 4e0d2ac8a..3f289bfa7 100644 --- a/internal/rpc/core/events.go +++ b/internal/rpc/core/events.go @@ -26,7 +26,7 @@ const ( // Subscribe for events via WebSocket. // More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe -func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) { +func (env *Environment) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) { callInfo := rpctypes.GetCallInfo(ctx) addr := callInfo.RemoteAddr() @@ -34,15 +34,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients) } else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient { return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient) - } else if len(query) > maxQueryLength { + } else if len(req.Query) > maxQueryLength { return nil, errors.New("maximum query length exceeded") } env.Logger.Info("WARNING: Websocket subscriptions are deprecated and will be removed " + "in Tendermint v0.37. See https://tinyurl.com/adr075 for more information.") - env.Logger.Info("Subscribe to query", "remote", addr, "query", query) + env.Logger.Info("Subscribe to query", "remote", addr, "query", req.Query) - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, fmt.Errorf("failed to parse query: %w", err) } @@ -83,7 +83,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes // We have a message to deliver to the client. resp := callInfo.RPCRequest.MakeResponse(&coretypes.ResultEvent{ - Query: query, + Query: req.Query, Data: msg.Data(), Events: msg.Events(), }) @@ -102,15 +102,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes // Unsubscribe from events via WebSocket. // More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe -func (env *Environment) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) { +func (env *Environment) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) { args := tmpubsub.UnsubscribeArgs{Subscriber: rpctypes.GetCallInfo(ctx).RemoteAddr()} - env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", query) + env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", req.Query) var err error - args.Query, err = tmquery.New(query) + args.Query, err = tmquery.New(req.Query) if err != nil { - args.ID = query + args.ID = req.Query } err = env.EventBus.Unsubscribe(ctx, args) @@ -148,17 +148,13 @@ func (env *Environment) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUn // If maxItems ≤ 0, a default positive number of events is chosen. The values // of maxItems and waitTime may be capped to sensible internal maxima without // reporting an error to the caller. -func (env *Environment) Events(ctx context.Context, - filter *coretypes.EventFilter, - maxItems int, - before, after cursor.Cursor, - waitTime time.Duration, -) (*coretypes.ResultEvents, error) { +func (env *Environment) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { if env.EventLog == nil { return nil, errors.New("the event log is not enabled") } // Parse and validate parameters. + maxItems := req.MaxItems if maxItems <= 0 { maxItems = 10 } else if maxItems > 100 { @@ -167,6 +163,8 @@ func (env *Environment) Events(ctx context.Context, const minWaitTime = 1 * time.Second const maxWaitTime = 30 * time.Second + + waitTime := req.WaitTime if waitTime < minWaitTime { waitTime = minWaitTime } else if waitTime > maxWaitTime { @@ -174,14 +172,22 @@ func (env *Environment) Events(ctx context.Context, } query := tmquery.All - if filter != nil && filter.Query != "" { - q, err := tmquery.New(filter.Query) + if req.Filter != nil && req.Filter.Query != "" { + q, err := tmquery.New(req.Filter.Query) if err != nil { return nil, fmt.Errorf("invalid filter query: %w", err) } query = q } + var before, after cursor.Cursor + if err := before.UnmarshalText([]byte(req.Before)); err != nil { + return nil, fmt.Errorf("invalid cursor %q: %w", req.Before, err) + } + if err := after.UnmarshalText([]byte(req.After)); err != nil { + return nil, fmt.Errorf("invalid cursor %q: %w", req.After, err) + } + var info eventlog.Info var items []*eventlog.Item var err error diff --git a/internal/rpc/core/evidence.go b/internal/rpc/core/evidence.go index c7e2bea8a..5de93d2c2 100644 --- a/internal/rpc/core/evidence.go +++ b/internal/rpc/core/evidence.go @@ -9,18 +9,15 @@ import ( // BroadcastEvidence broadcasts evidence of the misbehavior. // More: https://docs.tendermint.com/master/rpc/#/Evidence/broadcast_evidence -func (env *Environment) BroadcastEvidence( - ctx context.Context, - ev coretypes.Evidence, -) (*coretypes.ResultBroadcastEvidence, error) { - if ev.Value == nil { +func (env *Environment) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) { + if req.Evidence == nil { return nil, fmt.Errorf("%w: no evidence was provided", coretypes.ErrInvalidRequest) } - if err := ev.Value.ValidateBasic(); err != nil { + if err := req.Evidence.ValidateBasic(); err != nil { return nil, fmt.Errorf("evidence.ValidateBasic failed: %w", err) } - if err := env.EvidencePool.AddEvidence(ctx, ev.Value); err != nil { + if err := env.EvidencePool.AddEvidence(ctx, req.Evidence); err != nil { return nil, fmt.Errorf("failed to add evidence: %w", err) } - return &coretypes.ResultBroadcastEvidence{Hash: ev.Value.Hash()}, nil + return &coretypes.ResultBroadcastEvidence{Hash: req.Evidence.Hash()}, nil } diff --git a/internal/rpc/core/mempool.go b/internal/rpc/core/mempool.go index a852aeb1d..5fc2b9fcf 100644 --- a/internal/rpc/core/mempool.go +++ b/internal/rpc/core/mempool.go @@ -12,7 +12,6 @@ import ( "github.com/tendermint/tendermint/internal/state/indexer" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" - "github.com/tendermint/tendermint/types" ) //----------------------------------------------------------------------------- @@ -21,23 +20,23 @@ import ( // BroadcastTxAsync returns right away, with no response. Does not wait for // CheckTx nor DeliverTx results. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async -func (env *Environment) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - err := env.Mempool.CheckTx(ctx, tx, nil, mempool.TxInfo{}) +func (env *Environment) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + err := env.Mempool.CheckTx(ctx, req.Tx, nil, mempool.TxInfo{}) if err != nil { return nil, err } - return &coretypes.ResultBroadcastTx{Hash: tx.Hash()}, nil + return &coretypes.ResultBroadcastTx{Hash: req.Tx.Hash()}, nil } // BroadcastTxSync returns with the response from CheckTx. Does not wait for // DeliverTx result. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync -func (env *Environment) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { +func (env *Environment) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { resCh := make(chan *abci.ResponseCheckTx, 1) err := env.Mempool.CheckTx( ctx, - tx, + req.Tx, func(res *abci.ResponseCheckTx) { select { case <-ctx.Done(): @@ -60,19 +59,18 @@ func (env *Environment) BroadcastTxSync(ctx context.Context, tx types.Tx) (*core Log: r.Log, Codespace: r.Codespace, MempoolError: r.MempoolError, - Hash: tx.Hash(), + Hash: req.Tx.Hash(), }, nil } - } // BroadcastTxCommit returns with the responses from CheckTx and DeliverTx. // More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit -func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { +func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) { resCh := make(chan *abci.ResponseCheckTx, 1) err := env.Mempool.CheckTx( ctx, - tx, + req.Tx, func(res *abci.ResponseCheckTx) { select { case <-ctx.Done(): @@ -92,14 +90,14 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co if r.Code != abci.CodeTypeOK { return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, - Hash: tx.Hash(), + Hash: req.Tx.Hash(), }, fmt.Errorf("transaction encountered error (%s)", r.MempoolError) } if !indexer.KVSinkEnabled(env.EventSinks) { return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, - Hash: tx.Hash(), + Hash: req.Tx.Hash(), }, errors.New("cannot confirm transaction because kvEventSink is not enabled") } @@ -118,11 +116,14 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co "err", err) return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, - Hash: tx.Hash(), + Hash: req.Tx.Hash(), }, fmt.Errorf("timeout waiting for commit of tx %s (%s)", - tx.Hash(), time.Since(startAt)) + req.Tx.Hash(), time.Since(startAt)) case <-timer.C: - txres, err := env.Tx(ctx, tx.Hash(), false) + txres, err := env.Tx(ctx, &coretypes.RequestTx{ + Hash: req.Tx.Hash(), + Prove: false, + }) if err != nil { jitter := 100*time.Millisecond + time.Duration(rand.Int63n(int64(time.Second))) // nolint: gosec backoff := 100 * time.Duration(count) * time.Millisecond @@ -133,7 +134,7 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co return &coretypes.ResultBroadcastTxCommit{ CheckTx: *r, TxResult: txres.TxResult, - Hash: tx.Hash(), + Hash: req.Tx.Hash(), Height: txres.Height, }, nil } @@ -143,10 +144,10 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co // UnconfirmedTxs gets unconfirmed transactions from the mempool in order of priority // More: https://docs.tendermint.com/master/rpc/#/Info/unconfirmed_txs -func (env *Environment) UnconfirmedTxs(ctx context.Context, pagePtr, perPagePtr *int) (*coretypes.ResultUnconfirmedTxs, error) { +func (env *Environment) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) { totalCount := env.Mempool.Size() - perPage := env.validatePerPage(perPagePtr) - page, err := validatePage(pagePtr, perPage, totalCount) + perPage := env.validatePerPage(req.PerPage.IntPtr()) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -160,7 +161,8 @@ func (env *Environment) UnconfirmedTxs(ctx context.Context, pagePtr, perPagePtr Count: len(result), Total: totalCount, TotalBytes: env.Mempool.SizeBytes(), - Txs: result}, nil + Txs: result, + }, nil } // NumUnconfirmedTxs gets number of unconfirmed transactions. @@ -175,14 +177,14 @@ func (env *Environment) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul // CheckTx checks the transaction without executing it. The transaction won't // be added to the mempool either. // More: https://docs.tendermint.com/master/rpc/#/Tx/check_tx -func (env *Environment) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - res, err := env.ProxyApp.CheckTx(ctx, abci.RequestCheckTx{Tx: tx}) +func (env *Environment) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) { + res, err := env.ProxyApp.CheckTx(ctx, &abci.RequestCheckTx{Tx: req.Tx}) if err != nil { return nil, err } return &coretypes.ResultCheckTx{ResponseCheckTx: *res}, nil } -func (env *Environment) RemoveTx(ctx context.Context, txkey types.TxKey) error { - return env.Mempool.RemoveTxByKey(txkey) +func (env *Environment) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error { + return env.Mempool.RemoveTxByKey(req.TxKey) } diff --git a/internal/rpc/core/net.go b/internal/rpc/core/net.go index 5444b77b7..b18f1e2fc 100644 --- a/internal/rpc/core/net.go +++ b/internal/rpc/core/net.go @@ -44,7 +44,7 @@ func (env *Environment) Genesis(ctx context.Context) (*coretypes.ResultGenesis, return &coretypes.ResultGenesis{Genesis: env.GenDoc}, nil } -func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error) { +func (env *Environment) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) { if env.genChunks == nil { return nil, fmt.Errorf("service configuration error, genesis chunks are not initialized") } @@ -53,7 +53,7 @@ func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*corety return nil, fmt.Errorf("service configuration error, there are no chunks") } - id := int(chunk) + id := int(req.Chunk) if id > len(env.genChunks)-1 { return nil, fmt.Errorf("there are %d chunks, %d is invalid", len(env.genChunks)-1, id) diff --git a/internal/rpc/core/routes.go b/internal/rpc/core/routes.go index 945798ed7..4bc1ca414 100644 --- a/internal/rpc/core/routes.go +++ b/internal/rpc/core/routes.go @@ -2,13 +2,9 @@ package core import ( "context" - "time" - "github.com/tendermint/tendermint/internal/eventlog/cursor" - "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/rpc/coretypes" rpc "github.com/tendermint/tendermint/rpc/jsonrpc/server" - "github.com/tendermint/tendermint/types" ) // TODO: better system than "unsafe" prefix @@ -32,47 +28,47 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap { out := RoutesMap{ // Event subscription. Note that subscribe, unsubscribe, and // unsubscribe_all are only available via the websocket endpoint. - "events": rpc.NewRPCFunc(svc.Events, "filter", "maxItems", "before", "after", "waitTime"), - "subscribe": rpc.NewWSRPCFunc(svc.Subscribe, "query"), - "unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe, "query"), + "events": rpc.NewRPCFunc(svc.Events), + "subscribe": rpc.NewWSRPCFunc(svc.Subscribe), + "unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe), "unsubscribe_all": rpc.NewWSRPCFunc(svc.UnsubscribeAll), // info API "health": rpc.NewRPCFunc(svc.Health), "status": rpc.NewRPCFunc(svc.Status), "net_info": rpc.NewRPCFunc(svc.NetInfo), - "blockchain": rpc.NewRPCFunc(svc.BlockchainInfo, "minHeight", "maxHeight"), + "blockchain": rpc.NewRPCFunc(svc.BlockchainInfo), "genesis": rpc.NewRPCFunc(svc.Genesis), - "genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked, "chunk"), - "header": rpc.NewRPCFunc(svc.Header, "height"), - "header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash, "hash"), - "block": rpc.NewRPCFunc(svc.Block, "height"), - "block_by_hash": rpc.NewRPCFunc(svc.BlockByHash, "hash"), - "block_results": rpc.NewRPCFunc(svc.BlockResults, "height"), - "commit": rpc.NewRPCFunc(svc.Commit, "height"), - "check_tx": rpc.NewRPCFunc(svc.CheckTx, "tx"), - "remove_tx": rpc.NewRPCFunc(svc.RemoveTx, "txkey"), - "tx": rpc.NewRPCFunc(svc.Tx, "hash", "prove"), - "tx_search": rpc.NewRPCFunc(svc.TxSearch, "query", "prove", "page", "per_page", "order_by"), - "block_search": rpc.NewRPCFunc(svc.BlockSearch, "query", "page", "per_page", "order_by"), - "validators": rpc.NewRPCFunc(svc.Validators, "height", "page", "per_page"), + "genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked), + "header": rpc.NewRPCFunc(svc.Header), + "header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash), + "block": rpc.NewRPCFunc(svc.Block), + "block_by_hash": rpc.NewRPCFunc(svc.BlockByHash), + "block_results": rpc.NewRPCFunc(svc.BlockResults), + "commit": rpc.NewRPCFunc(svc.Commit), + "check_tx": rpc.NewRPCFunc(svc.CheckTx), + "remove_tx": rpc.NewRPCFunc(svc.RemoveTx), + "tx": rpc.NewRPCFunc(svc.Tx), + "tx_search": rpc.NewRPCFunc(svc.TxSearch), + "block_search": rpc.NewRPCFunc(svc.BlockSearch), + "validators": rpc.NewRPCFunc(svc.Validators), "dump_consensus_state": rpc.NewRPCFunc(svc.DumpConsensusState), "consensus_state": rpc.NewRPCFunc(svc.GetConsensusState), - "consensus_params": rpc.NewRPCFunc(svc.ConsensusParams, "height"), - "unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs, "page", "per_page"), + "consensus_params": rpc.NewRPCFunc(svc.ConsensusParams), + "unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs), "num_unconfirmed_txs": rpc.NewRPCFunc(svc.NumUnconfirmedTxs), // tx broadcast API - "broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit, "tx"), - "broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync, "tx"), - "broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync, "tx"), + "broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit), + "broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync), + "broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync), // abci API - "abci_query": rpc.NewRPCFunc(svc.ABCIQuery, "path", "data", "height", "prove"), + "abci_query": rpc.NewRPCFunc(svc.ABCIQuery), "abci_info": rpc.NewRPCFunc(svc.ABCIInfo), // evidence API - "broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence, "evidence"), + "broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence), } if u, ok := svc.(RPCUnsafe); ok && opts.Unsafe { out["unsafe_flush_mempool"] = rpc.NewRPCFunc(u.UnsafeFlushMempool) @@ -84,38 +80,38 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap { // implementation, for use in constructing a routing table. type RPCService interface { ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) - ABCIQuery(ctx context.Context, path string, data bytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error) - Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) - BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) - BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) - BlockSearch(ctx context.Context, query string, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultBlockSearch, error) - BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) - BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error) - BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) - BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) - BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) - CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) - Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) - ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error) + ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) + Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) + BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) + BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) + BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) + BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) + BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) + BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) + BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) + BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) + CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) + Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) + ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) - Events(ctx context.Context, filter *coretypes.EventFilter, maxItems int, before, after cursor.Cursor, waitTime time.Duration) (*coretypes.ResultEvents, error) + Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) - GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error) + GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) - Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) - HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) + Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) + HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) Health(ctx context.Context) (*coretypes.ResultHealth, error) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) - RemoveTx(ctx context.Context, txkey types.TxKey) error + RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error Status(ctx context.Context) (*coretypes.ResultStatus, error) - Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) - Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) - TxSearch(ctx context.Context, query string, prove bool, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultTxSearch, error) - UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) - Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) + Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) + Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) + TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) + UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) + Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) - Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) + Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) } // RPCUnsafe defines the set of "unsafe" methods that may optionally be diff --git a/internal/rpc/core/tx.go b/internal/rpc/core/tx.go index 73fa6d2c8..cd643b844 100644 --- a/internal/rpc/core/tx.go +++ b/internal/rpc/core/tx.go @@ -8,7 +8,6 @@ import ( tmquery "github.com/tendermint/tendermint/internal/pubsub/query" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/rpc/coretypes" "github.com/tendermint/tendermint/types" @@ -18,32 +17,27 @@ import ( // transaction is in the mempool, invalidated, or was not sent in the first // place. // More: https://docs.tendermint.com/master/rpc/#/Info/tx -func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { +func (env *Environment) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) { // if index is disabled, return error - - // N.B. The hash parameter is HexBytes so that the reflective parameter - // decoding logic in the HTTP service will correctly translate from JSON. - // See https://github.com/tendermint/tendermint/issues/6802 for context. - if !indexer.KVSinkEnabled(env.EventSinks) { return nil, errors.New("transaction querying is disabled due to no kvEventSink") } for _, sink := range env.EventSinks { if sink.Type() == indexer.KV { - r, err := sink.GetTxByHash(hash) + r, err := sink.GetTxByHash(req.Hash) if r == nil { - return nil, fmt.Errorf("tx (%X) not found, err: %w", hash, err) + return nil, fmt.Errorf("tx (%X) not found, err: %w", req.Hash, err) } var proof types.TxProof - if prove { + if req.Prove { block := env.BlockStore.LoadBlock(r.Height) proof = block.Data.Txs.Proof(int(r.Index)) } return &coretypes.ResultTx{ - Hash: hash, + Hash: req.Hash, Height: r.Height, Index: r.Index, TxResult: r.Result, @@ -59,21 +53,14 @@ func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) // TxSearch allows you to query for multiple transactions results. It returns a // list of transactions (maximum ?per_page entries) and the total count. // More: https://docs.tendermint.com/master/rpc/#/Info/tx_search -func (env *Environment) TxSearch( - ctx context.Context, - query string, - prove bool, - pagePtr, perPagePtr *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { - +func (env *Environment) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) { if !indexer.KVSinkEnabled(env.EventSinks) { return nil, fmt.Errorf("transaction searching is disabled due to no kvEventSink") - } else if len(query) > maxQueryLength { + } else if len(req.Query) > maxQueryLength { return nil, errors.New("maximum query length exceeded") } - q, err := tmquery.New(query) + q, err := tmquery.New(req.Query) if err != nil { return nil, err } @@ -86,7 +73,7 @@ func (env *Environment) TxSearch( } // sort results (must be done before pagination) - switch orderBy { + switch req.OrderBy { case "desc", "": sort.Slice(results, func(i, j int) bool { if results[i].Height == results[j].Height { @@ -107,9 +94,9 @@ func (env *Environment) TxSearch( // paginate results totalCount := len(results) - perPage := env.validatePerPage(perPagePtr) + perPage := env.validatePerPage(req.PerPage.IntPtr()) - page, err := validatePage(pagePtr, perPage, totalCount) + page, err := validatePage(req.Page.IntPtr(), perPage, totalCount) if err != nil { return nil, err } @@ -122,7 +109,7 @@ func (env *Environment) TxSearch( r := results[i] var proof types.TxProof - if prove { + if req.Prove { block := env.BlockStore.LoadBlock(r.Height) proof = block.Data.Txs.Proof(int(r.Index)) } diff --git a/internal/state/execution.go b/internal/state/execution.go index c098f9c9d..cfacb816d 100644 --- a/internal/state/execution.go +++ b/internal/state/execution.go @@ -7,6 +7,7 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/encoding" "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/internal/eventbus" @@ -105,7 +106,7 @@ func (blockExec *BlockExecutor) CreateProposalBlock( localLastCommit := buildLastCommitInfo(block, blockExec.store, state.InitialHeight) rpp, err := blockExec.appClient.PrepareProposal( ctx, - abci.RequestPrepareProposal{ + &abci.RequestPrepareProposal{ MaxTxBytes: maxDataBytes, Txs: block.Txs.ToSliceOfBytes(), LocalLastCommit: extendedCommitInfo(localLastCommit, votes), @@ -147,7 +148,7 @@ func (blockExec *BlockExecutor) ProcessProposal( block *types.Block, state State, ) (bool, error) { - req := abci.RequestProcessProposal{ + resp, err := blockExec.appClient.ProcessProposal(ctx, &abci.RequestProcessProposal{ Hash: block.Header.Hash(), Height: block.Header.Height, Time: block.Header.Time, @@ -156,9 +157,7 @@ func (blockExec *BlockExecutor) ProcessProposal( ByzantineValidators: block.Evidence.ToABCI(), ProposerAddress: block.ProposerAddress, NextValidatorsHash: block.NextValidatorsHash, - } - - resp, err := blockExec.appClient.ProcessProposal(ctx, req) + }) if err != nil { return false, ErrInvalidBlock(err) } @@ -210,7 +209,7 @@ func (blockExec *BlockExecutor) ApplyBlock( startTime := time.Now().UnixNano() finalizeBlockResponse, err := blockExec.appClient.FinalizeBlock( ctx, - abci.RequestFinalizeBlock{ + &abci.RequestFinalizeBlock{ Hash: block.Hash(), Height: block.Header.Height, Time: block.Header.Time, @@ -248,6 +247,10 @@ func (blockExec *BlockExecutor) ApplyBlock( } if len(validatorUpdates) > 0 { blockExec.logger.Debug("updates to validators", "updates", types.ValidatorListString(validatorUpdates)) + blockExec.metrics.ValidatorSetUpdates.Add(1) + } + if finalizeBlockResponse.ConsensusParamUpdates != nil { + blockExec.metrics.ConsensusParamUpdates.Add(1) } // Update the state with the block and responses. @@ -296,29 +299,29 @@ func (blockExec *BlockExecutor) ApplyBlock( return state, nil } -func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote) (types.VoteExtension, error) { - req := abci.RequestExtendVote{ - Vote: vote.ToProto(), - } - - resp, err := blockExec.appClient.ExtendVote(ctx, req) +func (blockExec *BlockExecutor) ExtendVote(ctx context.Context, vote *types.Vote) ([]byte, error) { + resp, err := blockExec.appClient.ExtendVote(ctx, &abci.RequestExtendVote{ + Hash: vote.BlockID.Hash, + Height: vote.Height, + }) if err != nil { - return types.VoteExtension{}, err + panic(fmt.Errorf("ExtendVote call failed: %w", err)) } - return types.VoteExtensionFromProto(resp.VoteExtension), nil + return resp.VoteExtension, nil } func (blockExec *BlockExecutor) VerifyVoteExtension(ctx context.Context, vote *types.Vote) error { - req := abci.RequestVerifyVoteExtension{ - Vote: vote.ToProto(), - } - - resp, err := blockExec.appClient.VerifyVoteExtension(ctx, req) + resp, err := blockExec.appClient.VerifyVoteExtension(ctx, &abci.RequestVerifyVoteExtension{ + Hash: vote.BlockID.Hash, + ValidatorAddress: vote.ValidatorAddress, + Height: vote.Height, + VoteExtension: vote.Extension, + }) if err != nil { - return err + panic(fmt.Errorf("VerifyVoteExtension call failed: %w", err)) } - if resp.IsErr() { + if !resp.IsOK() { return types.ErrVoteInvalidExtension } @@ -417,16 +420,39 @@ func buildLastCommitInfo(block *types.Block, store Store, initialHeight int64) a } } +// extendedCommitInfo expects a CommitInfo struct along with all of the +// original votes relating to that commit, including their vote extensions. The +// order of votes does not matter. func extendedCommitInfo(c abci.CommitInfo, votes []*types.Vote) abci.ExtendedCommitInfo { + if len(c.Votes) != len(votes) { + panic(fmt.Sprintf("extendedCommitInfo: number of votes from commit differ from the number of votes supplied (%d != %d)", len(c.Votes), len(votes))) + } + votesByVal := make(map[string]*types.Vote) + for _, vote := range votes { + if vote != nil { + valAddr := vote.ValidatorAddress.String() + if _, ok := votesByVal[valAddr]; ok { + panic(fmt.Sprintf("extendedCommitInfo: found duplicate vote for validator with address %s", valAddr)) + } + votesByVal[valAddr] = vote + } + } vs := make([]abci.ExtendedVoteInfo, len(c.Votes)) for i := range vs { + var ext []byte + // votes[i] will be nil if c.Votes[i].SignedLastBlock is false + if c.Votes[i].SignedLastBlock { + valAddr := crypto.Address(c.Votes[i].Validator.Address).String() + vote, ok := votesByVal[valAddr] + if !ok || vote == nil { + panic(fmt.Sprintf("extendedCommitInfo: validator with address %s signed last block, but could not find vote for it", valAddr)) + } + ext = vote.Extension + } vs[i] = abci.ExtendedVoteInfo{ Validator: c.Votes[i].Validator, SignedLastBlock: c.Votes[i].SignedLastBlock, - /* - TODO: Include vote extensions information when implementing vote extensions. - VoteExtension: []byte{}, - */ + VoteExtension: ext, } } return abci.ExtendedCommitInfo{ @@ -608,7 +634,7 @@ func ExecCommitBlock( ) ([]byte, error) { finalizeBlockResponse, err := appConn.FinalizeBlock( ctx, - abci.RequestFinalizeBlock{ + &abci.RequestFinalizeBlock{ Hash: block.Hash(), Height: block.Height, Time: block.Time, diff --git a/internal/state/execution_test.go b/internal/state/execution_test.go index c70286e28..0937b9990 100644 --- a/internal/state/execution_test.go +++ b/internal/state/execution_test.go @@ -18,7 +18,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/proxy" @@ -180,14 +179,14 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) { Height: 10, Time: defaultEvidenceTime, LastBlockID: blockID, - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), ValidatorsHash: state.Validators.Hash(), NextValidatorsHash: state.Validators.Hash(), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } @@ -277,7 +276,7 @@ func TestProcessProposal(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - app := abcimocks.NewBaseMock() + app := abcimocks.NewApplication(t) logger := log.NewNopLogger() cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -329,7 +328,7 @@ func TestProcessProposal(t *testing.T) { block1 := sf.MakeBlock(state, height, lastCommit) block1.Txs = txs - expectedRpp := abci.RequestProcessProposal{ + expectedRpp := &abci.RequestProcessProposal{ Txs: block1.Txs.ToSliceOfBytes(), Hash: block1.Hash(), Height: block1.Header.Height, @@ -343,12 +342,12 @@ func TestProcessProposal(t *testing.T) { ProposerAddress: block1.ProposerAddress, } - app.On("ProcessProposal", mock.Anything).Return(abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}) + app.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil) acceptBlock, err := blockExec.ProcessProposal(ctx, block1, state) require.NoError(t, err) require.True(t, acceptBlock) app.AssertExpectations(t) - app.AssertCalled(t, "ProcessProposal", expectedRpp) + app.AssertCalled(t, "ProcessProposal", ctx, expectedRpp) } func TestValidateValidatorUpdates(t *testing.T) { @@ -621,8 +620,8 @@ func TestEmptyPrepareProposal(t *testing.T) { eventBus := eventbus.NewDefault(logger) require.NoError(t, eventBus.Start(ctx)) - app := abcimocks.NewBaseMock() - app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{}) + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) err := proxyApp.Start(ctx) @@ -654,8 +653,8 @@ func TestEmptyPrepareProposal(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - _, err = blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + _, err = blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.NoError(t, err) } @@ -680,10 +679,10 @@ func TestPrepareProposalErrorOnNonExistingRemoved(t *testing.T) { mp := &mpmocks.Mempool{} mp.On("ReapMaxBytesMaxGas", mock.Anything, mock.Anything).Return(types.Txs{}) - app := abcimocks.NewBaseMock() + app := abcimocks.NewApplication(t) // create an invalid ResponsePrepareProposal - rpp := abci.ResponsePrepareProposal{ + rpp := &abci.ResponsePrepareProposal{ TxRecords: []*abci.TxRecord{ { Action: abci.TxRecord_REMOVED, @@ -691,7 +690,7 @@ func TestPrepareProposalErrorOnNonExistingRemoved(t *testing.T) { }, }, } - app.On("PrepareProposal", mock.Anything).Return(rpp) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(rpp, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -709,10 +708,10 @@ func TestPrepareProposalErrorOnNonExistingRemoved(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) - require.Nil(t, block) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.ErrorContains(t, err, "new transaction incorrectly marked as removed") + require.Nil(t, block) mp.AssertExpectations(t) } @@ -744,10 +743,10 @@ func TestPrepareProposalRemoveTxs(t *testing.T) { trs[1].Action = abci.TxRecord_REMOVED mp.On("RemoveTxByKey", mock.Anything).Return(nil).Twice() - app := abcimocks.NewBaseMock() - app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ TxRecords: trs, - }) + }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -765,8 +764,8 @@ func TestPrepareProposalRemoveTxs(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.NoError(t, err) require.Len(t, block.Data.Txs.ToSliceOfBytes(), len(trs)-2) @@ -803,10 +802,10 @@ func TestPrepareProposalAddedTxsIncluded(t *testing.T) { trs[0].Action = abci.TxRecord_ADDED trs[1].Action = abci.TxRecord_ADDED - app := abcimocks.NewBaseMock() - app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ TxRecords: trs, - }) + }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -824,8 +823,8 @@ func TestPrepareProposalAddedTxsIncluded(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.NoError(t, err) require.Equal(t, txs[0], block.Data.Txs[0]) @@ -859,10 +858,10 @@ func TestPrepareProposalReorderTxs(t *testing.T) { trs = trs[2:] trs = append(trs[len(trs)/2:], trs[:len(trs)/2]...) - app := abcimocks.NewBaseMock() - app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ TxRecords: trs, - }) + }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -880,8 +879,8 @@ func TestPrepareProposalReorderTxs(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.NoError(t, err) for i, tx := range block.Data.Txs { require.Equal(t, types.Tx(trs[i].Tx), tx) @@ -919,10 +918,10 @@ func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { trs := txsToTxRecords(types.Txs(txs)) - app := abcimocks.NewBaseMock() - app.On("PrepareProposal", mock.Anything).Return(abci.ResponsePrepareProposal{ + app := abcimocks.NewApplication(t) + app.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{ TxRecords: trs, - }) + }, nil) cc := abciclient.NewLocalClient(logger, app) proxyApp := proxy.New(cc, logger, proxy.NopMetrics()) @@ -940,11 +939,11 @@ func TestPrepareProposalErrorOnTooManyTxs(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) - require.Nil(t, block) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.ErrorContains(t, err, "transaction data size exceeds maximum") + require.Nil(t, block, "") mp.AssertExpectations(t) } @@ -992,9 +991,9 @@ func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { sm.NopMetrics(), ) pa, _ := state.Validators.GetByIndex(0) - commit := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) + commit, votes := makeValidCommit(ctx, t, height, types.BlockID{}, state.Validators, privVals) - block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, nil) + block, err := blockExec.CreateProposalBlock(ctx, height, state, commit, pa, votes) require.Nil(t, block) require.ErrorContains(t, err, "an injected error") @@ -1003,8 +1002,8 @@ func TestPrepareProposalErrorOnPrepareProposalError(t *testing.T) { func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) diff --git a/internal/state/helpers_test.go b/internal/state/helpers_test.go index 1a926a91f..07dd0d865 100644 --- a/internal/state/helpers_test.go +++ b/internal/state/helpers_test.go @@ -46,7 +46,7 @@ func makeAndCommitGoodBlock( state, blockID := makeAndApplyGoodBlock(ctx, t, state, height, lastCommit, proposerAddr, blockExec, evidence) // Simulate a lastCommit for this block from all validators for the next height - commit := makeValidCommit(ctx, t, height, blockID, state.Validators, privVals) + commit, _ := makeValidCommit(ctx, t, height, blockID, state.Validators, privVals) return state, blockID, commit } @@ -82,17 +82,19 @@ func makeValidCommit( blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator, -) *types.Commit { +) (*types.Commit, []*types.Vote) { t.Helper() - sigs := make([]types.CommitSig, 0) + sigs := make([]types.CommitSig, vals.Size()) + votes := make([]*types.Vote, vals.Size()) for i := 0; i < vals.Size(); i++ { _, val := vals.GetByIndex(int32(i)) vote, err := factory.MakeVote(ctx, privVals[val.Address.String()], chainID, int32(i), height, 0, 2, blockID, time.Now()) require.NoError(t, err) - sigs = append(sigs, vote.CommitSig()) + sigs[i] = vote.CommitSig() + votes[i] = vote } - return types.NewCommit(height, 0, blockID, sigs) + return types.NewCommit(height, 0, blockID, sigs), votes } func makeState(t *testing.T, nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { @@ -276,11 +278,11 @@ type testApp struct { var _ abci.Application = (*testApp)(nil) -func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { - return abci.ResponseInfo{} +func (app *testApp) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { + return &abci.ResponseInfo{}, nil } -func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { app.CommitVotes = req.DecidedLastCommit.Votes app.ByzantineValidators = req.ByzantineValidators @@ -293,7 +295,7 @@ func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFi } } - return abci.ResponseFinalizeBlock{ + return &abci.ResponseFinalizeBlock{ ValidatorUpdates: app.ValidatorUpdates, ConsensusParamUpdates: &tmproto.ConsensusParams{ Version: &tmproto.VersionParams{ @@ -302,26 +304,26 @@ func (app *testApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFi }, Events: []abci.Event{}, TxResults: resTxs, - } + }, nil } -func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { - return abci.ResponseCheckTx{} +func (app *testApp) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { + return &abci.ResponseCheckTx{}, nil } -func (app *testApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{RetainHeight: 1} +func (app *testApp) Commit(context.Context) (*abci.ResponseCommit, error) { + return &abci.ResponseCommit{RetainHeight: 1}, nil } -func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - return +func (app *testApp) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { + return &abci.ResponseQuery{}, nil } -func (app *testApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { +func (app *testApp) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { for _, tx := range req.Txs { if len(tx) == 0 { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil } } - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil } diff --git a/internal/state/indexer/metrics.gen.go b/internal/state/indexer/metrics.gen.go new file mode 100644 index 000000000..8b079d8d5 --- /dev/null +++ b/internal/state/indexer/metrics.gen.go @@ -0,0 +1,51 @@ +// Code generated by metricsgen. DO NOT EDIT. + +package indexer + +import ( + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + BlockEventsSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "block_events_seconds", + Help: "Latency for indexing block events.", + }, labels).With(labelsAndValues...), + TxEventsSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "tx_events_seconds", + Help: "Latency for indexing transaction events.", + }, labels).With(labelsAndValues...), + BlocksIndexed: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "blocks_indexed", + Help: "Number of complete blocks indexed.", + }, labels).With(labelsAndValues...), + TransactionsIndexed: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "transactions_indexed", + Help: "Number of transactions indexed.", + }, labels).With(labelsAndValues...), + } +} + +func NopMetrics() *Metrics { + return &Metrics{ + BlockEventsSeconds: discard.NewHistogram(), + TxEventsSeconds: discard.NewHistogram(), + BlocksIndexed: discard.NewCounter(), + TransactionsIndexed: discard.NewCounter(), + } +} diff --git a/internal/state/indexer/metrics.go b/internal/state/indexer/metrics.go index aa64a4bb2..0b92b879e 100644 --- a/internal/state/indexer/metrics.go +++ b/internal/state/indexer/metrics.go @@ -2,12 +2,10 @@ package indexer import ( "github.com/go-kit/kit/metrics" - "github.com/go-kit/kit/metrics/discard" - - prometheus "github.com/go-kit/kit/metrics/prometheus" - stdprometheus "github.com/prometheus/client_golang/prometheus" ) +//go:generate go run github.com/tendermint/tendermint/scripts/metricsgen -struct=Metrics + // MetricsSubsystem is a the subsystem label for the indexer package. const MetricsSubsystem = "indexer" @@ -25,49 +23,3 @@ type Metrics struct { // Number of transactions indexed. TransactionsIndexed metrics.Counter } - -// PrometheusMetrics returns Metrics build using Prometheus client library. -// Optionally, labels can be provided along with their values ("foo", -// "fooValue"). -func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { - labels := []string{} - for i := 0; i < len(labelsAndValues); i += 2 { - labels = append(labels, labelsAndValues[i]) - } - return &Metrics{ - BlockEventsSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: MetricsSubsystem, - Name: "block_events_seconds", - Help: "Latency for indexing block events.", - }, labels).With(labelsAndValues...), - TxEventsSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: MetricsSubsystem, - Name: "tx_events_seconds", - Help: "Latency for indexing transaction events.", - }, labels).With(labelsAndValues...), - BlocksIndexed: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: namespace, - Subsystem: MetricsSubsystem, - Name: "blocks_indexed", - Help: "Number of complete blocks indexed.", - }, labels).With(labelsAndValues...), - TransactionsIndexed: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ - Namespace: namespace, - Subsystem: MetricsSubsystem, - Name: "transactions_indexed", - Help: "Number of transactions indexed.", - }, labels).With(labelsAndValues...), - } -} - -// NopMetrics returns an indexer metrics stub that discards all samples. -func NopMetrics() *Metrics { - return &Metrics{ - BlockEventsSeconds: discard.NewHistogram(), - TxEventsSeconds: discard.NewHistogram(), - BlocksIndexed: discard.NewCounter(), - TransactionsIndexed: discard.NewCounter(), - } -} diff --git a/internal/state/indexer/mocks/event_sink.go b/internal/state/indexer/mocks/event_sink.go index d5555a417..decf551ab 100644 --- a/internal/state/indexer/mocks/event_sink.go +++ b/internal/state/indexer/mocks/event_sink.go @@ -12,6 +12,8 @@ import ( tenderminttypes "github.com/tendermint/tendermint/types" + testing "testing" + types "github.com/tendermint/tendermint/abci/types" ) @@ -165,3 +167,13 @@ func (_m *EventSink) Type() indexer.EventSinkType { return r0 } + +// NewEventSink creates a new instance of EventSink. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewEventSink(t testing.TB) *EventSink { + mock := &EventSink{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/metrics.go b/internal/state/metrics.go index bcd713f5f..1d4a13b94 100644 --- a/internal/state/metrics.go +++ b/internal/state/metrics.go @@ -17,6 +17,14 @@ const ( type Metrics struct { // Time between BeginBlock and EndBlock. BlockProcessingTime metrics.Histogram + + // ConsensusParamUpdates is the total number of times the application has + // udated the consensus params since process start. + ConsensusParamUpdates metrics.Counter + + // ValidatorSetUpdates is the total number of times the application has + // udated the validator set since process start. + ValidatorSetUpdates metrics.Counter } // PrometheusMetrics returns Metrics build using Prometheus client library. @@ -35,12 +43,29 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Help: "Time between BeginBlock and EndBlock in ms.", Buckets: stdprometheus.LinearBuckets(1, 10, 10), }, labels).With(labelsAndValues...), + ConsensusParamUpdates: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "consensus_param_updates", + Help: "The total number of times the application as updated the consensus " + + "parameters since process start.", + }, labels).With(labelsAndValues...), + + ValidatorSetUpdates: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "validator_set_updates", + Help: "The total number of times the application as updated the validator " + + "set since process start.", + }, labels).With(labelsAndValues...), } } // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - BlockProcessingTime: discard.NewHistogram(), + BlockProcessingTime: discard.NewHistogram(), + ConsensusParamUpdates: discard.NewCounter(), + ValidatorSetUpdates: discard.NewCounter(), } } diff --git a/internal/state/mocks/block_store.go b/internal/state/mocks/block_store.go index 563183437..7cc7fa883 100644 --- a/internal/state/mocks/block_store.go +++ b/internal/state/mocks/block_store.go @@ -5,6 +5,8 @@ package mocks import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -208,3 +210,13 @@ func (_m *BlockStore) Size() int64 { return r0 } + +// NewBlockStore creates a new instance of BlockStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewBlockStore(t testing.TB) *BlockStore { + mock := &BlockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/mocks/evidence_pool.go b/internal/state/mocks/evidence_pool.go index 04e8be7bc..49633269b 100644 --- a/internal/state/mocks/evidence_pool.go +++ b/internal/state/mocks/evidence_pool.go @@ -8,6 +8,8 @@ import ( mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -71,3 +73,13 @@ func (_m *EvidencePool) PendingEvidence(maxBytes int64) ([]types.Evidence, int64 func (_m *EvidencePool) Update(_a0 context.Context, _a1 state.State, _a2 types.EvidenceList) { _m.Called(_a0, _a1, _a2) } + +// NewEvidencePool creates a new instance of EvidencePool. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewEvidencePool(t testing.TB) *EvidencePool { + mock := &EvidencePool{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/mocks/store.go b/internal/state/mocks/store.go index 02c69d3e0..9b41f3c1b 100644 --- a/internal/state/mocks/store.go +++ b/internal/state/mocks/store.go @@ -7,6 +7,8 @@ import ( state "github.com/tendermint/tendermint/internal/state" tendermintstate "github.com/tendermint/tendermint/proto/tendermint/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -186,3 +188,13 @@ func (_m *Store) SaveValidatorSets(_a0 int64, _a1 int64, _a2 *types.ValidatorSet return r0 } + +// NewStore creates a new instance of Store. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewStore(t testing.TB) *Store { + mock := &Store{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/state/validation_test.go b/internal/state/validation_test.go index 4fd0b49bd..376ce61bc 100644 --- a/internal/state/validation_test.go +++ b/internal/state/validation_test.go @@ -12,8 +12,8 @@ import ( abciclient "github.com/tendermint/tendermint/abci/client" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" mpmocks "github.com/tendermint/tendermint/internal/mempool/mocks" "github.com/tendermint/tendermint/internal/proxy" @@ -68,7 +68,7 @@ func TestValidateBlockHeader(t *testing.T) { lastCommit := types.NewCommit(0, 0, types.BlockID{}, nil) // some bad values - wrongHash := tmhash.Sum([]byte("this hash is wrong")) + wrongHash := crypto.Checksum([]byte("this hash is wrong")) wrongVersion1 := state.Version.Consensus wrongVersion1.Block += 2 wrongVersion2 := state.Version.Consensus diff --git a/internal/statesync/dispatcher_test.go b/internal/statesync/dispatcher_test.go index 65c517be4..8ec074bd1 100644 --- a/internal/statesync/dispatcher_test.go +++ b/internal/statesync/dispatcher_test.go @@ -30,7 +30,7 @@ func testChannel(size int) (*channelInternal, *p2p.Channel) { Out: make(chan p2p.Envelope, size), Error: make(chan p2p.PeerError, size), } - return in, p2p.NewChannel(0, nil, in.In, in.Out, in.Error) + return in, p2p.NewChannel(0, in.In, in.Out, in.Error) } func TestDispatcherBasic(t *testing.T) { diff --git a/internal/statesync/mocks/state_provider.go b/internal/statesync/mocks/state_provider.go index b8d681631..582ebcd9c 100644 --- a/internal/statesync/mocks/state_provider.go +++ b/internal/statesync/mocks/state_provider.go @@ -8,6 +8,8 @@ import ( mock "github.com/stretchr/testify/mock" state "github.com/tendermint/tendermint/internal/state" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -82,3 +84,13 @@ func (_m *StateProvider) State(ctx context.Context, height uint64) (state.State, return r0, r1 } + +// NewStateProvider creates a new instance of StateProvider. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewStateProvider(t testing.TB) *StateProvider { + mock := &StateProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/statesync/reactor.go b/internal/statesync/reactor.go index 795da5063..f4d72d017 100644 --- a/internal/statesync/reactor.go +++ b/internal/statesync/reactor.go @@ -305,7 +305,12 @@ func (r *Reactor) OnStart(ctx context.Context) error { return nil } - go r.processChannels(ctx, snapshotCh, chunkCh, blockCh, paramsCh) + go r.processChannels(ctx, map[p2p.ChannelID]*p2p.Channel{ + SnapshotChannel: snapshotCh, + ChunkChannel: chunkCh, + LightBlockChannel: blockCh, + ParamsChannel: paramsCh, + }) go r.processPeerUpdates(ctx, r.peerEvents(ctx)) if r.needsStateSync { @@ -661,7 +666,7 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel "failed to add snapshot", "height", msg.Height, "format", msg.Format, - "channel", snapshotCh.ID, + "channel", envelope.ChannelID, "err", err, ) return nil @@ -688,7 +693,7 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope "chunk", msg.Index, "peer", envelope.From, ) - resp, err := r.conn.LoadSnapshotChunk(ctx, abci.RequestLoadSnapshotChunk{ + resp, err := r.conn.LoadSnapshotChunk(ctx, &abci.RequestLoadSnapshotChunk{ Height: msg.Height, Format: msg.Format, Chunk: msg.Index, @@ -907,15 +912,14 @@ func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, cha // encountered during message execution will result in a PeerError being sent on // the respective channel. When the reactor is stopped, we will catch the signal // and close the p2p Channel gracefully. -func (r *Reactor) processChannels(ctx context.Context, chs ...*p2p.Channel) { +func (r *Reactor) processChannels(ctx context.Context, chanTable map[p2p.ChannelID]*p2p.Channel) { // make sure that the iterator gets cleaned up in case of error ctx, cancel := context.WithCancel(ctx) defer cancel() - chanTable := make(map[p2p.ChannelID]*p2p.Channel, len(chs)) - for idx := range chs { - ch := chs[idx] - chanTable[ch.ID] = ch + chs := make([]*p2p.Channel, 0, len(chanTable)) + for key := range chanTable { + chs = append(chs, chanTable[key]) } iter := p2p.MergedChannelIterator(ctx, chs...) @@ -1015,7 +1019,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerU // recentSnapshots fetches the n most recent snapshots from the app func (r *Reactor) recentSnapshots(ctx context.Context, n uint32) ([]*snapshot, error) { - resp, err := r.conn.ListSnapshots(ctx, abci.RequestListSnapshots{}) + resp, err := r.conn.ListSnapshots(ctx, &abci.RequestListSnapshots{}) if err != nil { return nil, err } diff --git a/internal/statesync/reactor_test.go b/internal/statesync/reactor_test.go index 86c84a9ed..55a9fcf8c 100644 --- a/internal/statesync/reactor_test.go +++ b/internal/statesync/reactor_test.go @@ -102,7 +102,6 @@ func setup( rts.snapshotChannel = p2p.NewChannel( SnapshotChannel, - new(ssproto.Message), rts.snapshotInCh, rts.snapshotOutCh, rts.snapshotPeerErrCh, @@ -110,7 +109,6 @@ func setup( rts.chunkChannel = p2p.NewChannel( ChunkChannel, - new(ssproto.Message), rts.chunkInCh, rts.chunkOutCh, rts.chunkPeerErrCh, @@ -118,7 +116,6 @@ func setup( rts.blockChannel = p2p.NewChannel( LightBlockChannel, - new(ssproto.Message), rts.blockInCh, rts.blockOutCh, rts.blockPeerErrCh, @@ -126,7 +123,6 @@ func setup( rts.paramsChannel = p2p.NewChannel( ParamsChannel, - new(ssproto.Message), rts.paramsInCh, rts.paramsOutCh, rts.paramsPeerErrCh, @@ -204,15 +200,15 @@ func TestReactor_Sync(t *testing.T) { rts := setup(ctx, t, nil, nil, 100) chain := buildLightBlockChain(ctx, t, 1, 10, time.Now()) // app accepts any snapshot - rts.conn.On("OfferSnapshot", ctx, mock.AnythingOfType("types.RequestOfferSnapshot")). + rts.conn.On("OfferSnapshot", ctx, mock.IsType(&abci.RequestOfferSnapshot{})). Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil) // app accepts every chunk - rts.conn.On("ApplySnapshotChunk", ctx, mock.AnythingOfType("types.RequestApplySnapshotChunk")). + rts.conn.On("ApplySnapshotChunk", ctx, mock.IsType(&abci.RequestApplySnapshotChunk{})). Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) // app query returns valid state app hash - rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{ + rts.conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: snapshotHeight, LastBlockAppHash: chain[snapshotHeight+1].AppHash, @@ -307,7 +303,7 @@ func TestReactor_ChunkRequest(t *testing.T) { // mock ABCI connection to return local snapshots conn := &clientmocks.Client{} - conn.On("LoadSnapshotChunk", mock.Anything, abci.RequestLoadSnapshotChunk{ + conn.On("LoadSnapshotChunk", mock.Anything, &abci.RequestLoadSnapshotChunk{ Height: tc.request.Height, Format: tc.request.Format, Chunk: tc.request.Index, @@ -396,7 +392,7 @@ func TestReactor_SnapshotsRequest(t *testing.T) { // mock ABCI connection to return local snapshots conn := &clientmocks.Client{} - conn.On("ListSnapshots", mock.Anything, abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{ + conn.On("ListSnapshots", mock.Anything, &abci.RequestListSnapshots{}).Return(&abci.ResponseListSnapshots{ Snapshots: tc.snapshots, }, nil) diff --git a/internal/statesync/syncer.go b/internal/statesync/syncer.go index c2cca9a7c..47e058c19 100644 --- a/internal/statesync/syncer.go +++ b/internal/statesync/syncer.go @@ -337,7 +337,7 @@ func (s *syncer) Sync(ctx context.Context, snapshot *snapshot, chunks *chunkQueu func (s *syncer) offerSnapshot(ctx context.Context, snapshot *snapshot) error { s.logger.Info("Offering snapshot to ABCI app", "height", snapshot.Height, "format", snapshot.Format, "hash", snapshot.Hash) - resp, err := s.conn.OfferSnapshot(ctx, abci.RequestOfferSnapshot{ + resp, err := s.conn.OfferSnapshot(ctx, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: snapshot.Height, Format: snapshot.Format, @@ -379,7 +379,7 @@ func (s *syncer) applyChunks(ctx context.Context, chunks *chunkQueue, start time return fmt.Errorf("failed to fetch chunk: %w", err) } - resp, err := s.conn.ApplySnapshotChunk(ctx, abci.RequestApplySnapshotChunk{ + resp, err := s.conn.ApplySnapshotChunk(ctx, &abci.RequestApplySnapshotChunk{ Index: chunk.Index, Chunk: chunk.Chunk, Sender: string(chunk.Sender), @@ -519,7 +519,7 @@ func (s *syncer) requestChunk(ctx context.Context, snapshot *snapshot, chunk uin // verifyApp verifies the sync, checking the app hash, last block height and app version func (s *syncer) verifyApp(ctx context.Context, snapshot *snapshot, appVersion uint64) error { - resp, err := s.conn.Info(ctx, proxy.RequestInfo) + resp, err := s.conn.Info(ctx, &proxy.RequestInfo) if err != nil { return fmt.Errorf("failed to query ABCI app for appHash: %w", err) } diff --git a/internal/statesync/syncer_test.go b/internal/statesync/syncer_test.go index e3bf49259..3fc3f0db4 100644 --- a/internal/statesync/syncer_test.go +++ b/internal/statesync/syncer_test.go @@ -109,7 +109,7 @@ func TestSyncer_SyncAny(t *testing.T) { // We start a sync, with peers sending back chunks when requested. We first reject the snapshot // with height 2 format 2, and accept the snapshot at height 1. - conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: 2, Format: 2, @@ -118,7 +118,7 @@ func TestSyncer_SyncAny(t *testing.T) { }, AppHash: []byte("app_hash_2"), }).Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil) - conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: &abci.Snapshot{ Height: s.Height, Format: s.Format, @@ -170,7 +170,7 @@ func TestSyncer_SyncAny(t *testing.T) { // The first time we're applying chunk 2 we tell it to retry the snapshot and discard chunk 1, // which should cause it to keep the existing chunk 0 and 2, and restart restoration from // beginning. We also wait for a little while, to exercise the retry logic in fetchChunks(). - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{1, 1, 2}, }).Once().Run(func(args mock.Arguments) { time.Sleep(1 * time.Second) }).Return( &abci.ResponseApplySnapshotChunk{ @@ -178,16 +178,16 @@ func TestSyncer_SyncAny(t *testing.T) { RefetchChunks: []uint32{1}, }, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{1, 1, 0}, }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1, 1, 1}, }).Times(2).Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{1, 1, 2}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - conn.On("Info", mock.Anything, proxy.RequestInfo).Return(&abci.ResponseInfo{ + conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(&abci.ResponseInfo{ AppVersion: testAppVersion, LastBlockHeight: 1, LastBlockAppHash: []byte("app_hash"), @@ -247,7 +247,7 @@ func TestSyncer_SyncAny_abort(t *testing.T) { _, err := rts.syncer.AddSnapshot(peerID, s) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil) @@ -281,15 +281,15 @@ func TestSyncer_SyncAny_reject(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerID, s11) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s22), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s12), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s11), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) @@ -323,11 +323,11 @@ func TestSyncer_SyncAny_reject_format(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerID, s11) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s22), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_FORMAT}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s11), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ABORT}, nil) @@ -372,11 +372,11 @@ func TestSyncer_SyncAny_reject_sender(t *testing.T) { _, err = rts.syncer.AddSnapshot(peerCID, sbc) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(sbc), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT_SENDER}, nil) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(sa), AppHash: []byte("app_hash"), }).Once().Return(&abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}, nil) @@ -402,7 +402,7 @@ func TestSyncer_SyncAny_abciError(t *testing.T) { _, err := rts.syncer.AddSnapshot(peerID, s) require.NoError(t, err) - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Once().Return(nil, errBoom) @@ -445,7 +445,7 @@ func TestSyncer_offerSnapshot(t *testing.T) { rts := setup(ctx, t, nil, stateProvider, 2) s := &snapshot{Height: 1, Format: 1, Chunks: 3, Hash: []byte{1, 2, 3}, trustedAppHash: []byte("app_hash")} - rts.conn.On("OfferSnapshot", mock.Anything, abci.RequestOfferSnapshot{ + rts.conn.On("OfferSnapshot", mock.Anything, &abci.RequestOfferSnapshot{ Snapshot: toABCI(s), AppHash: []byte("app_hash"), }).Return(&abci.ResponseOfferSnapshot{Result: tc.result}, tc.err) @@ -506,11 +506,11 @@ func TestSyncer_applyChunks_Results(t *testing.T) { _, err = chunks.Add(&chunk{Height: 1, Format: 1, Index: 0, Chunk: body}) require.NoError(t, err) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: body, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: tc.result}, tc.err) if tc.result == abci.ResponseApplySnapshotChunk_RETRY { - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: body, }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) @@ -573,13 +573,13 @@ func TestSyncer_applyChunks_RefetchChunks(t *testing.T) { require.NoError(t, err) // The first two chunks are accepted, before the last one asks for 1 to be refetched - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{0}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1}, }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: tc.result, @@ -673,13 +673,13 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) { require.NoError(t, err) // The first two chunks are accepted, before the last one asks for b sender to be rejected - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 0, Chunk: []byte{0}, Sender: "aa", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 1, Chunk: []byte{1}, Sender: "bb", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, Sender: "cc", }).Once().Return(&abci.ResponseApplySnapshotChunk{ Result: tc.result, @@ -688,7 +688,7 @@ func TestSyncer_applyChunks_RejectSenders(t *testing.T) { // On retry, the last chunk will be tried again, so we just accept it then. if tc.result == abci.ResponseApplySnapshotChunk_RETRY { - rts.conn.On("ApplySnapshotChunk", mock.Anything, abci.RequestApplySnapshotChunk{ + rts.conn.On("ApplySnapshotChunk", mock.Anything, &abci.RequestApplySnapshotChunk{ Index: 2, Chunk: []byte{2}, Sender: "cc", }).Once().Return(&abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil) } @@ -761,7 +761,7 @@ func TestSyncer_verifyApp(t *testing.T) { rts := setup(ctx, t, nil, nil, 2) - rts.conn.On("Info", mock.Anything, proxy.RequestInfo).Return(tc.response, tc.err) + rts.conn.On("Info", mock.Anything, &proxy.RequestInfo).Return(tc.response, tc.err) err := rts.syncer.verifyApp(ctx, s, appVersion) unwrapped := errors.Unwrap(err) if unwrapped != nil { diff --git a/internal/store/store.go b/internal/store/store.go index 599cc3377..a06989924 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -126,7 +126,7 @@ func (bs *BlockStore) LoadBaseMeta() *types.BlockMeta { return nil } -// LoadBlock returns the block with the given height in protobuf. +// LoadBlockProto returns the block with the given height in protobuf. // If no block is found for that height, it returns nil. func (bs *BlockStore) LoadBlockProto(height int64) *tmproto.Block { var blockMeta = bs.LoadBlockMeta(height) @@ -157,30 +157,11 @@ func (bs *BlockStore) LoadBlockProto(height int64) *tmproto.Block { // LoadBlock returns the block with the given height. // If no block is found for that height, it returns nil. func (bs *BlockStore) LoadBlock(height int64) *types.Block { - var blockMeta = bs.LoadBlockMeta(height) - if blockMeta == nil { + blockProto := bs.LoadBlockProto(height) + if blockProto == nil { return nil } - - pbb := new(tmproto.Block) - buf := []byte{} - for i := 0; i < int(blockMeta.BlockID.PartSetHeader.Total); i++ { - part := bs.LoadBlockPart(height, i) - // If the part is missing (e.g. since it has been deleted after we - // loaded the block meta) we consider the whole block to be missing. - if part == nil { - return nil - } - buf = append(buf, part.Bytes...) - } - err := proto.Unmarshal(buf, pbb) - if err != nil { - // NOTE: The existence of meta should imply the existence of the - // block. So, make sure meta is only saved after blocks are saved. - panic(fmt.Errorf("error reading block: %w", err)) - } - - block, err := types.BlockFromProto(pbb) + block, err := types.BlockFromProto(blockProto) if err != nil { panic(fmt.Errorf("error from proto block: %w", err)) } @@ -282,26 +263,6 @@ func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { return blockMeta } -// LoadBlockCommit returns the Commit for the given height in protobuf. -// This commit consists of the +2/3 and other Precommit-votes for block at `height`, -// and it comes from the block.LastCommit for `height+1`. -// If no commit is found for the given height, it returns nil. -func (bs *BlockStore) LoadBlockCommitProto(height int64) *tmproto.Commit { - var pbc = new(tmproto.Commit) - bz, err := bs.db.Get(blockCommitKey(height)) - if err != nil { - panic(err) - } - if len(bz) == 0 { - return nil - } - err = proto.Unmarshal(bz, pbc) - if err != nil { - panic(fmt.Errorf("error reading block commit: %w", err)) - } - return pbc -} - // LoadBlockCommit returns the Commit for the given height. // This commit consists of the +2/3 and other Precommit-votes for block at `height`, // and it comes from the block.LastCommit for `height+1`. diff --git a/internal/test/factory/block.go b/internal/test/factory/block.go index 1d5709dcc..2cb6a3161 100644 --- a/internal/test/factory/block.go +++ b/internal/test/factory/block.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" ) @@ -25,7 +24,7 @@ func RandomAddress() []byte { } func RandomHash() []byte { - return crypto.CRandBytes(tmhash.Size) + return crypto.CRandBytes(crypto.HashSize) } func MakeBlockID() types.BlockID { diff --git a/internal/test/factory/commit.go b/internal/test/factory/commit.go index 1a8691855..bc4022499 100644 --- a/internal/test/factory/commit.go +++ b/internal/test/factory/commit.go @@ -31,6 +31,7 @@ func MakeCommit(ctx context.Context, blockID types.BlockID, height int64, round return nil, err } vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature if _, err := voteSet.AddVote(vote); err != nil { return nil, err } diff --git a/internal/test/factory/vote.go b/internal/test/factory/vote.go index fc63e8d68..4e77473c2 100644 --- a/internal/test/factory/vote.go +++ b/internal/test/factory/vote.go @@ -40,5 +40,6 @@ func MakeVote( } v.Signature = vpb.Signature + v.ExtensionSignature = vpb.ExtensionSignature return v, nil } diff --git a/libs/bytes/bytes.go b/libs/bytes/bytes.go index dd8e39737..a6be32f05 100644 --- a/libs/bytes/bytes.go +++ b/libs/bytes/bytes.go @@ -1,21 +1,16 @@ package bytes import ( - "bytes" + "encoding/base64" "encoding/hex" - "encoding/json" "fmt" "strings" ) -// HexBytes enables HEX-encoding for json/encoding. +// HexBytes is a wrapper around []byte that encodes data as hexadecimal strings +// for use in JSON. type HexBytes []byte -var ( - _ json.Marshaler = HexBytes{} - _ json.Unmarshaler = &HexBytes{} -) - // Marshal needed for protobuf compatibility func (bz HexBytes) Marshal() ([]byte, error) { return bz, nil @@ -27,41 +22,30 @@ func (bz *HexBytes) Unmarshal(data []byte) error { return nil } -// MarshalJSON implements the json.Marshaler interface. The encoding is a JSON -// quoted string of hexadecimal digits. -func (bz HexBytes) MarshalJSON() ([]byte, error) { - size := hex.EncodedLen(len(bz)) + 2 // +2 for quotation marks - buf := make([]byte, size) - hex.Encode(buf[1:], []byte(bz)) - buf[0] = '"' - buf[size-1] = '"' - - // Ensure letter digits are capitalized. - for i := 1; i < size-1; i++ { - if buf[i] >= 'a' && buf[i] <= 'f' { - buf[i] = 'A' + (buf[i] - 'a') - } - } - return buf, nil +// MarshalText encodes a HexBytes value as hexadecimal digits. +// This method is used by json.Marshal. +func (bz HexBytes) MarshalText() ([]byte, error) { + enc := hex.EncodeToString([]byte(bz)) + return []byte(strings.ToUpper(enc)), nil } -// UnmarshalJSON implements the json.Umarshaler interface. -func (bz *HexBytes) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, []byte("null")) { +// UnmarshalText handles decoding of HexBytes from JSON strings. +// This method is used by json.Unmarshal. +// It allows decoding of both hex and base64-encoded byte arrays. +func (bz *HexBytes) UnmarshalText(data []byte) error { + input := string(data) + if input == "" || input == "null" { return nil } - - if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' { - return fmt.Errorf("invalid hex string: %s", data) - } - - bz2, err := hex.DecodeString(string(data[1 : len(data)-1])) + dec, err := hex.DecodeString(input) if err != nil { - return err + dec, err = base64.StdEncoding.DecodeString(input) + + if err != nil { + return err + } } - - *bz = bz2 - + *bz = HexBytes(dec) return nil } diff --git a/libs/bytes/bytes_test.go b/libs/bytes/bytes_test.go index ce39935e8..fb1200a04 100644 --- a/libs/bytes/bytes_test.go +++ b/libs/bytes/bytes_test.go @@ -61,7 +61,7 @@ func TestJSONMarshal(t *testing.T) { t.Fatal(err) } assert.Equal(t, ts2.B1, tc.input) - assert.Equal(t, ts2.B2, HexBytes(tc.input)) + assert.Equal(t, string(ts2.B2), string(tc.input)) }) } } diff --git a/libs/time/mocks/source.go b/libs/time/mocks/source.go index a8e49b314..7878d86f5 100644 --- a/libs/time/mocks/source.go +++ b/libs/time/mocks/source.go @@ -3,9 +3,11 @@ package mocks import ( - time "time" + testing "testing" mock "github.com/stretchr/testify/mock" + + time "time" ) // Source is an autogenerated mock type for the Source type @@ -26,3 +28,13 @@ func (_m *Source) Now() time.Time { return r0 } + +// NewSource creates a new instance of Source. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewSource(t testing.TB) *Source { + mock := &Source{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/helpers_test.go b/light/helpers_test.go index 4d002d936..d93735bb7 100644 --- a/light/helpers_test.go +++ b/light/helpers_test.go @@ -9,7 +9,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmtime "github.com/tendermint/tendermint/libs/time" provider_mocks "github.com/tendermint/tendermint/light/provider/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -222,5 +221,5 @@ func mockNodeFromHeadersAndVals(headers map[int64]*types.SignedHeader, } func hash(s string) []byte { - return tmhash.Sum([]byte(s)) + return crypto.Checksum([]byte(s)) } diff --git a/light/provider/mocks/provider.go b/light/provider/mocks/provider.go index 1b4e583de..e136046f9 100644 --- a/light/provider/mocks/provider.go +++ b/light/provider/mocks/provider.go @@ -7,6 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -65,3 +67,13 @@ func (_m *Provider) ReportEvidence(_a0 context.Context, _a1 types.Evidence) erro return r0 } + +// NewProvider creates a new instance of Provider. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewProvider(t testing.TB) *Provider { + mock := &Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/proxy/routes.go b/light/proxy/routes.go index 5dd934ed1..8331723e7 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -2,60 +2,148 @@ package proxy import ( "context" - "time" - "github.com/tendermint/tendermint/internal/eventlog/cursor" - tmbytes "github.com/tendermint/tendermint/libs/bytes" lrpc "github.com/tendermint/tendermint/light/rpc" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/coretypes" ) // proxyService wraps a light RPC client to export the RPC service interfaces. -// This is needed because the service and the client use different signatures -// for some of the methods. +// The interfaces are implemented by delegating to the underlying node via the +// specified client. type proxyService struct { - *lrpc.Client + Client *lrpc.Client } -func (p proxyService) ABCIQuery(ctx context.Context, path string, data tmbytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error) { - return p.ABCIQueryWithOptions(ctx, path, data, rpcclient.ABCIQueryOptions{ - Height: height, - Prove: prove, +func (p proxyService) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { panic("ok") } + +func (p proxyService) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) { + return p.Client.ABCIQueryWithOptions(ctx, req.Path, req.Data, rpcclient.ABCIQueryOptions{ + Height: int64(req.Height), + Prove: req.Prove, }) } +func (p proxyService) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) { + return p.Client.Block(ctx, (*int64)(req.Height)) +} + +func (p proxyService) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) { + return p.Client.BlockByHash(ctx, req.Hash) +} + +func (p proxyService) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) { + return p.Client.BlockResults(ctx, (*int64)(req.Height)) +} + +func (p proxyService) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) { + return p.Client.BlockSearch(ctx, req.Query, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy) +} + +func (p proxyService) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) { + return p.Client.BlockchainInfo(ctx, int64(req.MinHeight), int64(req.MaxHeight)) +} + +func (p proxyService) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) { + return p.Client.BroadcastEvidence(ctx, req.Evidence) +} + +func (p proxyService) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + return p.Client.BroadcastTxAsync(ctx, req.Tx) +} + +func (p proxyService) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) { + return p.Client.BroadcastTxCommit(ctx, req.Tx) +} + +func (p proxyService) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) { + return p.Client.BroadcastTxSync(ctx, req.Tx) +} + +func (p proxyService) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) { + return p.Client.CheckTx(ctx, req.Tx) +} + +func (p proxyService) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) { + return p.Client.Commit(ctx, (*int64)(req.Height)) +} + +func (p proxyService) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) { + return p.Client.ConsensusParams(ctx, (*int64)(req.Height)) +} + +func (p proxyService) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) { + return p.Client.DumpConsensusState(ctx) +} + +func (p proxyService) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { + return p.Client.Events(ctx, req) +} + +func (p proxyService) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { + return p.Client.Genesis(ctx) +} + +func (p proxyService) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) { + return p.Client.GenesisChunked(ctx, uint(req.Chunk)) +} + func (p proxyService) GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) { - return p.ConsensusState(ctx) + return p.Client.ConsensusState(ctx) } -func (p proxyService) Events(ctx context.Context, - filter *coretypes.EventFilter, - maxItems int, - before, after cursor.Cursor, - waitTime time.Duration, -) (*coretypes.ResultEvents, error) { - return p.Client.Events(ctx, &coretypes.RequestEvents{ - Filter: filter, - MaxItems: maxItems, - Before: before.String(), - After: after.String(), - WaitTime: waitTime, - }) +func (p proxyService) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) { + return p.Client.Header(ctx, (*int64)(req.Height)) } -func (p proxyService) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) { - return p.SubscribeWS(ctx, query) +func (p proxyService) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) { + return p.Client.HeaderByHash(ctx, req.Hash) } -func (p proxyService) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) { - return p.UnsubscribeWS(ctx, query) +func (p proxyService) Health(ctx context.Context) (*coretypes.ResultHealth, error) { + return p.Client.Health(ctx) +} + +func (p proxyService) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) { + return p.Client.NetInfo(ctx) +} + +func (p proxyService) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { + return p.Client.NumUnconfirmedTxs(ctx) +} + +func (p proxyService) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error { + return p.Client.RemoveTx(ctx, req.TxKey) +} + +func (p proxyService) Status(ctx context.Context) (*coretypes.ResultStatus, error) { + return p.Client.Status(ctx) +} + +func (p proxyService) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) { + return p.Client.SubscribeWS(ctx, req.Query) +} + +func (p proxyService) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) { + return p.Client.Tx(ctx, req.Hash, req.Prove) +} + +func (p proxyService) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) { + return p.Client.TxSearch(ctx, req.Query, req.Prove, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy) +} + +func (p proxyService) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) { + return p.Client.UnconfirmedTxs(ctx, req.Page.IntPtr(), req.PerPage.IntPtr()) +} + +func (p proxyService) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) { + return p.Client.UnsubscribeWS(ctx, req.Query) } func (p proxyService) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) { - return p.UnsubscribeAllWS(ctx) + return p.Client.UnsubscribeAllWS(ctx) } -func (p proxyService) BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return p.Client.BroadcastEvidence(ctx, ev.Value) +func (p proxyService) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) { + return p.Client.Validators(ctx, (*int64)(req.Height), req.Page.IntPtr(), req.PerPage.IntPtr()) } diff --git a/light/rpc/mocks/light_client.go b/light/rpc/mocks/light_client.go index 347d14707..ea6d6a2d4 100644 --- a/light/rpc/mocks/light_client.go +++ b/light/rpc/mocks/light_client.go @@ -7,6 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + time "time" types "github.com/tendermint/tendermint/types" @@ -115,3 +117,13 @@ func (_m *LightClient) VerifyLightBlockAtHeight(ctx context.Context, height int6 return r0, r1 } + +// NewLightClient creates a new instance of LightClient. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewLightClient(t testing.TB) *LightClient { + mock := &LightClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/light/store/db/db_test.go b/light/store/db/db_test.go index 7069eb11d..cae9bbfc5 100644 --- a/light/store/db/db_test.go +++ b/light/store/db/db_test.go @@ -11,7 +11,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/test/factory" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/types" @@ -205,14 +204,14 @@ func randLightBlock(ctx context.Context, t *testing.T, height int64) *types.Ligh Height: height, Time: time.Now(), LastBlockID: types.BlockID{}, - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), }, Commit: &types.Commit{}, diff --git a/light/trust_options.go b/light/trust_options.go index cbf3b1cd8..2bfad9857 100644 --- a/light/trust_options.go +++ b/light/trust_options.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" ) // TrustOptions are the trust parameters needed when a new light client @@ -43,9 +43,9 @@ func (opts TrustOptions) ValidateBasic() error { if opts.Height <= 0 { return errors.New("negative or zero height") } - if len(opts.Hash) != tmhash.Size { + if len(opts.Hash) != crypto.HashSize { return fmt.Errorf("expected hash size to be %d bytes, got %d bytes", - tmhash.Size, + crypto.HashSize, len(opts.Hash), ) } diff --git a/light/verifier.go b/light/verifier.go index f6156c5de..6bf0e787e 100644 --- a/light/verifier.go +++ b/light/verifier.go @@ -279,8 +279,7 @@ func checkRequiredHeaderFields(h *types.SignedHeader) error { return errors.New("height in trusted header must be set (non zero") } - zeroTime := time.Time{} - if h.Time == zeroTime { + if h.Time.IsZero() { return errors.New("time in trusted header must be set") } diff --git a/node/node.go b/node/node.go index 6c2809ffd..56379d2e2 100644 --- a/node/node.go +++ b/node/node.go @@ -29,6 +29,7 @@ import ( rpccore "github.com/tendermint/tendermint/internal/rpc/core" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" + "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/statesync" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" @@ -168,15 +169,19 @@ func makeNode( Metrics: nodeMetrics.eventlog, }) if err != nil { - return nil, fmt.Errorf("initializing event log: %w", err) + return nil, combineCloseError(fmt.Errorf("initializing event log: %w", err), makeCloser(closers)) } } - - indexerService, eventSinks, err := createIndexerService( - cfg, dbProvider, eventBus, logger, genDoc.ChainID, nodeMetrics.indexer) + eventSinks, err := sink.EventSinksFromConfig(cfg, dbProvider, genDoc.ChainID) if err != nil { return nil, combineCloseError(err, makeCloser(closers)) } + indexerService := indexer.NewService(indexer.ServiceArgs{ + Sinks: eventSinks, + EventBus: eventBus, + Logger: logger.With("module", "txindex"), + Metrics: nodeMetrics.indexer, + }) privValidator, err := createPrivval(ctx, logger, cfg, genDoc, filePrivval) if err != nil { @@ -439,7 +444,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { // Start Internal Services if n.config.RPC.PprofListenAddress != "" { - rpcCtx, rpcCancel := context.WithCancel(ctx) + signal := make(chan struct{}) srv := &http.Server{Addr: n.config.RPC.PprofListenAddress, Handler: nil} go func() { select { @@ -447,7 +452,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { sctx, scancel := context.WithTimeout(context.Background(), time.Second) defer scancel() _ = srv.Shutdown(sctx) - case <-rpcCtx.Done(): + case <-signal: } }() @@ -456,7 +461,7 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { if err := srv.ListenAndServe(); err != nil { n.logger.Error("pprof server error", "err", err) - rpcCancel() + close(signal) } }() } @@ -483,17 +488,6 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { return err } - n.rpcEnv.NodeInfo = n.nodeInfo - // Start the RPC server before the P2P server - // so we can eg. receive txs for the first block - if n.config.RPC.ListenAddress != "" { - var err error - n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config) - if err != nil { - return err - } - } - if n.config.Instrumentation.Prometheus && n.config.Instrumentation.PrometheusListenAddr != "" { n.prometheusSrv = n.startPrometheusServer(ctx, n.config.Instrumentation.PrometheusListenAddr) } @@ -510,12 +504,31 @@ func (n *nodeImpl) OnStart(ctx context.Context) error { } } + n.rpcEnv.NodeInfo = n.nodeInfo + // Start the RPC server before the P2P server + // so we can eg. receive txs for the first block + if n.config.RPC.ListenAddress != "" { + var err error + n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config) + if err != nil { + return err + } + } + return nil } // OnStop stops the Node. It implements service.Service. func (n *nodeImpl) OnStop() { n.logger.Info("Stopping Node") + // stop the listeners / external services first + for _, l := range n.rpcListeners { + n.logger.Info("Closing rpc listener", "listener", l) + if err := l.Close(); err != nil { + n.logger.Error("error closing listener", "listener", l, "err", err) + } + } + for _, es := range n.eventSinks { if err := es.Stop(); err != nil { n.logger.Error("failed to stop event sink", "err", err) @@ -529,14 +542,6 @@ func (n *nodeImpl) OnStop() { n.router.Wait() n.rpcEnv.IsListening = false - // finally stop the listeners / external services - for _, l := range n.rpcListeners { - n.logger.Info("Closing rpc listener", "listener", l) - if err := l.Close(); err != nil { - n.logger.Error("error closing listener", "listener", l, "err", err) - } - } - if pvsc, ok := n.privValidator.(service.Service); ok { pvsc.Wait() } @@ -578,21 +583,21 @@ func (n *nodeImpl) startPrometheusServer(ctx context.Context, addr string) *http ), } - promCtx, promCancel := context.WithCancel(ctx) + signal := make(chan struct{}) go func() { select { case <-ctx.Done(): sctx, scancel := context.WithTimeout(context.Background(), time.Second) defer scancel() _ = srv.Shutdown(sctx) - case <-promCtx.Done(): + case <-signal: } }() go func() { if err := srv.ListenAndServe(); err != nil { n.logger.Error("Prometheus HTTP server ListenAndServe", "err", err) - promCancel() + close(signal) } }() @@ -715,7 +720,7 @@ func getRouterConfig(conf *config.Config, appClient abciclient.Client) p2p.Route if conf.FilterPeers && appClient != nil { opts.FilterPeerByID = func(ctx context.Context, id types.NodeID) error { - res, err := appClient.Query(ctx, abci.RequestQuery{ + res, err := appClient.Query(ctx, &abci.RequestQuery{ Path: fmt.Sprintf("/p2p/filter/id/%s", id), }) if err != nil { @@ -729,7 +734,7 @@ func getRouterConfig(conf *config.Config, appClient abciclient.Client) p2p.Route } opts.FilterPeerByIP = func(ctx context.Context, ip net.IP, port uint16) error { - res, err := appClient.Query(ctx, abci.RequestQuery{ + res, err := appClient.Query(ctx, &abci.RequestQuery{ Path: fmt.Sprintf("/p2p/filter/addr/%s", net.JoinHostPort(ip.String(), strconv.Itoa(int(port)))), }) if err != nil { diff --git a/node/node_test.go b/node/node_test.go index 2736ca818..c5ff1f014 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -20,7 +20,6 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/evidence" "github.com/tendermint/tendermint/internal/mempool" @@ -28,6 +27,7 @@ import ( "github.com/tendermint/tendermint/internal/pubsub" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" + "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" @@ -454,7 +454,8 @@ func TestMaxProposalBlockSize(t *testing.T) { err = proxyApp.Start(ctx) require.NoError(t, err) - state, stateDB, _ := state(t, types.MaxVotesCount, int64(1)) + state, stateDB, privVals := state(t, types.MaxVotesCount, int64(1)) + stateStore := sm.NewStore(stateDB) blockStore := store.NewBlockStore(dbm.NewMemDB()) const maxBytes int64 = 1024 * 1024 * 2 @@ -496,10 +497,10 @@ func TestMaxProposalBlockSize(t *testing.T) { ) blockID := types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, } @@ -514,8 +515,8 @@ func TestMaxProposalBlockSize(t *testing.T) { state.LastBlockID = blockID state.LastBlockHeight = math.MaxInt64 - 2 state.LastBlockTime = timestamp - state.LastResultsHash = tmhash.Sum([]byte("last_results_hash")) - state.AppHash = tmhash.Sum([]byte("app_hash")) + state.LastResultsHash = crypto.Checksum([]byte("last_results_hash")) + state.AppHash = crypto.Checksum([]byte("app_hash")) state.Version.Consensus.Block = math.MaxInt64 state.Version.Consensus.App = math.MaxInt64 maxChainID := "" @@ -537,17 +538,25 @@ func TestMaxProposalBlockSize(t *testing.T) { BlockID: blockID, } + votes := make([]*types.Vote, types.MaxVotesCount) + // add maximum amount of signatures to a single commit for i := 0; i < types.MaxVotesCount; i++ { + pubKey, err := privVals[i].GetPubKey(ctx) + require.NoError(t, err) + votes[i] = &types.Vote{ + ValidatorAddress: pubKey.Address(), + } commit.Signatures = append(commit.Signatures, cs) } block, err := blockExec.CreateProposalBlock( ctx, math.MaxInt64, - state, commit, + state, + commit, proposerAddr, - nil, + votes, ) require.NoError(t, err) partSet, err := block.MakePartSet(types.BlockPartSizeBytes) @@ -627,11 +636,9 @@ func TestNodeSetEventSink(t *testing.T) { genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile()) require.NoError(t, err) - indexService, eventSinks, err := createIndexerService(cfg, - config.DefaultDBProvider, eventBus, logger, genDoc.ChainID, - indexer.NopMetrics()) + eventSinks, err := sink.EventSinksFromConfig(cfg, config.DefaultDBProvider, genDoc.ChainID) require.NoError(t, err) - t.Cleanup(indexService.Wait) + return eventSinks } cleanup := func(ns service.Service) func() { diff --git a/node/setup.go b/node/setup.go index 99fd68b28..d6966800a 100644 --- a/node/setup.go +++ b/node/setup.go @@ -23,7 +23,6 @@ import ( "github.com/tendermint/tendermint/internal/p2p/pex" sm "github.com/tendermint/tendermint/internal/state" "github.com/tendermint/tendermint/internal/state/indexer" - "github.com/tendermint/tendermint/internal/state/indexer/sink" "github.com/tendermint/tendermint/internal/statesync" "github.com/tendermint/tendermint/internal/store" "github.com/tendermint/tendermint/libs/log" @@ -95,29 +94,6 @@ func initDBs( return blockStore, stateDB, makeCloser(closers), nil } -func createIndexerService( - cfg *config.Config, - dbProvider config.DBProvider, - eventBus *eventbus.EventBus, - logger log.Logger, - chainID string, - metrics *indexer.Metrics, -) (*indexer.Service, []indexer.EventSink, error) { - eventSinks, err := sink.EventSinksFromConfig(cfg, dbProvider, chainID) - if err != nil { - return nil, nil, err - } - - indexerService := indexer.NewService(indexer.ServiceArgs{ - Sinks: eventSinks, - EventBus: eventBus, - Logger: logger.With("module", "txindex"), - Metrics: metrics, - }) - - return indexerService, eventSinks, nil -} - func logNodeStartupInfo(state sm.State, pubKey crypto.PubKey, logger log.Logger, mode string) { // Log the version info. logger.Info("Version info", @@ -215,20 +191,13 @@ func createEvidenceReactor( if err != nil { return nil, nil, func() error { return nil }, fmt.Errorf("unable to initialize evidence db: %w", err) } - dbCloser := evidenceDB.Close logger = logger.With("module", "evidence") evidencePool := evidence.NewPool(logger, evidenceDB, store, blockStore, metrics, eventBus) + evidenceReactor := evidence.NewReactor(logger, chCreator, peerEvents, evidencePool) - evidenceReactor := evidence.NewReactor( - logger, - chCreator, - peerEvents, - evidencePool, - ) - - return evidenceReactor, evidencePool, dbCloser, nil + return evidenceReactor, evidencePool, evidenceDB.Close, nil } func createPeerManager( @@ -341,8 +310,8 @@ func createRouter( nodeKey.PrivKey, peerManager, nodeInfoProducer, - []p2p.Transport{transport}, - []p2p.Endpoint{ep}, + transport, + ep, getRouterConfig(cfg, appClient), ) } diff --git a/privval/file.go b/privval/file.go index 728e0dc67..bf5803632 100644 --- a/privval/file.go +++ b/privval/file.go @@ -117,6 +117,14 @@ type FilePVLastSignState struct { filePath string } +func (lss *FilePVLastSignState) reset() { + lss.Height = 0 + lss.Round = 0 + lss.Step = 0 + lss.Signature = nil + lss.SignBytes = nil +} + // checkHRS checks the given height, round, step (HRS) against that of the // FilePVLastSignState. It returns an error if the arguments constitute a regression, // or if they match but the SignBytes are empty. @@ -328,12 +336,7 @@ func (pv *FilePV) Save() error { // Reset resets all fields in the FilePV. // NOTE: Unsafe! func (pv *FilePV) Reset() error { - var sig []byte - pv.LastSignState.Height = 0 - pv.LastSignState.Round = 0 - pv.LastSignState.Step = 0 - pv.LastSignState.Signature = sig - pv.LastSignState.SignBytes = nil + pv.LastSignState.reset() return pv.Save() } @@ -370,6 +373,21 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { signBytes := types.VoteSignBytes(chainID, vote) + // Vote extensions are non-deterministic, so it is possible that an + // application may have created a different extension. We therefore always + // re-sign the vote extensions of precommits. For prevotes, the extension + // signature will always be empty. + var extSig []byte + if vote.Type == tmproto.PrecommitType { + extSignBytes := types.VoteExtensionSignBytes(chainID, vote) + extSig, err = pv.Key.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - extensions are only allowed in precommits") + } + // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. @@ -379,6 +397,8 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { if bytes.Equal(signBytes, lss.SignBytes) { vote.Signature = lss.Signature } else { + // Compares the canonicalized votes (i.e. without vote extensions + // or vote extension signatures). timestamp, ok, err := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes) if err != nil { return err @@ -390,6 +410,9 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { vote.Timestamp = timestamp vote.Signature = lss.Signature } + + vote.ExtensionSignature = extSig + return nil } @@ -402,6 +425,8 @@ func (pv *FilePV) signVote(chainID string, vote *tmproto.Vote) error { return err } vote.Signature = sig + vote.ExtensionSignature = extSig + return nil } @@ -453,8 +478,10 @@ func (pv *FilePV) saveSigned(height int64, round int32, step int8, signBytes []b //----------------------------------------------------------------------------------------- -// returns the timestamp from the lastSignBytes. -// returns true if the only difference in the votes is their timestamp. +// Returns the timestamp from the lastSignBytes. +// Returns true if the only difference in the votes is their timestamp. +// Performs these checks on the canonical votes (excluding the vote extension +// and vote extension signatures). func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool, error) { var lastVote, newVote tmproto.CanonicalVote if err := protoio.UnmarshalDelimited(lastSignBytes, &lastVote); err != nil { diff --git a/privval/file_test.go b/privval/file_test.go index 91c2e2a9b..9f1105441 100644 --- a/privval/file_test.go +++ b/privval/file_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -21,20 +21,14 @@ import ( ) func TestGenLoadValidator(t *testing.T) { - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) - - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) + privVal, tempKeyFileName, tempStateFileName := newTestFilePV(t) height := int64(100) privVal.LastSignState.Height = height require.NoError(t, privVal.Save()) addr := privVal.GetAddress() - privVal, err = LoadFilePV(tempKeyFile.Name(), tempStateFile.Name()) + privVal, err := LoadFilePV(tempKeyFileName, tempStateFileName) assert.NoError(t, err) assert.Equal(t, addr, privVal.GetAddress(), "expected privval addr to be the same") assert.Equal(t, height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved") @@ -44,14 +38,8 @@ func TestResetValidator(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) - - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - emptyState := FilePVLastSignState{filePath: tempStateFile.Name()} + privVal, _, tempStateFileName := newTestFilePV(t) + emptyState := FilePVLastSignState{filePath: tempStateFileName} // new priv val has empty state assert.Equal(t, privVal.LastSignState, emptyState) @@ -59,10 +47,10 @@ func TestResetValidator(t *testing.T) { // test vote height, round := int64(10), int32(1) voteType := tmproto.PrevoteType - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) blockID := types.BlockID{Hash: randBytes, PartSetHeader: types.PartSetHeader{}} - vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) - err = privVal.SignVote(ctx, "mychainid", vote.ToProto()) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID, nil) + err := privVal.SignVote(ctx, "mychainid", vote.ToProto()) assert.NoError(t, err, "expected no error signing vote") // priv val after signing is not same as empty @@ -160,16 +148,10 @@ func TestSignVote(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) + privVal, _, _ := newTestFilePV(t) - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - - randbytes := tmrand.Bytes(tmhash.Size) - randbytes2 := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) + randbytes2 := tmrand.Bytes(crypto.HashSize) block1 := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{Total: 5, Hash: randbytes}} @@ -180,10 +162,10 @@ func TestSignVote(t *testing.T) { voteType := tmproto.PrevoteType // sign a vote for first time - vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1, nil) v := vote.ToProto() - err = privVal.SignVote(ctx, "mychainid", v) + err := privVal.SignVote(ctx, "mychainid", v) assert.NoError(t, err, "expected no error signing vote") // try to sign the same vote again; should be fine @@ -192,10 +174,10 @@ func TestSignVote(t *testing.T) { // now try some bad votes cases := []*types.Vote{ - newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression - newVote(privVal.Key.Address, 0, height-1, round, voteType, block1), // height regression - newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1), // height regression and different round - newVote(privVal.Key.Address, 0, height, round, voteType, block2), // different block + newVote(privVal.Key.Address, 0, height, round-1, voteType, block1, nil), // round regression + newVote(privVal.Key.Address, 0, height-1, round, voteType, block1, nil), // height regression + newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1, nil), // height regression and different round + newVote(privVal.Key.Address, 0, height, round, voteType, block2, nil), // different block } for _, c := range cases { @@ -215,16 +197,10 @@ func TestSignProposal(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") - require.NoError(t, err) - tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") - require.NoError(t, err) + privVal, _, _ := newTestFilePV(t) - privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") - require.NoError(t, err) - - randbytes := tmrand.Bytes(tmhash.Size) - randbytes2 := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) + randbytes2 := tmrand.Bytes(crypto.HashSize) block1 := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{Total: 5, Hash: randbytes}} @@ -237,7 +213,7 @@ func TestSignProposal(t *testing.T) { proposal := newProposal(height, round, block1, ts) pbp := proposal.ToProto() - err = privVal.SignProposal(ctx, "mychainid", pbp) + err := privVal.SignProposal(ctx, "mychainid", pbp) assert.NoError(t, err, "expected no error signing proposal") // try to sign the same proposal again; should be fine @@ -270,7 +246,7 @@ func TestDifferByTimestamp(t *testing.T) { privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") require.NoError(t, err) - randbytes := tmrand.Bytes(tmhash.Size) + randbytes := tmrand.Bytes(crypto.HashSize) height, round := int64(10), int32(1) chainID := "mychainid" @@ -278,7 +254,7 @@ func TestDifferByTimestamp(t *testing.T) { { voteType := tmproto.PrevoteType blockID := types.BlockID{Hash: randbytes, PartSetHeader: types.PartSetHeader{}} - vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID, nil) v := vote.ToProto() err := privVal.SignVote(ctx, "mychainid", v) require.NoError(t, err, "expected no error signing vote") @@ -300,8 +276,69 @@ func TestDifferByTimestamp(t *testing.T) { } } +func TestVoteExtensionsAreAlwaysSigned(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + privVal, _, _ := newTestFilePV(t) + pubKey, err := privVal.GetPubKey(ctx) + assert.NoError(t, err) + + block := types.BlockID{ + Hash: tmrand.Bytes(crypto.HashSize), + PartSetHeader: types.PartSetHeader{Total: 5, Hash: tmrand.Bytes(crypto.HashSize)}, + } + + height, round := int64(10), int32(1) + voteType := tmproto.PrecommitType + + // We initially sign this vote without an extension + vote1 := newVote(privVal.Key.Address, 0, height, round, voteType, block, nil) + vpb1 := vote1.ToProto() + + err = privVal.SignVote(ctx, "mychainid", vpb1) + assert.NoError(t, err, "expected no error signing vote") + assert.NotNil(t, vpb1.ExtensionSignature) + + vesb1 := types.VoteExtensionSignBytes("mychainid", vpb1) + assert.True(t, pubKey.VerifySignature(vesb1, vpb1.ExtensionSignature)) + + // We duplicate this vote precisely, including its timestamp, but change + // its extension + vote2 := vote1.Copy() + vote2.Extension = []byte("new extension") + vpb2 := vote2.ToProto() + + err = privVal.SignVote(ctx, "mychainid", vpb2) + assert.NoError(t, err, "expected no error signing same vote with manipulated vote extension") + + // We need to ensure that a valid new extension signature has been created + // that validates against the vote extension sign bytes with the new + // extension, and does not validate against the vote extension sign bytes + // with the old extension. + vesb2 := types.VoteExtensionSignBytes("mychainid", vpb2) + assert.True(t, pubKey.VerifySignature(vesb2, vpb2.ExtensionSignature)) + assert.False(t, pubKey.VerifySignature(vesb1, vpb2.ExtensionSignature)) + + // We now manipulate the timestamp of the vote with the extension, as per + // TestDifferByTimestamp + expectedTimestamp := vpb2.Timestamp + + vpb2.Timestamp = vpb2.Timestamp.Add(time.Millisecond) + vpb2.Signature = nil + vpb2.ExtensionSignature = nil + + err = privVal.SignVote(ctx, "mychainid", vpb2) + assert.NoError(t, err, "expected no error signing same vote with manipulated timestamp and vote extension") + assert.Equal(t, expectedTimestamp, vpb2.Timestamp) + + vesb3 := types.VoteExtensionSignBytes("mychainid", vpb2) + assert.True(t, pubKey.VerifySignature(vesb3, vpb2.ExtensionSignature)) + assert.False(t, pubKey.VerifySignature(vesb1, vpb2.ExtensionSignature)) +} + func newVote(addr types.Address, idx int32, height int64, round int32, - typ tmproto.SignedMsgType, blockID types.BlockID) *types.Vote { + typ tmproto.SignedMsgType, blockID types.BlockID, extension []byte) *types.Vote { return &types.Vote{ ValidatorAddress: addr, ValidatorIndex: idx, @@ -310,6 +347,7 @@ func newVote(addr types.Address, idx int32, height int64, round int32, Type: typ, Timestamp: tmtime.Now(), BlockID: blockID, + Extension: extension, } } @@ -321,3 +359,15 @@ func newProposal(height int64, round int32, blockID types.BlockID, t time.Time) Timestamp: t, } } + +func newTestFilePV(t *testing.T) (*FilePV, string, string) { + tempKeyFile, err := os.CreateTemp(t.TempDir(), "priv_validator_key_") + require.NoError(t, err) + tempStateFile, err := os.CreateTemp(t.TempDir(), "priv_validator_state_") + require.NoError(t, err) + + privVal, err := GenFilePV(tempKeyFile.Name(), tempStateFile.Name(), "") + require.NoError(t, err) + + return privVal, tempKeyFile.Name(), tempStateFile.Name() +} diff --git a/privval/grpc/client_test.go b/privval/grpc/client_test.go index ac7608274..a81f6ce07 100644 --- a/privval/grpc/client_test.go +++ b/privval/grpc/client_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmgrpc "github.com/tendermint/tendermint/privval/grpc" @@ -30,7 +29,7 @@ func dialer(t *testing.T, pv types.PrivValidator, logger log.Logger) (*grpc.Serv server := grpc.NewServer() - s := tmgrpc.NewSignerServer(chainID, pv, logger) + s := tmgrpc.NewSignerServer(logger, chainID, pv) privvalproto.RegisterPrivValidatorAPIServer(server, s) @@ -86,7 +85,7 @@ func TestSignerClient_SignVote(t *testing.T) { require.NoError(t, err) ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ @@ -141,7 +140,7 @@ func TestSignerClient_SignProposal(t *testing.T) { require.NoError(t, err) ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) have := &types.Proposal{ Type: tmproto.ProposalType, diff --git a/privval/grpc/server.go b/privval/grpc/server.go index 13e0c9073..40af82479 100644 --- a/privval/grpc/server.go +++ b/privval/grpc/server.go @@ -21,11 +21,9 @@ type SignerServer struct { privVal types.PrivValidator } -func NewSignerServer(chainID string, - privVal types.PrivValidator, log log.Logger) *SignerServer { - +func NewSignerServer(logger log.Logger, chainID string, privVal types.PrivValidator) *SignerServer { return &SignerServer{ - logger: log, + logger: logger, chainID: chainID, privVal: privVal, } @@ -56,8 +54,7 @@ func (ss *SignerServer) GetPubKey(ctx context.Context, req *privvalproto.PubKeyR // SignVote receives a vote sign requests, attempts to sign it // returns SignedVoteResponse on success and error on failure -func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVoteRequest) ( - *privvalproto.SignedVoteResponse, error) { +func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVoteRequest) (*privvalproto.SignedVoteResponse, error) { vote := req.Vote err := ss.privVal.SignVote(ctx, req.ChainId, vote) @@ -72,8 +69,7 @@ func (ss *SignerServer) SignVote(ctx context.Context, req *privvalproto.SignVote // SignProposal receives a proposal sign requests, attempts to sign it // returns SignedProposalResponse on success and error on failure -func (ss *SignerServer) SignProposal(ctx context.Context, req *privvalproto.SignProposalRequest) ( - *privvalproto.SignedProposalResponse, error) { +func (ss *SignerServer) SignProposal(ctx context.Context, req *privvalproto.SignProposalRequest) (*privvalproto.SignedProposalResponse, error) { proposal := req.Proposal err := ss.privVal.SignProposal(ctx, req.ChainId, proposal) diff --git a/privval/grpc/server_test.go b/privval/grpc/server_test.go index 78e9c79ed..ad63b87a6 100644 --- a/privval/grpc/server_test.go +++ b/privval/grpc/server_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" tmgrpc "github.com/tendermint/tendermint/privval/grpc" @@ -38,7 +37,7 @@ func TestGetPubKey(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.PubKeyRequest{ChainId: ChainID} resp, err := s.GetPubKey(ctx, req) @@ -57,7 +56,7 @@ func TestGetPubKey(t *testing.T) { func TestSignVote(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) testCases := []struct { @@ -113,7 +112,7 @@ func TestSignVote(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.SignVoteRequest{ChainId: ChainID, Vote: tc.have.ToProto()} resp, err := s.SignVote(ctx, req) @@ -133,7 +132,7 @@ func TestSignVote(t *testing.T) { func TestSignProposal(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) testCases := []struct { name string @@ -184,7 +183,7 @@ func TestSignProposal(t *testing.T) { defer cancel() logger := log.NewTestingLogger(t) - s := tmgrpc.NewSignerServer(ChainID, tc.pv, logger) + s := tmgrpc.NewSignerServer(logger, ChainID, tc.pv) req := &privvalproto.SignProposalRequest{ChainId: ChainID, Proposal: tc.have.ToProto()} resp, err := s.SignProposal(ctx, req) diff --git a/privval/msgs_test.go b/privval/msgs_test.go index bbd3f6319..01cef4641 100644 --- a/privval/msgs_test.go +++ b/privval/msgs_test.go @@ -11,7 +11,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/encoding" - "github.com/tendermint/tendermint/crypto/tmhash" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" privproto "github.com/tendermint/tendermint/proto/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -22,23 +21,14 @@ var stamp = time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC) func exampleVote() *types.Vote { return &types.Vote{ - Type: tmproto.SignedMsgType(1), - Height: 3, - Round: 2, - Timestamp: stamp, - BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), - PartSetHeader: types.PartSetHeader{ - Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), - }, - }, + Type: tmproto.PrecommitType, + Height: 3, + Round: 2, + BlockID: types.BlockID{Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{Total: 1000000, Hash: crypto.Checksum([]byte("blockID_part_set_header_hash"))}}, + Timestamp: stamp, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, - VoteExtension: types.VoteExtension{ - AppDataToSign: []byte("app_data_signed"), - AppDataSelfAuthenticating: []byte("app_data_self_authenticating"), - }, + Extension: []byte("extension"), } } @@ -52,10 +42,10 @@ func exampleProposal() *types.Proposal { POLRound: 2, Signature: []byte("it's a signature"), BlockID: types.BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: types.PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, } @@ -87,8 +77,8 @@ func TestPrivvalVectors(t *testing.T) { {"pubKey request", &privproto.PubKeyRequest{}, "0a00"}, {"pubKey response", &privproto.PubKeyResponse{PubKey: ppk, Error: nil}, "12240a220a20556a436f1218d30942efe798420f51dc9b6a311b929c578257457d05c5fcf230"}, {"pubKey response with error", &privproto.PubKeyResponse{PubKey: cryptoproto.PublicKey{}, Error: remoteError}, "12140a0012100801120c697427732061206572726f72"}, - {"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1aa8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"}, - {"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "22a8010aa501080110031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a2f0a0f6170705f646174615f7369676e6564121c6170705f646174615f73656c665f61757468656e7469636174696e67"}, + {"Vote Request", &privproto.SignVoteRequest{Vote: votepb}, "1a81010a7f080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a09657874656e73696f6e"}, + {"Vote Response", &privproto.SignedVoteResponse{Vote: *votepb, Error: nil}, "2281010a7f080210031802224a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a2a0608f49a8ded0532146af1f4111082efb388211bc72c55bcd61e9ac3d538d5bb034a09657874656e73696f6e"}, {"Vote Response with error", &privproto.SignedVoteResponse{Vote: tmproto.Vote{}, Error: remoteError}, "22250a11220212002a0b088092b8c398feffffff0112100801120c697427732061206572726f72"}, {"Proposal Request", &privproto.SignProposalRequest{Proposal: proposalpb}, "2a700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, {"Proposal Response", &privproto.SignedProposalResponse{Proposal: *proposalpb, Error: nil}, "32700a6e08011003180220022a4a0a208b01023386c371778ecb6368573e539afc3cc860ec3a2f614e54fe5652f4fc80122608c0843d122072db3d959635dff1bb567bedaa70573392c5159666a3f8caf11e413aac52207a320608f49a8ded053a10697427732061207369676e6174757265"}, diff --git a/privval/signer_client_test.go b/privval/signer_client_test.go index 272902fc9..fef16f2d6 100644 --- a/privval/signer_client_test.go +++ b/privval/signer_client_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" @@ -143,7 +142,7 @@ func TestSignerProposal(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) have := &types.Proposal{ Type: tmproto.ProposalType, Height: 1, @@ -183,7 +182,7 @@ func TestSignerVote(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -224,7 +223,7 @@ func TestSignerVoteResetDeadline(t *testing.T) { for _, tc := range getSignerTestCases(ctx, t, logger) { t.Run(tc.name, func(t *testing.T) { ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -277,7 +276,7 @@ func TestSignerVoteKeepAlive(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) want := &types.Vote{ Type: tmproto.PrecommitType, @@ -330,7 +329,7 @@ func TestSignerSignProposalErrors(t *testing.T) { tc.mockPV = types.NewErroringMockPV() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) proposal := &types.Proposal{ Type: tmproto.ProposalType, Height: 1, @@ -368,7 +367,7 @@ func TestSignerSignVoteErrors(t *testing.T) { defer tc.closer() ts := time.Now() - hash := tmrand.Bytes(tmhash.Size) + hash := tmrand.Bytes(crypto.HashSize) valAddr := tmrand.Bytes(crypto.AddressSize) vote := &types.Vote{ Type: tmproto.PrecommitType, diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index f9fc73b48..d8143feb3 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -3,8 +3,6 @@ package tendermint.abci; option go_package = "github.com/tendermint/tendermint/abci/types"; -// For more information on gogo.proto, see: -// https://github.com/gogo/protobuf/blob/master/extensions.md import "tendermint/crypto/proof.proto"; import "tendermint/types/types.proto"; import "tendermint/crypto/keys.proto"; @@ -354,7 +352,6 @@ message ResponseExtendVote { bytes vote_extension = 1; } - message ResponseVerifyVoteExtension { VerifyStatus status = 1; @@ -383,8 +380,14 @@ message CommitInfo { repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; } +// ExtendedCommitInfo is similar to CommitInfo except that it is only used in +// the PrepareProposal request such that Tendermint can provide vote extensions +// to the application. message ExtendedCommitInfo { - int32 round = 1; + // The round at which the block proposer decided in the previous height. + int32 round = 1; + // List of validators' addresses in the last validator set with their voting + // information, including vote extensions. repeated ExtendedVoteInfo votes = 2 [(gogoproto.nullable) = false]; } @@ -465,22 +468,14 @@ message VoteInfo { // ExtendedVoteInfo message ExtendedVoteInfo { - Validator validator = 1 [(gogoproto.nullable) = false]; - bool signed_last_block = 2; - bytes vote_extension = 3; + // The validator that sent the vote. + Validator validator = 1 [(gogoproto.nullable) = false]; + // Indicates whether the validator signed the last block, allowing for rewards based on validator availability. + bool signed_last_block = 2; + // Non-deterministic extension provided by the sending validator's application. + bytes vote_extension = 3; } -// CanonicalVoteExtension -// TODO: move this to core Tendermint data structures -message CanonicalVoteExtension { - bytes extension = 1; - int64 height = 2; - int32 round = 3; - string chain_id = 4; - bytes address = 5; -} - - enum MisbehaviorType { UNKNOWN = 0; DUPLICATE_VOTE = 1; diff --git a/proto/tendermint/abci/types.proto.intermediate b/proto/tendermint/abci/types.proto.intermediate deleted file mode 100644 index c752cd87b..000000000 --- a/proto/tendermint/abci/types.proto.intermediate +++ /dev/null @@ -1,521 +0,0 @@ -syntax = "proto3"; -package tendermint.abci; - -import "tendermint/crypto/proof.proto"; -import "tendermint/types/types.proto"; -import "tendermint/crypto/keys.proto"; -import "tendermint/types/params.proto"; -import "google/protobuf/timestamp.proto"; -import "gogoproto/gogo.proto"; - -// This file is a temporary workaround to enable development during the ABCI++ -// project. This file should be deleted and any references to it removed when -// the ongoing work on ABCI++ is completed. -// -// For the duration of ABCI++, this file should be able to build the `abci/types/types.pb.go` -// file. Any changes that update that file must come as a result of a change in -// this .proto file. -// For more information, see https://github.com/tendermint/tendermint/issues/8066 - -//---------------------------------------- -// Request types - -message Request { - oneof value { - RequestEcho echo = 1; - RequestFlush flush = 2; - RequestInfo info = 3; - RequestInitChain init_chain = 4; - RequestQuery query = 5; - RequestBeginBlock begin_block = 6 [deprecated = true]; - RequestCheckTx check_tx = 7; - RequestDeliverTx deliver_tx = 8 [deprecated = true]; - RequestEndBlock end_block = 9 [deprecated = true]; - RequestCommit commit = 10; - RequestListSnapshots list_snapshots = 11; - RequestOfferSnapshot offer_snapshot = 12; - RequestLoadSnapshotChunk load_snapshot_chunk = 13; - RequestApplySnapshotChunk apply_snapshot_chunk = 14; - RequestPrepareProposal prepare_proposal = 15; - RequestProcessProposal process_proposal = 16; - RequestExtendVote extend_vote = 17; - RequestVerifyVoteExtension verify_vote_extension = 18; - RequestFinalizeBlock finalize_block = 19; - } -} - -message RequestEcho { - string message = 1; -} - -message RequestFlush {} - -message RequestInfo { - string version = 1; - uint64 block_version = 2; - uint64 p2p_version = 3; - string abci_version = 4; -} - -message RequestInitChain { - google.protobuf.Timestamp time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - string chain_id = 2; - tendermint.types.ConsensusParams consensus_params = 3; - repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false]; - bytes app_state_bytes = 5; - int64 initial_height = 6; -} - -message RequestQuery { - bytes data = 1; - string path = 2; - int64 height = 3; - bool prove = 4; -} - -message RequestBeginBlock { - bytes hash = 1; - tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; - CommitInfo last_commit_info = 3 [(gogoproto.nullable) = false]; - repeated Misbehavior byzantine_validators = 4 [(gogoproto.nullable) = false]; -} - -enum CheckTxType { - NEW = 0 [(gogoproto.enumvalue_customname) = "New"]; - RECHECK = 1 [(gogoproto.enumvalue_customname) = "Recheck"]; -} - -message RequestCheckTx { - bytes tx = 1; - CheckTxType type = 2; -} - -message RequestDeliverTx { - bytes tx = 1; -} - -message RequestEndBlock { - int64 height = 1; -} - -message RequestCommit {} - -// lists available snapshots -message RequestListSnapshots {} - -// offers a snapshot to the application -message RequestOfferSnapshot { - Snapshot snapshot = 1; // snapshot offered by peers - bytes app_hash = 2; // light client-verified app hash for snapshot height -} - -// loads a snapshot chunk -message RequestLoadSnapshotChunk { - uint64 height = 1; - uint32 format = 2; - uint32 chunk = 3; -} - -// Applies a snapshot chunk -message RequestApplySnapshotChunk { - uint32 index = 1; - bytes chunk = 2; - string sender = 3; -} - -message RequestPrepareProposal { - // the modified transactions cannot exceed this size. - int64 max_tx_bytes = 1; - // txs is an array of transactions that will be included in a block, - // sent to the app for possible modifications. - repeated bytes txs = 2; - ExtendedCommitInfo local_last_commit = 3 [(gogoproto.nullable) = false]; - repeated Misbehavior byzantine_validators = 4 [(gogoproto.nullable) = false]; - int64 height = 5; - google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes next_validators_hash = 7; - // address of the public key of the validator proposing the block. - bytes proposer_address = 8; -} - -message RequestProcessProposal { - repeated bytes txs = 1; - CommitInfo proposed_last_commit = 2 [(gogoproto.nullable) = false]; - repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false]; - // hash is the merkle root hash of the fields of the proposed block. - bytes hash = 4; - int64 height = 5; - google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes next_validators_hash = 7; - // address of the public key of the original proposer of the block. - bytes proposer_address = 8; -} - -// Extends a vote with application-side injection -message RequestExtendVote { - types.Vote vote = 1; -} - -// Verify the vote extension -message RequestVerifyVoteExtension { - types.Vote vote = 1; -} - -message RequestFinalizeBlock { - repeated bytes txs = 1; - CommitInfo decided_last_commit = 2 [(gogoproto.nullable) = false]; - repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false]; - // hash is the merkle root hash of the fields of the proposed block. - bytes hash = 4; - int64 height = 5; - google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes next_validators_hash = 7; - // proposer_address is the address of the public key of the original proposer of the block. - bytes proposer_address = 8; -} - -//---------------------------------------- -// Response types - -message Response { - oneof value { - ResponseException exception = 1; - ResponseEcho echo = 2; - ResponseFlush flush = 3; - ResponseInfo info = 4; - ResponseInitChain init_chain = 5; - ResponseQuery query = 6; - ResponseBeginBlock begin_block = 7 [deprecated = true]; - ResponseCheckTx check_tx = 8; - ResponseDeliverTx deliver_tx = 9 [deprecated = true]; - ResponseEndBlock end_block = 10 [deprecated = true]; - ResponseCommit commit = 11; - ResponseListSnapshots list_snapshots = 12; - ResponseOfferSnapshot offer_snapshot = 13; - ResponseLoadSnapshotChunk load_snapshot_chunk = 14; - ResponseApplySnapshotChunk apply_snapshot_chunk = 15; - ResponsePrepareProposal prepare_proposal = 16; - ResponseProcessProposal process_proposal = 17; - ResponseExtendVote extend_vote = 18; - ResponseVerifyVoteExtension verify_vote_extension = 19; - ResponseFinalizeBlock finalize_block = 20; - } -} - -// nondeterministic -message ResponseException { - string error = 1; -} - -message ResponseEcho { - string message = 1; -} - -message ResponseFlush {} - -message ResponseInfo { - string data = 1; - - // this is the software version of the application. TODO: remove? - string version = 2; - uint64 app_version = 3; - - int64 last_block_height = 4; - bytes last_block_app_hash = 5; -} - -message ResponseInitChain { - tendermint.types.ConsensusParams consensus_params = 1; - repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false]; - bytes app_hash = 3; -} - -message ResponseQuery { - uint32 code = 1; - // bytes data = 2; // use "value" instead. - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 index = 5; - bytes key = 6; - bytes value = 7; - tendermint.crypto.ProofOps proof_ops = 8; - int64 height = 9; - string codespace = 10; -} - -message ResponseBeginBlock { - repeated Event events = 1 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; -} - -message ResponseCheckTx { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5; - int64 gas_used = 6; - repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; - string codespace = 8; - string sender = 9; - int64 priority = 10; - - // mempool_error is set by Tendermint. - - // ABCI applications creating a ResponseCheckTX should not set mempool_error. - string mempool_error = 11; -} - -message ResponseDeliverTx { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5 [json_name = "gas_wanted"]; - int64 gas_used = 6 [json_name = "gas_used"]; - repeated Event events = 7 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic - string codespace = 8; -} - -message ResponseEndBlock { - repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false]; - tendermint.types.ConsensusParams consensus_param_updates = 2; - repeated Event events = 3 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; -} - -message ResponseCommit { - // reserve 1 - bytes data = 2; - int64 retain_height = 3; -} - -message ResponseListSnapshots { - repeated Snapshot snapshots = 1; -} - -message ResponseOfferSnapshot { - Result result = 1; - - enum Result { - UNKNOWN = 0; // Unknown result, abort all snapshot restoration - ACCEPT = 1; // Snapshot accepted, apply chunks - ABORT = 2; // Abort all snapshot restoration - REJECT = 3; // Reject this specific snapshot, try others - REJECT_FORMAT = 4; // Reject all snapshots of this format, try others - REJECT_SENDER = 5; // Reject all snapshots from the sender(s), try others - } -} - -message ResponseLoadSnapshotChunk { - bytes chunk = 1; -} - -message ResponseApplySnapshotChunk { - Result result = 1; - repeated uint32 refetch_chunks = 2; // Chunks to refetch and reapply - repeated string reject_senders = 3; // Chunk senders to reject and ban - - enum Result { - UNKNOWN = 0; // Unknown result, abort all snapshot restoration - ACCEPT = 1; // Chunk successfully accepted - ABORT = 2; // Abort all snapshot restoration - RETRY = 3; // Retry chunk (combine with refetch and reject) - RETRY_SNAPSHOT = 4; // Retry snapshot (combine with refetch and reject) - REJECT_SNAPSHOT = 5; // Reject this snapshot, try others - } -} - -message ResponsePrepareProposal { - repeated TxRecord tx_records = 1; - bytes app_hash = 2; - repeated ExecTxResult tx_results = 3; - repeated ValidatorUpdate validator_updates = 4; - tendermint.types.ConsensusParams consensus_param_updates = 5; -} - -message ResponseProcessProposal { - ProposalStatus status = 1; - bytes app_hash = 2; - repeated ExecTxResult tx_results = 3; - repeated ValidatorUpdate validator_updates = 4; - tendermint.types.ConsensusParams consensus_param_updates = 5; - - enum ProposalStatus { - UNKNOWN = 0; - ACCEPT = 1; - REJECT = 2; - } -} - -message ResponseExtendVote { - tendermint.types.VoteExtension vote_extension = 1; -} - -message ResponseVerifyVoteExtension { - VerifyStatus status = 1; - - enum VerifyStatus { - UNKNOWN = 0; - ACCEPT = 1; - REJECT = 2; - } -} - -message ResponseFinalizeBlock { - repeated Event events = 1 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; - repeated ExecTxResult tx_results = 2; - repeated ValidatorUpdate validator_updates = 3 [(gogoproto.nullable) = false]; - tendermint.types.ConsensusParams consensus_param_updates = 4; - bytes app_hash = 5; - int64 retain_height = 6; -} - -//---------------------------------------- -// Misc. - -message CommitInfo { - int32 round = 1; - repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; -} - -message ExtendedCommitInfo { - int32 round = 1; - repeated ExtendedVoteInfo votes = 2 [(gogoproto.nullable) = false]; -} - -// Event allows application developers to attach additional information to -// ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx. -// Later, transactions may be queried using these events. -message Event { - string type = 1; - repeated EventAttribute attributes = 2 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "attributes,omitempty"]; -} - -// EventAttribute is a single key-value pair, associated with an event. -message EventAttribute { - string key = 1; - string value = 2; - bool index = 3; // nondeterministic -} - -// ExecTxResult contains results of executing one individual transaction. -// -// * Its structure is equivalent to #ResponseDeliverTx which will be deprecated/deleted -message ExecTxResult { - uint32 code = 1; - bytes data = 2; - string log = 3; // nondeterministic - string info = 4; // nondeterministic - int64 gas_wanted = 5; - int64 gas_used = 6; - repeated Event events = 7 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic - string codespace = 8; -} - -// TxResult contains results of executing the transaction. -// -// One usage is indexing transaction results. -message TxResult { - int64 height = 1; - uint32 index = 2; - bytes tx = 3; - ExecTxResult result = 4 [(gogoproto.nullable) = false]; -} - -message TxRecord { - TxAction action = 1; - bytes tx = 2; - - // TxAction contains App-provided information on what to do with a transaction that is part of a raw proposal - enum TxAction { - UNKNOWN = 0; // Unknown action - UNMODIFIED = 1; // The Application did not modify this transaction. - ADDED = 2; // The Application added this transaction. - REMOVED = 3; // The Application wants this transaction removed from the proposal and the mempool. - } -} - -//---------------------------------------- -// Blockchain Types - -// Validator -message Validator { - bytes address = 1; // The first 20 bytes of SHA256(public key) - // PubKey pub_key = 2 [(gogoproto.nullable)=false]; - int64 power = 3; // The voting power -} - -// ValidatorUpdate -message ValidatorUpdate { - tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; - int64 power = 2; -} - -// VoteInfo -message VoteInfo { - Validator validator = 1 [(gogoproto.nullable) = false]; - bool signed_last_block = 2; - reserved 3; // Placeholder for tendermint_signed_extension in v0.37 - reserved 4; // Placeholder for app_signed_extension in v0.37 -} - -message ExtendedVoteInfo { - Validator validator = 1 [(gogoproto.nullable) = false]; - bool signed_last_block = 2; - bytes vote_extension = 3; -} - -enum MisbehaviorType { - UNKNOWN = 0; - DUPLICATE_VOTE = 1; - LIGHT_CLIENT_ATTACK = 2; -} - -message Misbehavior { - MisbehaviorType type = 1; - // The offending validator - Validator validator = 2 [(gogoproto.nullable) = false]; - // The height when the offense occurred - int64 height = 3; - // The corresponding time where the offense occurred - google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - // Total voting power of the validator set in case the ABCI application does - // not store historical validators. - // https://github.com/tendermint/tendermint/issues/4581 - int64 total_voting_power = 5; -} - -//---------------------------------------- -// State Sync Types - -message Snapshot { - uint64 height = 1; // The height at which the snapshot was taken - uint32 format = 2; // The application-specific snapshot format - uint32 chunks = 3; // Number of chunks in the snapshot - bytes hash = 4; // Arbitrary snapshot hash, equal only if identical - bytes metadata = 5; // Arbitrary application metadata -} - -//---------------------------------------- -// Service Definition - -service ABCIApplication { - rpc Echo(RequestEcho) returns (ResponseEcho); - rpc Flush(RequestFlush) returns (ResponseFlush); - rpc Info(RequestInfo) returns (ResponseInfo); - rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); - rpc Query(RequestQuery) returns (ResponseQuery); - rpc Commit(RequestCommit) returns (ResponseCommit); - rpc InitChain(RequestInitChain) returns (ResponseInitChain); - rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots); - rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot); - rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) returns (ResponseLoadSnapshotChunk); - rpc ApplySnapshotChunk(RequestApplySnapshotChunk) returns (ResponseApplySnapshotChunk); - rpc PrepareProposal(RequestPrepareProposal) returns (ResponsePrepareProposal); - rpc ProcessProposal(RequestProcessProposal) returns (ResponseProcessProposal); - rpc ExtendVote(RequestExtendVote) returns (ResponseExtendVote); - rpc VerifyVoteExtension(RequestVerifyVoteExtension) returns (ResponseVerifyVoteExtension); - rpc FinalizeBlock(RequestFinalizeBlock) returns (ResponseFinalizeBlock); -} diff --git a/proto/tendermint/types/canonical.pb.go b/proto/tendermint/types/canonical.pb.go index 0cd7386f7..50c0c84fa 100644 --- a/proto/tendermint/types/canonical.pb.go +++ b/proto/tendermint/types/canonical.pb.go @@ -225,13 +225,12 @@ func (m *CanonicalProposal) GetChainID() string { } type CanonicalVote struct { - Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` - Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` - Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` - BlockID *CanonicalBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` - Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - ChainID string `protobuf:"bytes,6,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - VoteExtension *VoteExtensionToSign `protobuf:"bytes,7,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` + Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` + BlockID *CanonicalBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id,omitempty"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + ChainID string `protobuf:"bytes,6,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` } func (m *CanonicalVote) Reset() { *m = CanonicalVote{} } @@ -309,57 +308,121 @@ func (m *CanonicalVote) GetChainID() string { return "" } -func (m *CanonicalVote) GetVoteExtension() *VoteExtensionToSign { +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +type CanonicalVoteExtension struct { + Extension []byte `protobuf:"bytes,1,opt,name=extension,proto3" json:"extension,omitempty"` + Height int64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + Round int64 `protobuf:"fixed64,3,opt,name=round,proto3" json:"round,omitempty"` + ChainId string `protobuf:"bytes,4,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +func (m *CanonicalVoteExtension) Reset() { *m = CanonicalVoteExtension{} } +func (m *CanonicalVoteExtension) String() string { return proto.CompactTextString(m) } +func (*CanonicalVoteExtension) ProtoMessage() {} +func (*CanonicalVoteExtension) Descriptor() ([]byte, []int) { + return fileDescriptor_8d1a1a84ff7267ed, []int{4} +} +func (m *CanonicalVoteExtension) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CanonicalVoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CanonicalVoteExtension.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CanonicalVoteExtension) XXX_Merge(src proto.Message) { + xxx_messageInfo_CanonicalVoteExtension.Merge(m, src) +} +func (m *CanonicalVoteExtension) XXX_Size() int { + return m.Size() +} +func (m *CanonicalVoteExtension) XXX_DiscardUnknown() { + xxx_messageInfo_CanonicalVoteExtension.DiscardUnknown(m) +} + +var xxx_messageInfo_CanonicalVoteExtension proto.InternalMessageInfo + +func (m *CanonicalVoteExtension) GetExtension() []byte { if m != nil { - return m.VoteExtension + return m.Extension } return nil } +func (m *CanonicalVoteExtension) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *CanonicalVoteExtension) GetRound() int64 { + if m != nil { + return m.Round + } + return 0 +} + +func (m *CanonicalVoteExtension) GetChainId() string { + if m != nil { + return m.ChainId + } + return "" +} + func init() { proto.RegisterType((*CanonicalBlockID)(nil), "tendermint.types.CanonicalBlockID") proto.RegisterType((*CanonicalPartSetHeader)(nil), "tendermint.types.CanonicalPartSetHeader") proto.RegisterType((*CanonicalProposal)(nil), "tendermint.types.CanonicalProposal") proto.RegisterType((*CanonicalVote)(nil), "tendermint.types.CanonicalVote") + proto.RegisterType((*CanonicalVoteExtension)(nil), "tendermint.types.CanonicalVoteExtension") } func init() { proto.RegisterFile("tendermint/types/canonical.proto", fileDescriptor_8d1a1a84ff7267ed) } var fileDescriptor_8d1a1a84ff7267ed = []byte{ - // 522 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0x3f, 0x6f, 0xd3, 0x40, - 0x18, 0xc6, 0xe3, 0xd4, 0x49, 0x9c, 0x4b, 0x53, 0xc2, 0xa9, 0xaa, 0xac, 0x08, 0xd9, 0x96, 0x25, - 0x90, 0x59, 0x6c, 0x29, 0x1d, 0xd8, 0x5d, 0x90, 0x08, 0x2a, 0xa2, 0x5c, 0xa3, 0x0e, 0x2c, 0xd6, - 0xc5, 0x3e, 0x6c, 0x0b, 0xc7, 0x67, 0xd9, 0x97, 0x8a, 0x2e, 0x7c, 0x86, 0x7e, 0xac, 0x8e, 0x1d, - 0x61, 0x09, 0xc8, 0xf9, 0x12, 0x8c, 0xe8, 0xce, 0x49, 0x1c, 0x25, 0xc0, 0x02, 0xea, 0x12, 0xbd, - 0x7f, 0x1e, 0xbf, 0xef, 0xa3, 0xdf, 0xab, 0x1c, 0x30, 0x18, 0x49, 0x03, 0x92, 0xcf, 0xe2, 0x94, - 0x39, 0xec, 0x26, 0x23, 0x85, 0xe3, 0xe3, 0x94, 0xa6, 0xb1, 0x8f, 0x13, 0x3b, 0xcb, 0x29, 0xa3, - 0x70, 0x50, 0x2b, 0x6c, 0xa1, 0x18, 0x1e, 0x87, 0x34, 0xa4, 0xa2, 0xe9, 0xf0, 0xa8, 0xd2, 0x0d, - 0x9f, 0xec, 0x4d, 0x12, 0xbf, 0xab, 0xae, 0x1e, 0x52, 0x1a, 0x26, 0xc4, 0x11, 0xd9, 0x74, 0xfe, - 0xd1, 0x61, 0xf1, 0x8c, 0x14, 0x0c, 0xcf, 0xb2, 0x4a, 0x60, 0x7e, 0x01, 0x83, 0xb3, 0xf5, 0x66, - 0x37, 0xa1, 0xfe, 0xa7, 0xf1, 0x4b, 0x08, 0x81, 0x1c, 0xe1, 0x22, 0x52, 0x25, 0x43, 0xb2, 0x0e, - 0x91, 0x88, 0xe1, 0x15, 0x78, 0x94, 0xe1, 0x9c, 0x79, 0x05, 0x61, 0x5e, 0x44, 0x70, 0x40, 0x72, - 0xb5, 0x69, 0x48, 0x56, 0x6f, 0x64, 0xd9, 0xbb, 0x46, 0xed, 0xcd, 0xc0, 0x0b, 0x9c, 0xb3, 0x4b, - 0xc2, 0x5e, 0x0b, 0xbd, 0x2b, 0xdf, 0x2d, 0xf4, 0x06, 0xea, 0x67, 0xdb, 0x45, 0xd3, 0x05, 0x27, - 0xbf, 0x97, 0xc3, 0x63, 0xd0, 0x62, 0x94, 0xe1, 0x44, 0xd8, 0xe8, 0xa3, 0x2a, 0xd9, 0x78, 0x6b, - 0xd6, 0xde, 0xcc, 0x6f, 0x4d, 0xf0, 0xb8, 0x1e, 0x92, 0xd3, 0x8c, 0x16, 0x38, 0x81, 0xa7, 0x40, - 0xe6, 0x76, 0xc4, 0xe7, 0x47, 0x23, 0x7d, 0xdf, 0xe6, 0x65, 0x1c, 0xa6, 0x24, 0x78, 0x5b, 0x84, - 0x93, 0x9b, 0x8c, 0x20, 0x21, 0x86, 0x27, 0xa0, 0x1d, 0x91, 0x38, 0x8c, 0x98, 0x58, 0x30, 0x40, - 0xab, 0x8c, 0x9b, 0xc9, 0xe9, 0x3c, 0x0d, 0xd4, 0x03, 0x51, 0xae, 0x12, 0xf8, 0x1c, 0x74, 0x33, - 0x9a, 0x78, 0x55, 0x47, 0x36, 0x24, 0xeb, 0xc0, 0x3d, 0x2c, 0x17, 0xba, 0x72, 0xf1, 0xee, 0x1c, - 0xf1, 0x1a, 0x52, 0x32, 0x9a, 0x88, 0x08, 0xbe, 0x01, 0xca, 0x94, 0xe3, 0xf5, 0xe2, 0x40, 0x6d, - 0x09, 0x70, 0xe6, 0x5f, 0xc0, 0xad, 0x2e, 0xe1, 0xf6, 0xca, 0x85, 0xde, 0x59, 0x25, 0xa8, 0x23, - 0x06, 0x8c, 0x03, 0xe8, 0x82, 0xee, 0xe6, 0x8c, 0x6a, 0x5b, 0x0c, 0x1b, 0xda, 0xd5, 0xa1, 0xed, - 0xf5, 0xa1, 0xed, 0xc9, 0x5a, 0xe1, 0x2a, 0x9c, 0xfb, 0xed, 0x77, 0x5d, 0x42, 0xf5, 0x67, 0xf0, - 0x19, 0x50, 0xfc, 0x08, 0xc7, 0x29, 0xf7, 0xd3, 0x31, 0x24, 0xab, 0x5b, 0xed, 0x3a, 0xe3, 0x35, - 0xbe, 0x4b, 0x34, 0xc7, 0x81, 0xf9, 0xb3, 0x09, 0xfa, 0x1b, 0x5b, 0x57, 0x94, 0x91, 0x87, 0xe0, - 0xba, 0x0d, 0x4b, 0xfe, 0x9f, 0xb0, 0x5a, 0xff, 0x0e, 0xab, 0xfd, 0x67, 0x58, 0xf0, 0x1c, 0x1c, - 0x5d, 0x53, 0x46, 0x3c, 0xf2, 0x99, 0x91, 0xb4, 0x88, 0x69, 0x2a, 0xd0, 0xf6, 0x46, 0x4f, 0xf7, - 0xdd, 0x73, 0x94, 0xaf, 0xd6, 0xb2, 0x09, 0xe5, 0xcc, 0x50, 0xff, 0x7a, 0xbb, 0xe8, 0xbe, 0xbf, - 0x2b, 0x35, 0xe9, 0xbe, 0xd4, 0xa4, 0x1f, 0xa5, 0x26, 0xdd, 0x2e, 0xb5, 0xc6, 0xfd, 0x52, 0x6b, - 0x7c, 0x5d, 0x6a, 0x8d, 0x0f, 0x2f, 0xc2, 0x98, 0x45, 0xf3, 0xa9, 0xed, 0xd3, 0x99, 0xb3, 0xfd, - 0xf7, 0xaf, 0xc3, 0xea, 0x99, 0xd8, 0x7d, 0x1a, 0xa6, 0x6d, 0x51, 0x3f, 0xfd, 0x15, 0x00, 0x00, - 0xff, 0xff, 0x4e, 0x61, 0xcb, 0xc4, 0x7f, 0x04, 0x00, 0x00, + // 523 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x8d, 0x53, 0x27, 0xb1, 0xb7, 0x0d, 0x84, 0x55, 0x55, 0x99, 0xa8, 0xb2, 0x2d, 0x1f, 0x90, + 0xb9, 0xd8, 0x52, 0x7b, 0xe0, 0xee, 0x82, 0x44, 0x10, 0x88, 0xe2, 0x56, 0x3d, 0x70, 0x89, 0x36, + 0xf6, 0x62, 0x5b, 0x38, 0xde, 0x95, 0xbd, 0x91, 0xe8, 0x05, 0x7e, 0xa1, 0xdf, 0xc1, 0x97, 0xf4, + 0xd8, 0x23, 0x5c, 0x02, 0x72, 0x7e, 0x04, 0xed, 0xda, 0xb1, 0xad, 0x16, 0x55, 0x42, 0x20, 0x2e, + 0xd1, 0xcc, 0x9b, 0xb7, 0x33, 0x4f, 0x6f, 0xe2, 0x01, 0x26, 0xc3, 0x59, 0x88, 0xf3, 0x65, 0x92, + 0x31, 0x97, 0x5d, 0x52, 0x5c, 0xb8, 0x01, 0xca, 0x48, 0x96, 0x04, 0x28, 0x75, 0x68, 0x4e, 0x18, + 0x81, 0x93, 0x96, 0xe1, 0x08, 0xc6, 0x74, 0x3f, 0x22, 0x11, 0x11, 0x45, 0x97, 0x47, 0x15, 0x6f, + 0x7a, 0x78, 0xa7, 0x93, 0xf8, 0xad, 0xab, 0x46, 0x44, 0x48, 0x94, 0x62, 0x57, 0x64, 0x8b, 0xd5, + 0x07, 0x97, 0x25, 0x4b, 0x5c, 0x30, 0xb4, 0xa4, 0x15, 0xc1, 0xfa, 0x0c, 0x26, 0x27, 0xdb, 0xc9, + 0x5e, 0x4a, 0x82, 0x8f, 0xb3, 0xe7, 0x10, 0x02, 0x39, 0x46, 0x45, 0xac, 0x49, 0xa6, 0x64, 0xef, + 0xf9, 0x22, 0x86, 0x17, 0xe0, 0x21, 0x45, 0x39, 0x9b, 0x17, 0x98, 0xcd, 0x63, 0x8c, 0x42, 0x9c, + 0x6b, 0x7d, 0x53, 0xb2, 0x77, 0x8f, 0x6c, 0xe7, 0xb6, 0x50, 0xa7, 0x69, 0x78, 0x8a, 0x72, 0x76, + 0x86, 0xd9, 0x4b, 0xc1, 0xf7, 0xe4, 0xeb, 0xb5, 0xd1, 0xf3, 0xc7, 0xb4, 0x0b, 0x5a, 0x1e, 0x38, + 0xf8, 0x3d, 0x1d, 0xee, 0x83, 0x01, 0x23, 0x0c, 0xa5, 0x42, 0xc6, 0xd8, 0xaf, 0x92, 0x46, 0x5b, + 0xbf, 0xd5, 0x66, 0x7d, 0xef, 0x83, 0x47, 0x6d, 0x93, 0x9c, 0x50, 0x52, 0xa0, 0x14, 0x1e, 0x03, + 0x99, 0xcb, 0x11, 0xcf, 0x1f, 0x1c, 0x19, 0x77, 0x65, 0x9e, 0x25, 0x51, 0x86, 0xc3, 0x37, 0x45, + 0x74, 0x7e, 0x49, 0xb1, 0x2f, 0xc8, 0xf0, 0x00, 0x0c, 0x63, 0x9c, 0x44, 0x31, 0x13, 0x03, 0x26, + 0x7e, 0x9d, 0x71, 0x31, 0x39, 0x59, 0x65, 0xa1, 0xb6, 0x23, 0xe0, 0x2a, 0x81, 0x4f, 0x81, 0x4a, + 0x49, 0x3a, 0xaf, 0x2a, 0xb2, 0x29, 0xd9, 0x3b, 0xde, 0x5e, 0xb9, 0x36, 0x94, 0xd3, 0xb7, 0xaf, + 0x7d, 0x8e, 0xf9, 0x0a, 0x25, 0xa9, 0x88, 0xe0, 0x2b, 0xa0, 0x2c, 0xb8, 0xbd, 0xf3, 0x24, 0xd4, + 0x06, 0xc2, 0x38, 0xeb, 0x1e, 0xe3, 0xea, 0x4d, 0x78, 0xbb, 0xe5, 0xda, 0x18, 0xd5, 0x89, 0x3f, + 0x12, 0x0d, 0x66, 0x21, 0xf4, 0x80, 0xda, 0xac, 0x51, 0x1b, 0x8a, 0x66, 0x53, 0xa7, 0x5a, 0xb4, + 0xb3, 0x5d, 0xb4, 0x73, 0xbe, 0x65, 0x78, 0x0a, 0xf7, 0xfd, 0xea, 0x87, 0x21, 0xf9, 0xed, 0x33, + 0xf8, 0x04, 0x28, 0x41, 0x8c, 0x92, 0x8c, 0xeb, 0x19, 0x99, 0x92, 0xad, 0x56, 0xb3, 0x4e, 0x38, + 0xc6, 0x67, 0x89, 0xe2, 0x2c, 0xb4, 0xbe, 0xf6, 0xc1, 0xb8, 0x91, 0x75, 0x41, 0x18, 0xfe, 0x1f, + 0xbe, 0x76, 0xcd, 0x92, 0xff, 0xa5, 0x59, 0x83, 0xbf, 0x37, 0x6b, 0x78, 0x8f, 0x59, 0x5f, 0x3a, + 0x7f, 0x66, 0xee, 0xd5, 0x8b, 0x4f, 0x0c, 0x67, 0x45, 0x42, 0x32, 0x78, 0x08, 0x54, 0xbc, 0x4d, + 0xea, 0xef, 0xaa, 0x05, 0xfe, 0xd0, 0x9d, 0xc7, 0x1d, 0x35, 0xdc, 0x1d, 0xb5, 0x11, 0xe0, 0xbd, + 0xbb, 0x2e, 0x75, 0xe9, 0xa6, 0xd4, 0xa5, 0x9f, 0xa5, 0x2e, 0x5d, 0x6d, 0xf4, 0xde, 0xcd, 0x46, + 0xef, 0x7d, 0xdb, 0xe8, 0xbd, 0xf7, 0xcf, 0xa2, 0x84, 0xc5, 0xab, 0x85, 0x13, 0x90, 0xa5, 0xdb, + 0xbd, 0x18, 0x6d, 0x58, 0x5d, 0x96, 0xdb, 0xd7, 0x64, 0x31, 0x14, 0xf8, 0xf1, 0xaf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x2b, 0x89, 0x89, 0x5b, 0xb2, 0x04, 0x00, 0x00, } func (m *CanonicalBlockID) Marshal() (dAtA []byte, err error) { @@ -529,18 +592,6 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintCanonical(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x3a - } if len(m.ChainID) > 0 { i -= len(m.ChainID) copy(dAtA[i:], m.ChainID) @@ -548,12 +599,12 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err5 != nil { - return 0, err5 + n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err4 != nil { + return 0, err4 } - i -= n5 - i = encodeVarintCanonical(dAtA, i, uint64(n5)) + i -= n4 + i = encodeVarintCanonical(dAtA, i, uint64(n4)) i-- dAtA[i] = 0x2a if m.BlockID != nil { @@ -588,6 +639,55 @@ func (m *CanonicalVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *CanonicalVoteExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CanonicalVoteExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CanonicalVoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0x22 + } + if m.Round != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Round)) + i-- + dAtA[i] = 0x19 + } + if m.Height != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Height)) + i-- + dAtA[i] = 0x11 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintCanonical(dAtA, i, uint64(len(m.Extension))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintCanonical(dAtA []byte, offset int, v uint64) int { offset -= sovCanonical(v) base := offset @@ -686,8 +786,27 @@ func (m *CanonicalVote) Size() (n int) { if l > 0 { n += 1 + l + sovCanonical(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() + return n +} + +func (m *CanonicalVoteExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Extension) + if l > 0 { + n += 1 + l + sovCanonical(uint64(l)) + } + if m.Height != 0 { + n += 9 + } + if m.Round != 0 { + n += 9 + } + l = len(m.ChainId) + if l > 0 { n += 1 + l + sovCanonical(uint64(l)) } return n @@ -1297,11 +1416,61 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error { } m.ChainID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) + default: + iNdEx = preIndex + skippy, err := skipCanonical(dAtA[iNdEx:]) + if err != nil { + return err } - var msglen int + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCanonical + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CanonicalVoteExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CanonicalVoteExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CanonicalVoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) + } + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowCanonical @@ -1311,27 +1480,77 @@ func (m *CanonicalVote) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthCanonical } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthCanonical } if postIndex > l { return io.ErrUnexpectedEOF } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtensionToSign{} + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + iNdEx = postIndex + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) } + m.Height = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Height = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Round", wireType) + } + m.Round = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Round = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCanonical + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCanonical + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCanonical + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/proto/tendermint/types/canonical.proto b/proto/tendermint/types/canonical.proto index e88fd6ffe..e9ed1e55d 100644 --- a/proto/tendermint/types/canonical.proto +++ b/proto/tendermint/types/canonical.proto @@ -35,3 +35,12 @@ message CanonicalVote { google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; string chain_id = 6 [(gogoproto.customname) = "ChainID"]; } + +// CanonicalVoteExtension provides us a way to serialize a vote extension from +// a particular validator such that we can sign over those serialized bytes. +message CanonicalVoteExtension { + bytes extension = 1; + sfixed64 height = 2; + sfixed64 round = 3; + string chain_id = 4; +} diff --git a/proto/tendermint/types/params.pb.go b/proto/tendermint/types/params.pb.go index c8b8594e1..41d417b91 100644 --- a/proto/tendermint/types/params.pb.go +++ b/proto/tendermint/types/params.pb.go @@ -394,7 +394,7 @@ func (m *HashedParams) GetBlockMaxGas() int64 { // see the specification of proposer-based timestamps: // https://github.com/tendermint/tendermint/tree/master/spec/consensus/proposer-based-timestamp type SynchronyParams struct { - // message_delay bounds how long a proposal message may take to reach all validators on a newtork + // message_delay bounds how long a proposal message may take to reach all validators on a network // and still be considered valid. MessageDelay *time.Duration `protobuf:"bytes,1,opt,name=message_delay,json=messageDelay,proto3,stdduration" json:"message_delay,omitempty"` // precision bounds how skewed a proposer's clock may be from any validator diff --git a/proto/tendermint/types/types.pb.go b/proto/tendermint/types/types.pb.go index 5c1f99d23..1904afcd1 100644 --- a/proto/tendermint/types/types.pb.go +++ b/proto/tendermint/types/types.pb.go @@ -29,7 +29,7 @@ var _ = time.Kitchen // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// BlockIdFlag indicates which BlcokID the signature is for +// BlockIdFlag indicates which BlockID the signature is for type BlockIDFlag int32 const ( @@ -466,15 +466,22 @@ func (m *Data) GetTxs() [][]byte { // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { - Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` - Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` - Round int32 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` - BlockID BlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id"` - Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` - ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3" json:"validator_index,omitempty"` - Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` - VoteExtension *VoteExtension `protobuf:"bytes,9,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + Round int32 `protobuf:"varint,3,opt,name=round,proto3" json:"round,omitempty"` + BlockID BlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3" json:"block_id"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3" json:"validator_index,omitempty"` + // Vote signature by the validator if they participated in consensus for the + // associated block. + Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3" json:"signature,omitempty"` + // Vote extension provided by the application. Only valid for precommit + // messages. + Extension []byte `protobuf:"bytes,9,opt,name=extension,proto3" json:"extension,omitempty"` + // Vote extension signature by the validator if they participated in + // consensus for the associated block. Only valid for precommit messages. + ExtensionSignature []byte `protobuf:"bytes,10,opt,name=extension_signature,json=extensionSignature,proto3" json:"extension_signature,omitempty"` } func (m *Vote) Reset() { *m = Vote{} } @@ -566,108 +573,16 @@ func (m *Vote) GetSignature() []byte { return nil } -func (m *Vote) GetVoteExtension() *VoteExtension { +func (m *Vote) GetExtension() []byte { if m != nil { - return m.VoteExtension + return m.Extension } return nil } -// VoteExtension is app-defined additional information to the validator votes. -type VoteExtension struct { - AppDataToSign []byte `protobuf:"bytes,1,opt,name=app_data_to_sign,json=appDataToSign,proto3" json:"app_data_to_sign,omitempty"` - AppDataSelfAuthenticating []byte `protobuf:"bytes,2,opt,name=app_data_self_authenticating,json=appDataSelfAuthenticating,proto3" json:"app_data_self_authenticating,omitempty"` -} - -func (m *VoteExtension) Reset() { *m = VoteExtension{} } -func (m *VoteExtension) String() string { return proto.CompactTextString(m) } -func (*VoteExtension) ProtoMessage() {} -func (*VoteExtension) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{6} -} -func (m *VoteExtension) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *VoteExtension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_VoteExtension.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *VoteExtension) XXX_Merge(src proto.Message) { - xxx_messageInfo_VoteExtension.Merge(m, src) -} -func (m *VoteExtension) XXX_Size() int { - return m.Size() -} -func (m *VoteExtension) XXX_DiscardUnknown() { - xxx_messageInfo_VoteExtension.DiscardUnknown(m) -} - -var xxx_messageInfo_VoteExtension proto.InternalMessageInfo - -func (m *VoteExtension) GetAppDataToSign() []byte { +func (m *Vote) GetExtensionSignature() []byte { if m != nil { - return m.AppDataToSign - } - return nil -} - -func (m *VoteExtension) GetAppDataSelfAuthenticating() []byte { - if m != nil { - return m.AppDataSelfAuthenticating - } - return nil -} - -// VoteExtensionToSign is a subset of VoteExtension that is signed by the validators private key. -// VoteExtensionToSign is extracted from an existing VoteExtension. -type VoteExtensionToSign struct { - AppDataToSign []byte `protobuf:"bytes,1,opt,name=app_data_to_sign,json=appDataToSign,proto3" json:"app_data_to_sign,omitempty"` -} - -func (m *VoteExtensionToSign) Reset() { *m = VoteExtensionToSign{} } -func (m *VoteExtensionToSign) String() string { return proto.CompactTextString(m) } -func (*VoteExtensionToSign) ProtoMessage() {} -func (*VoteExtensionToSign) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{7} -} -func (m *VoteExtensionToSign) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *VoteExtensionToSign) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_VoteExtensionToSign.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *VoteExtensionToSign) XXX_Merge(src proto.Message) { - xxx_messageInfo_VoteExtensionToSign.Merge(m, src) -} -func (m *VoteExtensionToSign) XXX_Size() int { - return m.Size() -} -func (m *VoteExtensionToSign) XXX_DiscardUnknown() { - xxx_messageInfo_VoteExtensionToSign.DiscardUnknown(m) -} - -var xxx_messageInfo_VoteExtensionToSign proto.InternalMessageInfo - -func (m *VoteExtensionToSign) GetAppDataToSign() []byte { - if m != nil { - return m.AppDataToSign + return m.ExtensionSignature } return nil } @@ -685,7 +600,7 @@ func (m *Commit) Reset() { *m = Commit{} } func (m *Commit) String() string { return proto.CompactTextString(m) } func (*Commit) ProtoMessage() {} func (*Commit) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{8} + return fileDescriptor_d3a6e55e2345de56, []int{6} } func (m *Commit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -744,18 +659,17 @@ func (m *Commit) GetSignatures() []CommitSig { // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { - BlockIdFlag BlockIDFlag `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3,enum=tendermint.types.BlockIDFlag" json:"block_id_flag,omitempty"` - ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` - Timestamp time.Time `protobuf:"bytes,3,opt,name=timestamp,proto3,stdtime" json:"timestamp"` - Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` - VoteExtension *VoteExtensionToSign `protobuf:"bytes,5,opt,name=vote_extension,json=voteExtension,proto3" json:"vote_extension,omitempty"` + BlockIdFlag BlockIDFlag `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3,enum=tendermint.types.BlockIDFlag" json:"block_id_flag,omitempty"` + ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Timestamp time.Time `protobuf:"bytes,3,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` } func (m *CommitSig) Reset() { *m = CommitSig{} } func (m *CommitSig) String() string { return proto.CompactTextString(m) } func (*CommitSig) ProtoMessage() {} func (*CommitSig) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{9} + return fileDescriptor_d3a6e55e2345de56, []int{7} } func (m *CommitSig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -812,13 +726,6 @@ func (m *CommitSig) GetSignature() []byte { return nil } -func (m *CommitSig) GetVoteExtension() *VoteExtensionToSign { - if m != nil { - return m.VoteExtension - } - return nil -} - type Proposal struct { Type SignedMsgType `protobuf:"varint,1,opt,name=type,proto3,enum=tendermint.types.SignedMsgType" json:"type,omitempty"` Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` @@ -833,7 +740,7 @@ func (m *Proposal) Reset() { *m = Proposal{} } func (m *Proposal) String() string { return proto.CompactTextString(m) } func (*Proposal) ProtoMessage() {} func (*Proposal) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{10} + return fileDescriptor_d3a6e55e2345de56, []int{8} } func (m *Proposal) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -920,7 +827,7 @@ func (m *SignedHeader) Reset() { *m = SignedHeader{} } func (m *SignedHeader) String() string { return proto.CompactTextString(m) } func (*SignedHeader) ProtoMessage() {} func (*SignedHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{11} + return fileDescriptor_d3a6e55e2345de56, []int{9} } func (m *SignedHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -972,7 +879,7 @@ func (m *LightBlock) Reset() { *m = LightBlock{} } func (m *LightBlock) String() string { return proto.CompactTextString(m) } func (*LightBlock) ProtoMessage() {} func (*LightBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{12} + return fileDescriptor_d3a6e55e2345de56, []int{10} } func (m *LightBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1026,7 +933,7 @@ func (m *BlockMeta) Reset() { *m = BlockMeta{} } func (m *BlockMeta) String() string { return proto.CompactTextString(m) } func (*BlockMeta) ProtoMessage() {} func (*BlockMeta) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{13} + return fileDescriptor_d3a6e55e2345de56, []int{11} } func (m *BlockMeta) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1095,7 +1002,7 @@ func (m *TxProof) Reset() { *m = TxProof{} } func (m *TxProof) String() string { return proto.CompactTextString(m) } func (*TxProof) ProtoMessage() {} func (*TxProof) Descriptor() ([]byte, []int) { - return fileDescriptor_d3a6e55e2345de56, []int{14} + return fileDescriptor_d3a6e55e2345de56, []int{12} } func (m *TxProof) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1154,8 +1061,6 @@ func init() { proto.RegisterType((*Header)(nil), "tendermint.types.Header") proto.RegisterType((*Data)(nil), "tendermint.types.Data") proto.RegisterType((*Vote)(nil), "tendermint.types.Vote") - proto.RegisterType((*VoteExtension)(nil), "tendermint.types.VoteExtension") - proto.RegisterType((*VoteExtensionToSign)(nil), "tendermint.types.VoteExtensionToSign") proto.RegisterType((*Commit)(nil), "tendermint.types.Commit") proto.RegisterType((*CommitSig)(nil), "tendermint.types.CommitSig") proto.RegisterType((*Proposal)(nil), "tendermint.types.Proposal") @@ -1168,96 +1073,91 @@ func init() { func init() { proto.RegisterFile("tendermint/types/types.proto", fileDescriptor_d3a6e55e2345de56) } var fileDescriptor_d3a6e55e2345de56 = []byte{ - // 1423 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4f, 0x6f, 0xdb, 0x64, - 0x18, 0xaf, 0x13, 0xb7, 0x49, 0x9e, 0xc4, 0x6d, 0xfa, 0xd2, 0x6d, 0x69, 0xb6, 0xa6, 0x91, 0xd1, - 0x58, 0x37, 0x50, 0x3a, 0x3a, 0xc4, 0x9f, 0x03, 0xa0, 0x24, 0xcd, 0xb6, 0x68, 0x6d, 0x1a, 0x9c, - 0x6c, 0x08, 0x2e, 0x96, 0x93, 0xbc, 0x4d, 0xcc, 0x1c, 0xdb, 0xb2, 0xdf, 0x94, 0x76, 0x9f, 0x00, - 0xf5, 0xb4, 0x13, 0xb7, 0x9e, 0xe0, 0x80, 0xc4, 0x05, 0x89, 0x2f, 0x80, 0x38, 0xed, 0xb8, 0x1b, - 0x9c, 0x06, 0xea, 0x24, 0x3e, 0x07, 0x7a, 0xff, 0xc4, 0xb1, 0x9b, 0x86, 0x4d, 0xd5, 0xc4, 0x25, - 0xf2, 0xfb, 0x3c, 0xbf, 0xe7, 0xff, 0xcf, 0x8f, 0xdf, 0xc0, 0x35, 0x82, 0xed, 0x1e, 0xf6, 0x86, - 0xa6, 0x4d, 0x36, 0xc9, 0x91, 0x8b, 0x7d, 0xfe, 0x5b, 0x72, 0x3d, 0x87, 0x38, 0x28, 0x3b, 0xd1, - 0x96, 0x98, 0x3c, 0xbf, 0xd2, 0x77, 0xfa, 0x0e, 0x53, 0x6e, 0xd2, 0x27, 0x8e, 0xcb, 0xaf, 0xf7, - 0x1d, 0xa7, 0x6f, 0xe1, 0x4d, 0x76, 0xea, 0x8c, 0xf6, 0x37, 0x89, 0x39, 0xc4, 0x3e, 0x31, 0x86, - 0xae, 0x00, 0xac, 0x85, 0xc2, 0x74, 0xbd, 0x23, 0x97, 0x38, 0x14, 0xeb, 0xec, 0x0b, 0x75, 0x21, - 0xa4, 0x3e, 0xc0, 0x9e, 0x6f, 0x3a, 0x76, 0x38, 0x8f, 0x7c, 0x71, 0x2a, 0xcb, 0x03, 0xc3, 0x32, - 0x7b, 0x06, 0x71, 0x3c, 0x8e, 0x50, 0x3f, 0x01, 0xa5, 0x69, 0x78, 0xa4, 0x85, 0xc9, 0x7d, 0x6c, - 0xf4, 0xb0, 0x87, 0x56, 0x60, 0x9e, 0x38, 0xc4, 0xb0, 0x72, 0x52, 0x51, 0xda, 0x50, 0x34, 0x7e, - 0x40, 0x08, 0xe4, 0x81, 0xe1, 0x0f, 0x72, 0xb1, 0xa2, 0xb4, 0x91, 0xd1, 0xd8, 0xb3, 0x3a, 0x00, - 0x99, 0x9a, 0x52, 0x0b, 0xd3, 0xee, 0xe1, 0xc3, 0xb1, 0x05, 0x3b, 0x50, 0x69, 0xe7, 0x88, 0x60, - 0x5f, 0x98, 0xf0, 0x03, 0xfa, 0x00, 0xe6, 0x59, 0xfe, 0xb9, 0x78, 0x51, 0xda, 0x48, 0x6f, 0xe5, - 0x4a, 0xa1, 0x46, 0xf1, 0xfa, 0x4a, 0x4d, 0xaa, 0xaf, 0xc8, 0xcf, 0x5e, 0xac, 0xcf, 0x69, 0x1c, - 0xac, 0x5a, 0x90, 0xa8, 0x58, 0x4e, 0xf7, 0x71, 0x7d, 0x3b, 0x48, 0x44, 0x9a, 0x24, 0x82, 0x76, - 0x61, 0xc9, 0x35, 0x3c, 0xa2, 0xfb, 0x98, 0xe8, 0x03, 0x56, 0x05, 0x0b, 0x9a, 0xde, 0x5a, 0x2f, - 0x9d, 0x9d, 0x43, 0x29, 0x52, 0xac, 0x88, 0xa2, 0xb8, 0x61, 0xa1, 0xfa, 0x8f, 0x0c, 0x0b, 0xa2, - 0x19, 0x9f, 0x42, 0x42, 0xb4, 0x95, 0x05, 0x4c, 0x6f, 0xad, 0x85, 0x3d, 0x0a, 0x55, 0xa9, 0xea, - 0xd8, 0x3e, 0xb6, 0xfd, 0x91, 0x2f, 0xfc, 0x8d, 0x6d, 0xd0, 0x3b, 0x90, 0xec, 0x0e, 0x0c, 0xd3, - 0xd6, 0xcd, 0x1e, 0xcb, 0x28, 0x55, 0x49, 0x9f, 0xbe, 0x58, 0x4f, 0x54, 0xa9, 0xac, 0xbe, 0xad, - 0x25, 0x98, 0xb2, 0xde, 0x43, 0x97, 0x61, 0x61, 0x80, 0xcd, 0xfe, 0x80, 0xb0, 0xb6, 0xc4, 0x35, - 0x71, 0x42, 0x1f, 0x83, 0x4c, 0x09, 0x91, 0x93, 0x59, 0xec, 0x7c, 0x89, 0xb3, 0xa5, 0x34, 0x66, - 0x4b, 0xa9, 0x3d, 0x66, 0x4b, 0x25, 0x49, 0x03, 0x3f, 0xfd, 0x6b, 0x5d, 0xd2, 0x98, 0x05, 0xaa, - 0x82, 0x62, 0x19, 0x3e, 0xd1, 0x3b, 0xb4, 0x6d, 0x34, 0xfc, 0x3c, 0x73, 0xb1, 0x3a, 0xdd, 0x10, - 0xd1, 0x58, 0x91, 0x7a, 0x9a, 0x5a, 0x71, 0x51, 0x0f, 0x6d, 0x40, 0x96, 0x39, 0xe9, 0x3a, 0xc3, - 0xa1, 0x49, 0x74, 0xd6, 0xf7, 0x05, 0xd6, 0xf7, 0x45, 0x2a, 0xaf, 0x32, 0xf1, 0x7d, 0x3a, 0x81, - 0xab, 0x90, 0xea, 0x19, 0xc4, 0xe0, 0x90, 0x04, 0x83, 0x24, 0xa9, 0x80, 0x29, 0x6f, 0xc0, 0x52, - 0xc0, 0x3a, 0x9f, 0x43, 0x92, 0xdc, 0xcb, 0x44, 0xcc, 0x80, 0xb7, 0x61, 0xc5, 0xc6, 0x87, 0x44, - 0x3f, 0x8b, 0x4e, 0x31, 0x34, 0xa2, 0xba, 0x47, 0x51, 0x8b, 0xeb, 0xb0, 0xd8, 0x1d, 0x37, 0x9f, - 0x63, 0x81, 0x61, 0x95, 0x40, 0xca, 0x60, 0xab, 0x90, 0x34, 0x5c, 0x97, 0x03, 0xd2, 0x0c, 0x90, - 0x30, 0x5c, 0x97, 0xa9, 0x6e, 0xc1, 0x32, 0xab, 0xd1, 0xc3, 0xfe, 0xc8, 0x22, 0xc2, 0x49, 0x86, - 0x61, 0x96, 0xa8, 0x42, 0xe3, 0x72, 0x86, 0x7d, 0x1b, 0x14, 0x7c, 0x60, 0xf6, 0xb0, 0xdd, 0xc5, - 0x1c, 0xa7, 0x30, 0x5c, 0x66, 0x2c, 0x64, 0xa0, 0x9b, 0x90, 0x75, 0x3d, 0xc7, 0x75, 0x7c, 0xec, - 0xe9, 0x46, 0xaf, 0xe7, 0x61, 0xdf, 0xcf, 0x2d, 0x72, 0x7f, 0x63, 0x79, 0x99, 0x8b, 0xd5, 0x1c, - 0xc8, 0xdb, 0x06, 0x31, 0x50, 0x16, 0xe2, 0xe4, 0xd0, 0xcf, 0x49, 0xc5, 0xf8, 0x46, 0x46, 0xa3, - 0x8f, 0xea, 0x2f, 0x71, 0x90, 0x1f, 0x39, 0x04, 0xa3, 0x3b, 0x20, 0xd3, 0x31, 0x31, 0xf6, 0x2d, - 0x9e, 0xc7, 0xe7, 0x96, 0xd9, 0xb7, 0x71, 0x6f, 0xd7, 0xef, 0xb7, 0x8f, 0x5c, 0xac, 0x31, 0x70, - 0x88, 0x4e, 0xb1, 0x08, 0x9d, 0x56, 0x60, 0xde, 0x73, 0x46, 0x76, 0x8f, 0xb1, 0x6c, 0x5e, 0xe3, - 0x07, 0x54, 0x83, 0x64, 0xc0, 0x12, 0xf9, 0x55, 0x2c, 0x59, 0xa2, 0x2c, 0xa1, 0x1c, 0x16, 0x02, - 0x2d, 0xd1, 0x11, 0x64, 0xa9, 0x40, 0x2a, 0x58, 0x5e, 0x82, 0x6d, 0xaf, 0x47, 0xd8, 0x89, 0x19, - 0x7a, 0x17, 0x96, 0x83, 0xd9, 0x07, 0xcd, 0xe3, 0x8c, 0xcb, 0x06, 0x0a, 0xd1, 0xbd, 0x08, 0xad, - 0x74, 0xbe, 0x80, 0x12, 0xac, 0xae, 0x09, 0xad, 0xea, 0x6c, 0x13, 0x5d, 0x83, 0x94, 0x6f, 0xf6, - 0x6d, 0x83, 0x8c, 0x3c, 0x2c, 0x98, 0x37, 0x11, 0xa0, 0xbb, 0xb0, 0x78, 0xe0, 0x10, 0xac, 0xe3, - 0x43, 0x82, 0x6d, 0xf6, 0xa6, 0xa7, 0x66, 0xed, 0x0e, 0x3a, 0x91, 0xda, 0x18, 0xa6, 0x29, 0x07, - 0xe1, 0xa3, 0x7a, 0x04, 0x4a, 0x44, 0x8f, 0x6e, 0x40, 0x96, 0x92, 0x8e, 0xbd, 0x17, 0xc4, 0xd1, - 0x69, 0x44, 0xb1, 0xb5, 0x14, 0xc3, 0x75, 0xe9, 0xe0, 0xdb, 0x0e, 0x9d, 0x1e, 0xfa, 0x1c, 0xae, - 0x05, 0x40, 0x1f, 0x5b, 0xfb, 0xba, 0x31, 0x22, 0x03, 0x6c, 0x13, 0xb3, 0x6b, 0x10, 0xd3, 0xee, - 0x8b, 0x05, 0xba, 0x2a, 0x8c, 0x5a, 0xd8, 0xda, 0x2f, 0x47, 0x00, 0xea, 0x67, 0xf0, 0x56, 0x24, - 0xb4, 0xf0, 0xfb, 0xba, 0x09, 0xa8, 0xbf, 0x49, 0xb0, 0xc0, 0x5f, 0xe6, 0x10, 0x75, 0xa4, 0xf3, - 0xa9, 0x13, 0x9b, 0x45, 0x9d, 0xf8, 0xc5, 0xa9, 0x53, 0x06, 0x08, 0xe6, 0xe1, 0xe7, 0xe4, 0x62, - 0x7c, 0x23, 0xbd, 0x75, 0x75, 0xda, 0x11, 0x4f, 0xb1, 0x65, 0xf6, 0xc5, 0xae, 0x0a, 0x19, 0xa9, - 0x3f, 0xc7, 0x20, 0x15, 0xe8, 0x51, 0x19, 0x94, 0x71, 0x5e, 0xfa, 0xbe, 0x65, 0xf4, 0xc5, 0xeb, - 0xb3, 0x36, 0x33, 0xb9, 0xbb, 0x96, 0xd1, 0xd7, 0xd2, 0x22, 0x1f, 0x7a, 0x38, 0x9f, 0x8a, 0xb1, - 0x19, 0x54, 0x8c, 0x70, 0x3f, 0x7e, 0x31, 0xee, 0x47, 0x58, 0x2a, 0x9f, 0x65, 0xe9, 0xce, 0x14, - 0x4b, 0xf9, 0x2b, 0x76, 0xfd, 0x15, 0x2c, 0xe5, 0x13, 0x3e, 0xcb, 0xd5, 0x5f, 0x63, 0x90, 0x6c, - 0xb2, 0x65, 0x64, 0x58, 0xff, 0xc7, 0x8a, 0xb9, 0x0a, 0x29, 0xd7, 0xb1, 0x74, 0xae, 0x91, 0x99, - 0x26, 0xe9, 0x3a, 0x96, 0x36, 0x45, 0xa2, 0xf9, 0x37, 0xb4, 0x7f, 0x16, 0xde, 0xc0, 0x0c, 0x12, - 0x67, 0x66, 0xa0, 0x7a, 0x90, 0xe1, 0xad, 0x10, 0x97, 0x83, 0xdb, 0xb4, 0x07, 0xec, 0xb6, 0x21, - 0x4d, 0x5f, 0x66, 0x78, 0xda, 0x1c, 0xa9, 0x09, 0x1c, 0xb5, 0xe0, 0xdf, 0x52, 0x71, 0x3f, 0xc9, - 0xcd, 0x22, 0xb9, 0x26, 0x70, 0xea, 0xf7, 0x12, 0xc0, 0x0e, 0xed, 0x2c, 0xab, 0x97, 0x7e, 0xd6, - 0x7d, 0x96, 0x82, 0x1e, 0x89, 0x5c, 0x98, 0x35, 0x34, 0x11, 0x3f, 0xe3, 0x87, 0xf3, 0xae, 0x82, - 0x32, 0xa1, 0xb6, 0x8f, 0xc7, 0xc9, 0x9c, 0xe3, 0x24, 0xf8, 0xda, 0xb6, 0x30, 0xd1, 0x32, 0x07, - 0xa1, 0x93, 0xfa, 0xbb, 0x04, 0x29, 0x96, 0xd3, 0x2e, 0x26, 0x46, 0x64, 0x86, 0xd2, 0xc5, 0x67, - 0xb8, 0x06, 0xc0, 0xdd, 0xf8, 0xe6, 0x13, 0x2c, 0x98, 0x95, 0x62, 0x92, 0x96, 0xf9, 0x04, 0xa3, - 0x0f, 0x83, 0x86, 0xc7, 0xff, 0xbb, 0xe1, 0x62, 0x41, 0x8c, 0xdb, 0x7e, 0x05, 0x12, 0xf6, 0x68, - 0xa8, 0xd3, 0x6f, 0xac, 0xcc, 0xd9, 0x6a, 0x8f, 0x86, 0xed, 0x43, 0x5f, 0xfd, 0x06, 0x12, 0xed, - 0x43, 0x76, 0xdf, 0xa4, 0x14, 0xf5, 0x1c, 0x47, 0x5c, 0x72, 0xf8, 0x96, 0x4c, 0x52, 0x01, 0xfb, - 0xa6, 0x23, 0x90, 0xe9, 0x16, 0x1d, 0xdf, 0x7e, 0xe9, 0x33, 0x2a, 0xbd, 0xe6, 0x4d, 0x56, 0xdc, - 0x61, 0x6f, 0xfd, 0x21, 0x41, 0x3a, 0xb4, 0x6d, 0xd0, 0xfb, 0x70, 0xa9, 0xb2, 0xb3, 0x57, 0x7d, - 0xa0, 0xd7, 0xb7, 0xf5, 0xbb, 0x3b, 0xe5, 0x7b, 0xfa, 0xc3, 0xc6, 0x83, 0xc6, 0xde, 0x97, 0x8d, - 0xec, 0x5c, 0xfe, 0xf2, 0xf1, 0x49, 0x11, 0x85, 0xb0, 0x0f, 0xed, 0xc7, 0xb6, 0xf3, 0xad, 0x8d, - 0x36, 0x61, 0x25, 0x6a, 0x52, 0xae, 0xb4, 0x6a, 0x8d, 0x76, 0x56, 0xca, 0x5f, 0x3a, 0x3e, 0x29, - 0x2e, 0x87, 0x2c, 0xca, 0x1d, 0x1f, 0xdb, 0x64, 0xda, 0xa0, 0xba, 0xb7, 0xbb, 0x5b, 0x6f, 0x67, - 0x63, 0x53, 0x06, 0x62, 0xfd, 0xdf, 0x84, 0xe5, 0xa8, 0x41, 0xa3, 0xbe, 0x93, 0x8d, 0xe7, 0xd1, - 0xf1, 0x49, 0x71, 0x31, 0x84, 0x6e, 0x98, 0x56, 0x3e, 0xf9, 0xdd, 0x0f, 0x85, 0xb9, 0x9f, 0x7e, - 0x2c, 0x48, 0xb4, 0x32, 0x25, 0xb2, 0x23, 0xd0, 0x7b, 0x70, 0xa5, 0x55, 0xbf, 0xd7, 0xa8, 0x6d, - 0xeb, 0xbb, 0xad, 0x7b, 0x7a, 0xfb, 0xab, 0x66, 0x2d, 0x54, 0xdd, 0xd2, 0xf1, 0x49, 0x31, 0x2d, - 0x4a, 0x9a, 0x85, 0x6e, 0x6a, 0xb5, 0x47, 0x7b, 0xed, 0x5a, 0x56, 0xe2, 0xe8, 0xa6, 0x87, 0xe9, - 0x02, 0x63, 0xe8, 0xdb, 0xb0, 0x7a, 0x0e, 0x3a, 0x28, 0x6c, 0xf9, 0xf8, 0xa4, 0xa8, 0x34, 0x3d, - 0xcc, 0xdf, 0x1f, 0x66, 0x51, 0x82, 0xdc, 0xb4, 0xc5, 0x5e, 0x73, 0xaf, 0x55, 0xde, 0xc9, 0x16, - 0xf3, 0xd9, 0xe3, 0x93, 0x62, 0x66, 0xbc, 0x0c, 0x29, 0x7e, 0x52, 0x59, 0xe5, 0x8b, 0x67, 0xa7, - 0x05, 0xe9, 0xf9, 0x69, 0x41, 0xfa, 0xfb, 0xb4, 0x20, 0x3d, 0x7d, 0x59, 0x98, 0x7b, 0xfe, 0xb2, - 0x30, 0xf7, 0xe7, 0xcb, 0xc2, 0xdc, 0xd7, 0x1f, 0xf5, 0x4d, 0x32, 0x18, 0x75, 0x4a, 0x5d, 0x67, - 0xb8, 0x19, 0xfe, 0x8f, 0x35, 0x79, 0xe4, 0xff, 0xf5, 0xce, 0xfe, 0xff, 0xea, 0x2c, 0x30, 0xf9, - 0x9d, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x38, 0xfc, 0x14, 0xb1, 0x40, 0x0e, 0x00, 0x00, + // 1341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcf, 0x73, 0xdb, 0xc4, + 0x17, 0x8f, 0x62, 0x25, 0xb6, 0x9f, 0xed, 0xc4, 0xd9, 0x6f, 0xda, 0xba, 0x6e, 0xe3, 0x68, 0xfc, + 0x1d, 0x20, 0x2d, 0x8c, 0x52, 0x52, 0x86, 0x1f, 0x07, 0x0e, 0xb6, 0x93, 0xb6, 0x9e, 0x26, 0x8e, + 0x91, 0xdd, 0x32, 0x70, 0xd1, 0xc8, 0xd6, 0xd6, 0x16, 0x95, 0x25, 0x8d, 0x76, 0x1d, 0x92, 0xfe, + 0x05, 0x4c, 0x4e, 0x3d, 0x71, 0xcb, 0x09, 0x0e, 0xdc, 0x39, 0x70, 0x65, 0x38, 0xf5, 0xd8, 0x1b, + 0x5c, 0x28, 0x4c, 0x3a, 0xc3, 0xdf, 0xc1, 0xec, 0x0f, 0xc9, 0x72, 0x9c, 0x40, 0xa7, 0xd3, 0xe1, + 0xe2, 0xd1, 0xbe, 0xf7, 0x79, 0x6f, 0xdf, 0x8f, 0xcf, 0xee, 0x5b, 0xc3, 0x75, 0x8a, 0x3d, 0x1b, + 0x87, 0x23, 0xc7, 0xa3, 0x9b, 0xf4, 0x28, 0xc0, 0x44, 0xfc, 0xea, 0x41, 0xe8, 0x53, 0x1f, 0x15, + 0x27, 0x5a, 0x9d, 0xcb, 0xcb, 0xab, 0x03, 0x7f, 0xe0, 0x73, 0xe5, 0x26, 0xfb, 0x12, 0xb8, 0xf2, + 0xfa, 0xc0, 0xf7, 0x07, 0x2e, 0xde, 0xe4, 0xab, 0xde, 0xf8, 0xd1, 0x26, 0x75, 0x46, 0x98, 0x50, + 0x6b, 0x14, 0x48, 0xc0, 0x5a, 0x62, 0x9b, 0x7e, 0x78, 0x14, 0x50, 0x9f, 0x61, 0xfd, 0x47, 0x52, + 0x5d, 0x49, 0xa8, 0x0f, 0x70, 0x48, 0x1c, 0xdf, 0x4b, 0xc6, 0x51, 0xd6, 0x66, 0xa2, 0x3c, 0xb0, + 0x5c, 0xc7, 0xb6, 0xa8, 0x1f, 0x0a, 0x44, 0xf5, 0x13, 0x28, 0xb4, 0xad, 0x90, 0x76, 0x30, 0xbd, + 0x87, 0x2d, 0x1b, 0x87, 0x68, 0x15, 0x16, 0xa8, 0x4f, 0x2d, 0xb7, 0xa4, 0x68, 0xca, 0x46, 0xc1, + 0x10, 0x0b, 0x84, 0x40, 0x1d, 0x5a, 0x64, 0x58, 0x9a, 0xd7, 0x94, 0x8d, 0xbc, 0xc1, 0xbf, 0xab, + 0x43, 0x50, 0x99, 0x29, 0xb3, 0x70, 0x3c, 0x1b, 0x1f, 0x46, 0x16, 0x7c, 0xc1, 0xa4, 0xbd, 0x23, + 0x8a, 0x89, 0x34, 0x11, 0x0b, 0xf4, 0x01, 0x2c, 0xf0, 0xf8, 0x4b, 0x29, 0x4d, 0xd9, 0xc8, 0x6d, + 0x95, 0xf4, 0x44, 0xa1, 0x44, 0x7e, 0x7a, 0x9b, 0xe9, 0xeb, 0xea, 0xb3, 0x17, 0xeb, 0x73, 0x86, + 0x00, 0x57, 0x5d, 0x48, 0xd7, 0x5d, 0xbf, 0xff, 0xb8, 0xb9, 0x1d, 0x07, 0xa2, 0x4c, 0x02, 0x41, + 0x7b, 0xb0, 0x1c, 0x58, 0x21, 0x35, 0x09, 0xa6, 0xe6, 0x90, 0x67, 0xc1, 0x37, 0xcd, 0x6d, 0xad, + 0xeb, 0x67, 0xfb, 0xa0, 0x4f, 0x25, 0x2b, 0x77, 0x29, 0x04, 0x49, 0x61, 0xf5, 0x2f, 0x15, 0x16, + 0x65, 0x31, 0x3e, 0x85, 0xb4, 0x2c, 0x2b, 0xdf, 0x30, 0xb7, 0xb5, 0x96, 0xf4, 0x28, 0x55, 0x7a, + 0xc3, 0xf7, 0x08, 0xf6, 0xc8, 0x98, 0x48, 0x7f, 0x91, 0x0d, 0x7a, 0x1b, 0x32, 0xfd, 0xa1, 0xe5, + 0x78, 0xa6, 0x63, 0xf3, 0x88, 0xb2, 0xf5, 0xdc, 0xe9, 0x8b, 0xf5, 0x74, 0x83, 0xc9, 0x9a, 0xdb, + 0x46, 0x9a, 0x2b, 0x9b, 0x36, 0xba, 0x0c, 0x8b, 0x43, 0xec, 0x0c, 0x86, 0x94, 0x97, 0x25, 0x65, + 0xc8, 0x15, 0xfa, 0x18, 0x54, 0x46, 0x88, 0x92, 0xca, 0xf7, 0x2e, 0xeb, 0x82, 0x2d, 0x7a, 0xc4, + 0x16, 0xbd, 0x1b, 0xb1, 0xa5, 0x9e, 0x61, 0x1b, 0x3f, 0xfd, 0x63, 0x5d, 0x31, 0xb8, 0x05, 0x6a, + 0x40, 0xc1, 0xb5, 0x08, 0x35, 0x7b, 0xac, 0x6c, 0x6c, 0xfb, 0x05, 0xee, 0xe2, 0xea, 0x6c, 0x41, + 0x64, 0x61, 0x65, 0xe8, 0x39, 0x66, 0x25, 0x44, 0x36, 0xda, 0x80, 0x22, 0x77, 0xd2, 0xf7, 0x47, + 0x23, 0x87, 0x9a, 0xbc, 0xee, 0x8b, 0xbc, 0xee, 0x4b, 0x4c, 0xde, 0xe0, 0xe2, 0x7b, 0xac, 0x03, + 0xd7, 0x20, 0x6b, 0x5b, 0xd4, 0x12, 0x90, 0x34, 0x87, 0x64, 0x98, 0x80, 0x2b, 0xdf, 0x81, 0xe5, + 0x98, 0x75, 0x44, 0x40, 0x32, 0xc2, 0xcb, 0x44, 0xcc, 0x81, 0xb7, 0x60, 0xd5, 0xc3, 0x87, 0xd4, + 0x3c, 0x8b, 0xce, 0x72, 0x34, 0x62, 0xba, 0x87, 0xd3, 0x16, 0x6f, 0xc1, 0x52, 0x3f, 0x2a, 0xbe, + 0xc0, 0x02, 0xc7, 0x16, 0x62, 0x29, 0x87, 0x5d, 0x85, 0x8c, 0x15, 0x04, 0x02, 0x90, 0xe3, 0x80, + 0xb4, 0x15, 0x04, 0x5c, 0x75, 0x13, 0x56, 0x78, 0x8e, 0x21, 0x26, 0x63, 0x97, 0x4a, 0x27, 0x79, + 0x8e, 0x59, 0x66, 0x0a, 0x43, 0xc8, 0x39, 0xf6, 0xff, 0x50, 0xc0, 0x07, 0x8e, 0x8d, 0xbd, 0x3e, + 0x16, 0xb8, 0x02, 0xc7, 0xe5, 0x23, 0x21, 0x07, 0xdd, 0x80, 0x62, 0x10, 0xfa, 0x81, 0x4f, 0x70, + 0x68, 0x5a, 0xb6, 0x1d, 0x62, 0x42, 0x4a, 0x4b, 0xc2, 0x5f, 0x24, 0xaf, 0x09, 0x71, 0xb5, 0x04, + 0xea, 0xb6, 0x45, 0x2d, 0x54, 0x84, 0x14, 0x3d, 0x24, 0x25, 0x45, 0x4b, 0x6d, 0xe4, 0x0d, 0xf6, + 0x59, 0xfd, 0x29, 0x05, 0xea, 0x43, 0x9f, 0x62, 0x74, 0x1b, 0x54, 0xd6, 0x26, 0xce, 0xbe, 0xa5, + 0xf3, 0xf8, 0xdc, 0x71, 0x06, 0x1e, 0xb6, 0xf7, 0xc8, 0xa0, 0x7b, 0x14, 0x60, 0x83, 0x83, 0x13, + 0x74, 0x9a, 0x9f, 0xa2, 0xd3, 0x2a, 0x2c, 0x84, 0xfe, 0xd8, 0xb3, 0x39, 0xcb, 0x16, 0x0c, 0xb1, + 0x40, 0x3b, 0x90, 0x89, 0x59, 0xa2, 0xfe, 0x1b, 0x4b, 0x96, 0x19, 0x4b, 0x18, 0x87, 0xa5, 0xc0, + 0x48, 0xf7, 0x24, 0x59, 0xea, 0x90, 0x8d, 0x2f, 0x2f, 0xc9, 0xb6, 0x57, 0x23, 0xec, 0xc4, 0x0c, + 0xbd, 0x0b, 0x2b, 0x71, 0xef, 0xe3, 0xe2, 0x09, 0xc6, 0x15, 0x63, 0x85, 0xac, 0xde, 0x14, 0xad, + 0x4c, 0x71, 0x01, 0xa5, 0x79, 0x5e, 0x13, 0x5a, 0x35, 0xf9, 0x4d, 0x74, 0x1d, 0xb2, 0xc4, 0x19, + 0x78, 0x16, 0x1d, 0x87, 0x58, 0x32, 0x6f, 0x22, 0x60, 0x5a, 0x7c, 0x48, 0xb1, 0xc7, 0x0f, 0xb9, + 0x60, 0xda, 0x44, 0x80, 0x36, 0xe1, 0x7f, 0xf1, 0xc2, 0x9c, 0x78, 0x11, 0x2c, 0x43, 0xb1, 0xaa, + 0x13, 0x69, 0xaa, 0x3f, 0x2b, 0xb0, 0x28, 0x0e, 0x46, 0xa2, 0x0d, 0xca, 0xf9, 0x6d, 0x98, 0xbf, + 0xa8, 0x0d, 0xa9, 0xd7, 0x6f, 0x43, 0x0d, 0x20, 0x0e, 0x93, 0x94, 0x54, 0x2d, 0xb5, 0x91, 0xdb, + 0xba, 0x36, 0xeb, 0x48, 0x84, 0xd8, 0x71, 0x06, 0xf2, 0xdc, 0x27, 0x8c, 0xaa, 0xbf, 0x2b, 0x90, + 0x8d, 0xf5, 0xa8, 0x06, 0x85, 0x28, 0x2e, 0xf3, 0x91, 0x6b, 0x0d, 0x24, 0x15, 0xd7, 0x2e, 0x0c, + 0xee, 0x8e, 0x6b, 0x0d, 0x8c, 0x9c, 0x8c, 0x87, 0x2d, 0xce, 0x6f, 0xeb, 0xfc, 0x05, 0x6d, 0x9d, + 0xe2, 0x51, 0xea, 0xf5, 0x78, 0x34, 0xd5, 0x71, 0xf5, 0x4c, 0xc7, 0xab, 0x3f, 0xce, 0x43, 0xa6, + 0xcd, 0x8f, 0xa2, 0xe5, 0xfe, 0x17, 0x07, 0xec, 0x1a, 0x64, 0x03, 0xdf, 0x35, 0x85, 0x46, 0xe5, + 0x9a, 0x4c, 0xe0, 0xbb, 0xc6, 0x4c, 0xdb, 0x17, 0xde, 0xd0, 0xe9, 0x5b, 0x7c, 0x03, 0x55, 0x4b, + 0x9f, 0xad, 0x5a, 0x08, 0x79, 0x51, 0x0a, 0x39, 0x1a, 0x6f, 0xb1, 0x1a, 0xf0, 0x59, 0xab, 0xcc, + 0x8e, 0x72, 0x11, 0xb6, 0x40, 0x1a, 0x12, 0xc7, 0x2c, 0xc4, 0x24, 0x91, 0xd3, 0xb9, 0x74, 0x11, + 0x2d, 0x0d, 0x89, 0xab, 0x7e, 0xab, 0x00, 0xec, 0xb2, 0xca, 0xf2, 0x7c, 0xd9, 0x50, 0x23, 0x3c, + 0x04, 0x73, 0x6a, 0xe7, 0xca, 0x45, 0x4d, 0x93, 0xfb, 0xe7, 0x49, 0x32, 0xee, 0x06, 0x14, 0x26, + 0x64, 0x24, 0x38, 0x0a, 0xe6, 0x1c, 0x27, 0xf1, 0xac, 0xe9, 0x60, 0x6a, 0xe4, 0x0f, 0x12, 0xab, + 0xea, 0x2f, 0x0a, 0x64, 0x79, 0x4c, 0x7b, 0x98, 0x5a, 0x53, 0x3d, 0x54, 0x5e, 0xbf, 0x87, 0x6b, + 0x00, 0xc2, 0x0d, 0x71, 0x9e, 0x60, 0xc9, 0xac, 0x2c, 0x97, 0x74, 0x9c, 0x27, 0x18, 0x7d, 0x18, + 0x17, 0x3c, 0xf5, 0xcf, 0x05, 0x97, 0x47, 0x3a, 0x2a, 0xfb, 0x15, 0x48, 0x7b, 0xe3, 0x91, 0xc9, + 0x26, 0x8c, 0x2a, 0xd8, 0xea, 0x8d, 0x47, 0xdd, 0x43, 0x52, 0xfd, 0x0a, 0xd2, 0xdd, 0x43, 0xfe, + 0xda, 0x62, 0x14, 0x0d, 0x7d, 0x5f, 0x8e, 0x78, 0xf1, 0xb4, 0xca, 0x30, 0x01, 0x9f, 0x68, 0x08, + 0x54, 0x36, 0xcb, 0xa3, 0xb7, 0x1f, 0xfb, 0x46, 0xfa, 0x2b, 0xbe, 0xe3, 0xe4, 0x0b, 0xee, 0xe6, + 0xaf, 0x0a, 0xe4, 0x12, 0xf7, 0x03, 0x7a, 0x1f, 0x2e, 0xd5, 0x77, 0xf7, 0x1b, 0xf7, 0xcd, 0xe6, + 0xb6, 0x79, 0x67, 0xb7, 0x76, 0xd7, 0x7c, 0xd0, 0xba, 0xdf, 0xda, 0xff, 0xbc, 0x55, 0x9c, 0x2b, + 0x5f, 0x3e, 0x3e, 0xd1, 0x50, 0x02, 0xfb, 0xc0, 0x7b, 0xec, 0xf9, 0x5f, 0xb3, 0xab, 0x78, 0x75, + 0xda, 0xa4, 0x56, 0xef, 0xec, 0xb4, 0xba, 0x45, 0xa5, 0x7c, 0xe9, 0xf8, 0x44, 0x5b, 0x49, 0x58, + 0xd4, 0x7a, 0x04, 0x7b, 0x74, 0xd6, 0xa0, 0xb1, 0xbf, 0xb7, 0xd7, 0xec, 0x16, 0xe7, 0x67, 0x0c, + 0xe4, 0x85, 0x7d, 0x03, 0x56, 0xa6, 0x0d, 0x5a, 0xcd, 0xdd, 0x62, 0xaa, 0x8c, 0x8e, 0x4f, 0xb4, + 0xa5, 0x04, 0xba, 0xe5, 0xb8, 0xe5, 0xcc, 0x37, 0xdf, 0x55, 0xe6, 0x7e, 0xf8, 0xbe, 0xa2, 0xb0, + 0xcc, 0x0a, 0x53, 0x77, 0x04, 0x7a, 0x0f, 0xae, 0x74, 0x9a, 0x77, 0x5b, 0x3b, 0xdb, 0xe6, 0x5e, + 0xe7, 0xae, 0xd9, 0xfd, 0xa2, 0xbd, 0x93, 0xc8, 0x6e, 0xf9, 0xf8, 0x44, 0xcb, 0xc9, 0x94, 0x2e, + 0x42, 0xb7, 0x8d, 0x9d, 0x87, 0xfb, 0xdd, 0x9d, 0xa2, 0x22, 0xd0, 0xed, 0x10, 0x1f, 0xf8, 0x14, + 0x73, 0xf4, 0x2d, 0xb8, 0x7a, 0x0e, 0x3a, 0x4e, 0x6c, 0xe5, 0xf8, 0x44, 0x2b, 0xb4, 0x43, 0x2c, + 0xce, 0x0f, 0xb7, 0xd0, 0xa1, 0x34, 0x6b, 0xb1, 0xdf, 0xde, 0xef, 0xd4, 0x76, 0x8b, 0x5a, 0xb9, + 0x78, 0x7c, 0xa2, 0xe5, 0xa3, 0xcb, 0x90, 0xe1, 0x27, 0x99, 0xd5, 0x3f, 0x7b, 0x76, 0x5a, 0x51, + 0x9e, 0x9f, 0x56, 0x94, 0x3f, 0x4f, 0x2b, 0xca, 0xd3, 0x97, 0x95, 0xb9, 0xe7, 0x2f, 0x2b, 0x73, + 0xbf, 0xbd, 0xac, 0xcc, 0x7d, 0xf9, 0xd1, 0xc0, 0xa1, 0xc3, 0x71, 0x4f, 0xef, 0xfb, 0xa3, 0xcd, + 0xe4, 0x3f, 0x8c, 0xc9, 0xa7, 0xf8, 0xa7, 0x73, 0xf6, 0xdf, 0x47, 0x6f, 0x91, 0xcb, 0x6f, 0xff, + 0x1d, 0x00, 0x00, 0xff, 0xff, 0xbb, 0xc0, 0x81, 0x37, 0x3e, 0x0d, 0x00, 0x00, } func (m *PartSetHeader) Marshal() (dAtA []byte, err error) { @@ -1558,15 +1458,17 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } + if len(m.ExtensionSignature) > 0 { + i -= len(m.ExtensionSignature) + copy(dAtA[i:], m.ExtensionSignature) + i = encodeVarintTypes(dAtA, i, uint64(len(m.ExtensionSignature))) + i-- + dAtA[i] = 0x52 + } + if len(m.Extension) > 0 { + i -= len(m.Extension) + copy(dAtA[i:], m.Extension) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Extension))) i-- dAtA[i] = 0x4a } @@ -1589,12 +1491,12 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err7 != nil { - return 0, err7 + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err6 != nil { + return 0, err6 } - i -= n7 - i = encodeVarintTypes(dAtA, i, uint64(n7)) + i -= n6 + i = encodeVarintTypes(dAtA, i, uint64(n6)) i-- dAtA[i] = 0x2a { @@ -1625,73 +1527,6 @@ func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *VoteExtension) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *VoteExtension) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *VoteExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.AppDataSelfAuthenticating) > 0 { - i -= len(m.AppDataSelfAuthenticating) - copy(dAtA[i:], m.AppDataSelfAuthenticating) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataSelfAuthenticating))) - i-- - dAtA[i] = 0x12 - } - if len(m.AppDataToSign) > 0 { - i -= len(m.AppDataToSign) - copy(dAtA[i:], m.AppDataToSign) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataToSign))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *VoteExtensionToSign) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *VoteExtensionToSign) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *VoteExtensionToSign) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.AppDataToSign) > 0 { - i -= len(m.AppDataToSign) - copy(dAtA[i:], m.AppDataToSign) - i = encodeVarintTypes(dAtA, i, uint64(len(m.AppDataToSign))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *Commit) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1769,18 +1604,6 @@ func (m *CommitSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.VoteExtension != nil { - { - size, err := m.VoteExtension.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTypes(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a - } if len(m.Signature) > 0 { i -= len(m.Signature) copy(dAtA[i:], m.Signature) @@ -1788,12 +1611,12 @@ func (m *CommitSig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n11, err11 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err11 != nil { - return 0, err11 + n9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err9 != nil { + return 0, err9 } - i -= n11 - i = encodeVarintTypes(dAtA, i, uint64(n11)) + i -= n9 + i = encodeVarintTypes(dAtA, i, uint64(n9)) i-- dAtA[i] = 0x1a if len(m.ValidatorAddress) > 0 { @@ -1838,12 +1661,12 @@ func (m *Proposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x3a } - n12, err12 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) - if err12 != nil { - return 0, err12 + n10, err10 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Timestamp):]) + if err10 != nil { + return 0, err10 } - i -= n12 - i = encodeVarintTypes(dAtA, i, uint64(n12)) + i -= n10 + i = encodeVarintTypes(dAtA, i, uint64(n10)) i-- dAtA[i] = 0x32 { @@ -2238,37 +2061,11 @@ func (m *Vote) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *VoteExtension) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AppDataToSign) + l = len(m.Extension) if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - l = len(m.AppDataSelfAuthenticating) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) - } - return n -} - -func (m *VoteExtensionToSign) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AppDataToSign) + l = len(m.ExtensionSignature) if l > 0 { n += 1 + l + sovTypes(uint64(l)) } @@ -2317,10 +2114,6 @@ func (m *CommitSig) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - if m.VoteExtension != nil { - l = m.VoteExtension.Size() - n += 1 + l + sovTypes(uint64(l)) - } return n } @@ -3618,93 +3411,7 @@ func (m *Vote) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 9: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtension{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *VoteExtension) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: VoteExtension: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: VoteExtension: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataToSign", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3731,14 +3438,14 @@ func (m *VoteExtension) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppDataToSign = append(m.AppDataToSign[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataToSign == nil { - m.AppDataToSign = []byte{} + m.Extension = append(m.Extension[:0], dAtA[iNdEx:postIndex]...) + if m.Extension == nil { + m.Extension = []byte{} } iNdEx = postIndex - case 2: + case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataSelfAuthenticating", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ExtensionSignature", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -3765,93 +3472,9 @@ func (m *VoteExtension) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppDataSelfAuthenticating = append(m.AppDataSelfAuthenticating[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataSelfAuthenticating == nil { - m.AppDataSelfAuthenticating = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTypes(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTypes - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *VoteExtensionToSign) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: VoteExtensionToSign: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: VoteExtensionToSign: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppDataToSign", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppDataToSign = append(m.AppDataToSign[:0], dAtA[iNdEx:postIndex]...) - if m.AppDataToSign == nil { - m.AppDataToSign = []byte{} + m.ExtensionSignature = append(m.ExtensionSignature[:0], dAtA[iNdEx:postIndex]...) + if m.ExtensionSignature == nil { + m.ExtensionSignature = []byte{} } iNdEx = postIndex default: @@ -4179,42 +3802,6 @@ func (m *CommitSig) Unmarshal(dAtA []byte) error { m.Signature = []byte{} } iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VoteExtension", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VoteExtension == nil { - m.VoteExtension = &VoteExtensionToSign{} - } - if err := m.VoteExtension.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/proto/tendermint/types/types.proto b/proto/tendermint/types/types.proto index bc2c53196..e2b8a46c8 100644 --- a/proto/tendermint/types/types.proto +++ b/proto/tendermint/types/types.proto @@ -9,7 +9,7 @@ import "tendermint/crypto/proof.proto"; import "tendermint/version/types.proto"; import "tendermint/types/validator.proto"; -// BlockIdFlag indicates which BlcokID the signature is for +// BlockIdFlag indicates which BlockID the signature is for enum BlockIDFlag { option (gogoproto.goproto_enum_stringer) = true; option (gogoproto.goproto_enum_prefix) = false; @@ -112,7 +112,15 @@ message Vote { [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; bytes validator_address = 6; int32 validator_index = 7; - bytes signature = 8; + // Vote signature by the validator if they participated in consensus for the + // associated block. + bytes signature = 8; + // Vote extension provided by the application. Only valid for precommit + // messages. + bytes extension = 9; + // Vote extension signature by the validator if they participated in + // consensus for the associated block. Only valid for precommit messages. + bytes extension_signature = 10; } // Commit contains the evidence that a block was committed by a set of diff --git a/proto/tendermint/types/types.proto.intermediate b/proto/tendermint/types/types.proto.intermediate deleted file mode 100644 index 280cd7133..000000000 --- a/proto/tendermint/types/types.proto.intermediate +++ /dev/null @@ -1,192 +0,0 @@ -syntax = "proto3"; -package tendermint.types; - -import "gogoproto/gogo.proto"; -import "google/protobuf/timestamp.proto"; -import "tendermint/crypto/proof.proto"; -import "tendermint/version/types.proto"; -import "tendermint/types/validator.proto"; - -// This file is a temporary workaround to enable development during the ABCI++ -// project. This file should be deleted and any references to it removed when -// the ongoing work on ABCI++ is completed. -// -// This file supports building of the `tendermint.abci` proto package. -// For more information, see https://github.com/tendermint/tendermint/issues/8066 - -// BlockIdFlag indicates which BlockID the signature is for -enum BlockIDFlag { - option (gogoproto.goproto_enum_stringer) = true; - option (gogoproto.goproto_enum_prefix) = false; - - BLOCK_ID_FLAG_UNKNOWN = 0 - [(gogoproto.enumvalue_customname) = "BlockIDFlagUnknown"]; - BLOCK_ID_FLAG_ABSENT = 1 - [(gogoproto.enumvalue_customname) = "BlockIDFlagAbsent"]; - BLOCK_ID_FLAG_COMMIT = 2 - [(gogoproto.enumvalue_customname) = "BlockIDFlagCommit"]; - BLOCK_ID_FLAG_NIL = 3 [(gogoproto.enumvalue_customname) = "BlockIDFlagNil"]; -} - -// SignedMsgType is a type of signed message in the consensus. -enum SignedMsgType { - option (gogoproto.goproto_enum_stringer) = true; - option (gogoproto.goproto_enum_prefix) = false; - - SIGNED_MSG_TYPE_UNKNOWN = 0 - [(gogoproto.enumvalue_customname) = "UnknownType"]; - // Votes - SIGNED_MSG_TYPE_PREVOTE = 1 - [(gogoproto.enumvalue_customname) = "PrevoteType"]; - SIGNED_MSG_TYPE_PRECOMMIT = 2 - [(gogoproto.enumvalue_customname) = "PrecommitType"]; - - // Proposals - SIGNED_MSG_TYPE_PROPOSAL = 32 - [(gogoproto.enumvalue_customname) = "ProposalType"]; -} - -// PartsetHeader -message PartSetHeader { - uint32 total = 1; - bytes hash = 2; -} - -message Part { - uint32 index = 1; - bytes bytes = 2; - tendermint.crypto.Proof proof = 3 [(gogoproto.nullable) = false]; -} - -// BlockID -message BlockID { - bytes hash = 1; - PartSetHeader part_set_header = 2 [(gogoproto.nullable) = false]; -} - -// -------------------------------- - -// Header defines the structure of a Tendermint block header. -message Header { - // basic block info - tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false]; - string chain_id = 2 [(gogoproto.customname) = "ChainID"]; - int64 height = 3; - google.protobuf.Timestamp time = 4 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - - // prev block info - BlockID last_block_id = 5 [(gogoproto.nullable) = false]; - - // hashes of block data - bytes last_commit_hash = 6; // commit from validators from the last block - bytes data_hash = 7; // transactions - - // hashes from the app output from the prev block - bytes validators_hash = 8; // validators for the current block - bytes next_validators_hash = 9; // validators for the next block - bytes consensus_hash = 10; // consensus params for current block - bytes app_hash = 11; // state after txs from the previous block - bytes last_results_hash = - 12; // root hash of all results from the txs from the previous block - - // consensus info - bytes evidence_hash = 13; // evidence included in the block - bytes proposer_address = 14; // original proposer of the block -} - -// Data contains the set of transactions included in the block -message Data { - // Txs that will be applied by state @ block.Height+1. - // NOTE: not all txs here are valid. We're just agreeing on the order first. - // This means that block.AppHash does not include these txs. - repeated bytes txs = 1; -} - -// Vote represents a prevote, precommit, or commit vote from validators for -// consensus. -message Vote { - SignedMsgType type = 1; - int64 height = 2; - int32 round = 3; - BlockID block_id = 4 [ - (gogoproto.nullable) = false, - (gogoproto.customname) = "BlockID" - ]; // zero if vote is nil. - google.protobuf.Timestamp timestamp = 5 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes validator_address = 6; - int32 validator_index = 7; - bytes signature = 8; - VoteExtension vote_extension = 9; -} - -// VoteExtension is app-defined additional information to the validator votes. -message VoteExtension { - bytes app_data_to_sign = 1; - bytes app_data_self_authenticating = 2; -} - -// VoteExtensionToSign is a subset of VoteExtension that is signed by the validators private key. -// VoteExtensionToSign is extracted from an existing VoteExtension. -message VoteExtensionToSign { - bytes app_data_to_sign = 1; -} - -// Commit contains the evidence that a block was committed by a set of -// validators. -message Commit { - int64 height = 1; - int32 round = 2; - BlockID block_id = 3 - [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; - repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; -} - -// CommitSig is a part of the Vote included in a Commit. -message CommitSig { - BlockIDFlag block_id_flag = 1; - bytes validator_address = 2; - google.protobuf.Timestamp timestamp = 3 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes signature = 4; - VoteExtensionToSign vote_extension = 5; -} - -message Proposal { - SignedMsgType type = 1; - int64 height = 2; - int32 round = 3; - int32 pol_round = 4; - BlockID block_id = 5 - [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; - google.protobuf.Timestamp timestamp = 6 - [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; - bytes signature = 7; -} - -message SignedHeader { - Header header = 1; - Commit commit = 2; -} - -message LightBlock { - SignedHeader signed_header = 1; - tendermint.types.ValidatorSet validator_set = 2; -} - -message BlockMeta { - BlockID block_id = 1 - [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; - int64 block_size = 2; - Header header = 3 [(gogoproto.nullable) = false]; - int64 num_txs = 4; -} - -// TxProof represents a Merkle proof of the presence of a transaction in the -// Merkle tree. -message TxProof { - bytes root_hash = 1; - bytes data = 2; - tendermint.crypto.Proof proof = 3; -} diff --git a/rpc/client/eventstream/eventstream.go b/rpc/client/eventstream/eventstream.go index 887e723ce..59cfc8b5f 100644 --- a/rpc/client/eventstream/eventstream.go +++ b/rpc/client/eventstream/eventstream.go @@ -189,6 +189,5 @@ type MissedItemsError struct { // Error satisfies the error interface. func (e *MissedItemsError) Error() string { - return fmt.Sprintf("missed events matching %q between %q and %q", - e.Query, e.NewestSeen, e.OldestPresent) + return fmt.Sprintf("missed events matching %q between %q and %q", e.Query, e.NewestSeen, e.OldestPresent) } diff --git a/rpc/client/eventstream/eventstream_test.go b/rpc/client/eventstream/eventstream_test.go index ca27734e2..8cd9df30f 100644 --- a/rpc/client/eventstream/eventstream_test.go +++ b/rpc/client/eventstream/eventstream_test.go @@ -103,13 +103,16 @@ func TestMinPollTime(t *testing.T) { // wait time and reports no events. ctx := context.Background() filter := &coretypes.EventFilter{Query: `tm.event = 'good'`} - var zero cursor.Cursor t.Run("NoneMatch", func(t *testing.T) { start := time.Now() // Request a very short delay, and affirm we got the server's minimum. - rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Millisecond) + rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{ + Filter: filter, + MaxItems: 1, + WaitTime: 10 * time.Millisecond, + }) if err != nil { t.Fatalf("Events failed: %v", err) } else if elapsed := time.Since(start); elapsed < time.Second { @@ -128,7 +131,11 @@ func TestMinPollTime(t *testing.T) { // Request a long-ish delay and affirm we don't block for it. // Check for this by ensuring we return sooner than the minimum delay, // since we don't know the exact timing. - rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Second) + rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{ + Filter: filter, + MaxItems: 1, + WaitTime: 10 * time.Second, + }) if err != nil { t.Fatalf("Events failed: %v", err) } else if elapsed := time.Since(start); elapsed > 500*time.Millisecond { @@ -263,12 +270,5 @@ func (s *streamTester) advance(d time.Duration) { s.clock += int64(d) } // environment as if it were a local RPC client. This works because the Events // method only requires the event log, the other fields are unused. func (s *streamTester) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { - var before, after cursor.Cursor - if err := before.UnmarshalText([]byte(req.Before)); err != nil { - return nil, err - } - if err := after.UnmarshalText([]byte(req.After)); err != nil { - return nil, err - } - return s.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime) + return s.env.Events(ctx, req) } diff --git a/rpc/client/evidence_test.go b/rpc/client/evidence_test.go index 9187ddc1a..0096d0924 100644 --- a/rpc/client/evidence_test.go +++ b/rpc/client/evidence_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/privval" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -54,16 +54,16 @@ func makeEvidences( Type: tmproto.PrevoteType, Timestamp: timestamp, BlockID: types.BlockID{ - Hash: tmhash.Sum(tmrand.Bytes(tmhash.Size)), + Hash: crypto.Checksum(tmrand.Bytes(crypto.HashSize)), PartSetHeader: types.PartSetHeader{ Total: 1000, - Hash: tmhash.Sum([]byte("partset")), + Hash: crypto.Checksum([]byte("partset")), }, }, } vote2 := vote - vote2.BlockID.Hash = tmhash.Sum([]byte("blockhash2")) + vote2.BlockID.Hash = crypto.Checksum([]byte("blockhash2")) correct = newEvidence(t, val, &vote, &vote2, chainID, timestamp) fakes = make([]*types.DuplicateVoteEvidence, 0) diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index 2a9507f8e..50d78d279 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -207,24 +207,16 @@ func (c *baseRPCClient) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo return result, nil } -func (c *baseRPCClient) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, -) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -func (c *baseRPCClient) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { result := new(coretypes.ResultABCIQuery) - if err := c.caller.Call(ctx, "abci_query", abciQueryArgs{ + if err := c.caller.Call(ctx, "abci_query", &coretypes.RequestABCIQuery{ Path: path, Data: data, - Height: opts.Height, + Height: coretypes.Int64(opts.Height), Prove: opts.Prove, }, result); err != nil { return nil, err @@ -232,51 +224,39 @@ func (c *baseRPCClient) ABCIQueryWithOptions( return result, nil } -func (c *baseRPCClient) BroadcastTxCommit( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTxCommit, error) { +func (c *baseRPCClient) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { result := new(coretypes.ResultBroadcastTxCommit) - if err := c.caller.Call(ctx, "broadcast_tx_commit", txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, "broadcast_tx_commit", &coretypes.RequestBroadcastTx{ + Tx: tx, + }, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BroadcastTxAsync( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { return c.broadcastTX(ctx, "broadcast_tx_async", tx) } -func (c *baseRPCClient) BroadcastTxSync( - ctx context.Context, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { return c.broadcastTX(ctx, "broadcast_tx_sync", tx) } -func (c *baseRPCClient) broadcastTX( - ctx context.Context, - route string, - tx types.Tx, -) (*coretypes.ResultBroadcastTx, error) { +func (c *baseRPCClient) broadcastTX(ctx context.Context, route string, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { result := new(coretypes.ResultBroadcastTx) - if err := c.caller.Call(ctx, route, txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, route, &coretypes.RequestBroadcastTx{Tx: tx}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) UnconfirmedTxs( - ctx context.Context, - page *int, - perPage *int, -) (*coretypes.ResultUnconfirmedTxs, error) { +func (c *baseRPCClient) UnconfirmedTxs(ctx context.Context, page *int, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { result := new(coretypes.ResultUnconfirmedTxs) - if err := c.caller.Call(ctx, "unconfirmed_txs", unconfirmedArgs{Page: page, PerPage: perPage}, result); err != nil { + if err := c.caller.Call(ctx, "unconfirmed_txs", &coretypes.RequestUnconfirmedTxs{ + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }, result); err != nil { return nil, err } return result, nil @@ -292,14 +272,14 @@ func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul func (c *baseRPCClient) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { result := new(coretypes.ResultCheckTx) - if err := c.caller.Call(ctx, "check_tx", txArgs{Tx: tx}, result); err != nil { + if err := c.caller.Call(ctx, "check_tx", &coretypes.RequestCheckTx{Tx: tx}, result); err != nil { return nil, err } return result, nil } func (c *baseRPCClient) RemoveTx(ctx context.Context, txKey types.TxKey) error { - if err := c.caller.Call(ctx, "remove_tx", txKeyArgs{TxKey: txKey[:]}, nil); err != nil { + if err := c.caller.Call(ctx, "remove_tx", &coretypes.RequestRemoveTx{TxKey: txKey}, nil); err != nil { return err } return nil @@ -329,12 +309,11 @@ func (c *baseRPCClient) ConsensusState(ctx context.Context) (*coretypes.ResultCo return result, nil } -func (c *baseRPCClient) ConsensusParams( - ctx context.Context, - height *int64, -) (*coretypes.ResultConsensusParams, error) { +func (c *baseRPCClient) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { result := new(coretypes.ResultConsensusParams) - if err := c.caller.Call(ctx, "consensus_params", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "consensus_params", &coretypes.RequestConsensusParams{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -356,15 +335,11 @@ func (c *baseRPCClient) Health(ctx context.Context) (*coretypes.ResultHealth, er return result, nil } -func (c *baseRPCClient) BlockchainInfo( - ctx context.Context, - minHeight, - maxHeight int64, -) (*coretypes.ResultBlockchainInfo, error) { +func (c *baseRPCClient) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { result := new(coretypes.ResultBlockchainInfo) - if err := c.caller.Call(ctx, "blockchain", blockchainInfoArgs{ - MinHeight: minHeight, - MaxHeight: maxHeight, + if err := c.caller.Call(ctx, "blockchain", &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), }, result); err != nil { return nil, err } @@ -381,7 +356,9 @@ func (c *baseRPCClient) Genesis(ctx context.Context) (*coretypes.ResultGenesis, func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) { result := new(coretypes.ResultGenesisChunk) - if err := c.caller.Call(ctx, "genesis_chunked", genesisChunkArgs{Chunk: id}, result); err != nil { + if err := c.caller.Call(ctx, "genesis_chunked", &coretypes.RequestGenesisChunked{ + Chunk: coretypes.Int64(id), + }, result); err != nil { return nil, err } return result, nil @@ -389,7 +366,9 @@ func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { result := new(coretypes.ResultBlock) - if err := c.caller.Call(ctx, "block", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "block", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -397,18 +376,17 @@ func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.Re func (c *baseRPCClient) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { result := new(coretypes.ResultBlock) - if err := c.caller.Call(ctx, "block_by_hash", hashArgs{Hash: hash}, result); err != nil { + if err := c.caller.Call(ctx, "block_by_hash", &coretypes.RequestBlockByHash{Hash: hash}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BlockResults( - ctx context.Context, - height *int64, -) (*coretypes.ResultBlockResults, error) { +func (c *baseRPCClient) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { result := new(coretypes.ResultBlockResults) - if err := c.caller.Call(ctx, "block_results", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "block_results", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -416,7 +394,9 @@ func (c *baseRPCClient) BlockResults( func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) { result := new(coretypes.ResultHeader) - if err := c.caller.Call(ctx, "header", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "header", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -424,7 +404,9 @@ func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.R func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { result := new(coretypes.ResultHeader) - if err := c.caller.Call(ctx, "header_by_hash", hashArgs{Hash: hash}, result); err != nil { + if err := c.caller.Call(ctx, "header_by_hash", &coretypes.RequestBlockByHash{ + Hash: hash, + }, result); err != nil { return nil, err } return result, nil @@ -432,7 +414,9 @@ func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) ( func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { result := new(coretypes.ResultCommit) - if err := c.caller.Call(ctx, "commit", heightArgs{Height: height}, result); err != nil { + if err := c.caller.Call(ctx, "commit", &coretypes.RequestBlockInfo{ + Height: (*coretypes.Int64)(height), + }, result); err != nil { return nil, err } return result, nil @@ -440,27 +424,20 @@ func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.R func (c *baseRPCClient) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { result := new(coretypes.ResultTx) - if err := c.caller.Call(ctx, "tx", hashArgs{Hash: hash, Prove: prove}, result); err != nil { + if err := c.caller.Call(ctx, "tx", &coretypes.RequestTx{Hash: hash, Prove: prove}, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) TxSearch( - ctx context.Context, - query string, - prove bool, - page, - perPage *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { +func (c *baseRPCClient) TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { result := new(coretypes.ResultTxSearch) - if err := c.caller.Call(ctx, "tx_search", searchArgs{ + if err := c.caller.Call(ctx, "tx_search", &coretypes.RequestTxSearch{ Query: query, Prove: prove, OrderBy: orderBy, - Page: page, - PerPage: perPage, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } @@ -468,18 +445,13 @@ func (c *baseRPCClient) TxSearch( return result, nil } -func (c *baseRPCClient) BlockSearch( - ctx context.Context, - query string, - page, perPage *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { +func (c *baseRPCClient) BlockSearch(ctx context.Context, query string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { result := new(coretypes.ResultBlockSearch) - if err := c.caller.Call(ctx, "block_search", searchArgs{ + if err := c.caller.Call(ctx, "block_search", &coretypes.RequestBlockSearch{ Query: query, OrderBy: orderBy, - Page: page, - PerPage: perPage, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } @@ -487,30 +459,22 @@ func (c *baseRPCClient) BlockSearch( return result, nil } -func (c *baseRPCClient) Validators( - ctx context.Context, - height *int64, - page, - perPage *int, -) (*coretypes.ResultValidators, error) { +func (c *baseRPCClient) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { result := new(coretypes.ResultValidators) - if err := c.caller.Call(ctx, "validators", validatorArgs{ - Height: height, - Page: page, - PerPage: perPage, + if err := c.caller.Call(ctx, "validators", &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), }, result); err != nil { return nil, err } return result, nil } -func (c *baseRPCClient) BroadcastEvidence( - ctx context.Context, - ev types.Evidence, -) (*coretypes.ResultBroadcastEvidence, error) { +func (c *baseRPCClient) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { result := new(coretypes.ResultBroadcastEvidence) - if err := c.caller.Call(ctx, "broadcast_evidence", evidenceArgs{ - Evidence: coretypes.Evidence{Value: ev}, + if err := c.caller.Call(ctx, "broadcast_evidence", &coretypes.RequestBroadcastEvidence{ + Evidence: ev, }, result); err != nil { return nil, err } diff --git a/rpc/client/http/request.go b/rpc/client/http/request.go deleted file mode 100644 index 746cb776d..000000000 --- a/rpc/client/http/request.go +++ /dev/null @@ -1,65 +0,0 @@ -package http - -// The types in this file define the JSON encoding for RPC method parameters -// from the client to the server. - -import ( - "github.com/tendermint/tendermint/libs/bytes" - "github.com/tendermint/tendermint/rpc/coretypes" -) - -type abciQueryArgs struct { - Path string `json:"path"` - Data bytes.HexBytes `json:"data"` - Height int64 `json:"height,string"` - Prove bool `json:"prove"` -} - -type txArgs struct { - Tx []byte `json:"tx"` -} - -type txKeyArgs struct { - TxKey []byte `json:"tx_key"` -} - -type unconfirmedArgs struct { - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type heightArgs struct { - Height *int64 `json:"height,string,omitempty"` -} - -type hashArgs struct { - Hash bytes.HexBytes `json:"hash"` - Prove bool `json:"prove,omitempty"` -} - -type blockchainInfoArgs struct { - MinHeight int64 `json:"minHeight,string"` - MaxHeight int64 `json:"maxHeight,string"` -} - -type genesisChunkArgs struct { - Chunk uint `json:"chunk,string"` -} - -type searchArgs struct { - Query string `json:"query"` - Prove bool `json:"prove,omitempty"` - OrderBy string `json:"order_by,omitempty"` - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type validatorArgs struct { - Height *int64 `json:"height,string,omitempty"` - Page *int `json:"page,string,omitempty"` - PerPage *int `json:"per_page,string,omitempty"` -} - -type evidenceArgs struct { - Evidence coretypes.Evidence `json:"evidence"` -} diff --git a/rpc/client/http/ws.go b/rpc/client/http/ws.go index 2f188a24d..e9a5ac829 100644 --- a/rpc/client/http/ws.go +++ b/rpc/client/http/ws.go @@ -107,6 +107,7 @@ func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) er } w.mtx.Lock() + defer w.mtx.Unlock() info, ok := w.subscriptions[query] if ok { if info.id != "" { @@ -114,7 +115,6 @@ func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) er } delete(w.subscriptions, info.query) } - w.mtx.Unlock() return nil } @@ -129,8 +129,8 @@ func (w *wsEvents) UnsubscribeAll(ctx context.Context, subscriber string) error } w.mtx.Lock() + defer w.mtx.Unlock() w.subscriptions = make(map[string]*wsSubscription) - w.mtx.Unlock() return nil } diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index b9091efac..8718ee504 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -7,7 +7,6 @@ import ( "time" "github.com/tendermint/tendermint/internal/eventbus" - "github.com/tendermint/tendermint/internal/eventlog/cursor" "github.com/tendermint/tendermint/internal/pubsub" "github.com/tendermint/tendermint/internal/pubsub/query" rpccore "github.com/tendermint/tendermint/internal/rpc/core" @@ -78,28 +77,29 @@ func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -func (c *Local) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove) +func (c *Local) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{ + Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove, + }) } func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - return c.env.BroadcastTxCommit(ctx, tx) + return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxAsync(ctx, tx) + return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxSync(ctx, tx) + return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c *Local) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { - return c.env.UnconfirmedTxs(ctx, page, perPage) + return c.env.UnconfirmedTxs(ctx, &coretypes.RequestUnconfirmedTxs{ + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { @@ -107,7 +107,7 @@ func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfi } func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - return c.env.CheckTx(ctx, tx) + return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx}) } func (c *Local) RemoveTx(ctx context.Context, txKey types.TxKey) error { @@ -127,18 +127,11 @@ func (c *Local) ConsensusState(ctx context.Context) (*coretypes.ResultConsensusS } func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { - return c.env.ConsensusParams(ctx, height) + return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)}) } func (c *Local) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) { - var before, after cursor.Cursor - if err := before.UnmarshalText([]byte(req.Before)); err != nil { - return nil, err - } - if err := after.UnmarshalText([]byte(req.After)); err != nil { - return nil, err - } - return c.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime) + return c.env.Events(ctx, req) } func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) { @@ -146,7 +139,10 @@ func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) { } func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - return c.env.BlockchainInfo(ctx, minHeight, maxHeight) + return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), + }) } func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { @@ -154,70 +150,69 @@ func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { } func (c *Local) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) { - return c.env.GenesisChunked(ctx, id) + return c.env.GenesisChunked(ctx, &coretypes.RequestGenesisChunked{Chunk: coretypes.Int64(id)}) } func (c *Local) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { - return c.env.Block(ctx, height) + return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - return c.env.BlockByHash(ctx, hash) + return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c *Local) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { - return c.env.BlockResults(ctx, height) + return c.env.BlockResults(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) { - return c.env.Header(ctx, height) + return c.env.Header(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { - return c.env.HeaderByHash(ctx, hash) + return c.env.HeaderByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c *Local) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { - return c.env.Commit(ctx, height) + return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { - return c.env.Validators(ctx, height, page, perPage) + return c.env.Validators(ctx, &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c *Local) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { - return c.env.Tx(ctx, hash, prove) + return c.env.Tx(ctx, &coretypes.RequestTx{Hash: hash, Prove: prove}) } -func (c *Local) TxSearch( - ctx context.Context, - queryString string, - prove bool, - page, - perPage *int, - orderBy string, -) (*coretypes.ResultTxSearch, error) { - return c.env.TxSearch(ctx, queryString, prove, page, perPage, orderBy) +func (c *Local) TxSearch(ctx context.Context, queryString string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) { + return c.env.TxSearch(ctx, &coretypes.RequestTxSearch{ + Query: queryString, + Prove: prove, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + OrderBy: orderBy, + }) } -func (c *Local) BlockSearch( - ctx context.Context, - queryString string, - page, perPage *int, - orderBy string, -) (*coretypes.ResultBlockSearch, error) { - return c.env.BlockSearch(ctx, queryString, page, perPage, orderBy) +func (c *Local) BlockSearch(ctx context.Context, queryString string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { + return c.env.BlockSearch(ctx, &coretypes.RequestBlockSearch{ + Query: queryString, + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + OrderBy: orderBy, + }) } func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev}) + return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev}) } -func (c *Local) Subscribe( - ctx context.Context, - subscriber, - queryString string, - capacity ...int) (out <-chan coretypes.ResultEvent, err error) { +func (c *Local) Subscribe(ctx context.Context, subscriber, queryString string, capacity ...int) (<-chan coretypes.ResultEvent, error) { q, err := query.New(queryString) if err != nil { return nil, fmt.Errorf("failed to parse query: %w", err) @@ -251,12 +246,7 @@ func (c *Local) Subscribe( return outc, nil } -func (c *Local) eventsRoutine( - ctx context.Context, - sub eventbus.Subscription, - subArgs pubsub.SubscribeArgs, - outc chan<- coretypes.ResultEvent, -) { +func (c *Local) eventsRoutine(ctx context.Context, sub eventbus.Subscription, subArgs pubsub.SubscribeArgs, outc chan<- coretypes.ResultEvent) { qstr := subArgs.Query.String() for { msg, err := sub.Next(ctx) @@ -271,17 +261,24 @@ func (c *Local) eventsRoutine( } continue } - outc <- coretypes.ResultEvent{ + select { + case outc <- coretypes.ResultEvent{ SubscriptionID: msg.SubscriptionID(), Query: qstr, Data: msg.Data(), Events: msg.Events(), + }: + case <-ctx.Done(): + return } } } // Try to resubscribe with exponential backoff. func (c *Local) resubscribe(ctx context.Context, subArgs pubsub.SubscribeArgs) eventbus.Subscription { + timer := time.NewTimer(0) + defer timer.Stop() + attempts := 0 for { if !c.IsRunning() { @@ -294,7 +291,13 @@ func (c *Local) resubscribe(ctx context.Context, subArgs pubsub.SubscribeArgs) e } attempts++ - time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms + timer.Reset((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms + select { + case <-timer.C: + continue + case <-ctx.Done(): + return nil + } } } diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index 8c652444b..a6aebdb14 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -25,47 +25,65 @@ var ( ) func (a ABCIApp) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { - return &coretypes.ResultABCIInfo{Response: a.App.Info(proxy.RequestInfo)}, nil + res, err := a.App.Info(ctx, &proxy.RequestInfo) + if err != nil { + return nil, err + } + + return &coretypes.ResultABCIInfo{Response: *res}, nil } func (a ABCIApp) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return a.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } -func (a ABCIApp) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - q := a.App.Query(abci.RequestQuery{ +func (a ABCIApp) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { + q, err := a.App.Query(ctx, &abci.RequestQuery{ Data: data, Path: path, Height: opts.Height, Prove: opts.Prove, }) - return &coretypes.ResultABCIQuery{Response: q}, nil + if err != nil { + return nil, err + } + + return &coretypes.ResultABCIQuery{Response: *q}, nil } // NOTE: Caller should call a.App.Commit() separately, // this function does not actually wait for a commit. // TODO: Make it wait for a commit and set res.Height appropriately. func (a ABCIApp) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - res := coretypes.ResultBroadcastTxCommit{} - res.CheckTx = a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) - if res.CheckTx.IsErr() { - return &res, nil + resp, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err } - fb := a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) + + res := &coretypes.ResultBroadcastTxCommit{CheckTx: *resp} + if res.CheckTx.IsErr() { + return res, nil + } + + fb, err := a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) + if err != nil { + return nil, err + } + res.TxResult = *fb.TxResults[0] res.Height = -1 // TODO - return &res, nil + return res, nil } func (a ABCIApp) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) + c, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err + } + // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() + go func() { _, _ = a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, @@ -77,10 +95,14 @@ func (a ABCIApp) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes. } func (a ABCIApp) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) + c, err := a.App.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + if err != nil { + return nil, err + } + // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() + go func() { _, _ = a.App.FinalizeBlock(ctx, &abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, @@ -113,11 +135,7 @@ func (m ABCIMock) ABCIQuery(ctx context.Context, path string, data bytes.HexByte return m.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } -func (m ABCIMock) ABCIQueryWithOptions( - ctx context.Context, - path string, - data bytes.HexBytes, - opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { +func (m ABCIMock) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { res, err := m.Query.GetResponse(QueryArgs{path, data, opts.Height, opts.Prove}) if err != nil { return nil, err @@ -185,11 +203,7 @@ func (r *ABCIRecorder) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, return res, err } -func (r *ABCIRecorder) ABCIQuery( - ctx context.Context, - path string, - data bytes.HexBytes, -) (*coretypes.ResultABCIQuery, error) { +func (r *ABCIRecorder) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return r.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go index 489133a5b..e35ccf29c 100644 --- a/rpc/client/mock/abci_test.go +++ b/rpc/client/mock/abci_test.go @@ -185,7 +185,8 @@ func TestABCIApp(t *testing.T) { // commit // TODO: This may not be necessary in the future if res.Height == -1 { - m.App.Commit() + _, err := m.App.Commit(ctx) + require.NoError(t, err) } // check the key diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index b47ff1e76..a3272cb17 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -91,23 +91,25 @@ func (c Client) ABCIQueryWithOptions( path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { - return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove) + return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{ + Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove, + }) } func (c Client) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { - return c.env.BroadcastTxCommit(ctx, tx) + return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxAsync(ctx, tx) + return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { - return c.env.BroadcastTxSync(ctx, tx) + return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx}) } func (c Client) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) { - return c.env.CheckTx(ctx, tx) + return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx}) } func (c Client) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) { @@ -123,7 +125,7 @@ func (c Client) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpCo } func (c Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) { - return c.env.ConsensusParams(ctx, height) + return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)}) } func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) { @@ -131,7 +133,10 @@ func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) { } func (c Client) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { - return c.env.BlockchainInfo(ctx, minHeight, maxHeight) + return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{ + MinHeight: coretypes.Int64(minHeight), + MaxHeight: coretypes.Int64(maxHeight), + }) } func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { @@ -139,21 +144,25 @@ func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) { } func (c Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { - return c.env.Block(ctx, height) + return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c Client) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { - return c.env.BlockByHash(ctx, hash) + return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash}) } func (c Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { - return c.env.Commit(ctx, height) + return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)}) } func (c Client) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) { - return c.env.Validators(ctx, height, page, perPage) + return c.env.Validators(ctx, &coretypes.RequestValidators{ + Height: (*coretypes.Int64)(height), + Page: coretypes.Int64Ptr(page), + PerPage: coretypes.Int64Ptr(perPage), + }) } func (c Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) { - return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev}) + return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev}) } diff --git a/rpc/client/mocks/client.go b/rpc/client/mocks/client.go index d9049ea44..9a286eaf2 100644 --- a/rpc/client/mocks/client.go +++ b/rpc/client/mocks/client.go @@ -12,6 +12,8 @@ import ( mock "github.com/stretchr/testify/mock" + testing "testing" + types "github.com/tendermint/tendermint/types" ) @@ -795,3 +797,13 @@ func (_m *Client) Validators(ctx context.Context, height *int64, page *int, perP return r0, r1 } + +// NewClient creates a new instance of Client. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t testing.TB) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rpc/coretypes/requests.go b/rpc/coretypes/requests.go new file mode 100644 index 000000000..cd4d22726 --- /dev/null +++ b/rpc/coretypes/requests.go @@ -0,0 +1,188 @@ +package coretypes + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/tendermint/tendermint/internal/jsontypes" + "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/types" +) + +type RequestSubscribe struct { + Query string `json:"query"` +} + +type RequestUnsubscribe struct { + Query string `json:"query"` +} + +type RequestBlockchainInfo struct { + MinHeight Int64 `json:"minHeight"` + MaxHeight Int64 `json:"maxHeight"` +} + +type RequestGenesisChunked struct { + Chunk Int64 `json:"chunk"` +} + +type RequestBlockInfo struct { + Height *Int64 `json:"height"` +} + +type RequestBlockByHash struct { + Hash bytes.HexBytes `json:"hash"` +} + +type RequestCheckTx struct { + Tx types.Tx `json:"tx"` +} + +type RequestRemoveTx struct { + TxKey types.TxKey `json:"txkey"` +} + +type RequestTx struct { + Hash bytes.HexBytes `json:"hash"` + Prove bool `json:"prove"` +} + +type RequestTxSearch struct { + Query string `json:"query"` + Prove bool `json:"prove"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` + OrderBy string `json:"order_by"` +} + +type RequestBlockSearch struct { + Query string `json:"query"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` + OrderBy string `json:"order_by"` +} + +type RequestValidators struct { + Height *Int64 `json:"height"` + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` +} + +type RequestConsensusParams struct { + Height *Int64 `json:"height"` +} + +type RequestUnconfirmedTxs struct { + Page *Int64 `json:"page"` + PerPage *Int64 `json:"per_page"` +} + +type RequestBroadcastTx struct { + Tx types.Tx `json:"tx"` +} + +type RequestABCIQuery struct { + Path string `json:"path"` + Data bytes.HexBytes `json:"data"` + Height Int64 `json:"height"` + Prove bool `json:"prove"` +} + +type RequestBroadcastEvidence struct { + Evidence types.Evidence +} + +type requestBroadcastEvidenceJSON struct { + Evidence json.RawMessage `json:"evidence"` +} + +func (r RequestBroadcastEvidence) MarshalJSON() ([]byte, error) { + ev, err := jsontypes.Marshal(r.Evidence) + if err != nil { + return nil, err + } + return json.Marshal(requestBroadcastEvidenceJSON{ + Evidence: ev, + }) +} + +func (r *RequestBroadcastEvidence) UnmarshalJSON(data []byte) error { + var val requestBroadcastEvidenceJSON + if err := json.Unmarshal(data, &val); err != nil { + return err + } + if err := jsontypes.Unmarshal(val.Evidence, &r.Evidence); err != nil { + return err + } + return nil +} + +// RequestEvents is the argument for the "/events" RPC endpoint. +type RequestEvents struct { + // Optional filter spec. If nil or empty, all items are eligible. + Filter *EventFilter `json:"filter"` + + // The maximum number of eligible items to return. + // If zero or negative, the server will report a default number. + MaxItems int `json:"maxItems"` + + // Return only items after this cursor. If empty, the limit is just + // before the the beginning of the event log. + After string `json:"after"` + + // Return only items before this cursor. If empty, the limit is just + // after the head of the event log. + Before string `json:"before"` + + // Wait for up to this long for events to be available. + WaitTime time.Duration `json:"waitTime"` +} + +// An EventFilter specifies which events are selected by an /events request. +type EventFilter struct { + Query string `json:"query"` +} + +// Int64 is a wrapper for int64 that encodes to JSON as a string and can be +// decoded from either a string or a number value. +type Int64 int64 + +func (z *Int64) UnmarshalJSON(data []byte) error { + var s string + if len(data) != 0 && data[0] == '"' { + if err := json.Unmarshal(data, &s); err != nil { + return err + } + } else { + s = string(data) + } + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + *z = Int64(v) + return nil +} + +func (z Int64) MarshalJSON() ([]byte, error) { + return []byte(strconv.FormatInt(int64(z), 10)), nil +} + +// IntPtr returns a pointer to the value of *z as an int, or nil if z == nil. +func (z *Int64) IntPtr() *int { + if z == nil { + return nil + } + v := int(*z) + return &v +} + +// Int64Ptr returns an *Int64 that points to the same value as v, or nil. +func Int64Ptr(v *int) *Int64 { + if v == nil { + return nil + } + z := Int64(*v) + return &z +} diff --git a/rpc/coretypes/responses.go b/rpc/coretypes/responses.go index 8968f9868..51d988f3a 100644 --- a/rpc/coretypes/responses.go +++ b/rpc/coretypes/responses.go @@ -357,32 +357,6 @@ type Evidence struct { func (e Evidence) MarshalJSON() ([]byte, error) { return jsontypes.Marshal(e.Value) } func (e *Evidence) UnmarshalJSON(data []byte) error { return jsontypes.Unmarshal(data, &e.Value) } -// RequestEvents is the argument for the "/events" RPC endpoint. -type RequestEvents struct { - // Optional filter spec. If nil or empty, all items are eligible. - Filter *EventFilter `json:"filter"` - - // The maximum number of eligible items to return. - // If zero or negative, the server will report a default number. - MaxItems int `json:"maxItems"` - - // Return only items after this cursor. If empty, the limit is just - // before the the beginning of the event log. - After string `json:"after"` - - // Return only items before this cursor. If empty, the limit is just - // after the head of the event log. - Before string `json:"before"` - - // Wait for up to this long for events to be available. - WaitTime time.Duration `json:"waitTime"` -} - -// An EventFilter specifies which events are selected by an /events request. -type EventFilter struct { - Query string `json:"query"` -} - // ResultEvents is the response from the "/events" RPC endpoint. type ResultEvents struct { // The items matching the request parameters, from newest diff --git a/rpc/jsonrpc/doc.go b/rpc/jsonrpc/doc.go index b014fe38d..58b522861 100644 --- a/rpc/jsonrpc/doc.go +++ b/rpc/jsonrpc/doc.go @@ -55,7 +55,7 @@ // Define some routes // // var Routes = map[string]*rpcserver.RPCFunc{ -// "status": rpcserver.NewRPCFunc(Status, "arg"), +// "status": rpcserver.NewRPCFunc(Status), // } // // An rpc function: diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index 1ba853a62..236db9b32 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -34,49 +34,65 @@ const ( testVal = "acbd" ) +type RequestEcho struct { + Value string `json:"arg"` +} + type ResultEcho struct { Value string `json:"value"` } +type RequestEchoInt struct { + Value int `json:"arg"` +} + type ResultEchoInt struct { Value int `json:"value"` } +type RequestEchoBytes struct { + Value []byte `json:"arg"` +} + type ResultEchoBytes struct { Value []byte `json:"value"` } +type RequestEchoDataBytes struct { + Value tmbytes.HexBytes `json:"arg"` +} + type ResultEchoDataBytes struct { Value tmbytes.HexBytes `json:"value"` } // Define some routes var Routes = map[string]*server.RPCFunc{ - "echo": server.NewRPCFunc(EchoResult, "arg"), - "echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"), - "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"), - "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"), - "echo_int": server.NewRPCFunc(EchoIntResult, "arg"), + "echo": server.NewRPCFunc(EchoResult), + "echo_ws": server.NewWSRPCFunc(EchoWSResult), + "echo_bytes": server.NewRPCFunc(EchoBytesResult), + "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult), + "echo_int": server.NewRPCFunc(EchoIntResult), } -func EchoResult(ctx context.Context, v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil +func EchoResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { + return &ResultEcho{v.Value}, nil } -func EchoWSResult(ctx context.Context, v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil +func EchoWSResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { + return &ResultEcho{v.Value}, nil } -func EchoIntResult(ctx context.Context, v int) (*ResultEchoInt, error) { - return &ResultEchoInt{v}, nil +func EchoIntResult(ctx context.Context, v *RequestEchoInt) (*ResultEchoInt, error) { + return &ResultEchoInt{v.Value}, nil } -func EchoBytesResult(ctx context.Context, v []byte) (*ResultEchoBytes, error) { - return &ResultEchoBytes{v}, nil +func EchoBytesResult(ctx context.Context, v *RequestEchoBytes) (*ResultEchoBytes, error) { + return &ResultEchoBytes{v.Value}, nil } -func EchoDataBytesResult(ctx context.Context, v tmbytes.HexBytes) (*ResultEchoDataBytes, error) { - return &ResultEchoDataBytes{v}, nil +func EchoDataBytesResult(ctx context.Context, v *RequestEchoDataBytes) (*ResultEchoDataBytes, error) { + return &ResultEchoDataBytes{v.Value}, nil } // launch unix and tcp servers diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 94da974de..2eeded2d7 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -2,15 +2,11 @@ package server import ( "bytes" - "context" "encoding/json" - "errors" "fmt" "html/template" "io" "net/http" - "reflect" - "strconv" "strings" "github.com/tendermint/tendermint/libs/log" @@ -70,19 +66,11 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han RPCRequest: &req, HTTPRequest: hreq, }) - args, err := parseParams(ctx, rpcFunc, req.Params) + result, err := rpcFunc.Call(ctx, req.Params) if err != nil { - responses = append(responses, - req.MakeErrorf(rpctypes.CodeInvalidParams, "converting JSON parameters: %v", err)) - continue - } - - returns := rpcFunc.f.Call(args) - result, err := unreflectResult(returns) - if err == nil { - responses = append(responses, req.MakeResponse(result)) - } else { responses = append(responses, req.MakeError(err)) + } else { + responses = append(responses, req.MakeResponse(result)) } } @@ -124,120 +112,21 @@ func parseRequests(data []byte) ([]rpctypes.RPCRequest, error) { return reqs, nil } -// parseParams parses the JSON parameters of rpcReq into the arguments of fn, -// returning the corresponding argument values or an error. -func parseParams(ctx context.Context, fn *RPCFunc, paramData []byte) ([]reflect.Value, error) { - params, err := parseJSONParams(fn, paramData) - if err != nil { - return nil, err - } - - args := make([]reflect.Value, 1+len(params)) - args[0] = reflect.ValueOf(ctx) - for i, param := range params { - ptype := fn.args[i+1] - if len(param) == 0 { - args[i+1] = reflect.Zero(ptype) - continue - } - - var pval reflect.Value - isPtr := ptype.Kind() == reflect.Ptr - if isPtr { - pval = reflect.New(ptype.Elem()) - } else { - pval = reflect.New(ptype) - } - baseType := pval.Type().Elem() - - if isIntType(baseType) && isStringValue(param) { - var z int64String - if err := json.Unmarshal(param, &z); err != nil { - return nil, fmt.Errorf("decoding string %q: %w", fn.argNames[i], err) - } - pval.Elem().Set(reflect.ValueOf(z).Convert(baseType)) - } else if err := json.Unmarshal(param, pval.Interface()); err != nil { - return nil, fmt.Errorf("decoding %q: %w", fn.argNames[i], err) - } - - if isPtr { - args[i+1] = pval - } else { - args[i+1] = pval.Elem() - } - } - return args, nil -} - -// parseJSONParams parses data and returns a slice of JSON values matching the -// positional parameters of fn. It reports an error if data is not "null" and -// does not encode an object or an array, or if the number of array parameters -// does not match the argument list of fn (excluding the context). -func parseJSONParams(fn *RPCFunc, data []byte) ([]json.RawMessage, error) { - base := bytes.TrimSpace(data) - if bytes.HasPrefix(base, []byte("{")) { - var m map[string]json.RawMessage - if err := json.Unmarshal(base, &m); err != nil { - return nil, fmt.Errorf("decoding parameter object: %w", err) - } - out := make([]json.RawMessage, len(fn.argNames)) - for i, name := range fn.argNames { - if p, ok := m[name]; ok { - out[i] = p - } - } - return out, nil - - } else if bytes.HasPrefix(base, []byte("[")) { - var m []json.RawMessage - if err := json.Unmarshal(base, &m); err != nil { - return nil, fmt.Errorf("decoding parameter array: %w", err) - } - if len(m) != len(fn.argNames) { - return nil, fmt.Errorf("got %d parameters, want %d", len(m), len(fn.argNames)) - } - return m, nil - - } else if bytes.Equal(base, []byte("null")) { - return make([]json.RawMessage, len(fn.argNames)), nil - } - - return nil, errors.New("parameters must be an object or an array") -} - -// isStringValue reports whether data is a JSON string value. -func isStringValue(data json.RawMessage) bool { - return len(data) != 0 && data[0] == '"' -} - -type int64String int64 - -func (z *int64String) UnmarshalText(data []byte) error { - v, err := strconv.ParseInt(string(data), 10, 64) - if err != nil { - return err - } - *z = int64String(v) - return nil -} - // writes a list of available rpc endpoints as an html page func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) { hasArgs := make(map[string]string) noArgs := make(map[string]string) for name, rf := range funcMap { base := fmt.Sprintf("//%s/%s", r.Host, name) - // N.B. Check argNames, not args, since the type list includes the type - // of the leading context argument. - if len(rf.argNames) == 0 { + if len(rf.args) == 0 { noArgs[name] = base - } else { - query := append([]string(nil), rf.argNames...) - for i, arg := range query { - query[i] = arg + "=_" - } - hasArgs[name] = base + "?" + strings.Join(query, "&") + continue } + var query []string + for _, arg := range rf.args { + query = append(query, arg.name+"=_") + } + hasArgs[name] = base + "?" + strings.Join(query, "&") } w.Header().Set("Content-Type", "text/html") _ = listOfEndpoints.Execute(w, map[string]map[string]string{ diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index 1f5d2c320..77c74ffbc 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -17,9 +17,16 @@ import ( ) func testMux() *http.ServeMux { + type testArgs struct { + S string `json:"s"` + I json.Number `json:"i"` + } + type blockArgs struct { + H json.Number `json:"h"` + } funcMap := map[string]*RPCFunc{ - "c": NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"), - "block": NewRPCFunc(func(ctx context.Context, h int) (string, error) { return "block", nil }, "height"), + "c": NewRPCFunc(func(ctx context.Context, arg *testArgs) (string, error) { return "foo", nil }), + "block": NewRPCFunc(func(ctx context.Context, arg *blockArgs) (string, error) { return "block", nil }), } mux := http.NewServeMux() logger := log.NewNopLogger() @@ -46,7 +53,7 @@ func TestRPCParams(t *testing.T) { // id not captured in JSON parsing failures {`{"method": "c", "id": "0", "params": a}`, "invalid character", ""}, {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", `"0"`}, - {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid syntax", `"0"`}, + {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid number", `"0"`}, {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", `"0"`}, // no ID - notification diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index 32917b8cb..0b715835d 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -46,13 +46,7 @@ func DefaultConfig() *Config { // Serve creates a http.Server and calls Serve with the given listener. It // wraps handler to recover panics and limit the request body size. -func Serve( - ctx context.Context, - listener net.Listener, - handler http.Handler, - logger log.Logger, - config *Config, -) error { +func Serve(ctx context.Context, listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error { logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger) s := &http.Server{ @@ -83,19 +77,14 @@ func Serve( // Serve creates a http.Server and calls ServeTLS with the given listener, // certFile and keyFile. It wraps handler to recover panics and limit the // request body size. -func ServeTLS( - ctx context.Context, - listener net.Listener, - handler http.Handler, - certFile, keyFile string, - logger log.Logger, - config *Config, -) error { - logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", - listener.Addr(), certFile, keyFile)) - h := recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger) +func ServeTLS(ctx context.Context, listener net.Listener, handler http.Handler, certFile, keyFile string, logger log.Logger, config *Config) error { + logger.Info("Starting RPC HTTPS server", + "listenterAddr", listener.Addr(), + "certFile", certFile, + "keyFile", keyFile) + s := &http.Server{ - Handler: h, + Handler: recoverAndLogHandler(MaxBytesHandler(handler, config.MaxBodyBytes), logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, MaxHeaderBytes: config.MaxHeaderBytes, diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index ad0fbb7b3..c755bbaf1 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -1,13 +1,11 @@ package server import ( - "context" - "encoding" "encoding/hex" "encoding/json" + "errors" "fmt" "net/http" - "reflect" "strconv" "strings" @@ -25,7 +23,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{ HTTPRequest: req, }) - args, err := parseURLParams(ctx, rpcFunc, req) + args, err := parseURLParams(rpcFunc.args, req) if err != nil { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusBadRequest) @@ -33,10 +31,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit return } jreq := rpctypes.NewRequest(uriReqID) - outs := rpcFunc.f.Call(args) - - logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs) - result, err := unreflectResult(outs) + result, err := rpcFunc.Call(ctx, args) if err == nil { writeHTTPResponse(w, logger, jreq.MakeResponse(result)) } else { @@ -45,7 +40,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit } } -func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]reflect.Value, error) { +func parseURLParams(args []argInfo, req *http.Request) ([]byte, error) { if err := req.ParseForm(); err != nil { return nil, fmt.Errorf("invalid HTTP request: %w", err) } @@ -56,112 +51,43 @@ func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]refl return "", false } - vals := make([]reflect.Value, len(rf.argNames)+1) - vals[0] = reflect.ValueOf(ctx) - for i, name := range rf.argNames { - atype := rf.args[i+1] - - text, ok := getArg(name) + params := make(map[string]interface{}) + for _, arg := range args { + v, ok := getArg(arg.name) if !ok { - vals[i+1] = reflect.Zero(atype) continue } - - val, err := parseArgValue(atype, text) - if err != nil { - return nil, fmt.Errorf("decoding parameter %q: %w", name, err) + if z, err := decodeInteger(v); err == nil { + params[arg.name] = z + } else if b, err := strconv.ParseBool(v); err == nil { + params[arg.name] = b + } else if lc := strings.ToLower(v); strings.HasPrefix(lc, "0x") { + dec, err := hex.DecodeString(lc[2:]) + if err != nil { + return nil, fmt.Errorf("invalid hex string: %w", err) + } else if len(dec) == 0 { + return nil, errors.New("invalid empty hex string") + } + if arg.isBinary { + params[arg.name] = dec + } else { + params[arg.name] = string(dec) + } + } else if isQuotedString(v) { + var dec string + if err := json.Unmarshal([]byte(v), &dec); err != nil { + return nil, fmt.Errorf("invalid quoted string: %w", err) + } + if arg.isBinary { + params[arg.name] = []byte(dec) + } else { + params[arg.name] = dec + } + } else { + params[arg.name] = v } - vals[i+1] = val - } - return vals, nil -} - -func parseArgValue(atype reflect.Type, text string) (reflect.Value, error) { - // Regardless whether the argument is a pointer type, allocate a pointer so - // we can set the computed value. - var out reflect.Value - isPtr := atype.Kind() == reflect.Ptr - if isPtr { - out = reflect.New(atype.Elem()) - } else { - out = reflect.New(atype) - } - - baseType := out.Type().Elem() - if isIntType(baseType) { - // Integral type: Require a base-10 digit string. For compatibility with - // existing use allow quotation marks. - v, err := decodeInteger(text) - if err != nil { - return reflect.Value{}, fmt.Errorf("invalid integer: %w", err) - } - out.Elem().Set(reflect.ValueOf(v).Convert(baseType)) - - } else if isStringOrBytes(baseType) { - // String or byte slice: Check for quotes, hex encoding. - dec, err := decodeString(text) - if err != nil { - return reflect.Value{}, err - } - out.Elem().Set(reflect.ValueOf(dec).Convert(baseType)) - - } else if baseType.Kind() == reflect.Bool { - b, err := strconv.ParseBool(text) - if err != nil { - return reflect.Value{}, fmt.Errorf("invalid boolean: %w", err) - } - out.Elem().Set(reflect.ValueOf(b)) - - } else if out.Type().Implements(textUnmarshalerType) { - s, err := decodeString(text) - if err != nil { - return reflect.Value{}, err - } - v := reflect.New(baseType) - dec := v.Interface().(encoding.TextUnmarshaler) - if err := dec.UnmarshalText(s); err != nil { - return reflect.Value{}, fmt.Errorf("invalid text: %w", err) - } - out.Elem().Set(v.Elem()) - - } else { - // We don't know how to represent other types. - return reflect.Value{}, fmt.Errorf("unsupported argument type %v", baseType) - } - - // If the argument wants a pointer, return the value as-is, otherwise - // indirect the pointer back off. - if isPtr { - return out, nil - } - return out.Elem(), nil -} - -var ( - uint64Type = reflect.TypeOf(uint64(0)) - textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() -) - -// isIntType reports whether atype is an integer-shaped type. -func isIntType(atype reflect.Type) bool { - switch atype.Kind() { - case reflect.Float32, reflect.Float64: - return false - default: - return atype.ConvertibleTo(uint64Type) - } -} - -// isStringOrBytes reports whether atype is a string or []byte. -func isStringOrBytes(atype reflect.Type) bool { - switch atype.Kind() { - case reflect.String: - return true - case reflect.Slice: - return atype.Elem().Kind() == reflect.Uint8 - default: - return false } + return json.Marshal(params) } // isQuotedString reports whether s is enclosed in double quotes. @@ -177,19 +103,3 @@ func decodeInteger(s string) (int64, error) { } return strconv.ParseInt(s, 10, 64) } - -// decodeString decodes s into a byte slice. If s has an 0x prefix, it is -// treated as a hex-encoded string. If it is "double quoted" it is treated as a -// JSON string value. Otherwise, s is converted to bytes directly. -func decodeString(s string) ([]byte, error) { - if lc := strings.ToLower(s); strings.HasPrefix(lc, "0x") { - return hex.DecodeString(lc[2:]) - } else if isQuotedString(s) { - var dec string - if err := json.Unmarshal([]byte(s), &dec); err != nil { - return nil, fmt.Errorf("invalid quoted string: %w", err) - } - return []byte(dec), nil - } - return []byte(s), nil -} diff --git a/rpc/jsonrpc/server/parse_test.go b/rpc/jsonrpc/server/parse_test.go index ee0ab5d79..4a0e92ad1 100644 --- a/rpc/jsonrpc/server/parse_test.go +++ b/rpc/jsonrpc/server/parse_test.go @@ -3,7 +3,6 @@ package server import ( "context" "encoding/json" - "fmt" "net/http" "strconv" "testing" @@ -134,8 +133,12 @@ func TestParseJSONArray(t *testing.T) { } func TestParseJSONRPC(t *testing.T) { - demo := func(ctx context.Context, height int, name string) error { return nil } - call := NewRPCFunc(demo, "height", "name") + type demoArgs struct { + Height int `json:"height,string"` + Name string `json:"name"` + } + demo := func(ctx context.Context, _ *demoArgs) error { return nil } + rfunc := NewRPCFunc(demo) cases := []struct { raw string @@ -156,14 +159,16 @@ func TestParseJSONRPC(t *testing.T) { ctx := context.Background() for idx, tc := range cases { i := strconv.Itoa(idx) - vals, err := parseParams(ctx, call, []byte(tc.raw)) + vals, err := rfunc.parseParams(ctx, []byte(tc.raw)) if tc.fail { assert.Error(t, err, i) } else { assert.NoError(t, err, "%s: %+v", i, err) - if assert.Equal(t, 3, len(vals), i) { // ctx, height, name - assert.Equal(t, tc.height, vals[1].Int(), i) - assert.Equal(t, tc.name, vals[2].String(), i) + assert.Equal(t, 2, len(vals), i) + p, ok := vals[1].Interface().(*demoArgs) + if assert.True(t, ok) { + assert.Equal(t, tc.height, int64(p.Height), i) + assert.Equal(t, tc.name, p.Name, i) } } @@ -171,50 +176,153 @@ func TestParseJSONRPC(t *testing.T) { } func TestParseURI(t *testing.T) { - demo := func(ctx context.Context, height int, name string) error { return nil } - call := NewRPCFunc(demo, "height", "name") + // URI parameter parsing happens in two phases: + // + // Phase 1 swizzles the query parameters into JSON. The result of this + // phase must be valid JSON, but may fail the second stage. + // + // Phase 2 decodes the JSON to obtain the actual arguments. A failure at + // this stage means the JSON is not compatible with the target. - cases := []struct { - raw []string - height int64 - name string - fail bool - }{ - // can parse numbers unquoted and strings quoted - {[]string{"7", `"flew"`}, 7, "flew", false}, - {[]string{"22", `"john"`}, 22, "john", false}, - {[]string{"-10", `"bob"`}, -10, "bob", false}, - // can parse numbers quoted, too - {[]string{`"7"`, `"flew"`}, 7, "flew", false}, - {[]string{`"-10"`, `"bob"`}, -10, "bob", false}, - // can parse strings hex-escaped, in either case - {[]string{`-9`, `0x626f62`}, -9, "bob", false}, - {[]string{`-9`, `0X646F7567`}, -9, "doug", false}, - // can parse strings unquoted (as per OpenAPI docs) - {[]string{`0`, `hey you`}, 0, "hey you", false}, - // fail for invalid numbers, strings, hex - {[]string{`"-xx"`, `bob`}, 0, "", true}, // bad number - {[]string{`"95""`, `"bob`}, 0, "", true}, // bad string - {[]string{`15`, `0xa`}, 0, "", true}, // bad hex - } - for idx, tc := range cases { - i := strconv.Itoa(idx) - // data := []byte(tc.raw) - url := fmt.Sprintf( - "test.com/method?height=%v&name=%v", - tc.raw[0], tc.raw[1]) - req, err := http.NewRequest("GET", url, nil) - assert.NoError(t, err) - vals, err := parseURLParams(context.Background(), call, req) - if tc.fail { - assert.Error(t, err, i) - } else { - assert.NoError(t, err, "%s: %+v", i, err) - if assert.Equal(t, 3, len(vals), i) { - assert.Equal(t, tc.height, vals[1].Int(), i) - assert.Equal(t, tc.name, vals[2].String(), i) - } + t.Run("Swizzle", func(t *testing.T) { + tests := []struct { + name string + url string + args []argInfo + want string + fail bool + }{ + { + name: "quoted numbers and strings", + url: `http://localhost?num="7"&str="flew"&neg="-10"`, + args: []argInfo{{name: "neg"}, {name: "num"}, {name: "str"}, {name: "other"}}, + want: `{"neg":-10,"num":7,"str":"flew"}`, + }, + { + name: "unquoted numbers and strings", + url: `http://localhost?num1=7&str1=cabbage&num2=-199&str2=hey+you`, + args: []argInfo{{name: "num1"}, {name: "num2"}, {name: "str1"}, {name: "str2"}, {name: "other"}}, + want: `{"num1":7,"num2":-199,"str1":"cabbage","str2":"hey you"}`, + }, + { + name: "quoted byte strings", + url: `http://localhost?left="Fahrvergnügen"&right="Applesauce"`, + args: []argInfo{{name: "left", isBinary: true}, {name: "right", isBinary: false}}, + want: `{"left":"RmFocnZlcmduw7xnZW4=","right":"Applesauce"}`, + }, + { + name: "hexadecimal byte strings", + url: `http://localhost?lower=0x626f62&upper=0X646F7567`, + args: []argInfo{{name: "upper", isBinary: true}, {name: "lower", isBinary: false}, {name: "other"}}, + want: `{"lower":"bob","upper":"ZG91Zw=="}`, + }, + { + name: "invalid hex odd length", + url: `http://localhost?bad=0xa`, + args: []argInfo{{name: "bad"}, {name: "superbad"}}, + fail: true, + }, + { + name: "invalid hex empty", + url: `http://localhost?bad=0x`, + args: []argInfo{{name: "bad"}}, + fail: true, + }, + { + name: "invalid quoted string", + url: `http://localhost?bad="double""`, + args: []argInfo{{name: "bad"}}, + fail: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + hreq, err := http.NewRequest("GET", test.url, nil) + if err != nil { + t.Fatalf("NewRequest for %q: %v", test.url, err) + } + + bits, err := parseURLParams(test.args, hreq) + if err != nil && !test.fail { + t.Fatalf("Parse %q: unexpected error: %v", test.url, err) + } else if err == nil && test.fail { + t.Fatalf("Parse %q: got %#q, wanted error", test.url, string(bits)) + } + if got := string(bits); got != test.want { + t.Errorf("Parse %q: got %#q, want %#q", test.url, got, test.want) + } + }) + } + }) + + t.Run("Decode", func(t *testing.T) { + type argValue struct { + Height json.Number `json:"height"` + Name string `json:"name"` + Flag bool `json:"flag"` } - } + echo := NewRPCFunc(func(_ context.Context, arg *argValue) (*argValue, error) { + return arg, nil + }) + + tests := []struct { + name string + url string + fail string + want interface{} + }{ + { + name: "valid all args", + url: `http://localhost?height=235&flag=true&name="bogart"`, + want: &argValue{ + Height: "235", + Flag: true, + Name: "bogart", + }, + }, + { + name: "valid partial args", + url: `http://localhost?height="1987"&name=free+willy`, + want: &argValue{ + Height: "1987", + Name: "free willy", + }, + }, + { + name: "invalid quoted number", + url: `http://localhost?height="-xx"`, + fail: "invalid number literal", + }, + { + name: "invalid unquoted number", + url: `http://localhost?height=25*q`, + fail: "invalid number literal", + }, + { + name: "invalid boolean", + url: `http://localhost?flag="garbage"`, + fail: "flag of type bool", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + hreq, err := http.NewRequest("GET", test.url, nil) + if err != nil { + t.Fatalf("NewRequest for %q: %v", test.url, err) + } + bits, err := parseURLParams(echo.args, hreq) + if err != nil { + t.Fatalf("Parse %#q: unexpected error: %v", test.url, err) + } + rsp, err := echo.Call(context.Background(), bits) + if test.want != nil { + assert.Equal(t, test.want, rsp) + } + if test.fail != "" { + assert.ErrorContains(t, err, test.fail) + } + }) + } + }) } diff --git a/rpc/jsonrpc/server/rpc_func.go b/rpc/jsonrpc/server/rpc_func.go index a58973c6e..8eba28728 100644 --- a/rpc/jsonrpc/server/rpc_func.go +++ b/rpc/jsonrpc/server/rpc_func.go @@ -1,13 +1,17 @@ package server import ( + "bytes" "context" + "encoding/json" "errors" "fmt" "net/http" "reflect" + "strings" "github.com/tendermint/tendermint/libs/log" + rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) // RegisterRPCFuncs adds a route to mux for each non-websocket function in the @@ -28,27 +32,106 @@ func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger lo // RPCFunc contains the introspected type information for a function. type RPCFunc struct { - f reflect.Value // underlying rpc function - args []reflect.Type // type of each function arg - returns []reflect.Type // type of each return arg - argNames []string // name of each argument - ws bool // websocket only + f reflect.Value // underlying rpc function + param reflect.Type // the parameter struct, or nil + result reflect.Type // the non-error result type, or nil + args []argInfo // names and type information (for URL decoding) + ws bool // websocket only +} + +// argInfo records the name of a field, along with a bit to tell whether the +// value of the field requires binary data, having underlying type []byte. The +// flag is needed when decoding URL parameters, where we permit quoted strings +// to be passed for either argument type. +type argInfo struct { + name string + isBinary bool // value wants binary data +} + +// Call parses the given JSON parameters and calls the function wrapped by rf +// with the resulting argument value. It reports an error if parameter parsing +// fails, otherwise it returns the result from the wrapped function. +func (rf *RPCFunc) Call(ctx context.Context, params json.RawMessage) (interface{}, error) { + args, err := rf.parseParams(ctx, params) + if err != nil { + return nil, err + } + returns := rf.f.Call(args) + + // Case 1: There is no non-error result type. + if rf.result == nil { + if oerr := returns[0].Interface(); oerr != nil { + return nil, oerr.(error) + } + return nil, nil + } + + // Case 2: There is a non-error result. + if oerr := returns[1].Interface(); oerr != nil { + // In case of error, report the error and ignore the result. + return nil, oerr.(error) + } + return returns[0].Interface(), nil +} + +// parseParams parses the parameters of a JSON-RPC request and returns the +// corresponding argument values. On success, the first argument value will be +// the value of ctx. +func (rf *RPCFunc) parseParams(ctx context.Context, params json.RawMessage) ([]reflect.Value, error) { + // If rf does not accept parameters, there is no decoding to do, but verify + // that no parameters were passed. + if rf.param == nil { + if !isNullOrEmpty(params) { + return nil, invalidParamsError("no parameters accepted for this method") + } + return []reflect.Value{reflect.ValueOf(ctx)}, nil + } + bits, err := rf.adjustParams(params) + if err != nil { + return nil, invalidParamsError(err.Error()) + } + arg := reflect.New(rf.param) + if err := json.Unmarshal(bits, arg.Interface()); err != nil { + return nil, invalidParamsError(err.Error()) + } + return []reflect.Value{reflect.ValueOf(ctx), arg}, nil +} + +// adjustParams checks whether data is encoded as a JSON array, and if so +// adjusts the values to match the corresponding parameter names. +func (rf *RPCFunc) adjustParams(data []byte) (json.RawMessage, error) { + base := bytes.TrimSpace(data) + if bytes.HasPrefix(base, []byte("[")) { + var args []json.RawMessage + if err := json.Unmarshal(base, &args); err != nil { + return nil, err + } else if len(args) != len(rf.args) { + return nil, fmt.Errorf("got %d arguments, want %d", len(args), len(rf.args)) + } + m := make(map[string]json.RawMessage) + for i, arg := range args { + m[rf.args[i].name] = arg + } + return json.Marshal(m) + } else if bytes.HasPrefix(base, []byte("{")) || bytes.Equal(base, []byte("null")) { + return base, nil + } + return nil, errors.New("parameters must be an object or an array") + } // NewRPCFunc constructs an RPCFunc for f, which must be a function whose type // signature matches one of these schemes: // -// func(context.Context, T1, T2, ...) error -// func(context.Context, T1, T2, ...) (R, error) +// func(context.Context) error +// func(context.Context) (R, error) +// func(context.Context, *T) error +// func(context.Context, *T) (R, error) // -// for arbitrary types T_i and R. The number of argNames must exactly match the -// number of non-context arguments to f. Otherwise, NewRPCFunc panics. -// -// The parameter names given are used to map JSON object keys to the -// corresonding parameter of the function. The names do not need to match the -// declared names, but must match what the client sends in a request. -func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc { - rf, err := newRPCFunc(f, argNames) +// for an arbitrary struct type T and type R. NewRPCFunc will panic if f does +// not have one of these forms. +func NewRPCFunc(f interface{}) *RPCFunc { + rf, err := newRPCFunc(f) if err != nil { panic("invalid RPC function: " + err.Error()) } @@ -57,8 +140,8 @@ func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc { // NewWSRPCFunc behaves as NewRPCFunc, but marks the resulting function for use // via websocket. -func NewWSRPCFunc(f interface{}, argNames ...string) *RPCFunc { - rf := NewRPCFunc(f, argNames...) +func NewWSRPCFunc(f interface{}) *RPCFunc { + rf := NewRPCFunc(f) rf.ws = true return rf } @@ -69,7 +152,7 @@ var ( ) // newRPCFunc constructs an RPCFunc for f. See the comment at NewRPCFunc. -func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) { +func newRPCFunc(f interface{}) (*RPCFunc, error) { if f == nil { return nil, errors.New("nil function") } @@ -80,49 +163,85 @@ func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) { return nil, errors.New("not a function") } + var ptype reflect.Type ft := fv.Type() - if np := ft.NumIn(); np == 0 { + if np := ft.NumIn(); np == 0 || np > 2 { return nil, errors.New("wrong number of parameters") } else if ft.In(0) != ctxType { return nil, errors.New("first parameter is not context.Context") - } else if np-1 != len(argNames) { - return nil, fmt.Errorf("have %d names for %d parameters", len(argNames), np-1) + } else if np == 2 { + ptype = ft.In(1) + if ptype.Kind() != reflect.Ptr { + return nil, errors.New("parameter type is not a pointer") + } + ptype = ptype.Elem() + if ptype.Kind() != reflect.Struct { + return nil, errors.New("parameter type is not a struct") + } } + var rtype reflect.Type if no := ft.NumOut(); no < 1 || no > 2 { return nil, errors.New("wrong number of results") } else if ft.Out(no-1) != errType { return nil, errors.New("last result is not error") + } else if no == 2 { + rtype = ft.Out(0) } - args := make([]reflect.Type, ft.NumIn()) - for i := 0; i < ft.NumIn(); i++ { - args[i] = ft.In(i) - } - outs := make([]reflect.Type, ft.NumOut()) - for i := 0; i < ft.NumOut(); i++ { - outs[i] = ft.Out(i) + var args []argInfo + if ptype != nil { + for i := 0; i < ptype.NumField(); i++ { + field := ptype.Field(i) + if tag := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]; tag != "" && tag != "-" { + args = append(args, argInfo{ + name: tag, + isBinary: isByteArray(field.Type), + }) + } else if tag == "-" { + // If the tag is "-" the field should explicitly be ignored, even + // if it is otherwise eligible. + } else if field.IsExported() && !field.Anonymous { + // Examples: Name → name, MaxEffort → maxEffort. + // Note that this is an aesthetic choice; the standard decoder will + // match without regard to case anyway. + name := strings.ToLower(field.Name[:1]) + field.Name[1:] + args = append(args, argInfo{ + name: name, + isBinary: isByteArray(field.Type), + }) + } + } } + return &RPCFunc{ - f: fv, - args: args, - returns: outs, - argNames: argNames, + f: fv, + param: ptype, + result: rtype, + args: args, }, nil } -//------------------------------------------------------------- - -// NOTE: assume returns is result struct and error. If error is not nil, return it -func unreflectResult(returns []reflect.Value) (interface{}, error) { - errV := returns[1] - if err, ok := errV.Interface().(error); ok && err != nil { - return nil, err +// invalidParamsError returns an RPC invalid parameters error with the given +// detail message. +func invalidParamsError(msg string, args ...interface{}) error { + return &rpctypes.RPCError{ + Code: int(rpctypes.CodeInvalidParams), + Message: rpctypes.CodeInvalidParams.String(), + Data: fmt.Sprintf(msg, args...), } - rv := returns[0] - // the result is a registered interface, - // we need a pointer to it so we can marshal with type byte - rvp := reflect.New(rv.Type()) - rvp.Elem().Set(rv) - return rvp.Interface(), nil +} + +// isNullOrEmpty reports whether params is either itself empty or represents an +// empty parameter (null, empty object, or empty array). +func isNullOrEmpty(params json.RawMessage) bool { + return len(params) == 0 || + bytes.Equal(params, []byte("null")) || + bytes.Equal(params, []byte("{}")) || + bytes.Equal(params, []byte("[]")) +} + +// isByteArray reports whether t is (equivalent to) []byte. +func isByteArray(t reflect.Type) bool { + return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 } diff --git a/rpc/jsonrpc/server/ws_handler.go b/rpc/jsonrpc/server/ws_handler.go index 3fc86b86f..3a259757b 100644 --- a/rpc/jsonrpc/server/ws_handler.go +++ b/rpc/jsonrpc/server/ws_handler.go @@ -36,11 +36,7 @@ type WebsocketManager struct { // NewWebsocketManager returns a new WebsocketManager that passes a map of // functions, connection options and logger to new WS connections. -func NewWebsocketManager( - logger log.Logger, - funcMap map[string]*RPCFunc, - wsConnOptions ...func(*wsConnection), -) *WebsocketManager { +func NewWebsocketManager(logger log.Logger, funcMap map[string]*RPCFunc, wsConnOptions ...func(*wsConnection)) *WebsocketManager { return &WebsocketManager{ funcMap: funcMap, Upgrader: websocket.Upgrader{ @@ -137,12 +133,7 @@ type wsConnection struct { // description of how to configure ping period and pong wait time. NOTE: if the // write buffer is full, pongs may be dropped, which may cause clients to // disconnect. see https://github.com/gorilla/websocket/issues/97 -func newWSConnection( - baseConn *websocket.Conn, - funcMap map[string]*RPCFunc, - logger log.Logger, - options ...func(*wsConnection), -) *wsConnection { +func newWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, logger log.Logger, options ...func(*wsConnection)) *wsConnection { wsc := &wsConnection{ Logger: logger, remoteAddr: baseConn.RemoteAddr().String(), @@ -340,22 +331,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { RPCRequest: &request, WSConn: wsc, }) - args, err := parseParams(fctx, rpcFunc, request.Params) - if err != nil { - if err := wsc.WriteRPCResponse(writeCtx, request.MakeErrorf(rpctypes.CodeInvalidParams, - "converting JSON parameters: %v", err)); err != nil { - wsc.Logger.Error("error writing RPC response", "err", err) - } - continue - } - - returns := rpcFunc.f.Call(args) - - // TODO: Need to encode args/returns to string if we want to log them - wsc.Logger.Info("WSJSONRPC", "method", request.Method) - var resp rpctypes.RPCResponse - result, err := unreflectResult(returns) + result, err := rpcFunc.Call(fctx, request.Params) if err == nil { resp = request.MakeResponse(result) } else { diff --git a/rpc/jsonrpc/server/ws_handler_test.go b/rpc/jsonrpc/server/ws_handler_test.go index ae73a953b..ce1bcd973 100644 --- a/rpc/jsonrpc/server/ws_handler_test.go +++ b/rpc/jsonrpc/server/ws_handler_test.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "net/http" "net/http/httptest" "testing" @@ -44,8 +45,12 @@ func TestWebsocketManagerHandler(t *testing.T) { } func newWSServer(t *testing.T, logger log.Logger) *httptest.Server { + type args struct { + S string `json:"s"` + I json.Number `json:"i"` + } funcMap := map[string]*RPCFunc{ - "c": NewWSRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"), + "c": NewWSRPCFunc(func(context.Context, *args) (string, error) { return "foo", nil }), } wm := NewWebsocketManager(logger, funcMap) diff --git a/rpc/jsonrpc/test/main.go b/rpc/jsonrpc/test/main.go index 07a3b28f3..2ed013c17 100644 --- a/rpc/jsonrpc/test/main.go +++ b/rpc/jsonrpc/test/main.go @@ -14,7 +14,7 @@ import ( ) var routes = map[string]*rpcserver.RPCFunc{ - "hello_world": rpcserver.NewRPCFunc(HelloWorld, "name", "num"), + "hello_world": rpcserver.NewRPCFunc(HelloWorld), } func HelloWorld(ctx context.Context, name string, num int) (Result, error) { diff --git a/scripts/abci-gen.sh b/scripts/abci-gen.sh deleted file mode 100755 index f4666dee9..000000000 --- a/scripts/abci-gen.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -# This file was added during development of ABCI++. This file is a script to allow -# the intermediate proto files to be built while active development proceeds -# on ABCI++. -# This file should be removed when work on ABCI++ is complete. -# For more information, see https://github.com/tendermint/tendermint/issues/8066. -set -euo pipefail - -cp ./proto/tendermint/abci/types.proto.intermediate ./proto/tendermint/abci/types.proto -cp ./proto/tendermint/types/types.proto.intermediate ./proto/tendermint/types/types.proto - -MODNAME="$(go list -m)" -find ./proto/tendermint -name '*.proto' -not -path "./proto/tendermint/abci/types.proto" \ - -exec sh ./scripts/protopackage.sh {} "$MODNAME" ';' - -sh ./scripts/protopackage.sh ./proto/tendermint/abci/types.proto $MODNAME "abci/types" - -make proto-gen - -echo "proto files have been compiled" - -echo "checking out copied files" - -find proto/tendermint/ -name '*.proto' -not -path "*.intermediate"\ - | xargs -I {} git checkout {} - -find proto/tendermint/ -name '*.pb.go' \ - | xargs -I {} git checkout {} diff --git a/scripts/confix/confix_test.go b/scripts/confix/confix_test.go index b7be1247d..ec258f4ca 100644 --- a/scripts/confix/confix_test.go +++ b/scripts/confix/confix_test.go @@ -25,6 +25,7 @@ func TestGuessConfigVersion(t *testing.T) { path, want string }{ {"testdata/non-config.toml", ""}, + {"testdata/v30-config.toml", ""}, {"testdata/v31-config.toml", ""}, {"testdata/v32-config.toml", "v0.32"}, {"testdata/v33-config.toml", "v0.33"}, diff --git a/scripts/confix/testdata/README.md b/scripts/confix/testdata/README.md new file mode 100644 index 000000000..5bbfa795f --- /dev/null +++ b/scripts/confix/testdata/README.md @@ -0,0 +1,52 @@ +# Test data for `confix` and `condiff` + +The files in this directory are stock Tendermint configuration files generated +by the last point release of each version series from v0.26 to present, along +with diffs between consecutive versions. + +## Config Samples + +The files named `vXX-config.toml` were generated by checking out and building +the corresponding version of Tendermint v0.xx.y and initializing a new node in +an empty home directory. The resulting `config.toml` file was copied here. +The exact build instructions vary a bit, but a general repro looks like: + +```shell +# This example uses v0.31, substitute the version of your choice. +# Note that the branch names and tags may differ. +# Versions prior to v0.26 may not build. +git checkout v0.31.9 +git clean -fdx + +# Versions prior to v0.32 do not have Go module files. +# Those that do may need some dependencies manually updated. +go mod init github.com/tendermint/tendermint +go mod tidy +go get golang.org/x/sys + +# Once you sort out the dependencies, this should usually work. +make build + +# Confirm you go the version you expected, and generate the file. +./build/tendermint --home=tmhome version +./build/tendermint --home=tmhome init + +# Copy the file out. +cp ./tmhome/config/config.toml v31-config.toml +``` + +## Version Diffs + +The files named `diff-XX-YY.txt` were generated by using the `condiff` tool on +the config samples for versions v0.XX and v0.YY: + +```shell +go run ./scripts/confix/condiff -desnake vXX-config vYY-config.toml > diff-XX-YY.txt +``` + +The `baseline.txt` was computed in the same way, but using an empty starting +file so that we capture all the settings in the target: + +```shell +go run ./scripts/confix/condiff -desnake /dev/null v26-config.toml > baseline.txt +``` diff --git a/scripts/confix/testdata/baseline.txt b/scripts/confix/testdata/baseline.txt new file mode 100644 index 000000000..421343704 --- /dev/null +++ b/scripts/confix/testdata/baseline.txt @@ -0,0 +1,73 @@ ++M abci ++M db-backend ++M db-dir ++M fast-sync ++M filter-peers ++M genesis-file ++M log-format ++M log-level ++M moniker ++M node-key-file ++M priv-validator-file ++M priv-validator-laddr ++M prof-laddr ++M proxy-app ++S consensus ++M consensus.wal-file ++M consensus.timeout-propose ++M consensus.timeout-propose-delta ++M consensus.timeout-prevote ++M consensus.timeout-prevote-delta ++M consensus.timeout-precommit ++M consensus.timeout-precommit-delta ++M consensus.timeout-commit ++M consensus.skip-timeout-commit ++M consensus.create-empty-blocks ++M consensus.create-empty-blocks-interval ++M consensus.peer-gossip-sleep-duration ++M consensus.peer-query-maj23-sleep-duration ++M consensus.blocktime-iota ++S instrumentation ++M instrumentation.prometheus ++M instrumentation.prometheus-listen-addr ++M instrumentation.max-open-connections ++M instrumentation.namespace ++S mempool ++M mempool.recheck ++M mempool.broadcast ++M mempool.wal-dir ++M mempool.size ++M mempool.cache-size ++S p2p ++M p2p.laddr ++M p2p.external-address ++M p2p.seeds ++M p2p.persistent-peers ++M p2p.upnp ++M p2p.addr-book-file ++M p2p.addr-book-strict ++M p2p.max-num-inbound-peers ++M p2p.max-num-outbound-peers ++M p2p.flush-throttle-timeout ++M p2p.max-packet-msg-payload-size ++M p2p.send-rate ++M p2p.recv-rate ++M p2p.pex ++M p2p.seed-mode ++M p2p.private-peer-ids ++M p2p.allow-duplicate-ip ++M p2p.handshake-timeout ++M p2p.dial-timeout ++S rpc ++M rpc.laddr ++M rpc.cors-allowed-origins ++M rpc.cors-allowed-methods ++M rpc.cors-allowed-headers ++M rpc.grpc-laddr ++M rpc.grpc-max-open-connections ++M rpc.unsafe ++M rpc.max-open-connections ++S tx-index ++M tx-index.indexer ++M tx-index.index-tags ++M tx-index.index-all-tags diff --git a/test/fuzz/mempool/testdata/cases/empty b/scripts/confix/testdata/diff-26-27.txt similarity index 100% rename from test/fuzz/mempool/testdata/cases/empty rename to scripts/confix/testdata/diff-26-27.txt diff --git a/scripts/confix/testdata/diff-27-28.txt b/scripts/confix/testdata/diff-27-28.txt new file mode 100644 index 000000000..c5c00449d --- /dev/null +++ b/scripts/confix/testdata/diff-27-28.txt @@ -0,0 +1,3 @@ +-M priv-validator-file ++M priv-validator-key-file ++M priv-validator-state-file diff --git a/test/fuzz/p2p/secretconnection/testdata/cases/empty b/scripts/confix/testdata/diff-28-29.txt similarity index 100% rename from test/fuzz/p2p/secretconnection/testdata/cases/empty rename to scripts/confix/testdata/diff-28-29.txt diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/empty b/scripts/confix/testdata/diff-29-30.txt similarity index 100% rename from test/fuzz/rpc/jsonrpc/server/testdata/cases/empty rename to scripts/confix/testdata/diff-29-30.txt diff --git a/scripts/confix/testdata/diff-30-31.txt b/scripts/confix/testdata/diff-30-31.txt new file mode 100644 index 000000000..0f93b761e --- /dev/null +++ b/scripts/confix/testdata/diff-30-31.txt @@ -0,0 +1,7 @@ +-M consensus.blocktime-iota ++M mempool.max-txs-bytes ++M rpc.max-subscription-clients ++M rpc.max-subscriptions-per-client ++M rpc.timeout-broadcast-tx-commit ++M rpc.tls-cert-file ++M rpc.tls-key-file diff --git a/scripts/confix/testdata/v26-config.toml b/scripts/confix/testdata/v26-config.toml new file mode 100644 index 000000000..a9237056b --- /dev/null +++ b/scripts/confix/testdata/v26-config.toml @@ -0,0 +1,249 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_file = "config/priv_validator.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = "[]" + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = "[HEAD GET POST]" + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = "[Origin Accept Content-Type X-Requested-With X-Server-Time]" + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = true + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" (default) +# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept more significant number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v27-config.toml b/scripts/confix/testdata/v27-config.toml new file mode 100644 index 000000000..25e3b582f --- /dev/null +++ b/scripts/confix/testdata/v27-config.toml @@ -0,0 +1,249 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_file = "config/priv_validator.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = true + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v28-config.toml b/scripts/confix/testdata/v28-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v28-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v29-config.toml b/scripts/confix/testdata/v29-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v29-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/confix/testdata/v30-config.toml b/scripts/confix/testdata/v30-config.toml new file mode 100644 index 000000000..b4aaa5aae --- /dev/null +++ b/scripts/confix/testdata/v30-config.toml @@ -0,0 +1,252 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base config options ##### + +# TCP or UNIX socket address of the ABCI application, +# or the name of an ABCI application compiled in with the Tendermint binary +proxy_app = "tcp://127.0.0.1:26658" + +# A custom human readable name for this node +moniker = "localhost" + +# If this node is many blocks behind the tip of the chain, FastSync +# allows them to catchup quickly by downloading blocks in parallel +# and verifying their commits +fast_sync = true + +# Database backend: leveldb | memdb | cleveldb +db_backend = "leveldb" + +# Database directory +db_dir = "data" + +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" + +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + +##### additional base config options ##### + +# Path to the JSON file containing the initial validator set and other meta data +genesis_file = "config/genesis.json" + +# Path to the JSON file containing the private key to use as a validator in the consensus protocol +priv_validator_key_file = "config/priv_validator_key.json" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "data/priv_validator_state.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" + +# Mechanism to connect to the ABCI application: socket | grpc +abci = "socket" + +# TCP or UNIX socket address for the profiling server to listen on +prof_laddr = "" + +# If true, query the ABCI app on connecting to a new peer +# so the app can decide if we should keep the connection or not +filter_peers = false + +##### advanced configuration options ##### + +##### rpc server configuration options ##### +[rpc] + +# TCP or UNIX socket address for the RPC server to listen on +laddr = "tcp://0.0.0.0:26657" + +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = [] + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = ["HEAD", "GET", "POST", ] + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ] + +# TCP or UNIX socket address for the gRPC server to listen on +# NOTE: This server only supports /broadcast_tx_commit +grpc_laddr = "" + +# Maximum number of simultaneous connections. +# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +grpc_max_open_connections = 900 + +# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool +unsafe = false + +# Maximum number of simultaneous connections (including WebSocket). +# Does not include gRPC connections. See grpc_max_open_connections +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} +# 1024 - 40 - 10 - 50 = 924 = ~900 +max_open_connections = 900 + +##### peer to peer configuration options ##### +[p2p] + +# Address to listen for incoming connections +laddr = "tcp://0.0.0.0:26656" + +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + +# Comma separated list of seed nodes to connect to +seeds = "" + +# Comma separated list of nodes to keep persistent connections to +persistent_peers = "" + +# UPNP port forwarding +upnp = false + +# Path to address book +addr_book_file = "config/addrbook.json" + +# Set true for strict address routability rules +# Set false for private or local networks +addr_book_strict = true + +# Maximum number of inbound peers +max_num_inbound_peers = 40 + +# Maximum number of outbound peers to connect to, excluding persistent peers +max_num_outbound_peers = 10 + +# Time to wait before flushing messages out on the connection +flush_throttle_timeout = "100ms" + +# Maximum size of a message packet payload, in bytes +max_packet_msg_payload_size = 1024 + +# Rate at which packets can be sent, in bytes/second +send_rate = 5120000 + +# Rate at which packets can be received, in bytes/second +recv_rate = 5120000 + +# Set true to enable the peer-exchange reactor +pex = true + +# Seed mode, in which node constantly crawls the network and looks for +# peers. If another node asks it for addresses, it responds and disconnects. +# +# Does not work if the peer-exchange reactor is disabled. +seed_mode = false + +# Comma separated list of peer IDs to keep private (will not be gossiped to other peers) +private_peer_ids = "" + +# Toggle to disable guard against peers connecting from the same ip. +allow_duplicate_ip = false + +# Peer connection configuration. +handshake_timeout = "20s" +dial_timeout = "3s" + +##### mempool configuration options ##### +[mempool] + +recheck = true +broadcast = true +wal_dir = "" + +# size of the mempool +size = 5000 + +# size of the cache (used to filter transactions we saw earlier) +cache_size = 10000 + +##### consensus configuration options ##### +[consensus] + +wal_file = "data/cs.wal/wal" + +timeout_propose = "3s" +timeout_propose_delta = "500ms" +timeout_prevote = "1s" +timeout_prevote_delta = "500ms" +timeout_precommit = "1s" +timeout_precommit_delta = "500ms" +timeout_commit = "1s" + +# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) +skip_timeout_commit = false + +# EmptyBlocks mode and possible interval between empty blocks +create_empty_blocks = true +create_empty_blocks_interval = "0s" + +# Reactor sleep duration parameters +peer_gossip_sleep_duration = "100ms" +peer_query_maj23_sleep_duration = "2s" + +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1s" + +##### transactions indexer configuration options ##### +[tx_index] + +# What indexer to use for transactions +# +# Options: +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +indexer = "kv" + +# Comma-separated list of tags to index (by default the only tag is "tx.hash") +# +# You can also index transactions by height by adding "tx.height" tag here. +# +# It's recommended to index only a subset of tags due to possible memory +# bloat. This is, of course, depends on the indexer's DB and the volume of +# transactions. +index_tags = "" + +# When set to true, tells indexer to index all tags (predefined tags: +# "tx.hash", "tx.height" and all tags from DeliverTx responses). +# +# Note this may be not desirable (see the comment above). IndexTags has a +# precedence over IndexAllTags (i.e. when given both, IndexTags will be +# indexed). +index_all_tags = false + +##### instrumentation configuration options ##### +[instrumentation] + +# When true, Prometheus metrics are served under /metrics on +# PrometheusListenAddr. +# Check out the documentation for the list of available metrics. +prometheus = false + +# Address to listen for Prometheus collector(s) connections +prometheus_listen_addr = ":26660" + +# Maximum number of simultaneous connections. +# If you want to accept a larger number than the default, make sure +# you increase your OS limits. +# 0 - unlimited. +max_open_connections = 3 + +# Instrumentation namespace +namespace = "tendermint" diff --git a/scripts/keymigrate/migrate.go b/scripts/keymigrate/migrate.go index ca2c528e2..a0b43aef6 100644 --- a/scripts/keymigrate/migrate.go +++ b/scripts/keymigrate/migrate.go @@ -86,27 +86,30 @@ const ( var prefixes = []struct { prefix []byte ktype keyType + check func(keyID) bool }{ - {[]byte("consensusParamsKey:"), consensusParamsKey}, - {[]byte("abciResponsesKey:"), abciResponsesKey}, - {[]byte("validatorsKey:"), validatorsKey}, - {[]byte("stateKey"), stateStoreKey}, - {[]byte("H:"), blockMetaKey}, - {[]byte("P:"), blockPartKey}, - {[]byte("C:"), commitKey}, - {[]byte("SC:"), seenCommitKey}, - {[]byte("BH:"), blockHashKey}, - {[]byte("size"), lightSizeKey}, - {[]byte("lb/"), lightBlockKey}, - {[]byte("\x00"), evidenceCommittedKey}, - {[]byte("\x01"), evidencePendingKey}, + {[]byte("consensusParamsKey:"), consensusParamsKey, nil}, + {[]byte("abciResponsesKey:"), abciResponsesKey, nil}, + {[]byte("validatorsKey:"), validatorsKey, nil}, + {[]byte("stateKey"), stateStoreKey, nil}, + {[]byte("H:"), blockMetaKey, nil}, + {[]byte("P:"), blockPartKey, nil}, + {[]byte("C:"), commitKey, nil}, + {[]byte("SC:"), seenCommitKey, nil}, + {[]byte("BH:"), blockHashKey, nil}, + {[]byte("size"), lightSizeKey, nil}, + {[]byte("lb/"), lightBlockKey, nil}, + {[]byte("\x00"), evidenceCommittedKey, checkEvidenceKey}, + {[]byte("\x01"), evidencePendingKey, checkEvidenceKey}, } // checkKeyType classifies a candidate key based on its structure. func checkKeyType(key keyID) keyType { for _, p := range prefixes { if bytes.HasPrefix(key, p.prefix) { - return p.ktype + if p.check == nil || p.check(key) { + return p.ktype + } } } @@ -342,6 +345,35 @@ func convertEvidence(key keyID, newPrefix int64) ([]byte, error) { return orderedcode.Append(nil, newPrefix, binary.BigEndian.Uint64(hb), string(evidenceHash)) } +// checkEvidenceKey reports whether a candidate key with one of the legacy +// evidence prefixes has the correct structure for a legacy evidence key. +// +// This check is needed because transaction hashes are stored without a prefix, +// so checking the one-byte prefix alone is not enough to distinguish them. +// Legacy evidence keys are suffixed with a string of the format: +// +// "%0.16X/%X" +// +// where the first element is the height and the second is the hash. Thus, we +// check +func checkEvidenceKey(key keyID) bool { + parts := bytes.SplitN(key[1:], []byte("/"), 2) + if len(parts) != 2 || len(parts[0]) != 16 || !isHex(parts[0]) || !isHex(parts[1]) { + return false + } + return true +} + +func isHex(data []byte) bool { + for _, b := range data { + if ('0' <= b && b <= '9') || ('a' <= b && b <= 'f') || ('A' <= b && b <= 'F') { + continue + } + return false + } + return len(data) != 0 +} + func replaceKey(db dbm.DB, key keyID, gooseFn migrateFunc) error { exists, err := db.Has(key) if err != nil { diff --git a/scripts/keymigrate/migrate_test.go b/scripts/keymigrate/migrate_test.go index b2727a5df..f7322b352 100644 --- a/scripts/keymigrate/migrate_test.go +++ b/scripts/keymigrate/migrate_test.go @@ -1,11 +1,11 @@ package keymigrate import ( - "bytes" "context" "errors" "fmt" "math" + "strings" "testing" "github.com/google/orderedcode" @@ -21,6 +21,7 @@ func makeKey(t *testing.T, elems ...interface{}) []byte { } func getLegacyPrefixKeys(val int) map[string][]byte { + vstr := fmt.Sprintf("%02x", byte(val)) return map[string][]byte{ "Height": []byte(fmt.Sprintf("H:%d", val)), "BlockPart": []byte(fmt.Sprintf("P:%d:%d", val, val)), @@ -40,14 +41,19 @@ func getLegacyPrefixKeys(val int) map[string][]byte { "UserKey1": []byte(fmt.Sprintf("foo/bar/baz/%d/%d", val, val)), "TxHeight": []byte(fmt.Sprintf("tx.height/%s/%d/%d", fmt.Sprint(val), val, val)), "TxHash": append( - bytes.Repeat([]byte{fmt.Sprint(val)[0]}, 16), - bytes.Repeat([]byte{fmt.Sprint(val)[len([]byte(fmt.Sprint(val)))-1]}, 16)..., + []byte(strings.Repeat(vstr[:1], 16)), + []byte(strings.Repeat(vstr[1:], 16))..., ), + + // Transaction hashes that could be mistaken for evidence keys. + "TxHashMimic0": append([]byte{0}, []byte(strings.Repeat(vstr, 16)[:31])...), + "TxHashMimic1": append([]byte{1}, []byte(strings.Repeat(vstr, 16)[:31])...), } } func getNewPrefixKeys(t *testing.T, val int) map[string][]byte { t.Helper() + vstr := fmt.Sprintf("%02x", byte(val)) return map[string][]byte{ "Height": makeKey(t, int64(0), int64(val)), "BlockPart": makeKey(t, int64(1), int64(val), int64(val)), @@ -66,7 +72,9 @@ func getNewPrefixKeys(t *testing.T, val int) map[string][]byte { "UserKey0": makeKey(t, "foo", "bar", int64(val), int64(val)), "UserKey1": makeKey(t, "foo", "bar/baz", int64(val), int64(val)), "TxHeight": makeKey(t, "tx.height", fmt.Sprint(val), int64(val), int64(val+2), int64(val+val)), - "TxHash": makeKey(t, "tx.hash", string(bytes.Repeat([]byte{[]byte(fmt.Sprint(val))[0]}, 32))), + "TxHash": makeKey(t, "tx.hash", strings.Repeat(vstr, 16)), + "TxHashMimic0": makeKey(t, "tx.hash", "\x00"+strings.Repeat(vstr, 16)[:31]), + "TxHashMimic1": makeKey(t, "tx.hash", "\x01"+strings.Repeat(vstr, 16)[:31]), } } diff --git a/scripts/metricsgen/metricsgen.go b/scripts/metricsgen/metricsgen.go new file mode 100644 index 000000000..70cb36a77 --- /dev/null +++ b/scripts/metricsgen/metricsgen.go @@ -0,0 +1,334 @@ +// metricsgen is a code generation tool for creating constructors for Tendermint +// metrics types. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "go/types" + "io" + "io/fs" + "log" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "text/template" +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `Usage: %[1]s -struct + +Generate constructors for the metrics type specified by -struct contained in +the current directory. The tool creates a new file in the current directory +containing the generated code. + +Options: +`, filepath.Base(os.Args[0])) + flag.PrintDefaults() + } +} + +const metricsPackageName = "github.com/go-kit/kit/metrics" + +const ( + metricNameTag = "metrics_name" + labelsTag = "metrics_labels" + bucketTypeTag = "metrics_buckettype" + bucketSizeTag = "metrics_bucketsizes" +) + +var ( + dir = flag.String("dir", ".", "Path to the directory containing the target package") + strct = flag.String("struct", "Metrics", "Struct to parse for metrics") +) + +var bucketType = map[string]string{ + "exprange": "stdprometheus.ExponentialBucketsRange", + "exp": "stdprometheus.ExponentialBuckets", + "lin": "stdprometheus.LinearBuckets", +} + +var tmpl = template.Must(template.New("tmpl").Parse(`// Code generated by metricsgen. DO NOT EDIT. + +package {{ .Package }} + +import ( + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +func PrometheusMetrics(namespace string, labelsAndValues...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + {{ range $metric := .ParsedMetrics }} + {{- $metric.FieldName }}: prometheus.New{{ $metric.TypeName }}From(stdprometheus.{{$metric.TypeName }}Opts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "{{$metric.MetricName }}", + Help: "{{ $metric.Description }}", + {{ if ne $metric.HistogramOptions.BucketType "" }} + Buckets: {{ $metric.HistogramOptions.BucketType }}({{ $metric.HistogramOptions.BucketSizes }}), + {{ else if ne $metric.HistogramOptions.BucketSizes "" }} + Buckets: []float64{ {{ $metric.HistogramOptions.BucketSizes }} }, + {{ end }} + {{- if eq (len $metric.Labels) 0 }} + }, labels).With(labelsAndValues...), + {{ else }} + }, append(labels, {{$metric.Labels | printf "%q" }})).With(labelsAndValues...), + {{- end }} + {{- end }} + } +} + + +func NopMetrics() *Metrics { + return &Metrics{ + {{- range $metric := .ParsedMetrics }} + {{ $metric.FieldName }}: discard.New{{ $metric.TypeName }}(), + {{- end }} + } +} +`)) + +// ParsedMetricField is the data parsed for a single field of a metric struct. +type ParsedMetricField struct { + TypeName string + FieldName string + MetricName string + Description string + Labels string + + HistogramOptions HistogramOpts +} + +type HistogramOpts struct { + BucketType string + BucketSizes string +} + +// TemplateData is all of the data required for rendering a metric file template. +type TemplateData struct { + Package string + ParsedMetrics []ParsedMetricField +} + +func main() { + flag.Parse() + if *strct == "" { + log.Fatal("You must specify a non-empty -struct") + } + td, err := ParseMetricsDir(".", *strct) + if err != nil { + log.Fatalf("Parsing file: %v", err) + } + out := filepath.Join(*dir, "metrics.gen.go") + f, err := os.Create(out) + if err != nil { + log.Fatalf("Opening file: %v", err) + } + err = GenerateMetricsFile(f, td) + if err != nil { + log.Fatalf("Generating code: %v", err) + } +} +func ignoreTestFiles(f fs.FileInfo) bool { + return !strings.Contains(f.Name(), "_test.go") +} + +// ParseMetricsDir parses the dir and scans for a struct matching structName, +// ignoring all test files. ParseMetricsDir iterates the fields of the metrics +// struct and builds a TemplateData using the data obtained from the abstract syntax tree. +func ParseMetricsDir(dir string, structName string) (TemplateData, error) { + fs := token.NewFileSet() + d, err := parser.ParseDir(fs, dir, ignoreTestFiles, parser.ParseComments) + if err != nil { + return TemplateData{}, err + } + if len(d) > 1 { + return TemplateData{}, fmt.Errorf("multiple packages found in %s", dir) + } + if len(d) == 0 { + return TemplateData{}, fmt.Errorf("no go pacakges found in %s", dir) + } + + // Grab the package name. + var pkgName string + var pkg *ast.Package + for pkgName, pkg = range d { + } + td := TemplateData{ + Package: pkgName, + } + // Grab the metrics struct + m, mPkgName, err := findMetricsStruct(pkg.Files, structName) + if err != nil { + return TemplateData{}, err + } + for _, f := range m.Fields.List { + if !isMetric(f.Type, mPkgName) { + continue + } + pmf := parseMetricField(f) + td.ParsedMetrics = append(td.ParsedMetrics, pmf) + } + + return td, err +} + +// GenerateMetricsFile executes the metrics file template, writing the result +// into the io.Writer. +func GenerateMetricsFile(w io.Writer, td TemplateData) error { + b := []byte{} + buf := bytes.NewBuffer(b) + err := tmpl.Execute(buf, td) + if err != nil { + return err + } + b, err = format.Source(buf.Bytes()) + if err != nil { + return err + } + _, err = io.Copy(w, bytes.NewBuffer(b)) + if err != nil { + return err + } + return nil +} + +func findMetricsStruct(files map[string]*ast.File, structName string) (*ast.StructType, string, error) { + var ( + st *ast.StructType + ) + for _, file := range files { + mPkgName, err := extractMetricsPackageName(file.Imports) + if err != nil { + return nil, "", fmt.Errorf("unable to determine metrics package name: %v", err) + } + if !ast.FilterFile(file, func(name string) bool { + return name == structName + }) { + continue + } + ast.Inspect(file, func(n ast.Node) bool { + switch f := n.(type) { + case *ast.TypeSpec: + if f.Name.Name == structName { + var ok bool + st, ok = f.Type.(*ast.StructType) + if !ok { + err = fmt.Errorf("found identifier for %q of wrong type", structName) + } + } + return false + default: + return true + } + }) + if err != nil { + return nil, "", err + } + if st != nil { + return st, mPkgName, nil + } + } + return nil, "", fmt.Errorf("target struct %q not found in dir", structName) +} + +func parseMetricField(f *ast.Field) ParsedMetricField { + var comment string + if f.Doc != nil { + for _, c := range f.Doc.List { + comment += strings.TrimPrefix(c.Text, "// ") + } + } + pmf := ParsedMetricField{ + Description: comment, + MetricName: extractFieldName(f.Names[0].String(), f.Tag), + FieldName: f.Names[0].String(), + TypeName: extractTypeName(f.Type), + Labels: extractLabels(f.Tag), + } + if pmf.TypeName == "Histogram" { + pmf.HistogramOptions = extractHistogramOptions(f.Tag) + } + return pmf +} + +func extractTypeName(e ast.Expr) string { + return strings.TrimPrefix(path.Ext(types.ExprString(e)), ".") +} + +func isMetric(e ast.Expr, mPkgName string) bool { + return strings.Contains(types.ExprString(e), fmt.Sprintf("%s.", mPkgName)) +} + +func extractLabels(bl *ast.BasicLit) string { + if bl != nil { + t := reflect.StructTag(strings.Trim(bl.Value, "`")) + if v := t.Get(labelsTag); v != "" { + return v + } + } + return "" +} + +func extractFieldName(name string, tag *ast.BasicLit) string { + if tag != nil { + t := reflect.StructTag(strings.Trim(tag.Value, "`")) + if v := t.Get(metricNameTag); v != "" { + return v + } + } + return toSnakeCase(name) +} + +func extractHistogramOptions(tag *ast.BasicLit) HistogramOpts { + h := HistogramOpts{} + if tag != nil { + t := reflect.StructTag(strings.Trim(tag.Value, "`")) + if v := t.Get(bucketTypeTag); v != "" { + h.BucketType = bucketType[v] + } + if v := t.Get(bucketSizeTag); v != "" { + h.BucketSizes = v + } + } + return h +} + +func extractMetricsPackageName(imports []*ast.ImportSpec) (string, error) { + for _, i := range imports { + u, err := strconv.Unquote(i.Path.Value) + if err != nil { + return "", err + } + if u == metricsPackageName { + if i.Name != nil { + return i.Name.Name, nil + } + return path.Base(u), nil + } + } + return "", nil +} + +var capitalChange = regexp.MustCompile("([a-z0-9])([A-Z])") + +func toSnakeCase(str string) string { + snake := capitalChange.ReplaceAllString(str, "${1}_${2}") + return strings.ToLower(snake) +} diff --git a/scripts/metricsgen/metricsgen_test.go b/scripts/metricsgen/metricsgen_test.go new file mode 100644 index 000000000..83251e651 --- /dev/null +++ b/scripts/metricsgen/metricsgen_test.go @@ -0,0 +1,259 @@ +package main_test + +import ( + "bytes" + "fmt" + "go/parser" + "go/token" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + metricsgen "github.com/tendermint/tendermint/scripts/metricsgen" +) + +const testDataDir = "./testdata" + +func TestSimpleTemplate(t *testing.T) { + m := metricsgen.ParsedMetricField{ + TypeName: "Histogram", + FieldName: "MyMetric", + MetricName: "request_count", + Description: "how many requests were made since the start of the process", + Labels: "first, second, third", + } + td := metricsgen.TemplateData{ + Package: "mypack", + ParsedMetrics: []metricsgen.ParsedMetricField{m}, + } + b := bytes.NewBuffer([]byte{}) + err := metricsgen.GenerateMetricsFile(b, td) + if err != nil { + t.Fatalf("unable to parse template %v", err) + } +} + +func TestFromData(t *testing.T) { + infos, err := ioutil.ReadDir(testDataDir) + if err != nil { + t.Fatalf("unable to open file %v", err) + } + for _, dir := range infos { + t.Run(dir.Name(), func(t *testing.T) { + if !dir.IsDir() { + t.Fatalf("expected file %s to be directory", dir.Name()) + } + dirName := path.Join(testDataDir, dir.Name()) + pt, err := metricsgen.ParseMetricsDir(dirName, "Metrics") + if err != nil { + t.Fatalf("unable to parse from dir %q: %v", dir, err) + } + outFile := path.Join(dirName, "out.go") + if err != nil { + t.Fatalf("unable to open file %s: %v", outFile, err) + } + of, err := os.Create(outFile) + if err != nil { + t.Fatalf("unable to open file %s: %v", outFile, err) + } + defer os.Remove(outFile) + if err := metricsgen.GenerateMetricsFile(of, pt); err != nil { + t.Fatalf("unable to generate metrics file %s: %v", outFile, err) + } + if _, err := parser.ParseFile(token.NewFileSet(), outFile, nil, parser.AllErrors); err != nil { + t.Fatalf("unable to parse generated file %s: %v", outFile, err) + } + bNew, err := ioutil.ReadFile(outFile) + if err != nil { + t.Fatalf("unable to read generated file %s: %v", outFile, err) + } + goldenFile := path.Join(dirName, "metrics.gen.go") + bOld, err := ioutil.ReadFile(goldenFile) + if err != nil { + t.Fatalf("unable to read file %s: %v", goldenFile, err) + } + if !bytes.Equal(bNew, bOld) { + t.Fatalf("newly generated code in file %s does not match golden file %s\n"+ + "if the output of the metricsgen tool is expected to change run the following make target: \n"+ + "\tmake metrics", outFile, goldenFile) + } + }) + } +} + +func TestParseMetricsStruct(t *testing.T) { + const pkgName = "mypkg" + metricsTests := []struct { + name string + shouldError bool + metricsStruct string + expected metricsgen.TemplateData + }{ + { + name: "basic", + metricsStruct: `type Metrics struct { + myGauge metrics.Gauge + }`, + expected: metricsgen.TemplateData{ + Package: pkgName, + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Gauge", + FieldName: "myGauge", + MetricName: "my_gauge", + }, + }, + }, + }, + { + name: "histogram", + metricsStruct: "type Metrics struct {\n" + + "myHistogram metrics.Histogram `metrics_buckettype:\"exp\" metrics_bucketsizes:\"1, 100, .8\"`\n" + + "}", + expected: metricsgen.TemplateData{ + Package: pkgName, + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Histogram", + FieldName: "myHistogram", + MetricName: "my_histogram", + + HistogramOptions: metricsgen.HistogramOpts{ + BucketType: "stdprometheus.ExponentialBuckets", + BucketSizes: "1, 100, .8", + }, + }, + }, + }, + }, + { + name: "labeled name", + metricsStruct: "type Metrics struct {\n" + + "myCounter metrics.Counter `metrics_name:\"new_name\"`\n" + + "}", + expected: metricsgen.TemplateData{ + Package: pkgName, + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Counter", + FieldName: "myCounter", + MetricName: "new_name", + }, + }, + }, + }, + { + name: "metric labels", + metricsStruct: "type Metrics struct {\n" + + "myCounter metrics.Counter `metrics_labels:\"label1, label2\"`\n" + + "}", + expected: metricsgen.TemplateData{ + Package: pkgName, + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Counter", + FieldName: "myCounter", + MetricName: "my_counter", + Labels: "label1, label2", + }, + }, + }, + }, + { + name: "ignore non-metric field", + metricsStruct: `type Metrics struct { + myCounter metrics.Counter + nonMetric string + }`, + expected: metricsgen.TemplateData{ + Package: pkgName, + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Counter", + FieldName: "myCounter", + MetricName: "my_counter", + }, + }, + }, + }, + } + for _, testCase := range metricsTests { + t.Run(testCase.name, func(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), "metricsdir") + if err != nil { + t.Fatalf("unable to create directory: %v", err) + } + defer os.Remove(dir) + f, err := os.Create(filepath.Join(dir, "metrics.go")) + if err != nil { + t.Fatalf("unable to open file: %v", err) + } + pkgLine := fmt.Sprintf("package %s\n", pkgName) + importClause := ` + import( + "github.com/go-kit/kit/metrics" + ) + ` + + _, err = io.WriteString(f, pkgLine) + require.NoError(t, err) + _, err = io.WriteString(f, importClause) + require.NoError(t, err) + _, err = io.WriteString(f, testCase.metricsStruct) + require.NoError(t, err) + + td, err := metricsgen.ParseMetricsDir(dir, "Metrics") + if testCase.shouldError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, testCase.expected, td) + } + }) + } +} + +func TestParseAliasedMetric(t *testing.T) { + aliasedData := ` + package mypkg + + import( + mymetrics "github.com/go-kit/kit/metrics" + ) + type Metrics struct { + m mymetrics.Gauge + } + ` + dir, err := os.MkdirTemp(os.TempDir(), "metricsdir") + if err != nil { + t.Fatalf("unable to create directory: %v", err) + } + defer os.Remove(dir) + f, err := os.Create(filepath.Join(dir, "metrics.go")) + if err != nil { + t.Fatalf("unable to open file: %v", err) + } + _, err = io.WriteString(f, aliasedData) + if err != nil { + t.Fatalf("unable to write to file: %v", err) + } + td, err := metricsgen.ParseMetricsDir(dir, "Metrics") + require.NoError(t, err) + + expected := + metricsgen.TemplateData{ + Package: "mypkg", + ParsedMetrics: []metricsgen.ParsedMetricField{ + { + TypeName: "Gauge", + FieldName: "m", + MetricName: "m", + }, + }, + } + require.Equal(t, expected, td) +} diff --git a/scripts/metricsgen/testdata/basic/metrics.gen.go b/scripts/metricsgen/testdata/basic/metrics.gen.go new file mode 100644 index 000000000..d541cb2db --- /dev/null +++ b/scripts/metricsgen/testdata/basic/metrics.gen.go @@ -0,0 +1,30 @@ +// Code generated by metricsgen. DO NOT EDIT. + +package basic + +import ( + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + Height: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "height", + Help: "simple metric that tracks the height of the chain.", + }, labels).With(labelsAndValues...), + } +} + +func NopMetrics() *Metrics { + return &Metrics{ + Height: discard.NewGauge(), + } +} diff --git a/scripts/metricsgen/testdata/basic/metrics.go b/scripts/metricsgen/testdata/basic/metrics.go new file mode 100644 index 000000000..1a361f90f --- /dev/null +++ b/scripts/metricsgen/testdata/basic/metrics.go @@ -0,0 +1,11 @@ +package basic + +import "github.com/go-kit/kit/metrics" + +//go:generate go run ../../../../scripts/metricsgen -struct=Metrics + +// Metrics contains metrics exposed by this package. +type Metrics struct { + // simple metric that tracks the height of the chain. + Height metrics.Gauge +} diff --git a/scripts/metricsgen/testdata/commented/metrics.gen.go b/scripts/metricsgen/testdata/commented/metrics.gen.go new file mode 100644 index 000000000..038da3d46 --- /dev/null +++ b/scripts/metricsgen/testdata/commented/metrics.gen.go @@ -0,0 +1,30 @@ +// Code generated by metricsgen. DO NOT EDIT. + +package commented + +import ( + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + Field: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "field", + Help: "Height of the chain.We expect multi-line comments to parse correctly.", + }, labels).With(labelsAndValues...), + } +} + +func NopMetrics() *Metrics { + return &Metrics{ + Field: discard.NewGauge(), + } +} diff --git a/scripts/metricsgen/testdata/commented/metrics.go b/scripts/metricsgen/testdata/commented/metrics.go new file mode 100644 index 000000000..174f1e233 --- /dev/null +++ b/scripts/metricsgen/testdata/commented/metrics.go @@ -0,0 +1,11 @@ +package commented + +import "github.com/go-kit/kit/metrics" + +//go:generate go run ../../../../scripts/metricsgen -struct=Metrics + +type Metrics struct { + // Height of the chain. + // We expect multi-line comments to parse correctly. + Field metrics.Gauge +} diff --git a/scripts/metricsgen/testdata/tags/metrics.gen.go b/scripts/metricsgen/testdata/tags/metrics.gen.go new file mode 100644 index 000000000..7ac292d3c --- /dev/null +++ b/scripts/metricsgen/testdata/tags/metrics.gen.go @@ -0,0 +1,54 @@ +// Code generated by metricsgen. DO NOT EDIT. + +package tags + +import ( + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } + return &Metrics{ + WithLabels: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "with_labels", + Help: "", + }, append(labels, "step,time")).With(labelsAndValues...), WithExpBuckets: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "with_exp_buckets", + Help: "", + + Buckets: stdprometheus.ExponentialBuckets(.1, 100, 8), + }, labels).With(labelsAndValues...), + WithBuckets: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "with_buckets", + Help: "", + + Buckets: []float64{1, 2, 3, 4, 5}, + }, labels).With(labelsAndValues...), + Named: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "metric_with_name", + Help: "", + }, labels).With(labelsAndValues...), + } +} + +func NopMetrics() *Metrics { + return &Metrics{ + WithLabels: discard.NewCounter(), + WithExpBuckets: discard.NewHistogram(), + WithBuckets: discard.NewHistogram(), + Named: discard.NewCounter(), + } +} diff --git a/scripts/metricsgen/testdata/tags/metrics.go b/scripts/metricsgen/testdata/tags/metrics.go new file mode 100644 index 000000000..8562dcf43 --- /dev/null +++ b/scripts/metricsgen/testdata/tags/metrics.go @@ -0,0 +1,12 @@ +package tags + +import "github.com/go-kit/kit/metrics" + +//go:generate go run ../../../../scripts/metricsgen -struct=Metrics + +type Metrics struct { + WithLabels metrics.Counter `metrics_labels:"step,time"` + WithExpBuckets metrics.Histogram `metrics_buckettype:"exp" metrics_bucketsizes:".1,100,8"` + WithBuckets metrics.Histogram `metrics_bucketsizes:"1, 2, 3, 4, 5"` + Named metrics.Counter `metrics_name:"metric_with_name"` +} diff --git a/scripts/protopackage.sh b/scripts/protopackage.sh deleted file mode 100755 index 5eace2752..000000000 --- a/scripts/protopackage.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/sh -set -eo pipefail - -# This script appends the "option go_package" proto option to the file located at $FNAME. -# This option specifies what the package will be named when imported by other packages. -# This option is not directly included in the proto files to allow the files to more easily -# be hosted in github.com/tendermint/spec and shared between other repos. -# If the option is already specified in the file, it will be replaced using the -# arguments passed to this script. - -FNAME="${1:?missing required .proto filename}" -MODNAME=$(echo $2| sed 's/\//\\\//g') -PACKAGE="$(dirname $FNAME | sed 's/^\.\/\(.*\)/\1/g' | sed 's/\//\\\//g')" -if [[ ! -z "$3" ]]; then - PACKAGE="$(echo $3 | sed 's/\//\\\//g')" -fi - - -if ! grep -q 'option\s\+go_package\s\+=\s\+.*;' $FNAME; then - sed -i "s/\(package tendermint.*\)/\1\n\noption go_package = \"$MODNAME\/$PACKAGE\";/g" $FNAME -else - sed -i "s/option\s\+go_package\s\+=\s\+.*;/option go_package = \"$MODNAME\/$PACKAGE\";/g" $FNAME -fi diff --git a/spec/abci++/abci++_app_requirements_002_draft.md b/spec/abci++/abci++_app_requirements_002_draft.md index 620b1cd5e..68014a536 100644 --- a/spec/abci++/abci++_app_requirements_002_draft.md +++ b/spec/abci++/abci++_app_requirements_002_draft.md @@ -6,47 +6,49 @@ title: Application Requirements # Application Requirements This section specifies what Tendermint expects from the Application. It is structured as a set -of formal requirement that can be used for testing and verification of the Application's logic. +of formal requirements that can be used for testing and verification of the Application's logic. -Let $p$ and $q$ be two different correct proposers in rounds $r_p$ and $r_q$ respectively, in height $h$. -Let $s_{p,h-1}$ be $p$'s Application's state committed for height $h-1$. -Let $v_p$ (resp. $v_q$) be the block that $p$'s (resp. $q$'s) Tendermint passes on to the Application -via `RequestPrepareProposal` as proposer of round $r_p$ (resp $r_q$), height $h$, also known as the -raw proposal. -Let $v'_p$ (resp. $v'_q$) the possibly modified block $p$'s (resp. $q$'s) Application returns via -`ResponsePrepareProposal` to Tendermint, also known as the prepared proposal. +Let *p* and *q* be two different correct proposers in rounds *rp* and *rq* +respectively, in height *h*. +Let *sp,h-1* be *p*'s Application's state committed for height *h-1*. +Let *vp* (resp. *vq*) be the block that *p*'s (resp. *q*'s) Tendermint passes +on to the Application +via `RequestPrepareProposal` as proposer of round *rp* (resp *rq*), height *h*, +also known as the raw proposal. +Let *v'p* (resp. *v'q*) the possibly modified block *p*'s (resp. *q*'s) Application +returns via `ResponsePrepareProposal` to Tendermint, also known as the prepared proposal. -Process $p$'s prepared proposal can differ in two different rounds where $p$ is the proposer. +Process *p*'s prepared proposal can differ in two different rounds where *p* is the proposer. -* Requirement 1 [`PrepareProposal`, header-changes] When the blockchain is in same-block execution mode, - $p$'s Application provides values for the following parameters in `ResponsePrepareProposal`: - _AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_. Provided values for - _ConsensusParams_ and _ValidatorUpdates_ MAY be empty to denote that the Application +* Requirement 1 [`PrepareProposal`, header-changes]: When the blockchain is in same-block execution mode, + *p*'s Application provides values for the following parameters in `ResponsePrepareProposal`: + `AppHash`, `TxResults`, `ConsensusParams`, `ValidatorUpdates`. Provided values for + `ConsensusParams` and `ValidatorUpdates` MAY be empty to denote that the Application wishes to keep the current values. -Parameters _AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_ are used by Tendermint to +Parameters `AppHash`, `TxResults`, `ConsensusParams`, and `ValidatorUpdates` are used by Tendermint to compute various hashes in the block header that will finally be part of the proposal. -* Requirement 2 [`PrepareProposal`, no-header-changes] When the blockchain is in next-block execution - mode, $p$'s Application does not provide values for the following parameters in `ResponsePrepareProposal`: - _AppHash_, _TxResults_, _ConsensusParams_, _ValidatorUpdates_. +* Requirement 2 [`PrepareProposal`, no-header-changes]: When the blockchain is in next-block execution + mode, *p*'s Application does not provide values for the following parameters in `ResponsePrepareProposal`: + `AppHash`, `TxResults`, `ConsensusParams`, `ValidatorUpdates`. In practical terms, Requirements 1 and 2 imply that Tendermint will (a) panic if the Application is in -same-block execution mode and _does_ _not_ provide values for -_AppHash_, _TxResults_, _ConsensusParams_, and _ValidatorUpdates_, or -(b) log an error if the Application is in next-block execution mode and _does_ provide values for -_AppHash_, _TxResults_, _ConsensusParams_, or _ValidatorUpdates_ (the values provided will be ignored). +same-block execution mode and *does not* provide values for +`AppHash`, `TxResults`, `ConsensusParams`, and `ValidatorUpdates`, or +(b) log an error if the Application is in next-block execution mode and *does* provide values for +`AppHash`, `TxResults`, `ConsensusParams`, or `ValidatorUpdates` (the values provided will be ignored). -* Requirement 3 [`PrepareProposal`, timeliness] If $p$'s Application fully executes prepared blocks in - `PrepareProposal` and the network is in a synchronous period while processes $p$ and $q$ are in $r_p$, then - the value of *TimeoutPropose* at $q$ must be such that $q$'s propose timer does not time out - (which would result in $q$ prevoting *nil* in $r_p$). +* Requirement 3 [`PrepareProposal`, timeliness]: If *p*'s Application fully executes prepared blocks in + `PrepareProposal` and the network is in a synchronous period while processes *p* and *q* are in *rp*, + then the value of *TimeoutPropose* at *q* must be such that *q*'s propose timer does not time out + (which would result in *q* prevoting `nil` in *rp*). Full execution of blocks at `PrepareProposal` time stands on Tendermint's critical path. Thus, -Requirement 3 ensures the Application will set a value for _TimeoutPropose_ such that the time it takes +Requirement 3 ensures the Application will set a value for `TimeoutPropose` such that the time it takes to fully execute blocks in `PrepareProposal` does not interfere with Tendermint's propose timer. -* Requirement 4 [`PrepareProposal`, tx-size] When $p$'s Application calls `ResponsePrepareProposal`, the +* Requirement 4 [`PrepareProposal`, tx-size]: When *p*'s Application calls `ResponsePrepareProposal`, the total size in bytes of the transactions returned does not exceed `RequestPrepareProposal.max_tx_bytes`. Busy blockchains might seek to maximize the amount of transactions included in each block. Under those conditions, @@ -54,29 +56,31 @@ Tendermint might choose to increase the transactions passed to the Application v beyond the `RequestPrepareProposal.max_tx_bytes` limit. The idea is that, if the Application drops some of those transactions, it can still return a transaction list whose byte size is as close to `RequestPrepareProposal.max_tx_bytes` as possible. Thus, Requirement 4 ensures that the size in bytes of the -transaction list returned by the application will never cause the resulting block to go beyond its byte limit. +transaction list returned by the application will never cause the resulting block to go beyond its byte size +limit. -* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes $p$ and $q$, - if $q$'s Tendermint calls `RequestProcessProposal` on $v'_p$, - $q$'s Application returns Accept in `ResponseProcessProposal`. +* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes *p* and *q*, + if *q*'s Tendermint calls `RequestProcessProposal` on *v'p*, + *q*'s Application returns Accept in `ResponseProcessProposal`. -Requirement 5 makes sure that blocks proposed by correct processes _always_ pass the correct receiving process's +Requirement 5 makes sure that blocks proposed by correct processes *always* pass the correct receiving process's `ProcessProposal` check. On the other hand, if there is a deterministic bug in `PrepareProposal` or `ProcessProposal` (or in both), strictly speaking, this makes all processes that hit the bug byzantine. This is a problem in practice, -as very often validators are running the Application from the same codebase, so potentially _all_ would +as very often validators are running the Application from the same codebase, so potentially *all* would likely hit the bug at the same time. This would result in most (or all) processes prevoting `nil`, with the serious consequences on Tendermint's liveness that this entails. Due to its criticality, Requirement 5 is a target for extensive testing and automated verification. * Requirement 6 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current - state and the block that is about to be applied. In other words, for any correct process $p$, and any arbitrary block $v'$, - if $p$'s Tendermint calls `RequestProcessProposal` on $v'$ at height $h$, - then $p$'s Application's acceptance or rejection **exclusively** depends on $v'$ and $s_{p,h-1}$. + state and the block that is about to be applied. In other words, for any correct process *p*, and any arbitrary block *v'*, + if *p*'s Tendermint calls `RequestProcessProposal` on *v'* at height *h*, + then *p*'s Application's acceptance or rejection **exclusively** depends on *v'* and *sp,h-1*. -* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes $p$ and $q$, and any arbitrary block $v'$, - if $p$'s (resp. $q$'s) Tendermint calls `RequestProcessProposal` on $v'$ at height $h$, - then $p$'s Application accepts $v'$ if and only if $q$'s Application accepts $v'$. +* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes *p* and *q*, and any arbitrary + block *v'*, + if *p*'s (resp. *q*'s) Tendermint calls `RequestProcessProposal` on *v'* at height *h*, + then *p*'s Application accepts *v'* if and only if *q*'s Application accepts *v'*. Note that this requirement follows from Requirement 6 and the Agreement property of consensus. Requirements 6 and 7 ensure that all correct processes will react in the same way to a proposed block, even @@ -87,20 +91,26 @@ In such a scenario, Tendermint's liveness cannot be guaranteed. Again, this is a problem in practice if most validators are running the same software, as they are likely to hit the bug at the same point. There is currently no clear solution to help with this situation, so the Application designers/implementors must proceed very carefully with the logic/implementation -of `ProcessProposal`. As a general rule `ProcessProposal` _should_ always accept the block. +of `ProcessProposal`. As a general rule `ProcessProposal` SHOULD always accept the block. -According to the Tendermint algorithm, a correct process can broadcast at most one precommit message in round $r$, height $h$. -Since, as stated in the [Description](#description) section, `ResponseExtendVote` is only called when Tendermint -is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension in round $r$, height $h$. -Let $e^r_p$ be the vote extension that the Application of a correct process $p$ returns via `ResponseExtendVote` in round $r$, height $h$. -Let $w^r_p$ be the proposed block that $p$'s Tendermint passes to the Application via `RequestExtendVote` in round $r$, height $h$. +According to the Tendermint algorithm, a correct process can broadcast at most one precommit +message in round *r*, height *h*. +Since, as stated in the [Methods](./abci++_methods_002_draft.md#extendvote) section, `ResponseExtendVote` +is only called when Tendermint +is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension +in round *r*, height *h*. +Let *erp* be the vote extension that the Application of a correct process *p* returns via +`ResponseExtendVote` in round *r*, height *h*. +Let *wrp* be the proposed block that *p*'s Tendermint passes to the Application via `RequestExtendVote` +in round *r*, height *h*. -* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes $p$ and $q$, if $q$ receives $e^r_p$ - from $p$ in height $h$, $q$'s Application returns Accept in `ResponseVerifyVoteExtension`. +* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes *p* and *q*, if *q* +receives *erp* + from *p* in height *h*, *q*'s Application returns Accept in `ResponseVerifyVoteExtension`. Requirement 8 constrains the creation and handling of vote extensions in a similar way as Requirement 5 -contrains the creation and handling of proposed blocks. -Requirement 8 ensures that extensions created by correct processes _always_ pass the `VerifyVoteExtension` +constrains the creation and handling of proposed blocks. +Requirement 8 ensures that extensions created by correct processes *always* pass the `VerifyVoteExtension` checks performed by correct processes receiving those extensions. However, if there is a (deterministic) bug in `ExtendVote` or `VerifyVoteExtension` (or in both), we will face the same liveness issues as described for Requirement 5, as Precommit messages with invalid vote @@ -108,58 +118,62 @@ extensions will be discarded. * Requirement 9 [`VerifyVoteExtension`, determinism-1]: `VerifyVoteExtension` is a (deterministic) function of the current state, the vote extension received, and the prepared proposal that the extension refers to. - In other words, for any correct process $p$, and any arbitrary vote extension $e$, and any arbitrary - block $w$, if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$, - then $p$'s Application's acceptance or rejection **exclusively** depends on $e$, $w$ and $s_{p,h-1}$. + In other words, for any correct process *p*, and any arbitrary vote extension *e*, and any arbitrary + block *w*, if *p*'s (resp. *q*'s) Tendermint calls `RequestVerifyVoteExtension` on *e* and *w* at height *h*, + then *p*'s Application's acceptance or rejection **exclusively** depends on *e*, *w* and *sp,h-1*. -* Requirement 10 [`VerifyVoteExtension`, determinism-2]: For any two correct processes $p$ and $q$, - and any arbitrary vote extension $e$, and any arbitrary block $w$, - if $p$'s (resp. $q$'s) Tendermint calls `RequestVerifyVoteExtension` on $e$ and $w$ at height $h$, - then $p$'s Application accepts $e$ if and only if $q$'s Application accepts $e$. +* Requirement 10 [`VerifyVoteExtension`, determinism-2]: For any two correct processes *p* and *q*, + and any arbitrary vote extension *e*, and any arbitrary block *w*, + if *p*'s (resp. *q*'s) Tendermint calls `RequestVerifyVoteExtension` on *e* and *w* at height *h*, + then *p*'s Application accepts *e* if and only if *q*'s Application accepts *e*. Note that this requirement follows from Requirement 9 and the Agreement property of consensus. Requirements 9 and 10 ensure that the validation of vote extensions will be deterministic at all correct processes. -Requirements 9 and 10 protect against arbitrary vote extension data from Byzantine processes -similarly to Requirements 6 and 7 and proposed blocks. +Requirements 9 and 10 protect against arbitrary vote extension data from Byzantine processes, +in a similar way as Requirements 6 and 7 protect against arbitrary proposed blocks. Requirements 9 and 10 can be violated by a bug inducing non-determinism in `VerifyVoteExtension`. In this case liveness can be compromised. -Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension` and, -as a general rule, `VerifyVoteExtension` _should_ always accept the vote extension. +Extra care should be put in the implementation of `ExtendVote` and `VerifyVoteExtension`. +As a general rule, `VerifyVoteExtension` SHOULD always accept the vote extension. -* Requirement 11 [_all_, no-side-effects]: $p$'s calls to `RequestPrepareProposal`, - `RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height $h$ do - not modify $s_{p,h-1}$. +* Requirement 11 [*all*, no-side-effects]: *p*'s calls to `RequestPrepareProposal`, + `RequestProcessProposal`, `RequestExtendVote`, and `RequestVerifyVoteExtension` at height *h* do + not modify *sp,h-1*. -* Requirement 12 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process $p$, -and any vote extension $e$ that $p$ received at height $h$, the computation of -$s_{p,h}$ does not depend on $e$. +* Requirement 12 [`ExtendVote`, `FinalizeBlock`, non-dependency]: for any correct process *p*, +and any vote extension *e* that *p* received at height *h*, the computation of +*sp,h* does not depend on *e*. -The call to correct process $p$'s `RequestFinalizeBlock` at height $h$, with block $v_{p,h}$ -passed as parameter, creates state $s_{p,h}$. +The call to correct process *p*'s `RequestFinalizeBlock` at height *h*, with block *vp,h* +passed as parameter, creates state *sp,h*. Additionally, -* in next-block execution mode, $p$'s `FinalizeBlock` creates a set of transaction results $T_{p,h}$, -* in same-block execution mode, $p$'s `PrepareProposal` creates a set of transaction results $T_{p,h}$ - if $p$ was the proposer of $v_{p,h}$, otherwise `FinalizeBlock` creates $T_{p,h}$. +* in next-block execution mode, *p*'s `FinalizeBlock` creates a set of transaction results *Tp,h*, +* in same-block execution mode, *p*'s `PrepareProposal` creates a set of transaction results *Tp,h* + if *p* was the proposer of *vp,h*. If *p* was not the proposer of *vp,h*, + `ProcessProposal` creates *Tp,h*. `FinalizeBlock` MAY re-create *Tp,h* if it was + removed from memory during the execution of height *h*. -* Requirement 13 [`FinalizeBlock`, determinism-1]: For any correct process $p$, - $s_{p,h}$ exclusively depends on $s_{p,h-1}$ and $v_{p,h}$. +* Requirement 13 [`FinalizeBlock`, determinism-1]: For any correct process *p*, + *sp,h* exclusively depends on *sp,h-1* and *vp,h*. -* Requirement 14 [`FinalizeBlock`, determinism-2]: For any correct process $p$, - the contents of $T_{p,h}$ exclusively depend on $s_{p,h-1}$ and $v_{p,h}$. +* Requirement 14 [`FinalizeBlock`, determinism-2]: For any correct process *p*, + the contents of *Tp,h* exclusively depend on *sp,h-1* and *vp,h*. Note that Requirements 13 and 14, combined with Agreement property of consensus ensure -the Application state evolves consistently at all correct processes. +state machine replication, i.e., the Application state evolves consistently at all correct processes. Finally, notice that neither `PrepareProposal` nor `ExtendVote` have determinism-related requirements associated. Indeed, `PrepareProposal` is not required to be deterministic: -* $v'_p$ may depend on $v_p$ and $s_{p,h-1}$, but may also depend on other values or operations. -* $v_p = v_q \nRightarrow v'_p = v'_q$. +* *v'p* may depend on *vp* and *sp,h-1*, but may also depend on other values or operations. +* *vp = vq ⇏ v'p = v'q*. Likewise, `ExtendVote` can also be non-deterministic: -* $e^r_p$ may depend on $w^r_p$ and $s_{p,h-1}$, but may also depend on other values or operations. -* $w^r_p = w^r_q \nRightarrow e^r_p = e^r_q$ +* *erp* may depend on *wrp* and *sp,h-1*, + but may also depend on other values or operations. +* *wrp = wrq ⇏ + erp = erq* diff --git a/spec/abci++/abci++_methods_002_draft.md b/spec/abci++/abci++_methods_002_draft.md index d1782bbdc..3a4ea073a 100644 --- a/spec/abci++/abci++_methods_002_draft.md +++ b/spec/abci++/abci++_methods_002_draft.md @@ -289,7 +289,7 @@ title: Methods |-------------------------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------|--------------| | max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 1 | | txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 2 | - | local_last_commit | [ExtendedCommitInfo](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 3 | + | local_last_commit | [Info](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 3 | | byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 4 | | height | int64 | The height of the block that will be proposed. | 5 | | time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp of the block that that will be proposed. | 6 | @@ -302,7 +302,7 @@ title: Methods |-------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------|--------------| | tx_records | repeated [TxRecord](#txrecord) | Possibly modified list of transactions that have been picked as part of the proposed block. | 2 | | app_hash | bytes | The Merkle root hash of the application state. | 3 | - | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 4 | + | tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 4 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 5 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 6 | @@ -414,7 +414,7 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos |-------------------------|--------------------------------------------------|-----------------------------------------------------------------------------------|--------------| | status | [ProposalStatus](#proposalstatus) | `enum` that signals if the application finds the proposal valid. | 1 | | app_hash | bytes | The Merkle root hash of the application state. | 2 | - | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions. | 3 | + | tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions. | 3 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 4 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 5 | @@ -582,7 +582,7 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou | Name | Type | Description | Field Number | |-------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------|--------------| | events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing | 1 | - | tx_results | repeated [ExecTxResult](#txresult) | List of structures containing the data resulting from executing the transactions | 2 | + | tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 2 | | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 | | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 4 | | app_hash | bytes | The Merkle root hash of the application state. | 5 | diff --git a/spec/abci/abci.md b/spec/abci/abci.md index 2a027d1d0..5d9d59b71 100644 --- a/spec/abci/abci.md +++ b/spec/abci/abci.md @@ -13,35 +13,11 @@ replication engine. When run within the same process, Tendermint will call the A application methods directly as Go method calls. When Tendermint and the ABCI application are run as separate processes, Tendermint -opens four connections to the application for ABCI methods. The connections each -handle a subset of the ABCI method calls. These subsets are defined as follows: +opens maintains a connection over either a native socket protocol or +gRPC. -#### **Consensus** connection - -* Driven by a consensus protocol and is responsible for block execution. -* Handles the `InitChain`, `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit` method -calls. - -#### **Mempool** connection - -* For validating new transactions, before they're shared or included in a block. -* Handles the `CheckTx` calls. - -#### **Info** connection - -* For initialization and for queries from the user. -* Handles the `Info` and `Query` calls. - -#### **Snapshot** connection - -* For serving and restoring [state sync snapshots](apps.md#state-sync). -* Handles the `ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk` calls. - -Additionally, there is a `Flush` method that is called on every connection, -and an `Echo` method that is just for debugging. - -More details on managing state across connections can be found in the section on -[ABCI Applications](apps.md). +More details on managing state across connections can be found in the +section on [ABCI Applications](apps.md). ## Errors @@ -54,13 +30,17 @@ These methods also return a `Codespace` string to Tendermint. This field is used to disambiguate `Code` values returned by different domains of the application. The `Codespace` is a namespace for the `Code`. -The `Echo`, `Info`, `InitChain`, `BeginBlock`, `EndBlock`, `Commit` methods -do not return errors. An error in any of these methods represents a critical -issue that Tendermint has no reasonable way to handle. If there is an error in one -of these methods, the application must crash to ensure that the error is safely -handled by an operator. +The handling of non-zero response codes by Tendermint is described +below. -The handling of non-zero response codes by Tendermint is described below +Applications should always terminate if they encounter an issue in a +method where continuing would corrupt their own state, or for which +tendermint should not continue. + +In the Go implementation these methods take a context and may return +an error. The context exists so that applications can terminate +gracefully during shutdown, and the error return value makes it +possible for applications to singal transient errors to Tendermint. ### CheckTx diff --git a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md index 832d11c9a..d6fcb54b6 100644 --- a/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md +++ b/spec/consensus/proposer-based-timestamp/pbts-sysmodel_002_draft.md @@ -1,16 +1,31 @@ # PBTS: System Model and Properties +## Outline + + - [System model](#system-model) + - [Synchronized clocks](#synchronized-clocks) + - [Message delays](#message-delays) + - [Problem Statement](#problem-statement) + - [Protocol Analysis - Timely Proposals](#protocol-analysis---timely-proposals) + - [Timely Proof-of-Locks](#timely-proof-of-locks) + - [Derived Proof-of-Locks](#derived-proof-of-locks) + - [Temporal Analysis](#temporal-analysis) + - [Safety](#safety) + - [Liveness](#liveness) + ## System Model #### **[PBTS-CLOCK-NEWTON.0]** -There is a reference Newtonian real-time `t` (UTC). +There is a reference Newtonian real-time `t`. No process has direct access to this reference time, used only for specification purposes. +The reference real-time is assumed to be aligned with the Coordinated Universal Time (UTC). ### Synchronized clocks -Processes are assumed to be equipped with synchronized clocks. +Processes are assumed to be equipped with synchronized clocks, +aligned with the Coordinated Universal Time (UTC). This requires processes to periodically synchronize their local clocks with an external and trusted source of the time (e.g. NTP servers). @@ -27,43 +42,35 @@ and drifts of local clocks from real time. #### **[PBTS-CLOCK-PRECISION.0]** There exists a system parameter `PRECISION`, such that -for any two processes `p` and `q`, with local clocks `C_p` and `C_q`, -that read their local clocks at the same real-time `t`, we have: +for any two processes `p` and `q`, with local clocks `C_p` and `C_q`: -- If `p` and `q` are equipped with synchronized clocks, then `|C_p(t) - C_q(t)| < PRECISION` +- If `p` and `q` are equipped with synchronized clocks, + then for any real-time `t` we have `|C_p(t) - C_q(t)| <= PRECISION`. `PRECISION` thus bounds the difference on the times simultaneously read by processes from their local clocks, so that their clocks can be considered synchronized. #### Accuracy -The [first draft][sysmodel_v1] of this specification included a second clock-related parameter, `ACCURACY`, -that relates the values read by processes from their synchronized clocks with real time: +A second relevant clock parameter is accuracy, which binds the values read by +processes from their clocks to real time. -- If `p` is a process is equipped with a synchronized clock, then at real time - `t` it reads from its clock time `C_p(t)` with `|C_p(t) - t| < ACCURACY` +##### **[PBTS-CLOCK-ACCURACY.0]** -The adoption of `ACCURACY` as the upper bound on the difference between clock -readings and real time, however, renders the `PRECISION` parameter redundant. -In fact, if we assume that clocks readings are at most `ACCURACY` from real -time, we would therefore be assuming that they cannot be more than `2 * ACCURACY` -apart from each other, thus establishing a worst-case upper bound for `PRECISION`. - -The approach we take is to assume that processes clocks are periodically -synchronized with an external source of time, thus improving their accuracy. -This allows us to adopt a relaxed version of the above `ACCURACY` definition: - -##### **[PBTS-CLOCK-FAIR.0]** +For the sake of completeness, we define a parameter `ACCURACY` such that: - At real time `t` there is at least one correct process `p` which clock marks - `C_p(t)` with `|C_p(t) - t| < ACCURACY` + `C_p(t)` with `|C_p(t) - t| <= ACCURACY`. -Then, through [PBTS-CLOCK-PRECISION] we can extend this relation of clock times -with real time to every correct process, which will have a clock with accuracy -bound by `ACCURACY + PRECISION`. -But, for the sake of simpler specification we can assume that the `PRECISION`, -which is a worst-case parameter that applies to all correct processes, -includes the best `ACCURACY` achieved by any of them. +As a consequence, applying the definition of `PRECISION`, we have: + +- At real time `t` the synchronized clock of any correct process `p` marks + `C_p(t)` with `|C_p(t) - t| <= ACCURACY + PRECISION`. + +The reason for not adopting `ACCURACY` as a system parameter is the assumption +that `PRECISION >> ACCURACY`. +This allows us to consider, for practical purposes, that the `PRECISION` system +parameter embodies the `ACCURACY` model parameter. ### Message Delays @@ -79,172 +86,264 @@ defining a lower bound, a *minimum time* that a correct process assigns to propo While *minimum delay* for delivering a proposal to a destination allows defining an upper bound, the *maximum time* assigned to a proposal. -#### **[PBTS-MSG-D.0]** +#### **[PBTS-MSG-DELAY.0]** -There exists a system parameter `MSGDELAY` for end-to-end delays of messages carrying proposals, -such for any two correct processes `p` and `q`, and any real time `t`: +There exists a system parameter `MSGDELAY` for end-to-end delays of proposal messages, +such for any two correct processes `p` and `q`: -- If `p` sends a message `m` carrying a proposal at time `ts`, -then if `q` receives the message and learns the proposal, -`q` does that at time `t` such that `ts <= t <= ts + MSGDELAY`. +- If `p` sends a proposal message `m` at real time `t` and `q` receives `m` at + real time `t'`, then `t <= t' <= t + MSGDELAY`. -While we don't want to impose particular restrictions regarding the format of `m`, -we need to assume that their size is upper bounded. -In practice, using messages with a fixed-size to carry proposals allows -for a more accurate estimation of `MSGDELAY`, and therefore is advised. +Notice that, as a system parameter, `MSGDELAY` should be observed for any +proposal message broadcast by correct processes: it is a *worst-case* parameter. +As message delays depends on the message size, the above requirement implicitly +indicates that the size of proposal messages is either fixed or upper bounded. ## Problem Statement In this section we define the properties of Tendermint consensus -(cf. the [arXiv paper][arXiv]) in this new system model. +(cf. the [arXiv paper][arXiv]) in this system model. -#### **[PBTS-PROPOSE.0]** +### **[PBTS-PROPOSE.0]** -A proposer proposes a consensus value `v` with an associated proposal time `v.time`. +A proposer proposes a consensus value `v` that includes a proposal time +`v.time`. + +> We then restrict the allowed decisions along the following lines: #### **[PBTS-INV-AGREEMENT.0]** -[Agreement] No two correct processes decide on different values `v`. (This implies that no two correct processes decide on different proposal times `v.time`.) +- [Agreement] No two correct processes decide on different values `v`. + +This implies that no two correct processes decide on different proposal times +`v.time`. #### **[PBTS-INV-VALID.0]** -[Validity] If a correct process decides on value `v`, -then `v` satisfies a predefined `valid` predicate. +- [Validity] If a correct process decides on value `v`, then `v` satisfies a + predefined `valid` predicate. + +With respect to PBTS, the `valid` predicate requires proposal times to be +[monotonic](./pbts-algorithm_002_draft.md#time-monotonicity) over heights of +consensus: + +##### **[PBTS-INV-MONOTONICITY.0]** + +- If a correct process decides on value `v` at the height `h` of consensus, + thus setting `decision[h] = v`, then `v.time > decision[h'].time` for all + previous heights `h' < h`. + +The monotonicity of proposal times, and external validity in general, +implicitly assumes that heights of consensus are executed in order. #### **[PBTS-INV-TIMELY.0]** -[Time-Validity] If a correct process decides on value `v`, -then the associated proposal time `v.time` satisfies a predefined `timely` predicate. +- [Time-Validity] If a correct process decides on value `v`, then the proposal + time `v.time` was considered `timely` by at least one correct process. -> Both [Validity] and [Time-Validity] must be observed even if up to `2f` validators are faulty. +PBTS introduces a `timely` predicate that restricts the allowed decisions based +on the proposal time `v.time` associated with a proposed value `v`. +As a synchronous predicate, the time at which it is evaluated impacts on +whether a process accepts or reject a proposal time. +For this reason, the Time-Validity property refers to the previous evaluation +of the `timely` predicate, detailed in the following section. -### Timely proposals +## Protocol Analysis - Timely proposals + +For PBTS, a `proposal` is a tuple `(v, v.time, v.round)`, where: + +- `v` is the proposed value; +- `v.time` is the associated proposal time; +- `v.round` is the round at which `v` was first proposed. + +We include the proposal round `v.round` in the proposal definition because a +value `v` and its associated proposal time `v.time` can be proposed in multiple +rounds, but the evaluation of the `timely` predicate is only relevant at round +`v.round`. + +> Considering the algorithm in the [arXiv paper][arXiv], a new proposal is +> produced by the `getValue()` method, invoked by the proposer `p` of round +> `round_p` when starting its proposing round with a nil `validValue_p`. +> The first round at which a value `v` is proposed is then the round at which +> the proposal for `v` was produced, and broadcast in a `PROPOSAL` message with +> `vr = -1`. + +#### **[PBTS-PROPOSAL-RECEPTION.0]** The `timely` predicate is evaluated when a process receives a proposal. -Let `now_p` be time a process `p` reads from its local clock when `p` receives a proposal. -Let `v` be the proposed value and `v.time` the proposal time. -The proposal is considered `timely` by `p` if: +More precisely, let `p` be a correct process: -#### **[PBTS-RECEPTION-STEP.1]** +- `proposalReceptionTime(p,r)` is the time `p` reads from its local clock when + `p` is at round `r` and receives the proposal of round `r`. -1. `now_p >= v.time - PRECISION` and -1. `now_p <= v.time + MSGDELAY + PRECISION` +#### **[PBTS-TIMELY.0]** + +The proposal `(v, v.time, v.round)` is considered `timely` by a correct process +`p` if: + +1. `proposalReceptionTime(p,v.round)` is set, and +1. `proposalReceptionTime(p,v.round) >= v.time - PRECISION`, and +1. `proposalReceptionTime(p,v.round) <= v.time + MSGDELAY + PRECISION`. + +A correct process at round `v.round` only sends a `PREVOTE` for `v` if the +associated proposal time `v.time` is considered `timely`. + +> Considering the algorithm in the [arXiv paper][arXiv], the `timely` predicate +> is evaluated by a process `p` when it receives a valid `PROPOSAL` message +> from the proposer of the current round `round_p` with `vr = -1`. ### Timely Proof-of-Locks -We denote by `POL(v,r)` a *Proof-of-Lock* of value `v` at the round `r` of consensus. -`POL(v,r)` consists of a set of `PREVOTE` messages of round `r` for the value `v` -from processes whose cumulative voting power is at least `2f + 1`. +A *Proof-of-Lock* is a set of `PREVOTE` messages of round of consensus for the +same value from processes whose cumulative voting power is at least `2f + 1`. +We denote as `POL(v,r)` a proof-of-lock of value `v` at round `r`. -#### **[PBTS-TIMELY-POL.1]** +For PBTS, we are particularly interested in the `POL(v,v.round)` produced in +the round `v.round` at which a value `v` was first proposed. +We call it a *timely* proof-of-lock for `v` because it can only be observed +if at least one correct process considered it `timely`: + +#### **[PBTS-TIMELY-POL.0]** If -- there is a valid `POL(v,r*)` for height `h`, and -- `r*` is the lowest-numbered round `r` of height `h` for which there is a valid `POL(v,r)`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, +- there is a valid `POL(v,r)` with `r = v.round`, and +- `POL(v,v.round)` contains a `PREVOTE` message from at least one correct process, -Then, where `p` is a such correct process: +Then, let `p` is a such correct process: -- `p` received a `PROPOSE` message of round `r*` and height `h`, and -- the `PROPOSE` message contained a proposal for value `v` with proposal time `v.time`, and -- a correct process `p` considered the proposal `timely`. +- `p` received a `PROPOSAL` message of round `v.round`, and +- the `PROPOSAL` message contained a proposal `(v, v.time, v.round)`, and +- `p` was in round `v.round` and evaluated the proposal time `v.time` as `timely`. -The round `r*` above defined will be, in most cases, -the round in which `v` was originally proposed, and when `v.time` was assigned, -using a `PROPOSE` message with `POLRound = -1`. -In any case, at least one correct process must consider the proposal `timely` at round `r*` -to enable a valid `POL(v,r*)` to be observed. +The existence of a such correct process `p` is guaranteed provided that the +voting power of Byzantine processes is bounded by `2f`. ### Derived Proof-of-Locks -#### **[PBTS-DERIVED-POL.1]** +The existence of `POL(v,r)` is a requirement for the decision of `v` at round +`r` of consensus. + +At the same time, the Time-Validity property establishes that if `v` is decided +then a timely proof-of-lock `POL(v,v.round)` must have been produced. + +So, we need to demonstrate here that any valid `POL(v,r)` is either a timely +proof-of-lock or it is derived from a timely proof-of-lock: + +#### **[PBTS-DERIVED-POL.0]** If -- there is a valid `POL(v,r)` for height `h`, and +- there is a valid `POL(v,r)`, and - `POL(v,r)` contains a `PREVOTE` message from at least one correct process, Then -- there is a valid `POL(v,r*)` for height `h`, with `r* <= r`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and -- a correct process considered the proposal for `v` `timely` at round `r*`. +- there is a valid `POL(v,v.round)` with `v.round <= r` which is a timely proof-of-lock. -The above relation derives from a recursion on the round number `r`. -It is trivially observed when `r = r*`, the base of the recursion, -when a timely `POL(v,r*)` is obtained. -We need to ensure that, once a timely `POL(v,r*)` is obtained, -it is possible to obtain a valid `POL(v,r)` with `r > r*`, -without the need of satisfying the `timely` predicate (again) in round `r`. -In fact, since rounds are started in order, it is not likely that -a proposal time `v.time`, assigned at round `r*`, -will still be considered `timely` when the round `r > r*` is in progress. +The above relation is trivially observed when `r = v.round`, as `POL(v,r)` must +be a timely proof-of-lock. +Notice that we cannot have `r < v.round`, as `v.round` is defined as the first +round at which `v` was proposed. -In other words, the algorithm should ensure that once a `POL(v,r*)` attests -that the proposal for `v` is `timely`, -further valid `POL(v,r)` with `r > r*` can be obtained, -even though processes do not consider the proposal for `v` `timely` any longer. +For `r > v.round` we need to demonstrate that if there is a valid `POL(v,r)`, +then a timely `POL(v,v.round)` was previously obtained. +We observe that a condition for observing a `POL(v,r)` is that the proposer of +round `r` has broadcast a `PROPOSAL` message for `v`. +As `r > v.round`, we can affirm that `v` was not produced in round `r`. +Instead, by the protocol operation, `v` was a *valid value* for the proposer of +round `r`, which means that if the proposer has observed a `POL(v,vr)` with `vr +< r`. +The above operation considers a *correct* proposer, but since a `POL(v,r)` was +produced (by hypothesis) we can affirm that at least one correct process (also) +observed a `POL(v,vr)`. -> This can be achieved if the proposer of round `r' > r*` proposes `v` in a `PROPOSE` message -with `POLRound = r*`, and at least one correct processes is aware of a `POL(v,r*)`. -> From this point, if a valid `POL(v,r')` is achieved, it can replace the adopted `POL(v,r*)`. +> Considering the algorithm in the [arXiv paper][arXiv], `v` was proposed by +> the proposer `p` of round `round_p` because its `validValue_p` variable was +> set to `v`. +> The `PROPOSAL` message broadcast by the proposer, in this case, had `vr > -1`, +> and it could only be accepted by processes that also observed a `POL(v,vr)`. -### SAFETY +Thus, if there is a `POL(v,r)` with `r > v.round`, then there is a valid +`POL(v,vr)` with `v.round <= vr < r`. +If `vr = v.round` then `POL(vr,v)` is a timely proof-of-lock and we are done. +Otherwise, there is another valid `POL(v,vr')` with `v.round <= vr' < vr`, +and the above reasoning can be recursively applied until we get `vr' = v.round` +and observe a timely proof-of-lock. -The safety of the algorithm requires a *timely* proof-of-lock for a decided value, -either directly evaluated by a correct process, -or indirectly received through a derived proof-of-lock. +## Temporal analysis -#### **[PBTS-CONSENSUS-TIME-VALID.0]** +In this section we present invariants that need be observed for ensuring that +PBTS is both safe and live. + +In addition to the variables and system parameters already defined, we use +`beginRound(p,r)` as the value of process `p`'s local clock +when it starts round `r` of consensus. + +### Safety + +The safety of PBTS requires that if a value `v` is decided, then at least one +correct process `p` considered the associated proposal time `v.time` timely. +Following the definition of [timely proposals](#pbts-timely0) and +proof-of-locks, we require this condition to be asserted at a specific round of +consensus, defined as `v.round`: + +#### **[PBTS-SAFETY.0]** If -- there is a valid commit `C` for height `k` and round `r`, and +- there is a valid commit `C` for a value `v` - `C` contains a `PRECOMMIT` message from at least one correct process -Then, where `p` is one such correct process: +then there is a correct process `p` (not necessarily the same above considered) such that: -- since `p` is correct, `p` received a valid `POL(v,r)`, and -- `POL(v,r)` contains a `PREVOTE` message from at least one correct process, and -- `POL(v,r)` is derived from a timely `POL(v,r*)` with `r* <= r`, and -- `POL(v,r*)` contains a `PREVOTE` message from at least one correct process, and -- a correct process considered a proposal for `v` `timely` at round `r*`. +- `beginRound(p,v.round) <= proposalReceptionTime(p,v.round) <= beginRound(p,v.round+1)` and +- `proposalReceptionTime (p,v.round) - MSGDELAY - PRECISION <= v.time <= proposalReceptionTime(p,v.round) + PRECISION` -### LIVENESS +That is, a correct process `p` started round `v.round` and, while still at +round `v.round`, received a `PROPOSAL` message from round `v.round` proposing +`v`. +Moreover, the reception time of the original proposal for `v`, according with +`p`'s local clock, enabled `p` to consider the proposal time `v.time` as +`timely`. +This is the requirement established by PBTS for issuing a `PREVOTE` for the +proposal `(v, v.time, v.round)`, so for the eventual decision of `v`. -In terms of liveness, we need to ensure that a proposal broadcast by a correct process -will be considered `timely` by any correct process that is ready to accept that proposal. -So, if: +### Liveness -- the proposer `p` of a round `r` is correct, -- there is no `POL(v',r')` for any value `v'` and any round `r' < r`, -- `p` proposes a valid value `v` and sets `v.time` to the time it reads from its local clock, +The liveness of PBTS relies on correct processes accepting proposal times +assigned by correct proposers. +We thus present a set of conditions for assigning a proposal time `v.time` so +that every correct process should be able to issue a `PREVOTE` for `v`. -Then let `q` be a correct process that receives `p`'s proposal, we have: +#### **[PBTS-LIVENESS.0]** -- `q` receives `p`'s proposal after its clock reads `v.time - PRECISION`, and -- if `q` is at or joins round `r` while `p`'s proposal is being transmitted, -then `q` receives `p`'s proposal before its clock reads `v.time + MSGDELAY + PRECISION` +If -> Note that, before `GST`, we cannot ensure that every correct process receives `p`'s proposals, nor that it does it while ready to accept a round `r` proposal. +- the proposer of a round `r` of consensus is correct +- and it proposes a value `v` for the first time, with associated proposal time `v.time` -A correct process `q` as above defined must then consider `p`'s proposal `timely`. -It will then broadcast a `PREVOTE` message for `v` at round `r`, -thus enabling, from the Time-Validity point of view, `v` to be eventually decided. +then the proposal `(v, v.time, r)` is accepted by every correct process provided that: -#### Under-estimated `MSGDELAY`s +- `min{p is correct : beginRound(p,r)} <= v.time <= max{p is correct : beginRound(p,r)}` and +- `max{p is correct : beginRound(p,r)} <= v.time + MSGDELAY + PRECISION <= min{p is correct : beginRound(p,r+1)}` -The liveness assumptions of PBTS are conditioned by a conservative and clever -choice of the timing parameters, specially of `MSGDELAY`. -In fact, if the transmission delay for a message carrying a proposal is wrongly -estimated, correct processes may never consider a valid proposal as `timely`. +The first condition establishes a range of safe proposal times `v.time` for round `r`. +This condition is trivially observed if a correct proposer `p` sets `v.time` to the time it +reads from its clock when starting round `r` and proposing `v`. +A `PROPOSAL` message sent by `p` at local time `v.time` should not be received +by any correct process before its local clock reads `v.time - PRECISION`, so +that condition 2 of [PBTS-TIMELY.0] is observed. -To circumvent this liveness issue, which could result from a misconfiguration, -we assume that the `MSGDELAY` parameter can be increased as rounds do not -succeed on deciding a value, possibly because no proposal is considered -`timely` by enough processes. -The precise behavior for this workaround is under [discussion](https://github.com/tendermint/spec/issues/371). +The second condition establishes that every correct process should start round +`v.round` at a local time that allows `v.time` to still be considered timely, +according to condition 3. of [PBTS-TIMELY.0]. +In addition, it requires correct processes to stay long enough in round +`v.round` so that they can receive the `PROPOSAL` message of round `v.round`. +It assumed here that the proposer of `v` broadcasts a `PROPOSAL` message at +time `v.time`, according to its local clock, so that every correct process +should receive this message by time `v.time + MSGDELAY + PRECISION`, according +to their local clocks. Back to [main document][main]. diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 2736dad04..64ccd2e28 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -2,21 +2,31 @@ package app import ( "bytes" + "context" "encoding/base64" + "encoding/binary" "errors" "fmt" + "math/rand" "path/filepath" "sort" "strconv" + "strings" "sync" "github.com/tendermint/tendermint/abci/example/code" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) +const ( + voteExtensionKey string = "extensionSum" + voteExtensionMaxVal int64 = 128 +) + // Application is an ABCI application for use by end-to-end tests. It is a // simple key/value store for strings, storing data in memory and persisting // to disk as JSON, taking state sync snapshots if requested. @@ -104,20 +114,20 @@ func NewApplication(cfg *Config) (*Application, error) { } // Info implements ABCI. -func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo { +func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { app.mu.Lock() defer app.mu.Unlock() - return abci.ResponseInfo{ + return &abci.ResponseInfo{ Version: version.ABCIVersion, AppVersion: 1, LastBlockHeight: int64(app.state.Height), LastBlockAppHash: app.state.Hash, - } + }, nil } // Info implements ABCI. -func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { +func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { app.mu.Lock() defer app.mu.Unlock() @@ -129,7 +139,7 @@ func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitCh panic(err) } } - resp := abci.ResponseInitChain{ + resp := &abci.ResponseInitChain{ AppHash: app.state.Hash, ConsensusParams: &types.ConsensusParams{ Version: &types.VersionParams{ @@ -140,26 +150,26 @@ func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitCh if resp.Validators, err = app.validatorUpdates(0); err != nil { panic(err) } - return resp + return resp, nil } // CheckTx implements ABCI. -func (app *Application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { +func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { app.mu.Lock() defer app.mu.Unlock() _, _, err := parseTx(req.Tx) if err != nil { - return abci.ResponseCheckTx{ + return &abci.ResponseCheckTx{ Code: code.CodeTypeEncodingError, Log: err.Error(), - } + }, nil } - return abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} + return &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil } // FinalizeBlock implements ABCI. -func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { +func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { var txs = make([]*abci.ExecTxResult, len(req.Txs)) app.mu.Lock() @@ -180,7 +190,7 @@ func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.Respon panic(err) } - return abci.ResponseFinalizeBlock{ + return &abci.ResponseFinalizeBlock{ TxResults: txs, ValidatorUpdates: valUpdates, Events: []abci.Event{ @@ -198,11 +208,11 @@ func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.Respon }, }, }, - } + }, nil } // Commit implements ABCI. -func (app *Application) Commit() abci.ResponseCommit { +func (app *Application) Commit(_ context.Context) (*abci.ResponseCommit, error) { app.mu.Lock() defer app.mu.Unlock() @@ -215,36 +225,36 @@ func (app *Application) Commit() abci.ResponseCommit { if err != nil { panic(err) } - app.logger.Info("Created state sync snapshot", "height", snapshot.Height) + app.logger.Info("created state sync snapshot", "height", snapshot.Height) err = app.snapshots.Prune(maxSnapshotCount) if err != nil { - app.logger.Error("Failed to prune snapshots", "err", err) + app.logger.Error("failed to prune snapshots", "err", err) } } retainHeight := int64(0) if app.cfg.RetainBlocks > 0 { retainHeight = int64(height - app.cfg.RetainBlocks + 1) } - return abci.ResponseCommit{ + return &abci.ResponseCommit{ Data: hash, RetainHeight: retainHeight, - } + }, nil } // Query implements ABCI. -func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery { +func (app *Application) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { app.mu.Lock() defer app.mu.Unlock() - return abci.ResponseQuery{ + return &abci.ResponseQuery{ Height: int64(app.state.Height), Key: req.Data, Value: []byte(app.state.Get(string(req.Data))), - } + }, nil } // ListSnapshots implements ABCI. -func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { +func (app *Application) ListSnapshots(_ context.Context, req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) { app.mu.Lock() defer app.mu.Unlock() @@ -252,11 +262,11 @@ func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.Respon if err != nil { panic(err) } - return abci.ResponseListSnapshots{Snapshots: snapshots} + return &abci.ResponseListSnapshots{Snapshots: snapshots}, nil } // LoadSnapshotChunk implements ABCI. -func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { +func (app *Application) LoadSnapshotChunk(_ context.Context, req *abci.RequestLoadSnapshotChunk) (*abci.ResponseLoadSnapshotChunk, error) { app.mu.Lock() defer app.mu.Unlock() @@ -264,11 +274,11 @@ func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abc if err != nil { panic(err) } - return abci.ResponseLoadSnapshotChunk{Chunk: chunk} + return &abci.ResponseLoadSnapshotChunk{Chunk: chunk}, nil } // OfferSnapshot implements ABCI. -func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { +func (app *Application) OfferSnapshot(_ context.Context, req *abci.RequestOfferSnapshot) (*abci.ResponseOfferSnapshot, error) { app.mu.Lock() defer app.mu.Unlock() @@ -277,11 +287,11 @@ func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.Respon } app.restoreSnapshot = req.Snapshot app.restoreChunks = [][]byte{} - return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT} + return &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil } // ApplySnapshotChunk implements ABCI. -func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { +func (app *Application) ApplySnapshotChunk(_ context.Context, req *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) { app.mu.Lock() defer app.mu.Unlock() @@ -301,10 +311,77 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a app.restoreSnapshot = nil app.restoreChunks = nil } - return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT} + return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil } -func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { +// PrepareProposal will take the given transactions and attempt to prepare a +// proposal from them when it's our turn to do so. In the process, vote +// extensions from the previous round of consensus, if present, will be used to +// construct a special transaction whose value is the sum of all of the vote +// extensions from the previous round. +// +// NB: Assumes that the supplied transactions do not exceed `req.MaxTxBytes`. +// If adding a special vote extension-generated transaction would cause the +// total number of transaction bytes to exceed `req.MaxTxBytes`, we will not +// append our special vote extension transaction. +func (app *Application) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { + var sum int64 + var extCount int + for _, vote := range req.LocalLastCommit.Votes { + if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 { + continue + } + extValue, err := parseVoteExtension(vote.VoteExtension) + // This should have been verified in VerifyVoteExtension + if err != nil { + panic(fmt.Errorf("failed to parse vote extension in PrepareProposal: %w", err)) + } + valAddr := crypto.Address(vote.Validator.Address) + app.logger.Info("got vote extension value in PrepareProposal", "valAddr", valAddr, "value", extValue) + sum += extValue + extCount++ + } + // We only generate our special transaction if we have vote extensions + if extCount > 0 { + var totalBytes int64 + extTxPrefix := fmt.Sprintf("%s=", voteExtensionKey) + extTx := []byte(fmt.Sprintf("%s%d", extTxPrefix, sum)) + app.logger.Info("preparing proposal with custom transaction from vote extensions", "tx", extTx) + // Our generated transaction takes precedence over any supplied + // transaction that attempts to modify the "extensionSum" value. + txRecords := make([]*abci.TxRecord, len(req.Txs)+1) + for i, tx := range req.Txs { + if strings.HasPrefix(string(tx), extTxPrefix) { + txRecords[i] = &abci.TxRecord{ + Action: abci.TxRecord_REMOVED, + Tx: tx, + } + } else { + txRecords[i] = &abci.TxRecord{ + Action: abci.TxRecord_UNMODIFIED, + Tx: tx, + } + totalBytes += int64(len(tx)) + } + } + if totalBytes+int64(len(extTx)) < req.MaxTxBytes { + txRecords[len(req.Txs)] = &abci.TxRecord{ + Action: abci.TxRecord_ADDED, + Tx: extTx, + } + } else { + app.logger.Info( + "too many txs to include special vote extension-generated tx", + "totalBytes", totalBytes, + "MaxTxBytes", req.MaxTxBytes, + "extTx", extTx, + "extTxLen", len(extTx), + ) + } + return &abci.ResponsePrepareProposal{ + TxRecords: txRecords, + }, nil + } // None of the transactions are modified by this application. trs := make([]*abci.TxRecord, 0, len(req.Txs)) var totalBytes int64 @@ -318,19 +395,92 @@ func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.Re Tx: tx, }) } - return abci.ResponsePrepareProposal{TxRecords: trs} + return &abci.ResponsePrepareProposal{TxRecords: trs}, nil } // ProcessProposal implements part of the Application interface. // It accepts any proposal that does not contain a malformed transaction. -func (app *Application) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { +func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { for _, tx := range req.Txs { - _, _, err := parseTx(tx) + k, v, err := parseTx(tx) if err != nil { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + app.logger.Error("malformed transaction in ProcessProposal", "tx", tx, "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } + // Additional check for vote extension-related txs + if k == voteExtensionKey { + _, err := strconv.Atoi(v) + if err != nil { + app.logger.Error("malformed vote extension transaction", k, v, "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } } } - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil +} + +// ExtendVote will produce vote extensions in the form of random numbers to +// demonstrate vote extension nondeterminism. +// +// In the next block, if there are any vote extensions from the previous block, +// a new transaction will be proposed that updates a special value in the +// key/value store ("extensionSum") with the sum of all of the numbers collected +// from the vote extensions. +func (app *Application) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { + // We ignore any requests for vote extensions that don't match our expected + // next height. + if req.Height != int64(app.state.Height)+1 { + app.logger.Error( + "got unexpected height in ExtendVote request", + "expectedHeight", app.state.Height+1, + "requestHeight", req.Height, + ) + return &abci.ResponseExtendVote{}, nil + } + ext := make([]byte, binary.MaxVarintLen64) + // We don't care that these values are generated by a weak random number + // generator. It's just for test purposes. + // nolint:gosec // G404: Use of weak random number generator + num := rand.Int63n(voteExtensionMaxVal) + extLen := binary.PutVarint(ext, num) + app.logger.Info("generated vote extension", "num", num, "ext", fmt.Sprintf("%x", ext[:extLen]), "state.Height", app.state.Height) + return &abci.ResponseExtendVote{ + VoteExtension: ext[:extLen], + }, nil +} + +// VerifyVoteExtension simply validates vote extensions from other validators +// without doing anything about them. In this case, it just makes sure that the +// vote extension is a well-formed integer value. +func (app *Application) VerifyVoteExtension(_ context.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) { + // We allow vote extensions to be optional + if len(req.VoteExtension) == 0 { + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil + } + if req.Height != int64(app.state.Height)+1 { + app.logger.Error( + "got unexpected height in VerifyVoteExtension request", + "expectedHeight", app.state.Height, + "requestHeight", req.Height, + ) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_REJECT, + }, nil + } + + num, err := parseVoteExtension(req.VoteExtension) + if err != nil { + app.logger.Error("failed to verify vote extension", "req", req, "err", err) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_REJECT, + }, nil + } + app.logger.Info("verified vote extension value", "req", req, "num", num) + return &abci.ResponseVerifyVoteExtension{ + Status: abci.ResponseVerifyVoteExtension_ACCEPT, + }, nil } func (app *Application) Rollback() error { @@ -378,3 +528,19 @@ func parseTx(tx []byte) (string, string, error) { } return string(parts[0]), string(parts[1]), nil } + +// parseVoteExtension attempts to parse the given extension data into a positive +// integer value. +func parseVoteExtension(ext []byte) (int64, error) { + num, errVal := binary.Varint(ext) + if errVal == 0 { + return 0, errors.New("vote extension is too small to parse") + } + if errVal < 0 { + return 0, errors.New("vote extension value is too large") + } + if num >= voteExtensionMaxVal { + return 0, fmt.Errorf("vote extension value must be smaller than %d (was %d)", voteExtensionMaxVal, num) + } + return num, nil +} diff --git a/test/e2e/node/main.go b/test/e2e/node/main.go index 54b9ef533..2cbb9e4b0 100644 --- a/test/e2e/node/main.go +++ b/test/e2e/node/main.go @@ -248,7 +248,7 @@ func startSigner(ctx context.Context, logger log.Logger, cfg *Config) error { if err != nil { return err } - ss := grpcprivval.NewSignerServer(cfg.ChainID, filePV, logger) + ss := grpcprivval.NewSignerServer(logger, cfg.ChainID, filePV) s := grpc.NewServer() diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index b29b574a2..849e4edc3 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -12,7 +12,6 @@ import ( "time" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/test/factory" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/privval" @@ -63,7 +62,6 @@ func InjectEvidence(ctx context.Context, logger log.Logger, r *rand.Rand, testne if err != nil { return err } - valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators) if err != nil { return err @@ -257,26 +255,26 @@ func makeHeaderRandom(chainID string, height int64) *types.Header { Height: height, Time: time.Now(), LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")), - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } } func makeRandomBlockID() types.BlockID { - return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size)) + return makeBlockID(crypto.CRandBytes(crypto.HashSize), 100, crypto.CRandBytes(crypto.HashSize)) } func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) @@ -289,8 +287,7 @@ func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.Bloc } } -func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet, -) ([]types.PrivValidator, *types.ValidatorSet, error) { +func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet) ([]types.PrivValidator, *types.ValidatorSet, error) { newVal, newPrivVal, err := factory.Validator(ctx, 10) if err != nil { return nil, nil, err diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 36115346b..ed041e186 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "math/rand" + "strconv" "testing" "time" @@ -45,9 +46,11 @@ func TestApp_Hash(t *testing.T) { testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) { client, err := node.Client() require.NoError(t, err) + info, err := client.ABCIInfo(ctx) require.NoError(t, err) require.NotEmpty(t, info.Response.LastBlockAppHash, "expected app to return app hash") + // In next-block execution, the app hash is stored in the next block blockHeight := info.Response.LastBlockHeight + 1 @@ -60,7 +63,6 @@ func TestApp_Hash(t *testing.T) { block, err := client.Block(ctx, &blockHeight) require.NoError(t, err) - require.Equal(t, blockHeight, block.Block.Height) require.Equal(t, fmt.Sprintf("%x", info.Response.LastBlockAppHash), @@ -185,3 +187,18 @@ func TestApp_Tx(t *testing.T) { } } + +func TestApp_VoteExtensions(t *testing.T) { + testNode(t, func(ctx context.Context, t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + + // This special value should have been created by way of vote extensions + resp, err := client.ABCIQuery(ctx, "", []byte("extensionSum")) + require.NoError(t, err) + + extSum, err := strconv.Atoi(string(resp.Response.Value)) + require.NoError(t, err) + require.GreaterOrEqual(t, extSum, 0) + }) +} diff --git a/test/fuzz/Makefile b/test/fuzz/Makefile deleted file mode 100644 index 3bf4486b8..000000000 --- a/test/fuzz/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/make -f - -.PHONY: fuzz-mempool -fuzz-mempool: - cd mempool && \ - rm -f *-fuzz.zip && \ - go-fuzz-build && \ - go-fuzz - -.PHONY: fuzz-p2p-sc -fuzz-p2p-sc: - cd p2p/secretconnection && \ - rm -f *-fuzz.zip && \ - go run ./init-corpus/main.go && \ - go-fuzz-build && \ - go-fuzz - -.PHONY: fuzz-rpc-server -fuzz-rpc-server: - cd rpc/jsonrpc/server && \ - rm -f *-fuzz.zip && \ - go-fuzz-build && \ - go-fuzz - -clean: - find . -name corpus -type d -exec rm -rf {} +; - find . -name crashers -type d -exec rm -rf {} +; - find . -name suppressions -type d -exec rm -rf {} +; - find . -name *\.zip -type f -delete diff --git a/test/fuzz/README.md b/test/fuzz/README.md index 707217afd..68077ad23 100644 --- a/test/fuzz/README.md +++ b/test/fuzz/README.md @@ -1,72 +1,23 @@ # fuzz -Fuzzing for various packages in Tendermint using [go-fuzz](https://github.com/dvyukov/go-fuzz) library. +Fuzzing for various packages in Tendermint using the fuzzing infrastructure included in +Go 1.18. Inputs: - mempool `CheckTx` (using kvstore in-process ABCI app) -- p2p `Addrbook#AddAddress` -- p2p `pex.Reactor#Receive` - p2p `SecretConnection#Read` and `SecretConnection#Write` - rpc jsonrpc server -## Directory structure - -``` -| test -| |- corpus/ -| |- crashers/ -| |- init-corpus/ -| |- suppressions/ -| |- testdata/ -| |- .go -``` - -`/corpus` directory contains corpus data. The idea is to help the fuzzier to -understand what bytes sequences are semantically valid (e.g. if we're testing -PNG decoder, then we would put black-white PNG into corpus directory; with -blockchain reactor - we would put blockchain messages into corpus). - -`/init-corpus` (if present) contains a script for generating corpus data. - -`/testdata` directory may contain an additional data (like `addrbook.json`). - -Upon running the fuzzier, `/crashers` and `/suppressions` dirs will be created, -along with .zip archive. `/crashers` will show any inputs, which have -lead to panics (plus a trace). `/suppressions` will show any suppressed inputs. - ## Running -```sh -make fuzz-mempool -make fuzz-p2p-addrbook -make fuzz-p2p-pex -make fuzz-p2p-sc -make fuzz-rpc-server -``` - -Each command will create corpus data (if needed), generate a fuzz archive and -call `go-fuzz` executable. - -Then watch out for the respective outputs in the fuzzer output to announce new -crashers which can be found in the directory `crashers`. - -For example if we find +The fuzz tests are in native Go fuzzing format. Use the `go` +tool to run them: ```sh -ls crashers/ -61bde465f47c93254d64d643c3b2480e0a54666e -61bde465f47c93254d64d643c3b2480e0a54666e.output -61bde465f47c93254d64d643c3b2480e0a54666e.quoted -da39a3ee5e6b4b0d3255bfef95601890afd80709 -da39a3ee5e6b4b0d3255bfef95601890afd80709.output -da39a3ee5e6b4b0d3255bfef95601890afd80709.quoted +go test -fuzz Mempool ./tests +go test -fuzz P2PSecretConnection ./tests +go test -fuzz RPCJSONRPCServer ./tests ``` -the crashing bytes generated by the fuzzer will be in -`61bde465f47c93254d64d643c3b2480e0a54666e` the respective crash report in -`61bde465f47c93254d64d643c3b2480e0a54666e.output` - -and the bug report can be created by retrieving the bytes in -`61bde465f47c93254d64d643c3b2480e0a54666e` and feeding those back into the -`Fuzz` function. +See [the Go Fuzzing introduction](https://go.dev/doc/fuzz/) for more information. diff --git a/test/fuzz/mempool/fuzz_test.go b/test/fuzz/mempool/fuzz_test.go deleted file mode 100644 index 69f34db64..000000000 --- a/test/fuzz/mempool/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package mempool_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - mempool "github.com/tendermint/tendermint/test/fuzz/mempool" -) - -const testdataCasesDir = "testdata/cases" - -func TestMempoolTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - mempool.Fuzz(input) - }) - } -} diff --git a/test/fuzz/oss-fuzz-build.sh b/test/fuzz/oss-fuzz-build.sh index ef2052614..836253d4d 100755 --- a/test/fuzz/oss-fuzz-build.sh +++ b/test/fuzz/oss-fuzz-build.sh @@ -1,10 +1,22 @@ -#!/bin/bash -eu +#!/bin/bash + +set -euo pipefail export FUZZ_ROOT="github.com/tendermint/tendermint" -(cd test/fuzz/p2p/secretconnection; go run ./init-corpus/main.go) -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/p2p/secretconnection Fuzz fuzz_p2p_secretconnection fuzz +build_go_fuzzer() { + local function="$1" + local fuzzer="$2" -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/mempool Fuzz fuzz_mempool fuzz + gotip run github.com/orijtech/otils/corpus2ossfuzz@latest -o "$OUT"/"$fuzzer"_seed_corpus.zip -corpus test/fuzz/tests/testdata/fuzz/"$function" + compile_native_go_fuzzer "$FUZZ_ROOT"/test/fuzz/tests "$function" "$fuzzer" +} -compile_go_fuzzer "$FUZZ_ROOT"/test/fuzz/rpc/jsonrpc/server Fuzz fuzz_rpc_jsonrpc_server fuzz +gotip get github.com/AdamKorcz/go-118-fuzz-build/utils +gotip get github.com/prometheus/common/expfmt@v0.32.1 + +build_go_fuzzer FuzzP2PSecretConnection fuzz_p2p_secretconnection + +build_go_fuzzer FuzzMempool fuzz_mempool + +build_go_fuzzer FuzzRPCJSONRPCServer fuzz_rpc_jsonrpc_server diff --git a/test/fuzz/p2p/secretconnection/fuzz_test.go b/test/fuzz/p2p/secretconnection/fuzz_test.go deleted file mode 100644 index 6fe19b03b..000000000 --- a/test/fuzz/p2p/secretconnection/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package secretconnection_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/test/fuzz/p2p/secretconnection" -) - -const testdataCasesDir = "testdata/cases" - -func TestSecretConnectionTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - secretconnection.Fuzz(input) - }) - } -} diff --git a/test/fuzz/p2p/secretconnection/init-corpus/main.go b/test/fuzz/p2p/secretconnection/init-corpus/main.go deleted file mode 100644 index 3a2537ff7..000000000 --- a/test/fuzz/p2p/secretconnection/init-corpus/main.go +++ /dev/null @@ -1,47 +0,0 @@ -// nolint: gosec -package main - -import ( - "flag" - "fmt" - "log" - "os" - "path/filepath" -) - -func main() { - baseDir := flag.String("base", ".", `where the "corpus" directory will live`) - flag.Parse() - - initCorpus(*baseDir) -} - -func initCorpus(baseDir string) { - log.SetFlags(0) - - corpusDir := filepath.Join(baseDir, "corpus") - if err := os.MkdirAll(corpusDir, 0755); err != nil { - log.Fatal(err) - } - - data := []string{ - "dadc04c2-cfb1-4aa9-a92a-c0bf780ec8b6", - "", - " ", - " a ", - `{"a": 12, "tsp": 999, k: "blue"}`, - `9999.999`, - `""`, - `Tendermint fuzzing`, - } - - for i, datum := range data { - filename := filepath.Join(corpusDir, fmt.Sprintf("%d", i)) - - if err := os.WriteFile(filename, []byte(datum), 0644); err != nil { - log.Fatalf("can't write %v to %q: %v", datum, filename, err) - } - - log.Printf("wrote %q", filename) - } -} diff --git a/test/fuzz/rpc/jsonrpc/server/fuzz_test.go b/test/fuzz/rpc/jsonrpc/server/fuzz_test.go deleted file mode 100644 index 8a34da8a6..000000000 --- a/test/fuzz/rpc/jsonrpc/server/fuzz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package server_test - -import ( - "io" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/test/fuzz/rpc/jsonrpc/server" -) - -const testdataCasesDir = "testdata/cases" - -func TestServerTestdataCases(t *testing.T) { - entries, err := os.ReadDir(testdataCasesDir) - require.NoError(t, err) - - for _, e := range entries { - entry := e - t.Run(entry.Name(), func(t *testing.T) { - defer func() { - r := recover() - require.Nilf(t, r, "testdata/cases test panic") - }() - f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name())) - require.NoError(t, err) - input, err := io.ReadAll(f) - require.NoError(t, err) - server.Fuzz(input) - }) - } -} diff --git a/test/fuzz/rpc/jsonrpc/server/handler.go b/test/fuzz/rpc/jsonrpc/server/handler.go deleted file mode 100644 index c9203e9f5..000000000 --- a/test/fuzz/rpc/jsonrpc/server/handler.go +++ /dev/null @@ -1,65 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - - "github.com/tendermint/tendermint/libs/log" - rs "github.com/tendermint/tendermint/rpc/jsonrpc/server" - "github.com/tendermint/tendermint/rpc/jsonrpc/types" -) - -var rpcFuncMap = map[string]*rs.RPCFunc{ - "c": rs.NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) { - return "foo", nil - }, "s", "i"), -} -var mux *http.ServeMux - -func init() { - mux = http.NewServeMux() - rs.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger()) -} - -func Fuzz(data []byte) int { - if len(data) == 0 { - return -1 - } - - req, _ := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data)) - rec := httptest.NewRecorder() - mux.ServeHTTP(rec, req) - res := rec.Result() - blob, err := io.ReadAll(res.Body) - if err != nil { - panic(err) - } - if err := res.Body.Close(); err != nil { - panic(err) - } - if len(blob) == 0 { - return 1 - } - - if outputJSONIsSlice(blob) { - recv := []types.RPCResponse{} - if err := json.Unmarshal(blob, &recv); err != nil { - panic(err) - } - return 1 - } - recv := &types.RPCResponse{} - if err := json.Unmarshal(blob, recv); err != nil { - panic(err) - } - return 1 -} - -func outputJSONIsSlice(input []byte) bool { - slice := []interface{}{} - return json.Unmarshal(input, &slice) == nil -} diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d b/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d deleted file mode 100644 index 6e7ea636e..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/1184f5b8d4b6dd08709cf1513f26744167065e0d +++ /dev/null @@ -1 +0,0 @@ -[0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d b/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d deleted file mode 100644 index 6e7ea636e..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/1184f5b8d4b6dd08709cf1513f26744167065e0d +++ /dev/null @@ -1 +0,0 @@ -[0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 b/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 deleted file mode 100644 index e0be2aa4b..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/bbcffb1cdb2cea50fd3dd8c1524905551d0b2e79 +++ /dev/null @@ -1 +0,0 @@ -[0,0] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 b/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 deleted file mode 100644 index 0f7836d2f..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/cases/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 +++ /dev/null @@ -1 +0,0 @@ -[{"iD":7},{"iD":7}] \ No newline at end of file diff --git a/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 b/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 deleted file mode 100644 index 0f7836d2f..000000000 --- a/test/fuzz/rpc/jsonrpc/server/testdata/clusterfuzz-testcase-minimized-fuzz_rpc_jsonrpc_server-4738572803506176 +++ /dev/null @@ -1 +0,0 @@ -[{"iD":7},{"iD":7}] \ No newline at end of file diff --git a/test/fuzz/mempool/checktx.go b/test/fuzz/tests/mempool_test.go similarity index 59% rename from test/fuzz/mempool/checktx.go rename to test/fuzz/tests/mempool_test.go index 8be90f0c2..2c8623036 100644 --- a/test/fuzz/mempool/checktx.go +++ b/test/fuzz/tests/mempool_test.go @@ -1,7 +1,10 @@ -package mempool +//go:build gofuzz || go1.18 + +package tests import ( "context" + "testing" abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" @@ -10,10 +13,7 @@ import ( "github.com/tendermint/tendermint/libs/log" ) -var mp *mempool.TxMempool -var getMp func() mempool.Mempool - -func init() { +func FuzzMempool(f *testing.F) { app := kvstore.NewApplication() logger := log.NewNopLogger() conn := abciclient.NewLocalClient(logger, app) @@ -25,19 +25,9 @@ func init() { cfg := config.DefaultMempoolConfig() cfg.Broadcast = false - getMp = func() mempool.Mempool { - if mp == nil { - mp = mempool.NewTxMempool(logger, cfg, conn) - } - return mp - } -} + mp := mempool.NewTxMempool(logger, cfg, conn) -func Fuzz(data []byte) int { - err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{}) - if err != nil { - return 0 - } - - return 1 + f.Fuzz(func(t *testing.T, data []byte) { + _ = mp.CheckTx(context.Background(), data, nil, mempool.TxInfo{}) + }) } diff --git a/test/fuzz/p2p/secretconnection/read_write.go b/test/fuzz/tests/p2p_secretconnection_test.go similarity index 94% rename from test/fuzz/p2p/secretconnection/read_write.go rename to test/fuzz/tests/p2p_secretconnection_test.go index 87d547e55..65f268a7b 100644 --- a/test/fuzz/p2p/secretconnection/read_write.go +++ b/test/fuzz/tests/p2p_secretconnection_test.go @@ -1,19 +1,28 @@ -package secretconnection +//go:build gofuzz || go1.18 + +package tests import ( "bytes" "fmt" "io" "log" + "testing" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/internal/libs/async" sc "github.com/tendermint/tendermint/internal/p2p/conn" ) -func Fuzz(data []byte) int { +func FuzzP2PSecretConnection(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} + +func fuzz(data []byte) { if len(data) == 0 { - return -1 + return } fooConn, barConn := makeSecretConnPair() @@ -44,14 +53,11 @@ func Fuzz(data []byte) int { } copy(dataRead[totalRead:], buf[:m]) totalRead += m - log.Printf("total read: %d", totalRead) } if !bytes.Equal(data, dataRead) { panic("bytes written != read") } - - return 1 } type kvstoreConn struct { diff --git a/test/fuzz/tests/rpc_jsonrpc_server_test.go b/test/fuzz/tests/rpc_jsonrpc_server_test.go new file mode 100644 index 000000000..67dee9ef2 --- /dev/null +++ b/test/fuzz/tests/rpc_jsonrpc_server_test.go @@ -0,0 +1,72 @@ +//go:build gofuzz || go1.18 + +package tests + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/tendermint/tendermint/libs/log" + rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" + "github.com/tendermint/tendermint/rpc/jsonrpc/types" +) + +func FuzzRPCJSONRPCServer(f *testing.F) { + type args struct { + S string `json:"s"` + I int `json:"i"` + } + var rpcFuncMap = map[string]*rpcserver.RPCFunc{ + "c": rpcserver.NewRPCFunc(func(context.Context, *args) (string, error) { + return "foo", nil + }), + } + + mux := http.NewServeMux() + rpcserver.RegisterRPCFuncs(mux, rpcFuncMap, log.NewNopLogger()) + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) == 0 { + return + } + + req, err := http.NewRequest("POST", "http://localhost/", bytes.NewReader(data)) + if err != nil { + panic(err) + } + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + res := rec.Result() + blob, err := io.ReadAll(res.Body) + if err != nil { + panic(err) + } + if err := res.Body.Close(); err != nil { + panic(err) + } + if len(blob) == 0 { + return + } + + if outputJSONIsSlice(blob) { + var recv []types.RPCResponse + if err := json.Unmarshal(blob, &recv); err != nil { + panic(err) + } + return + } + var recv types.RPCResponse + if err := json.Unmarshal(blob, &recv); err != nil { + panic(err) + } + }) +} + +func outputJSONIsSlice(input []byte) bool { + var slice []json.RawMessage + return json.Unmarshal(input, &slice) == nil +} diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 b/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 new file mode 100644 index 000000000..88467017a --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/1daffc1033a0bfc7f0c2bccb7440674e67a9e2cc0a4531863076254ada059863 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("S1") diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 b/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 new file mode 100644 index 000000000..a96f5599e --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0") diff --git a/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzMempool/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 new file mode 100644 index 000000000..f0b8ea88b --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/0f1a3d10e4d642e42a3ccd9bad652d355431f5824327271aca6f648e8cd4e786 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(" ") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 new file mode 100644 index 000000000..a3668a6db --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/172c521d1c5e7a5cce55e39b235928fc1c8c4adbb4635913c204c4724cf47d20 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("{\"a\": 12, \"tsp\": 999, k: \"blue\"}") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea new file mode 100644 index 000000000..98241189c --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/a9481542b8154bfe8fe868c8907cb66557347cb9b45709b17da861997d7cabea @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\"\"") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 new file mode 100644 index 000000000..c479f2604 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/ba3758980fe724f83bdf1cb97caa73657b4a78d48e5fd6fc3b1590d24799e803 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("9999.999") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 new file mode 100644 index 000000000..280f15bf7 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/c22ff3cdf5145a03ecc6a2c18a7ec4eb3c9e1384af92cfa14cf50951535b6c85 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(" a ") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd new file mode 100644 index 000000000..017f8d03f --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzP2PSecretConnection/dc7304b2cddeadd08647d30b1d027f749960376c338e14a81e0396ffc6e6d6bd @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("Tendermint fuzzing") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 new file mode 100644 index 000000000..53742f182 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/058ae08103537df220789dea46edb8b7cf7368e90da0cb35888a1452f4d114a2 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[{\"iD\":7},{\"iD\":7}]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 new file mode 100644 index 000000000..ef2bd593a --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/2ab633cb322fca9e76fc965b430076844ebd0b3c4f30f5263b94a3d50f00bce6 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[0,0]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f new file mode 100644 index 000000000..fb9f33963 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/aadb440fa55da05c1185e3e64b33c804d994cce06781e8c39481411793a8a73f @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[0]") \ No newline at end of file diff --git a/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d new file mode 100644 index 000000000..e0f2da225 --- /dev/null +++ b/test/fuzz/tests/testdata/fuzz/FuzzRPCJSONRPCServer/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") \ No newline at end of file diff --git a/types/block.go b/types/block.go index d6e45af6a..17e9812cf 100644 --- a/types/block.go +++ b/types/block.go @@ -13,7 +13,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bits" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmmath "github.com/tendermint/tendermint/libs/math" @@ -603,21 +602,19 @@ const ( // CommitSig is a part of the Vote included in a Commit. type CommitSig struct { - BlockIDFlag BlockIDFlag `json:"block_id_flag"` - ValidatorAddress Address `json:"validator_address"` - Timestamp time.Time `json:"timestamp"` - Signature []byte `json:"signature"` - VoteExtension VoteExtensionToSign `json:"vote_extension"` + BlockIDFlag BlockIDFlag `json:"block_id_flag"` + ValidatorAddress Address `json:"validator_address"` + Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` } // NewCommitSigForBlock returns new CommitSig with BlockIDFlagCommit. -func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time, ext VoteExtensionToSign) CommitSig { +func NewCommitSigForBlock(signature []byte, valAddr Address, ts time.Time) CommitSig { return CommitSig{ BlockIDFlag: BlockIDFlagCommit, ValidatorAddress: valAddr, Timestamp: ts, Signature: signature, - VoteExtension: ext, } } @@ -650,14 +647,12 @@ func (cs CommitSig) Absent() bool { // 1. first 6 bytes of signature // 2. first 6 bytes of validator address // 3. block ID flag -// 4. first 6 bytes of the vote extension -// 5. timestamp +// 4. timestamp func (cs CommitSig) String() string { - return fmt.Sprintf("CommitSig{%X by %X on %v with %X @ %s}", + return fmt.Sprintf("CommitSig{%X by %X on %v @ %s}", tmbytes.Fingerprint(cs.Signature), tmbytes.Fingerprint(cs.ValidatorAddress), cs.BlockIDFlag, - tmbytes.Fingerprint(cs.VoteExtension.BytesPacked()), CanonicalTime(cs.Timestamp)) } @@ -729,7 +724,6 @@ func (cs *CommitSig) ToProto() *tmproto.CommitSig { ValidatorAddress: cs.ValidatorAddress, Timestamp: cs.Timestamp, Signature: cs.Signature, - VoteExtension: cs.VoteExtension.ToProto(), } } @@ -741,7 +735,6 @@ func (cs *CommitSig) FromProto(csp tmproto.CommitSig) error { cs.ValidatorAddress = csp.ValidatorAddress cs.Timestamp = csp.Timestamp cs.Signature = csp.Signature - cs.VoteExtension = VoteExtensionToSignFromProto(csp.VoteExtension) return cs.ValidateBasic() } @@ -786,7 +779,11 @@ func CommitToVoteSet(chainID string, commit *Commit, vals *ValidatorSet) *VoteSe if commitSig.Absent() { continue // OK, some precommits can be missing. } - added, err := voteSet.AddVote(commit.GetVote(int32(idx))) + vote := commit.GetVote(int32(idx)) + if err := vote.ValidateBasic(); err != nil { + panic(fmt.Errorf("failed to validate vote reconstructed from LastCommit: %w", err)) + } + added, err := voteSet.AddVote(vote) if !added || err != nil { panic(fmt.Errorf("failed to reconstruct LastCommit: %w", err)) } @@ -808,7 +805,6 @@ func (commit *Commit) GetVote(valIdx int32) *Vote { ValidatorAddress: commitSig.ValidatorAddress, ValidatorIndex: valIdx, Signature: commitSig.Signature, - VoteExtension: commitSig.VoteExtension.ToVoteExtension(), } } @@ -1129,9 +1125,9 @@ func (blockID BlockID) IsNil() bool { // IsComplete returns true if this is a valid BlockID of a non-nil block. func (blockID BlockID) IsComplete() bool { - return len(blockID.Hash) == tmhash.Size && + return len(blockID.Hash) == crypto.HashSize && blockID.PartSetHeader.Total > 0 && - len(blockID.PartSetHeader.Hash) == tmhash.Size + len(blockID.PartSetHeader.Hash) == crypto.HashSize } // String returns a human readable string representation of the BlockID. diff --git a/types/block_meta_test.go b/types/block_meta_test.go index a1a382ffa..0ce90c40b 100644 --- a/types/block_meta_test.go +++ b/types/block_meta_test.go @@ -5,13 +5,13 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" ) func TestBlockMeta_ToProto(t *testing.T) { h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} bm := &BlockMeta{ BlockID: bi, @@ -48,9 +48,9 @@ func TestBlockMeta_ToProto(t *testing.T) { func TestBlockMeta_ValidateBasic(t *testing.T) { h := MakeRandHeader() - bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} - bi2 := BlockID{Hash: tmrand.Bytes(tmhash.Size), - PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(tmhash.Size)}} + bi := BlockID{Hash: h.Hash(), PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} + bi2 := BlockID{Hash: tmrand.Bytes(crypto.HashSize), + PartSetHeader: PartSetHeader{Total: 123, Hash: tmrand.Bytes(crypto.HashSize)}} bi3 := BlockID{Hash: []byte("incorrect hash"), PartSetHeader: PartSetHeader{Total: 123, Hash: []byte("incorrect hash")}} diff --git a/types/block_test.go b/types/block_test.go index 4ed47dd9d..7f2378505 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -19,7 +19,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bits" "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -213,8 +212,8 @@ func TestBlockString(t *testing.T) { func makeBlockIDRandom() BlockID { var ( - blockHash = make([]byte, tmhash.Size) - partSetHash = make([]byte, tmhash.Size) + blockHash = make([]byte, crypto.HashSize) + partSetHash = make([]byte, crypto.HashSize) ) rand.Read(blockHash) //nolint: errcheck // ignore errcheck for read rand.Read(partSetHash) //nolint: errcheck // ignore errcheck for read @@ -223,8 +222,8 @@ func makeBlockIDRandom() BlockID { func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) BlockID { var ( - h = make([]byte, tmhash.Size) - psH = make([]byte, tmhash.Size) + h = make([]byte, crypto.HashSize) + psH = make([]byte, crypto.HashSize) ) copy(h, hash) copy(psH, partSetHash) @@ -274,7 +273,7 @@ func TestCommit(t *testing.T) { require.NotNil(t, commit.BitArray()) assert.Equal(t, bits.NewBitArray(10).Size(), commit.BitArray().Size()) - assert.Equal(t, voteSet.GetByIndex(0), commit.GetByIndex(0)) + assert.Equal(t, voteWithoutExtension(voteSet.GetByIndex(0)), commit.GetByIndex(0)) assert.True(t, commit.IsCommit()) } @@ -324,10 +323,10 @@ func TestMaxCommitBytes(t *testing.T) { Height: math.MaxInt64, Round: math.MaxInt32, BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: PartSetHeader{ Total: math.MaxInt32, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, Signatures: []CommitSig{cs}, @@ -359,15 +358,15 @@ func TestHeaderHash(t *testing.T) { ChainID: "chainId", Height: 3, Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), 6, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), + ValidatorsHash: crypto.Checksum([]byte("validators_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), }, hexBytesFromString(t, "F740121F553B5418C3EFBD343C2DBFE9E007BB67B0D020A0741374BAB65242A4")}, {"nil header yields nil", nil, nil}, @@ -376,15 +375,15 @@ func TestHeaderHash(t *testing.T) { ChainID: "chainId", Height: 3, Time: time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC), - LastBlockID: makeBlockID(make([]byte, tmhash.Size), 6, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), 6, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), ValidatorsHash: nil, - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), }, nil}, } @@ -455,15 +454,15 @@ func TestMaxHeaderBytes(t *testing.T) { ChainID: maxChainID, Height: math.MaxInt64, Time: timestamp, - LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt32, make([]byte, tmhash.Size)), - LastCommitHash: tmhash.Sum([]byte("last_commit_hash")), - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: tmhash.Sum([]byte("validators_hash")), - NextValidatorsHash: tmhash.Sum([]byte("next_validators_hash")), - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: tmhash.Sum([]byte("app_hash")), - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), + LastBlockID: makeBlockID(make([]byte, crypto.HashSize), math.MaxInt32, make([]byte, crypto.HashSize)), + LastCommitHash: crypto.Checksum([]byte("last_commit_hash")), + DataHash: crypto.Checksum([]byte("data_hash")), + ValidatorsHash: crypto.Checksum([]byte("validators_hash")), + NextValidatorsHash: crypto.Checksum([]byte("next_validators_hash")), + ConsensusHash: crypto.Checksum([]byte("consensus_hash")), + AppHash: crypto.Checksum([]byte("app_hash")), + LastResultsHash: crypto.Checksum([]byte("last_results_hash")), + EvidenceHash: crypto.Checksum([]byte("evidence_hash")), ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } @@ -571,7 +570,7 @@ func TestCommitToVoteSet(t *testing.T) { voteSet2 := CommitToVoteSet(chainID, commit, valSet) for i := int32(0); int(i) < len(vals); i++ { - vote1 := voteSet.GetByIndex(i) + vote1 := voteWithoutExtension(voteSet.GetByIndex(i)) vote2 := voteSet2.GetByIndex(i) vote3 := commit.GetVote(i) @@ -765,7 +764,7 @@ func MakeRandHeader() Header { chainID := "test" t := time.Now() height := mrand.Int63() - randBytes := tmrand.Bytes(tmhash.Size) + randBytes := tmrand.Bytes(crypto.HashSize) randAddress := tmrand.Bytes(crypto.AddressSize) h := Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 1}, @@ -1014,7 +1013,7 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size+1), + Hash: make([]byte, crypto.HashSize+1), }, }, true, "wrong Hash", @@ -1026,9 +1025,9 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size+1), + Hash: make([]byte, crypto.HashSize+1), }, }, }, @@ -1041,12 +1040,12 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize+1), }, true, "wrong LastCommitHash", }, @@ -1057,13 +1056,13 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize+1), }, true, "wrong DataHash", }, @@ -1074,14 +1073,14 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size+1), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize+1), }, true, "wrong EvidenceHash", }, @@ -1092,14 +1091,14 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize+1), }, true, "invalid ProposerAddress length", @@ -1111,16 +1110,16 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize+1), }, true, "wrong ValidatorsHash", }, @@ -1131,17 +1130,17 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize+1), }, true, "wrong NextValidatorsHash", }, @@ -1152,18 +1151,18 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize+1), }, true, "wrong ConsensusHash", }, @@ -1174,19 +1173,19 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size+1), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize), + LastResultsHash: make([]byte, crypto.HashSize+1), }, true, "wrong LastResultsHash", }, @@ -1197,19 +1196,19 @@ func TestHeader_ValidateBasic(t *testing.T) { ChainID: string(make([]byte, MaxChainIDLen)), Height: 1, LastBlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, - LastCommitHash: make([]byte, tmhash.Size), - DataHash: make([]byte, tmhash.Size), - EvidenceHash: make([]byte, tmhash.Size), + LastCommitHash: make([]byte, crypto.HashSize), + DataHash: make([]byte, crypto.HashSize), + EvidenceHash: make([]byte, crypto.HashSize), ProposerAddress: make([]byte, crypto.AddressSize), - ValidatorsHash: make([]byte, tmhash.Size), - NextValidatorsHash: make([]byte, tmhash.Size), - ConsensusHash: make([]byte, tmhash.Size), - LastResultsHash: make([]byte, tmhash.Size), + ValidatorsHash: make([]byte, crypto.HashSize), + NextValidatorsHash: make([]byte, crypto.HashSize), + ConsensusHash: make([]byte, crypto.HashSize), + LastResultsHash: make([]byte, crypto.HashSize), }, false, "", }, @@ -1262,9 +1261,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, }, @@ -1276,9 +1275,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, Signatures: []CommitSig{ @@ -1297,9 +1296,9 @@ func TestCommit_ValidateBasic(t *testing.T) { Height: 1, Round: 1, BlockID: BlockID{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), PartSetHeader: PartSetHeader{ - Hash: make([]byte, tmhash.Size), + Hash: make([]byte, crypto.HashSize), }, }, Signatures: []CommitSig{ diff --git a/types/canonical.go b/types/canonical.go index 2e2063e36..9a3d995ec 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -51,26 +51,29 @@ func CanonicalizeProposal(chainID string, proposal *tmproto.Proposal) tmproto.Ca } } -func GetVoteExtensionToSign(ext *tmproto.VoteExtension) *tmproto.VoteExtensionToSign { - if ext == nil { - return nil - } - return &tmproto.VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, +// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does +// not contain ValidatorIndex and ValidatorAddress fields, or any fields +// relating to vote extensions. +func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { + return tmproto.CanonicalVote{ + Type: vote.Type, + Height: vote.Height, // encoded as sfixed64 + Round: int64(vote.Round), // encoded as sfixed64 + BlockID: CanonicalizeBlockID(vote.BlockID), + Timestamp: vote.Timestamp, + ChainID: chainID, } } -// CanonicalizeVote transforms the given Vote to a CanonicalVote, which does -// not contain ValidatorIndex and ValidatorAddress fields. -func CanonicalizeVote(chainID string, vote *tmproto.Vote) tmproto.CanonicalVote { - return tmproto.CanonicalVote{ - Type: vote.Type, - Height: vote.Height, // encoded as sfixed64 - Round: int64(vote.Round), // encoded as sfixed64 - BlockID: CanonicalizeBlockID(vote.BlockID), - Timestamp: vote.Timestamp, - ChainID: chainID, - VoteExtension: GetVoteExtensionToSign(vote.VoteExtension), +// CanonicalizeVoteExtension extracts the vote extension from the given vote +// and constructs a CanonicalizeVoteExtension struct, whose representation in +// bytes is what is signed in order to produce the vote extension's signature. +func CanonicalizeVoteExtension(chainID string, vote *tmproto.Vote) tmproto.CanonicalVoteExtension { + return tmproto.CanonicalVoteExtension{ + Extension: vote.Extension, + Height: vote.Height, + Round: int64(vote.Round), + ChainId: chainID, } } diff --git a/types/canonical_test.go b/types/canonical_test.go index 53a8ea52f..2ccb80ff7 100644 --- a/types/canonical_test.go +++ b/types/canonical_test.go @@ -4,13 +4,13 @@ import ( "reflect" "testing" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) func TestCanonicalizeBlockID(t *testing.T) { - randhash := tmrand.Bytes(tmhash.Size) + randhash := tmrand.Bytes(crypto.HashSize) block1 := tmproto.BlockID{Hash: randhash, PartSetHeader: tmproto.PartSetHeader{Total: 5, Hash: randhash}} block2 := tmproto.BlockID{Hash: randhash, diff --git a/types/evidence.go b/types/evidence.go index c8ea967cf..aed954a93 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -12,8 +12,8 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/jsontypes" tmmath "github.com/tendermint/tendermint/libs/math" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -113,7 +113,7 @@ func (dve *DuplicateVoteEvidence) Bytes() []byte { // Hash returns the hash of the evidence. func (dve *DuplicateVoteEvidence) Hash() []byte { - return tmhash.Sum(dve.Bytes()) + return crypto.Checksum(dve.Bytes()) } // Height returns the height of the infraction @@ -211,14 +211,28 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica return nil, errors.New("nil duplicate vote evidence") } - vA, err := VoteFromProto(pb.VoteA) - if err != nil { - return nil, err + var vA *Vote + if pb.VoteA != nil { + var err error + vA, err = VoteFromProto(pb.VoteA) + if err != nil { + return nil, err + } + if err = vA.ValidateBasic(); err != nil { + return nil, err + } } - vB, err := VoteFromProto(pb.VoteB) - if err != nil { - return nil, err + var vB *Vote + if pb.VoteB != nil { + var err error + vB, err = VoteFromProto(pb.VoteB) + if err != nil { + return nil, err + } + if err = vB.ValidateBasic(); err != nil { + return nil, err + } } dve := &DuplicateVoteEvidence{ @@ -360,10 +374,10 @@ func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *He func (l *LightClientAttackEvidence) Hash() []byte { buf := make([]byte, binary.MaxVarintLen64) n := binary.PutVarint(buf, l.CommonHeight) - bz := make([]byte, tmhash.Size+n) - copy(bz[:tmhash.Size-1], l.ConflictingBlock.Hash().Bytes()) - copy(bz[tmhash.Size:], buf) - return tmhash.Sum(bz) + bz := make([]byte, crypto.HashSize+n) + copy(bz[:crypto.HashSize-1], l.ConflictingBlock.Hash().Bytes()) + copy(bz[crypto.HashSize:], buf) + return crypto.Checksum(bz) } // Height returns the last height at which the primary provider and witness provider had the same header. @@ -829,10 +843,10 @@ func makeMockVote(height int64, round, index int32, addr Address, func randBlockID() BlockID { return BlockID{ - Hash: tmrand.Bytes(tmhash.Size), + Hash: tmrand.Bytes(crypto.HashSize), PartSetHeader: PartSetHeader{ Total: 1, - Hash: tmrand.Bytes(tmhash.Size), + Hash: tmrand.Bytes(crypto.HashSize), }, } } diff --git a/types/evidence_test.go b/types/evidence_test.go index e284c2fca..27e346343 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -13,7 +13,6 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" @@ -93,15 +92,15 @@ func TestDuplicateVoteEvidence(t *testing.T) { ev, err := NewMockDuplicateVoteEvidence(ctx, height, time.Now(), "mock-chain-id") require.NoError(t, err) - assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes())) + assert.Equal(t, ev.Hash(), crypto.Checksum(ev.Bytes())) assert.NotNil(t, ev.String()) assert.Equal(t, ev.Height(), height) } func TestDuplicateVoteEvidenceValidation(t *testing.T) { val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" ctx, cancel := context.WithCancel(context.Background()) @@ -153,7 +152,7 @@ func TestLightClientAttackEvidenceBasic(t *testing.T) { header := makeHeaderRandom() header.Height = height - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) lcae := &LightClientAttackEvidence{ @@ -217,7 +216,7 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) { header := makeHeaderRandom() header.Height = height header.ValidatorsHash = valSet.Hash() - blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, time.Now()) require.NoError(t, err) lcae := &LightClientAttackEvidence{ @@ -332,14 +331,14 @@ func makeHeaderRandom() *Header { Height: int64(mrand.Uint32() + 1), Time: time.Now(), LastBlockID: makeBlockIDRandom(), - LastCommitHash: crypto.CRandBytes(tmhash.Size), - DataHash: crypto.CRandBytes(tmhash.Size), - ValidatorsHash: crypto.CRandBytes(tmhash.Size), - NextValidatorsHash: crypto.CRandBytes(tmhash.Size), - ConsensusHash: crypto.CRandBytes(tmhash.Size), - AppHash: crypto.CRandBytes(tmhash.Size), - LastResultsHash: crypto.CRandBytes(tmhash.Size), - EvidenceHash: crypto.CRandBytes(tmhash.Size), + LastCommitHash: crypto.CRandBytes(crypto.HashSize), + DataHash: crypto.CRandBytes(crypto.HashSize), + ValidatorsHash: crypto.CRandBytes(crypto.HashSize), + NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), + ConsensusHash: crypto.CRandBytes(crypto.HashSize), + AppHash: crypto.CRandBytes(crypto.HashSize), + LastResultsHash: crypto.CRandBytes(crypto.HashSize), + EvidenceHash: crypto.CRandBytes(crypto.HashSize), ProposerAddress: crypto.CRandBytes(crypto.AddressSize), } } @@ -350,8 +349,8 @@ func TestEvidenceProto(t *testing.T) { // -------- Votes -------- val := NewMockPV() - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) @@ -395,8 +394,8 @@ func TestEvidenceVectors(t *testing.T) { // Votes for duplicateEvidence val := NewMockPV() val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) - blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) + blockID2 := makeBlockID(crypto.Checksum([]byte("blockhash2")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) const chainID = "mychain" v := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime) v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime) @@ -424,7 +423,7 @@ func TestEvidenceVectors(t *testing.T) { EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"), ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"), } - blockID3 := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID3 := makeBlockID(header.Hash(), math.MaxInt32, crypto.Checksum([]byte("partshash"))) commit, err := makeCommit(ctx, blockID3, height, 1, voteSet, privVals, defaultVoteTime) require.NoError(t, err) lcae := &LightClientAttackEvidence{ diff --git a/types/params.go b/types/params.go index c0e5e3a00..e8ee6fcdf 100644 --- a/types/params.go +++ b/types/params.go @@ -1,6 +1,7 @@ package types import ( + "crypto/sha256" "errors" "fmt" "time" @@ -8,7 +9,6 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/sr25519" - "github.com/tendermint/tendermint/crypto/tmhash" tmstrings "github.com/tendermint/tendermint/libs/strings" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -328,8 +328,6 @@ func (params ConsensusParams) ValidateConsensusParams() error { // This allows the ConsensusParams to evolve more without breaking the block // protocol. No need for a Merkle tree here, just a small struct to hash. func (params ConsensusParams) HashConsensusParams() []byte { - hasher := tmhash.New() - hp := tmproto.HashedParams{ BlockMaxBytes: params.Block.MaxBytes, BlockMaxGas: params.Block.MaxGas, @@ -340,11 +338,9 @@ func (params ConsensusParams) HashConsensusParams() []byte { panic(err) } - _, err = hasher.Write(bz) - if err != nil { - panic(err) - } - return hasher.Sum(nil) + sum := sha256.Sum256(bz) + + return sum[:] } func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { diff --git a/types/priv_validator.go b/types/priv_validator.go index 5a9b27cb6..72027c622 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -90,11 +90,24 @@ func (pv MockPV) SignVote(ctx context.Context, chainID string, vote *tmproto.Vot } signBytes := VoteSignBytes(useChainID, vote) + extSignBytes := VoteExtensionSignBytes(useChainID, vote) sig, err := pv.PrivKey.Sign(signBytes) if err != nil { return err } vote.Signature = sig + + var extSig []byte + // We only sign vote extensions for precommits + if vote.Type == tmproto.PrecommitType { + extSig, err = pv.PrivKey.Sign(extSignBytes) + if err != nil { + return err + } + } else if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension - vote extensions are only allowed in precommits") + } + vote.ExtensionSignature = extSig return nil } diff --git a/types/proposal_test.go b/types/proposal_test.go index b8b8b3a67..6b2b3dd59 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/internal/libs/protoio" tmrand "github.com/tendermint/tendermint/libs/rand" tmtime "github.com/tendermint/tendermint/libs/time" @@ -61,7 +61,7 @@ func TestProposalVerifySignature(t *testing.T) { prop := NewProposal( 4, 2, 2, - BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}, tmtime.Now()) + BlockID{tmrand.Bytes(crypto.HashSize), PartSetHeader{777, tmrand.Bytes(crypto.HashSize)}}, tmtime.Now()) p := prop.ToProto() signBytes := ProposalSignBytes("test_chain_id", p) @@ -163,7 +163,7 @@ func TestProposalValidateBasic(t *testing.T) { p.Signature = make([]byte, MaxSignatureSize+1) }, true}, } - blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash"))) + blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) for _, tc := range testCases { tc := tc diff --git a/types/test_util.go b/types/test_util.go index dbd3f81ec..8aea2f02c 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -43,5 +43,16 @@ func signAddVote(ctx context.Context, privVal PrivValidator, vote *Vote, voteSet return false, err } vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature return voteSet.AddVote(vote) } + +// Votes constructed from commits don't have extensions, because we don't store +// the extensions themselves in the commit. This method is used to construct a +// copy of a vote, but nil its extension and signature. +func voteWithoutExtension(v *Vote) *Vote { + vc := v.Copy() + vc.Extension = nil + vc.ExtensionSignature = nil + return vc +} diff --git a/types/tx.go b/types/tx.go index c879cf3da..1d429ddf1 100644 --- a/types/tx.go +++ b/types/tx.go @@ -8,8 +8,8 @@ import ( "sort" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -23,7 +23,7 @@ type Tx []byte func (tx Tx) Key() TxKey { return sha256.Sum256(tx) } // Hash computes the TMHASH hash of the wire encoded transaction. -func (tx Tx) Hash() []byte { return tmhash.Sum(tx) } +func (tx Tx) Hash() []byte { return crypto.Checksum(tx) } // String returns the hex-encoded transaction as a string. func (tx Tx) String() string { return fmt.Sprintf("Tx{%X}", []byte(tx)) } diff --git a/types/validation.go b/types/validation.go index 8655bdabd..21c8730f5 100644 --- a/types/validation.go +++ b/types/validation.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/batch" - "github.com/tendermint/tendermint/crypto/tmhash" tmmath "github.com/tendermint/tendermint/libs/math" ) @@ -132,11 +132,11 @@ func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commi } // ValidateHash returns an error if the hash is not empty, but its -// size != tmhash.Size. +// size != crypto.HashSize. func ValidateHash(h []byte) error { - if len(h) > 0 && len(h) != tmhash.Size { + if len(h) > 0 && len(h) != crypto.HashSize { return fmt.Errorf("expected size to be %d bytes, got %d bytes", - tmhash.Size, + crypto.HashSize, len(h), ) } diff --git a/types/vote.go b/types/vote.go index 7333f98fc..f20ee491e 100644 --- a/types/vote.go +++ b/types/vote.go @@ -46,86 +46,43 @@ func NewConflictingVoteError(vote1, vote2 *Vote) *ErrVoteConflictingVotes { // Address is hex bytes. type Address = crypto.Address -// VoteExtensionToSign is a subset of VoteExtension -// that is signed by the validators private key -type VoteExtensionToSign struct { - AppDataToSign []byte `json:"app_data_to_sign"` -} - -func (ext VoteExtensionToSign) ToProto() *tmproto.VoteExtensionToSign { - if ext.IsEmpty() { - return nil - } - return &tmproto.VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, - } -} - -func VoteExtensionToSignFromProto(pext *tmproto.VoteExtensionToSign) VoteExtensionToSign { - if pext == nil { - return VoteExtensionToSign{} - } - return VoteExtensionToSign{ - AppDataToSign: pext.AppDataToSign, - } -} - -func (ext VoteExtensionToSign) IsEmpty() bool { - return len(ext.AppDataToSign) == 0 -} - -// BytesPacked returns a bytes-packed representation for -// debugging and human identification. This function should -// not be used for any logical operations. -func (ext VoteExtensionToSign) BytesPacked() []byte { - res := []byte{} - res = append(res, ext.AppDataToSign...) - return res -} - -// ToVoteExtension constructs a VoteExtension from a VoteExtensionToSign -func (ext VoteExtensionToSign) ToVoteExtension() VoteExtension { - return VoteExtension{ - AppDataToSign: ext.AppDataToSign, - } -} - -// VoteExtension is a set of data provided by the application -// that is additionally included in the vote -type VoteExtension struct { - AppDataToSign []byte `json:"app_data_to_sign"` - AppDataSelfAuthenticating []byte `json:"app_data_self_authenticating"` -} - -// ToSign constructs a VoteExtensionToSign from a VoteExtenstion -func (ext VoteExtension) ToSign() VoteExtensionToSign { - return VoteExtensionToSign{ - AppDataToSign: ext.AppDataToSign, - } -} - -// BytesPacked returns a bytes-packed representation for -// debugging and human identification. This function should -// not be used for any logical operations. -func (ext VoteExtension) BytesPacked() []byte { - res := []byte{} - res = append(res, ext.AppDataToSign...) - res = append(res, ext.AppDataSelfAuthenticating...) - return res -} - // Vote represents a prevote, precommit, or commit vote from validators for // consensus. type Vote struct { - Type tmproto.SignedMsgType `json:"type"` - Height int64 `json:"height,string"` - Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds - BlockID BlockID `json:"block_id"` // zero if vote is nil. - Timestamp time.Time `json:"timestamp"` - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int32 `json:"validator_index"` - Signature []byte `json:"signature"` - VoteExtension VoteExtension `json:"vote_extension"` + Type tmproto.SignedMsgType `json:"type"` + Height int64 `json:"height,string"` + Round int32 `json:"round"` // assume there will not be greater than 2_147_483_647 rounds + BlockID BlockID `json:"block_id"` // zero if vote is nil. + Timestamp time.Time `json:"timestamp"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int32 `json:"validator_index"` + Signature []byte `json:"signature"` + Extension []byte `json:"extension"` + ExtensionSignature []byte `json:"extension_signature"` +} + +// VoteFromProto attempts to convert the given serialization (Protobuf) type to +// our Vote domain type. No validation is performed on the resulting vote - +// this is left up to the caller to decide whether to call ValidateBasic or +// ValidateWithExtension. +func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { + blockID, err := BlockIDFromProto(&pv.BlockID) + if err != nil { + return nil, err + } + + return &Vote{ + Type: pv.Type, + Height: pv.Height, + Round: pv.Round, + BlockID: *blockID, + Timestamp: pv.Timestamp, + ValidatorAddress: pv.ValidatorAddress, + ValidatorIndex: pv.ValidatorIndex, + Signature: pv.Signature, + Extension: pv.Extension, + ExtensionSignature: pv.ExtensionSignature, + }, nil } // CommitSig converts the Vote to a CommitSig. @@ -149,12 +106,11 @@ func (vote *Vote) CommitSig() CommitSig { ValidatorAddress: vote.ValidatorAddress, Timestamp: vote.Timestamp, Signature: vote.Signature, - VoteExtension: vote.VoteExtension.ToSign(), } } // VoteSignBytes returns the proto-encoding of the canonicalized Vote, for -// signing. Panics is the marshaling fails. +// signing. Panics if the marshaling fails. // // The encoded Protobuf message is varint length-prefixed (using MarshalDelimited) // for backwards-compatibility with the Amino encoding, due to e.g. hardware @@ -171,9 +127,23 @@ func VoteSignBytes(chainID string, vote *tmproto.Vote) []byte { return bz } +// VoteExtensionSignBytes returns the proto-encoding of the canonicalized vote +// extension for signing. Panics if the marshaling fails. +// +// Similar to VoteSignBytes, the encoded Protobuf message is varint +// length-prefixed for backwards-compatibility with the Amino encoding. +func VoteExtensionSignBytes(chainID string, vote *tmproto.Vote) []byte { + pb := CanonicalizeVoteExtension(chainID, vote) + bz, err := protoio.MarshalDelimited(&pb) + if err != nil { + panic(err) + } + + return bz +} + func (vote *Vote) Copy() *Vote { voteCopy := *vote - voteCopy.VoteExtension = vote.VoteExtension.Copy() return &voteCopy } @@ -213,23 +183,54 @@ func (vote *Vote) String() string { typeString, tmbytes.Fingerprint(vote.BlockID.Hash), tmbytes.Fingerprint(vote.Signature), - tmbytes.Fingerprint(vote.VoteExtension.BytesPacked()), + tmbytes.Fingerprint(vote.Extension), CanonicalTime(vote.Timestamp), ) } -func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { +func (vote *Vote) verifyAndReturnProto(chainID string, pubKey crypto.PubKey) (*tmproto.Vote, error) { if !bytes.Equal(pubKey.Address(), vote.ValidatorAddress) { - return ErrVoteInvalidValidatorAddress + return nil, ErrVoteInvalidValidatorAddress } v := vote.ToProto() if !pubKey.VerifySignature(VoteSignBytes(chainID, v), vote.Signature) { - return ErrVoteInvalidSignature + return nil, ErrVoteInvalidSignature + } + return v, nil +} + +// Verify checks whether the signature associated with this vote corresponds to +// the given chain ID and public key. This function does not validate vote +// extension signatures - to do so, use VerifyWithExtension instead. +func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { + _, err := vote.verifyAndReturnProto(chainID, pubKey) + return err +} + +// VerifyWithExtension performs the same verification as Verify, but +// additionally checks whether the vote extension signature corresponds to the +// given chain ID and public key. We only verify vote extension signatures for +// precommits. +func (vote *Vote) VerifyWithExtension(chainID string, pubKey crypto.PubKey) error { + v, err := vote.verifyAndReturnProto(chainID, pubKey) + if err != nil { + return err + } + // We only verify vote extension signatures for precommits. + if vote.Type == tmproto.PrecommitType { + extSignBytes := VoteExtensionSignBytes(chainID, v) + // TODO: Remove extension signature nil check to enforce vote extension + // signing once we resolve https://github.com/tendermint/tendermint/issues/8272 + if vote.ExtensionSignature != nil && !pubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { + return ErrVoteInvalidSignature + } } return nil } -// ValidateBasic performs basic validation. +// ValidateBasic checks whether the vote is well-formed. It does not, however, +// check vote extensions - for vote validation with vote extension validation, +// use ValidateWithExtension. func (vote *Vote) ValidateBasic() error { if !IsVoteTypeValid(vote.Type) { return errors.New("invalid Type") @@ -272,38 +273,41 @@ func (vote *Vote) ValidateBasic() error { return fmt.Errorf("signature is too big (max: %d)", MaxSignatureSize) } - // XXX: add length verification for vote extension? + // We should only ever see vote extensions in precommits. + if vote.Type != tmproto.PrecommitType { + if len(vote.Extension) > 0 { + return errors.New("unexpected vote extension") + } + if len(vote.ExtensionSignature) > 0 { + return errors.New("unexpected vote extension signature") + } + } return nil } -func (ext VoteExtension) Copy() VoteExtension { - res := VoteExtension{ - AppDataToSign: ext.AppDataToSign, - AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating, - } - return res -} - -func (ext VoteExtension) IsEmpty() bool { - if len(ext.AppDataToSign) != 0 { - return false - } - if len(ext.AppDataSelfAuthenticating) != 0 { - return false - } - return true -} - -func (ext VoteExtension) ToProto() *tmproto.VoteExtension { - if ext.IsEmpty() { - return nil +// ValidateWithExtension performs the same validations as ValidateBasic, but +// additionally checks whether a vote extension signature is present. This +// function is used in places where vote extension signatures are expected. +func (vote *Vote) ValidateWithExtension() error { + if err := vote.ValidateBasic(); err != nil { + return err } - return &tmproto.VoteExtension{ - AppDataToSign: ext.AppDataToSign, - AppDataSelfAuthenticating: ext.AppDataSelfAuthenticating, + // We should always see vote extension signatures in precommits + if vote.Type == tmproto.PrecommitType { + // TODO(thane): Remove extension length check once + // https://github.com/tendermint/tendermint/issues/8272 is + // resolved. + if len(vote.Extension) > 0 && len(vote.ExtensionSignature) == 0 { + return errors.New("vote extension signature is missing") + } + if len(vote.ExtensionSignature) > MaxSignatureSize { + return fmt.Errorf("vote extension signature is too big (max: %d)", MaxSignatureSize) + } } + + return nil } // ToProto converts the handwritten type to proto generated type @@ -314,15 +318,16 @@ func (vote *Vote) ToProto() *tmproto.Vote { } return &tmproto.Vote{ - Type: vote.Type, - Height: vote.Height, - Round: vote.Round, - BlockID: vote.BlockID.ToProto(), - Timestamp: vote.Timestamp, - ValidatorAddress: vote.ValidatorAddress, - ValidatorIndex: vote.ValidatorIndex, - Signature: vote.Signature, - VoteExtension: vote.VoteExtension.ToProto(), + Type: vote.Type, + Height: vote.Height, + Round: vote.Round, + BlockID: vote.BlockID.ToProto(), + Timestamp: vote.Timestamp, + ValidatorAddress: vote.ValidatorAddress, + ValidatorIndex: vote.ValidatorIndex, + Signature: vote.Signature, + Extension: vote.Extension, + ExtensionSignature: vote.ExtensionSignature, } } @@ -341,38 +346,3 @@ func VotesToProto(votes []*Vote) []*tmproto.Vote { } return res } - -func VoteExtensionFromProto(pext *tmproto.VoteExtension) VoteExtension { - ext := VoteExtension{} - if pext != nil { - ext.AppDataToSign = pext.AppDataToSign - ext.AppDataSelfAuthenticating = pext.AppDataSelfAuthenticating - } - return ext -} - -// FromProto converts a proto generetad type to a handwritten type -// return type, nil if everything converts safely, otherwise nil, error -func VoteFromProto(pv *tmproto.Vote) (*Vote, error) { - if pv == nil { - return nil, errors.New("nil vote") - } - - blockID, err := BlockIDFromProto(&pv.BlockID) - if err != nil { - return nil, err - } - - vote := new(Vote) - vote.Type = pv.Type - vote.Height = pv.Height - vote.Round = pv.Round - vote.BlockID = *blockID - vote.Timestamp = pv.Timestamp - vote.ValidatorAddress = pv.ValidatorAddress - vote.ValidatorIndex = pv.ValidatorIndex - vote.Signature = pv.Signature - vote.VoteExtension = VoteExtensionFromProto(pv.VoteExtension) - - return vote, vote.ValidateBasic() -} diff --git a/types/vote_set.go b/types/vote_set.go index 142a4130c..b4d149576 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -194,7 +194,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) { } // Check signature. - if err := vote.Verify(voteSet.chainID, val.PubKey); err != nil { + if err := vote.VerifyWithExtension(voteSet.chainID, val.PubKey); err != nil { return false, fmt.Errorf("failed to verify vote with ChainID %s and PubKey %s: %w", voteSet.chainID, val.PubKey, err) } diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 4de9b1837..1805b4c3e 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -127,6 +127,7 @@ func TestVoteSet_AddVote_Bad(t *testing.T) { t.Errorf("expected VoteSet.Add to fail, wrong type") } } + } func TestVoteSet_2_3Majority(t *testing.T) { @@ -509,7 +510,6 @@ func randVoteSet( ) (*VoteSet, *ValidatorSet, []PrivValidator) { t.Helper() valSet, privValidators := randValidatorPrivValSet(ctx, t, numValidators, votingPower) - return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators } diff --git a/types/vote_test.go b/types/vote_test.go index 4a852d81f..5673ccf57 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -11,8 +11,8 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" + tmtime "github.com/tendermint/tendermint/libs/time" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -23,7 +23,9 @@ func examplePrevote(t *testing.T) *Vote { func examplePrecommit(t testing.TB) *Vote { t.Helper() - return exampleVote(t, byte(tmproto.PrecommitType)) + vote := exampleVote(t, byte(tmproto.PrecommitType)) + vote.ExtensionSignature = []byte("signature") + return vote } func exampleVote(tb testing.TB, t byte) *Vote { @@ -37,16 +39,17 @@ func exampleVote(tb testing.TB, t byte) *Vote { Round: 2, Timestamp: stamp, BlockID: BlockID{ - Hash: tmhash.Sum([]byte("blockID_hash")), + Hash: crypto.Checksum([]byte("blockID_hash")), PartSetHeader: PartSetHeader{ Total: 1000000, - Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), + Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), }, }, ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, } } + func TestVoteSignable(t *testing.T) { vote := examplePrecommit(t) v := vote.ToProto() @@ -130,12 +133,13 @@ func TestVoteSignBytesTestVectors(t *testing.T) { }, // containing vote extension 5: { - "test_chain_id", &Vote{Height: 1, Round: 1, VoteExtension: VoteExtension{ - AppDataToSign: []byte("signed"), - AppDataSelfAuthenticating: []byte("auth"), - }}, + "test_chain_id", &Vote{ + Height: 1, + Round: 1, + Extension: []byte("extension"), + }, []byte{ - 0x38, // length + 0x2e, // length 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type @@ -146,13 +150,6 @@ func TestVoteSignBytesTestVectors(t *testing.T) { // (field_number << 3) | wire_type 0x32, 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, // chainID - // (field_number << 3) | wire_type - 0x3a, - 0x8, // length - 0xa, // (field_number << 3) | wire_type - 0x6, // length - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, // AppDataSigned - // SelfAuthenticating data is excluded on signing }, // chainID }, } @@ -208,6 +205,82 @@ func TestVoteVerifySignature(t *testing.T) { require.True(t, valid) } +// TestVoteExtension tests that the vote verification behaves correctly in each case +// of vote extension being set on the vote. +func TestVoteExtension(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testCases := []struct { + name string + extension []byte + includeSignature bool + expectError bool + }{ + { + name: "all fields present", + extension: []byte("extension"), + includeSignature: true, + expectError: false, + }, + // TODO(thane): Re-enable once + // https://github.com/tendermint/tendermint/issues/8272 is resolved + //{ + // name: "no extension signature", + // extension: []byte("extension"), + // includeSignature: false, + // expectError: true, + //}, + { + name: "empty extension", + includeSignature: true, + expectError: false, + }, + // TODO: Re-enable once + // https://github.com/tendermint/tendermint/issues/8272 is resolved. + //{ + // name: "no extension and no signature", + // includeSignature: false, + // expectError: true, + //}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + height, round := int64(1), int32(0) + privVal := NewMockPV() + pk, err := privVal.GetPubKey(ctx) + require.NoError(t, err) + blk := Block{} + ps, err := blk.MakePartSet(BlockPartSizeBytes) + require.NoError(t, err) + vote := &Vote{ + ValidatorAddress: pk.Address(), + ValidatorIndex: 0, + Height: height, + Round: round, + Timestamp: tmtime.Now(), + Type: tmproto.PrecommitType, + BlockID: BlockID{blk.Hash(), ps.Header()}, + } + + v := vote.ToProto() + err = privVal.SignVote(ctx, "test_chain_id", v) + require.NoError(t, err) + vote.Signature = v.Signature + if tc.includeSignature { + vote.ExtensionSignature = v.ExtensionSignature + } + err = vote.VerifyWithExtension("test_chain_id", pk) + if tc.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestIsVoteTypeValid(t *testing.T) { tc := []struct { name string @@ -265,39 +338,115 @@ func TestVoteString(t *testing.T) { } } -func TestVoteValidateBasic(t *testing.T) { +func signVote(ctx context.Context, t *testing.T, pv PrivValidator, chainID string, vote *Vote) { + t.Helper() + + v := vote.ToProto() + require.NoError(t, pv.SignVote(ctx, chainID, v)) + vote.Signature = v.Signature + vote.ExtensionSignature = v.ExtensionSignature +} + +func TestValidVotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() privVal := NewMockPV() testCases := []struct { - testName string + name string + vote *Vote malleateVote func(*Vote) - expectErr bool }{ - {"Good Vote", func(v *Vote) {}, false}, - {"Negative Height", func(v *Vote) { v.Height = -1 }, true}, - {"Negative Round", func(v *Vote) { v.Round = -1 }, true}, - {"Invalid BlockID", func(v *Vote) { - v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} - }, true}, - {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true}, - {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true}, - {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true}, - {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true}, + {"good prevote", examplePrevote(t), func(v *Vote) {}}, + {"good precommit without vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = nil }}, + {"good precommit with vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = []byte("extension") }}, } for _, tc := range testCases { - tc := tc - t.Run(tc.testName, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + signVote(ctx, t, privVal, "test_chain_id", tc.vote) + tc.malleateVote(tc.vote) + require.NoError(t, tc.vote.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.NoError(t, tc.vote.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) + } +} - vote := examplePrecommit(t) - v := vote.ToProto() - err := privVal.SignVote(ctx, "test_chain_id", v) - vote.Signature = v.Signature - require.NoError(t, err) - tc.malleateVote(vote) - assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result") - }) +func TestInvalidVotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"negative height", func(v *Vote) { v.Height = -1 }}, + {"negative round", func(v *Vote) { v.Round = -1 }}, + {"invalid block ID", func(v *Vote) { v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} }}, + {"invalid address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }}, + {"invalid validator index", func(v *Vote) { v.ValidatorIndex = -1 }}, + {"invalid signature", func(v *Vote) { v.Signature = nil }}, + {"oversized signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }}, + } + for _, tc := range testCases { + prevote := examplePrevote(t) + signVote(ctx, t, privVal, "test_chain_id", prevote) + tc.malleateVote(prevote) + require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s in invalid prevote", tc.name) + require.Error(t, prevote.ValidateWithExtension(), "ValidateWithExtension for %s in invalid prevote", tc.name) + + precommit := examplePrecommit(t) + signVote(ctx, t, privVal, "test_chain_id", precommit) + tc.malleateVote(precommit) + require.Error(t, precommit.ValidateBasic(), "ValidateBasic for %s in invalid precommit", tc.name) + require.Error(t, precommit.ValidateWithExtension(), "ValidateWithExtension for %s in invalid precommit", tc.name) + } +} + +func TestInvalidPrevotes(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"vote extension present", func(v *Vote) { v.Extension = []byte("extension") }}, + {"vote extension signature present", func(v *Vote) { v.ExtensionSignature = []byte("signature") }}, + } + for _, tc := range testCases { + prevote := examplePrevote(t) + signVote(ctx, t, privVal, "test_chain_id", prevote) + tc.malleateVote(prevote) + require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.Error(t, prevote.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) + } +} + +func TestInvalidPrecommitExtensions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + privVal := NewMockPV() + + testCases := []struct { + name string + malleateVote func(*Vote) + }{ + {"vote extension present without signature", func(v *Vote) { + v.Extension = []byte("extension") + v.ExtensionSignature = nil + }}, + // TODO(thane): Re-enable once https://github.com/tendermint/tendermint/issues/8272 is resolved + //{"missing vote extension signature", func(v *Vote) { v.ExtensionSignature = nil }}, + {"oversized vote extension signature", func(v *Vote) { v.ExtensionSignature = make([]byte, MaxSignatureSize+1) }}, + } + for _, tc := range testCases { + precommit := examplePrecommit(t) + signVote(ctx, t, privVal, "test_chain_id", precommit) + tc.malleateVote(precommit) + // We don't expect an error from ValidateBasic, because it doesn't + // handle vote extensions. + require.NoError(t, precommit.ValidateBasic(), "ValidateBasic for %s", tc.name) + require.Error(t, precommit.ValidateWithExtension(), "ValidateWithExtension for %s", tc.name) } } @@ -313,21 +462,28 @@ func TestVoteProtobuf(t *testing.T) { require.NoError(t, err) testCases := []struct { - msg string - v1 *Vote - expPass bool + msg string + vote *Vote + convertsOk bool + passesValidateBasic bool }{ - {"success", vote, true}, - {"fail vote validate basic", &Vote{}, false}, - {"failure nil", nil, false}, + {"success", vote, true, true}, + {"fail vote validate basic", &Vote{}, true, false}, } for _, tc := range testCases { - protoProposal := tc.v1.ToProto() + protoProposal := tc.vote.ToProto() v, err := VoteFromProto(protoProposal) - if tc.expPass { + if tc.convertsOk { require.NoError(t, err) - require.Equal(t, tc.v1, v, tc.msg) + } else { + require.Error(t, err) + } + + err = v.ValidateBasic() + if tc.passesValidateBasic { + require.NoError(t, err) + require.Equal(t, tc.vote, v, tc.msg) } else { require.Error(t, err) }