From 8e8f29f04172c4f61f0607a3fe415ef3a7244ef6 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:08:26 +0100 Subject: [PATCH 01/20] tendermint-rpc: Fork package from @iov-one --- packages/tendermint-rpc/.eslintignore | 1 + packages/tendermint-rpc/.gitignore | 3 + packages/tendermint-rpc/README.md | 86 ++ packages/tendermint-rpc/jasmine-testrunner.js | 26 + packages/tendermint-rpc/karma.conf.js | 45 + packages/tendermint-rpc/nonces/1552895838 | 0 packages/tendermint-rpc/nonces/1553085893 | 0 packages/tendermint-rpc/nonces/1554721221 | 0 packages/tendermint-rpc/nonces/1554724217 | 0 packages/tendermint-rpc/nonces/1554737147 | 0 packages/tendermint-rpc/nonces/1555314694 | 0 packages/tendermint-rpc/nonces/1556008552 | 0 packages/tendermint-rpc/nonces/1556028926 | 0 packages/tendermint-rpc/nonces/1556095341 | 0 packages/tendermint-rpc/nonces/1556616100 | 0 packages/tendermint-rpc/nonces/1557811966 | 0 packages/tendermint-rpc/nonces/1558346811 | 0 packages/tendermint-rpc/nonces/1558456815 | 0 packages/tendermint-rpc/nonces/1558460837 | 0 packages/tendermint-rpc/nonces/1559802671 | 0 packages/tendermint-rpc/nonces/1561970534 | 0 packages/tendermint-rpc/nonces/1562080432 | 0 packages/tendermint-rpc/nonces/1563468776 | 0 packages/tendermint-rpc/nonces/1563887488 | 0 packages/tendermint-rpc/nonces/1563960408 | 0 packages/tendermint-rpc/nonces/1563981076 | 0 packages/tendermint-rpc/nonces/1564503008 | 0 packages/tendermint-rpc/nonces/1564651088 | 0 packages/tendermint-rpc/nonces/1565101189 | 0 packages/tendermint-rpc/nonces/1565595547 | 0 packages/tendermint-rpc/nonces/1565876849 | 0 packages/tendermint-rpc/nonces/1566487600 | 0 packages/tendermint-rpc/nonces/1567435567 | 0 packages/tendermint-rpc/nonces/1567608963 | 0 packages/tendermint-rpc/nonces/1567694160 | 0 packages/tendermint-rpc/nonces/1568039925 | 0 packages/tendermint-rpc/nonces/1568116477 | 0 packages/tendermint-rpc/nonces/1568786866 | 0 packages/tendermint-rpc/nonces/1568910632 | 0 packages/tendermint-rpc/nonces/1569319493 | 0 packages/tendermint-rpc/nonces/1569487848 | 0 packages/tendermint-rpc/nonces/1569929617 | 0 packages/tendermint-rpc/nonces/1570527883 | 0 packages/tendermint-rpc/nonces/1573026590 | 0 packages/tendermint-rpc/nonces/1574869843 | 0 packages/tendermint-rpc/nonces/1576569788 | 0 packages/tendermint-rpc/nonces/1576595306 | 0 packages/tendermint-rpc/nonces/1576678551 | 0 packages/tendermint-rpc/nonces/1576746493 | 0 packages/tendermint-rpc/nonces/1576760285 | 0 packages/tendermint-rpc/nonces/1576767119 | 0 packages/tendermint-rpc/nonces/1579019908 | 0 packages/tendermint-rpc/nonces/1581606289 | 0 packages/tendermint-rpc/nonces/1581681020 | 0 packages/tendermint-rpc/nonces/1584038020 | 0 packages/tendermint-rpc/nonces/1588011428 | 0 packages/tendermint-rpc/nonces/1591293896 | 0 packages/tendermint-rpc/nonces/README.txt | 1 + packages/tendermint-rpc/package.json | 53 ++ packages/tendermint-rpc/src/adaptor.ts | 59 ++ .../tendermint-rpc/src/adaptorforversion.ts | 21 + packages/tendermint-rpc/src/client.spec.ts | 548 ++++++++++++ packages/tendermint-rpc/src/client.ts | 253 ++++++ packages/tendermint-rpc/src/config.spec.ts | 32 + packages/tendermint-rpc/src/encodings.spec.ts | 91 ++ packages/tendermint-rpc/src/encodings.ts | 231 +++++ packages/tendermint-rpc/src/index.ts | 40 + packages/tendermint-rpc/src/jsonrpc.spec.ts | 25 + packages/tendermint-rpc/src/jsonrpc.ts | 25 + packages/tendermint-rpc/src/requests.spec.ts | 41 + packages/tendermint-rpc/src/requests.ts | 179 ++++ packages/tendermint-rpc/src/responses.ts | 331 ++++++++ .../src/rpcclients/httpclient.spec.ts | 33 + .../src/rpcclients/httpclient.ts | 57 ++ .../tendermint-rpc/src/rpcclients/index.ts | 5 + .../src/rpcclients/rpcclient.spec.ts | 44 + .../src/rpcclients/rpcclient.ts | 36 + .../src/rpcclients/websocketclient.spec.ts | 210 +++++ .../src/rpcclients/websocketclient.ts | 212 +++++ packages/tendermint-rpc/src/types.ts | 41 + .../tendermint-rpc/src/v0-31/hasher.spec.ts | 98 +++ packages/tendermint-rpc/src/v0-31/hasher.ts | 71 ++ packages/tendermint-rpc/src/v0-31/index.ts | 12 + packages/tendermint-rpc/src/v0-31/requests.ts | 142 ++++ .../tendermint-rpc/src/v0-31/responses.ts | 773 +++++++++++++++++ .../tendermint-rpc/src/v0-32/hasher.spec.ts | 98 +++ packages/tendermint-rpc/src/v0-32/hasher.ts | 71 ++ packages/tendermint-rpc/src/v0-32/index.ts | 12 + packages/tendermint-rpc/src/v0-32/requests.ts | 142 ++++ .../tendermint-rpc/src/v0-32/responses.ts | 790 ++++++++++++++++++ packages/tendermint-rpc/tsconfig.json | 12 + packages/tendermint-rpc/tslint.json | 3 + packages/tendermint-rpc/typedoc.js | 14 + packages/tendermint-rpc/types/adaptor.d.ts | 49 ++ .../types/adaptorforversion.d.ts | 8 + packages/tendermint-rpc/types/client.d.ts | 60 ++ packages/tendermint-rpc/types/encodings.d.ts | 74 ++ packages/tendermint-rpc/types/index.d.ts | 38 + packages/tendermint-rpc/types/jsonrpc.d.ts | 3 + packages/tendermint-rpc/types/requests.d.ts | 151 ++++ packages/tendermint-rpc/types/responses.d.ts | 262 ++++++ .../types/rpcclients/httpclient.d.ts | 8 + .../types/rpcclients/index.d.ts | 3 + .../types/rpcclients/rpcclient.d.ts | 25 + .../types/rpcclients/websocketclient.d.ts | 20 + packages/tendermint-rpc/types/types.d.ts | 30 + .../tendermint-rpc/types/v0-31/hasher.d.ts | 4 + .../tendermint-rpc/types/v0-31/index.d.ts | 2 + .../tendermint-rpc/types/v0-31/requests.d.ts | 18 + .../tendermint-rpc/types/v0-31/responses.d.ts | 23 + .../tendermint-rpc/types/v0-32/hasher.d.ts | 4 + .../tendermint-rpc/types/v0-32/index.d.ts | 2 + .../tendermint-rpc/types/v0-32/requests.d.ts | 18 + .../tendermint-rpc/types/v0-32/responses.d.ts | 23 + packages/tendermint-rpc/webpack.web.config.js | 19 + 115 files changed, 5811 insertions(+) create mode 120000 packages/tendermint-rpc/.eslintignore create mode 100644 packages/tendermint-rpc/.gitignore create mode 100644 packages/tendermint-rpc/README.md create mode 100755 packages/tendermint-rpc/jasmine-testrunner.js create mode 100644 packages/tendermint-rpc/karma.conf.js create mode 100644 packages/tendermint-rpc/nonces/1552895838 create mode 100644 packages/tendermint-rpc/nonces/1553085893 create mode 100644 packages/tendermint-rpc/nonces/1554721221 create mode 100644 packages/tendermint-rpc/nonces/1554724217 create mode 100644 packages/tendermint-rpc/nonces/1554737147 create mode 100644 packages/tendermint-rpc/nonces/1555314694 create mode 100644 packages/tendermint-rpc/nonces/1556008552 create mode 100644 packages/tendermint-rpc/nonces/1556028926 create mode 100644 packages/tendermint-rpc/nonces/1556095341 create mode 100644 packages/tendermint-rpc/nonces/1556616100 create mode 100644 packages/tendermint-rpc/nonces/1557811966 create mode 100644 packages/tendermint-rpc/nonces/1558346811 create mode 100644 packages/tendermint-rpc/nonces/1558456815 create mode 100644 packages/tendermint-rpc/nonces/1558460837 create mode 100644 packages/tendermint-rpc/nonces/1559802671 create mode 100644 packages/tendermint-rpc/nonces/1561970534 create mode 100644 packages/tendermint-rpc/nonces/1562080432 create mode 100644 packages/tendermint-rpc/nonces/1563468776 create mode 100644 packages/tendermint-rpc/nonces/1563887488 create mode 100644 packages/tendermint-rpc/nonces/1563960408 create mode 100644 packages/tendermint-rpc/nonces/1563981076 create mode 100644 packages/tendermint-rpc/nonces/1564503008 create mode 100644 packages/tendermint-rpc/nonces/1564651088 create mode 100644 packages/tendermint-rpc/nonces/1565101189 create mode 100644 packages/tendermint-rpc/nonces/1565595547 create mode 100644 packages/tendermint-rpc/nonces/1565876849 create mode 100644 packages/tendermint-rpc/nonces/1566487600 create mode 100644 packages/tendermint-rpc/nonces/1567435567 create mode 100644 packages/tendermint-rpc/nonces/1567608963 create mode 100644 packages/tendermint-rpc/nonces/1567694160 create mode 100644 packages/tendermint-rpc/nonces/1568039925 create mode 100644 packages/tendermint-rpc/nonces/1568116477 create mode 100644 packages/tendermint-rpc/nonces/1568786866 create mode 100644 packages/tendermint-rpc/nonces/1568910632 create mode 100644 packages/tendermint-rpc/nonces/1569319493 create mode 100644 packages/tendermint-rpc/nonces/1569487848 create mode 100644 packages/tendermint-rpc/nonces/1569929617 create mode 100644 packages/tendermint-rpc/nonces/1570527883 create mode 100644 packages/tendermint-rpc/nonces/1573026590 create mode 100644 packages/tendermint-rpc/nonces/1574869843 create mode 100644 packages/tendermint-rpc/nonces/1576569788 create mode 100644 packages/tendermint-rpc/nonces/1576595306 create mode 100644 packages/tendermint-rpc/nonces/1576678551 create mode 100644 packages/tendermint-rpc/nonces/1576746493 create mode 100644 packages/tendermint-rpc/nonces/1576760285 create mode 100644 packages/tendermint-rpc/nonces/1576767119 create mode 100644 packages/tendermint-rpc/nonces/1579019908 create mode 100644 packages/tendermint-rpc/nonces/1581606289 create mode 100644 packages/tendermint-rpc/nonces/1581681020 create mode 100644 packages/tendermint-rpc/nonces/1584038020 create mode 100644 packages/tendermint-rpc/nonces/1588011428 create mode 100644 packages/tendermint-rpc/nonces/1591293896 create mode 100644 packages/tendermint-rpc/nonces/README.txt create mode 100644 packages/tendermint-rpc/package.json create mode 100644 packages/tendermint-rpc/src/adaptor.ts create mode 100644 packages/tendermint-rpc/src/adaptorforversion.ts create mode 100644 packages/tendermint-rpc/src/client.spec.ts create mode 100644 packages/tendermint-rpc/src/client.ts create mode 100644 packages/tendermint-rpc/src/config.spec.ts create mode 100644 packages/tendermint-rpc/src/encodings.spec.ts create mode 100644 packages/tendermint-rpc/src/encodings.ts create mode 100644 packages/tendermint-rpc/src/index.ts create mode 100644 packages/tendermint-rpc/src/jsonrpc.spec.ts create mode 100644 packages/tendermint-rpc/src/jsonrpc.ts create mode 100644 packages/tendermint-rpc/src/requests.spec.ts create mode 100644 packages/tendermint-rpc/src/requests.ts create mode 100644 packages/tendermint-rpc/src/responses.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/httpclient.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/index.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/rpcclient.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts create mode 100644 packages/tendermint-rpc/src/rpcclients/websocketclient.ts create mode 100644 packages/tendermint-rpc/src/types.ts create mode 100644 packages/tendermint-rpc/src/v0-31/hasher.spec.ts create mode 100644 packages/tendermint-rpc/src/v0-31/hasher.ts create mode 100644 packages/tendermint-rpc/src/v0-31/index.ts create mode 100644 packages/tendermint-rpc/src/v0-31/requests.ts create mode 100644 packages/tendermint-rpc/src/v0-31/responses.ts create mode 100644 packages/tendermint-rpc/src/v0-32/hasher.spec.ts create mode 100644 packages/tendermint-rpc/src/v0-32/hasher.ts create mode 100644 packages/tendermint-rpc/src/v0-32/index.ts create mode 100644 packages/tendermint-rpc/src/v0-32/requests.ts create mode 100644 packages/tendermint-rpc/src/v0-32/responses.ts create mode 100644 packages/tendermint-rpc/tsconfig.json create mode 100644 packages/tendermint-rpc/tslint.json create mode 100644 packages/tendermint-rpc/typedoc.js create mode 100644 packages/tendermint-rpc/types/adaptor.d.ts create mode 100644 packages/tendermint-rpc/types/adaptorforversion.d.ts create mode 100644 packages/tendermint-rpc/types/client.d.ts create mode 100644 packages/tendermint-rpc/types/encodings.d.ts create mode 100644 packages/tendermint-rpc/types/index.d.ts create mode 100644 packages/tendermint-rpc/types/jsonrpc.d.ts create mode 100644 packages/tendermint-rpc/types/requests.d.ts create mode 100644 packages/tendermint-rpc/types/responses.d.ts create mode 100644 packages/tendermint-rpc/types/rpcclients/httpclient.d.ts create mode 100644 packages/tendermint-rpc/types/rpcclients/index.d.ts create mode 100644 packages/tendermint-rpc/types/rpcclients/rpcclient.d.ts create mode 100644 packages/tendermint-rpc/types/rpcclients/websocketclient.d.ts create mode 100644 packages/tendermint-rpc/types/types.d.ts create mode 100644 packages/tendermint-rpc/types/v0-31/hasher.d.ts create mode 100644 packages/tendermint-rpc/types/v0-31/index.d.ts create mode 100644 packages/tendermint-rpc/types/v0-31/requests.d.ts create mode 100644 packages/tendermint-rpc/types/v0-31/responses.d.ts create mode 100644 packages/tendermint-rpc/types/v0-32/hasher.d.ts create mode 100644 packages/tendermint-rpc/types/v0-32/index.d.ts create mode 100644 packages/tendermint-rpc/types/v0-32/requests.d.ts create mode 100644 packages/tendermint-rpc/types/v0-32/responses.d.ts create mode 100644 packages/tendermint-rpc/webpack.web.config.js diff --git a/packages/tendermint-rpc/.eslintignore b/packages/tendermint-rpc/.eslintignore new file mode 120000 index 0000000000..86039baf54 --- /dev/null +++ b/packages/tendermint-rpc/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/tendermint-rpc/.gitignore b/packages/tendermint-rpc/.gitignore new file mode 100644 index 0000000000..68bf373524 --- /dev/null +++ b/packages/tendermint-rpc/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/tendermint-rpc/README.md b/packages/tendermint-rpc/README.md new file mode 100644 index 0000000000..0b7db50226 --- /dev/null +++ b/packages/tendermint-rpc/README.md @@ -0,0 +1,86 @@ +# @iov/tendermint-rpc + +[![npm version](https://img.shields.io/npm/v/@iov/tendermint-rpc.svg)](https://www.npmjs.com/package/@iov/tendermint-rpc) + +This package provides a type-safe wrapper around +[Tendermint RPC](https://docs.tendermint.com/master/rpc/). Notably, all binary +data is passed in and out as Uint8Array, and this module is reponsible for the +hex/base64 encoding/decoding depending on the field and version of Tendermint. +Also handles converting numbers to and from strings. + +## Getting started + +The simplest possible use of the module is to assume it does everything +automatically, and call: + +```ts +import { Client } from "@iov/tendermint-rpc"; + +const client = await Client.connect( + "ws://rpc-private-a-x-exchangenet.iov.one:16657", +); + +const genesis = await client.genesis(); +const status = await client.status(); +``` + +A query to the ABCI application looks like this: + +```ts +const results = await client.abciQuery({ + path: "/tokens?prefix", + data: new Uint8Array([]), +}); +``` + +## Supported Tendermint versions + +| IOV-Core version | Supported Tendermint versions | +| ---------------- | ----------------------------- | +| 1.1 | 0.31.x – 0.32.x | +| 1.0 | 0.31.x | +| 0.15 – 0.17 | 0.29.x – 0.31.x | +| 0.12 – 0.14 | 0.25.x, 0.27.x – 0.29.x | +| 0.11 | 0.25.x, 0.27.x | +| 0.9 – 0.10 | 0.20.x, 0.21.x, 0.25.x | +| 0.1 – 0.8 | 0.20.x, 0.21.x | + +Support for Tendermint versions is determined by demand for IOV's own products. +Please let us know if you need support for other versions of Tendermint or need +long term support for one specific Tendermint version. + +## Code Overview + +The main entry point is the +[Client](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/client.html). + +The connection to the blockchain is defined by a flexible +[RpcClient](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/interfaces/rpcclient.html) +interface, with implementations for HTTP (POST) and WebSockets. You can add your +own connection type or just wrap one with custom retry rules, error handling, +etc. The RPC client is just responsible for sending JSON-RPC requests and +returning the responses. + +The actual domain knowledge is embedded in the +[Adaptor](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/interfaces/adaptor.html), +which defines a class for encoding +[Params](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/params.html) +and another for decoding +[Responses](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/responses.html). +The Tendermint version-specific functionality is implemented in global objects +(like e.g. +[v0_31](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/globals.html#v0_31)). +This knowledge is mainly for those who want to add support for new versions, +which should be added to the +[auto-detect method](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/client.html#detectversion). + +## API Documentation + +[https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/) + +## License + +This package is part of the IOV-Core repository, licensed under the Apache +License 2.0 (see +[NOTICE](https://github.com/iov-one/iov-core/blob/master/NOTICE) and +[LICENSE](https://github.com/iov-one/iov-core/blob/master/LICENSE)). diff --git a/packages/tendermint-rpc/jasmine-testrunner.js b/packages/tendermint-rpc/jasmine-testrunner.js new file mode 100755 index 0000000000..9fada59b28 --- /dev/null +++ b/packages/tendermint-rpc/jasmine-testrunner.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +require("source-map-support").install(); +const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); + +// setup Jasmine +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); +jasmine.loadConfig({ + spec_dir: "build", + spec_files: ["**/*.spec.js"], + helpers: [], + random: false, + seed: null, + stopSpecOnExpectationFailure: false, +}); +jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; + +// setup reporter +const { SpecReporter } = require("jasmine-spec-reporter"); +const reporter = new SpecReporter({ ...defaultSpecReporterConfig }); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/tendermint-rpc/karma.conf.js b/packages/tendermint-rpc/karma.conf.js new file mode 100644 index 0000000000..38218d78c9 --- /dev/null +++ b/packages/tendermint-rpc/karma.conf.js @@ -0,0 +1,45 @@ +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: ".", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + }); +}; diff --git a/packages/tendermint-rpc/nonces/1552895838 b/packages/tendermint-rpc/nonces/1552895838 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1553085893 b/packages/tendermint-rpc/nonces/1553085893 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1554721221 b/packages/tendermint-rpc/nonces/1554721221 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1554724217 b/packages/tendermint-rpc/nonces/1554724217 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1554737147 b/packages/tendermint-rpc/nonces/1554737147 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1555314694 b/packages/tendermint-rpc/nonces/1555314694 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1556008552 b/packages/tendermint-rpc/nonces/1556008552 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1556028926 b/packages/tendermint-rpc/nonces/1556028926 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1556095341 b/packages/tendermint-rpc/nonces/1556095341 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1556616100 b/packages/tendermint-rpc/nonces/1556616100 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1557811966 b/packages/tendermint-rpc/nonces/1557811966 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1558346811 b/packages/tendermint-rpc/nonces/1558346811 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1558456815 b/packages/tendermint-rpc/nonces/1558456815 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1558460837 b/packages/tendermint-rpc/nonces/1558460837 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1559802671 b/packages/tendermint-rpc/nonces/1559802671 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1561970534 b/packages/tendermint-rpc/nonces/1561970534 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1562080432 b/packages/tendermint-rpc/nonces/1562080432 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1563468776 b/packages/tendermint-rpc/nonces/1563468776 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1563887488 b/packages/tendermint-rpc/nonces/1563887488 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1563960408 b/packages/tendermint-rpc/nonces/1563960408 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1563981076 b/packages/tendermint-rpc/nonces/1563981076 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1564503008 b/packages/tendermint-rpc/nonces/1564503008 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1564651088 b/packages/tendermint-rpc/nonces/1564651088 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1565101189 b/packages/tendermint-rpc/nonces/1565101189 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1565595547 b/packages/tendermint-rpc/nonces/1565595547 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1565876849 b/packages/tendermint-rpc/nonces/1565876849 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1566487600 b/packages/tendermint-rpc/nonces/1566487600 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1567435567 b/packages/tendermint-rpc/nonces/1567435567 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1567608963 b/packages/tendermint-rpc/nonces/1567608963 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1567694160 b/packages/tendermint-rpc/nonces/1567694160 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1568039925 b/packages/tendermint-rpc/nonces/1568039925 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1568116477 b/packages/tendermint-rpc/nonces/1568116477 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1568786866 b/packages/tendermint-rpc/nonces/1568786866 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1568910632 b/packages/tendermint-rpc/nonces/1568910632 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1569319493 b/packages/tendermint-rpc/nonces/1569319493 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1569487848 b/packages/tendermint-rpc/nonces/1569487848 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1569929617 b/packages/tendermint-rpc/nonces/1569929617 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1570527883 b/packages/tendermint-rpc/nonces/1570527883 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1573026590 b/packages/tendermint-rpc/nonces/1573026590 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1574869843 b/packages/tendermint-rpc/nonces/1574869843 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576569788 b/packages/tendermint-rpc/nonces/1576569788 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576595306 b/packages/tendermint-rpc/nonces/1576595306 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576678551 b/packages/tendermint-rpc/nonces/1576678551 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576746493 b/packages/tendermint-rpc/nonces/1576746493 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576760285 b/packages/tendermint-rpc/nonces/1576760285 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1576767119 b/packages/tendermint-rpc/nonces/1576767119 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1579019908 b/packages/tendermint-rpc/nonces/1579019908 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1581606289 b/packages/tendermint-rpc/nonces/1581606289 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1581681020 b/packages/tendermint-rpc/nonces/1581681020 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1584038020 b/packages/tendermint-rpc/nonces/1584038020 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1588011428 b/packages/tendermint-rpc/nonces/1588011428 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/1591293896 b/packages/tendermint-rpc/nonces/1591293896 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/tendermint-rpc/nonces/README.txt b/packages/tendermint-rpc/nonces/README.txt new file mode 100644 index 0000000000..092fe732f1 --- /dev/null +++ b/packages/tendermint-rpc/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json new file mode 100644 index 0000000000..d44dd59da8 --- /dev/null +++ b/packages/tendermint-rpc/package.json @@ -0,0 +1,53 @@ +{ + "name": "@iov/tendermint-rpc", + "version": "2.3.2", + "description": "Codec to encode/decode bns transactions and state objects", + "author": "IOV SAS ", + "license": "Apache-2.0", + "main": "build/index.js", + "types": "types/index.d.ts", + "files": [ + "build/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/iov-one/iov-core/tree/master/packages/iov-tendermint-rpc" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "shx rm -rf docs && typedoc --options typedoc.js", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\" && tslint -t verbose --project .", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", + "test-node": "node jasmine-testrunner.js", + "test-edge": "yarn pack-web && karma start --single-run --browsers Edge", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", + "test-safari": "yarn pack-web && karma start --single-run --browsers Safari", + "test": "yarn build-or-skip && yarn test-node", + "move-types": "shx rm -r ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts && shx rm ./types/**/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@iov/crypto": "^2.3.2", + "@iov/encoding": "^2.3.2", + "@iov/jsonrpc": "^2.3.2", + "@iov/socket": "^2.3.2", + "axios": "^0.19.0", + "readonly-date": "^1.0.0", + "type-tagger": "^1.0.0", + "xstream": "^11.10.0" + }, + "devDependencies": { + "@iov/utils": "^2.3.2" + } +} diff --git a/packages/tendermint-rpc/src/adaptor.ts b/packages/tendermint-rpc/src/adaptor.ts new file mode 100644 index 0000000000..8638ed6172 --- /dev/null +++ b/packages/tendermint-rpc/src/adaptor.ts @@ -0,0 +1,59 @@ +import { JsonRpcRequest, JsonRpcSuccessResponse } from "@iov/jsonrpc"; + +import * as requests from "./requests"; +import * as responses from "./responses"; +import { SubscriptionEvent } from "./rpcclients"; +import { BlockHash, TxBytes, TxHash } from "./types"; + +export interface Adaptor { + readonly params: Params; + readonly responses: Responses; + readonly hashTx: (tx: TxBytes) => TxHash; + readonly hashBlock: (header: responses.Header) => BlockHash; +} + +// Encoder is a generic that matches all methods of Params +export type Encoder = (req: T) => JsonRpcRequest; + +// Decoder is a generic that matches all methods of Responses +export type Decoder = (res: JsonRpcSuccessResponse) => T; + +export interface Params { + readonly encodeAbciInfo: (req: requests.AbciInfoRequest) => JsonRpcRequest; + readonly encodeAbciQuery: (req: requests.AbciQueryRequest) => JsonRpcRequest; + readonly encodeBlock: (req: requests.BlockRequest) => JsonRpcRequest; + readonly encodeBlockchain: (req: requests.BlockchainRequest) => JsonRpcRequest; + readonly encodeBlockResults: (req: requests.BlockResultsRequest) => JsonRpcRequest; + readonly encodeBroadcastTx: (req: requests.BroadcastTxRequest) => JsonRpcRequest; + readonly encodeCommit: (req: requests.CommitRequest) => JsonRpcRequest; + readonly encodeGenesis: (req: requests.GenesisRequest) => JsonRpcRequest; + readonly encodeHealth: (req: requests.HealthRequest) => JsonRpcRequest; + readonly encodeStatus: (req: requests.StatusRequest) => JsonRpcRequest; + readonly encodeSubscribe: (req: requests.SubscribeRequest) => JsonRpcRequest; + readonly encodeTx: (req: requests.TxRequest) => JsonRpcRequest; + readonly encodeTxSearch: (req: requests.TxSearchRequest) => JsonRpcRequest; + readonly encodeValidators: (req: requests.ValidatorsRequest) => JsonRpcRequest; +} + +export interface Responses { + readonly decodeAbciInfo: (response: JsonRpcSuccessResponse) => responses.AbciInfoResponse; + readonly decodeAbciQuery: (response: JsonRpcSuccessResponse) => responses.AbciQueryResponse; + readonly decodeBlock: (response: JsonRpcSuccessResponse) => responses.BlockResponse; + readonly decodeBlockResults: (response: JsonRpcSuccessResponse) => responses.BlockResultsResponse; + readonly decodeBlockchain: (response: JsonRpcSuccessResponse) => responses.BlockchainResponse; + readonly decodeBroadcastTxSync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxSyncResponse; + readonly decodeBroadcastTxAsync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxAsyncResponse; + readonly decodeBroadcastTxCommit: (response: JsonRpcSuccessResponse) => responses.BroadcastTxCommitResponse; + readonly decodeCommit: (response: JsonRpcSuccessResponse) => responses.CommitResponse; + readonly decodeGenesis: (response: JsonRpcSuccessResponse) => responses.GenesisResponse; + readonly decodeHealth: (response: JsonRpcSuccessResponse) => responses.HealthResponse; + readonly decodeStatus: (response: JsonRpcSuccessResponse) => responses.StatusResponse; + readonly decodeTx: (response: JsonRpcSuccessResponse) => responses.TxResponse; + readonly decodeTxSearch: (response: JsonRpcSuccessResponse) => responses.TxSearchResponse; + readonly decodeValidators: (response: JsonRpcSuccessResponse) => responses.ValidatorsResponse; + + // events + readonly decodeNewBlockEvent: (response: SubscriptionEvent) => responses.NewBlockEvent; + readonly decodeNewBlockHeaderEvent: (response: SubscriptionEvent) => responses.NewBlockHeaderEvent; + readonly decodeTxEvent: (response: SubscriptionEvent) => responses.TxEvent; +} diff --git a/packages/tendermint-rpc/src/adaptorforversion.ts b/packages/tendermint-rpc/src/adaptorforversion.ts new file mode 100644 index 0000000000..5196b07720 --- /dev/null +++ b/packages/tendermint-rpc/src/adaptorforversion.ts @@ -0,0 +1,21 @@ +// This module exposes translators for multiple tendermint versions +// Pick a version that matches the server to properly encode the data types +import { Adaptor } from "./adaptor"; +import { v0_31 } from "./v0-31"; +import { v0_32 } from "./v0-32"; + +/** + * Returns an Adaptor implementation for a given tendermint version. + * Throws when version is not supported. + * + * @param version full Tendermint version string, e.g. "0.20.1" + */ +export function adaptorForVersion(version: string): Adaptor { + if (version.startsWith("0.31.")) { + return v0_31; + } else if (version.startsWith("0.32.")) { + return v0_32; + } else { + throw new Error(`Unsupported tendermint version: ${version}`); + } +} diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts new file mode 100644 index 0000000000..0a5bd6e502 --- /dev/null +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -0,0 +1,548 @@ +// tslint:disable:readonly-array +import { toAscii } from "@iov/encoding"; +import { firstEvent, toListPromise } from "@iov/stream"; +import { sleep } from "@iov/utils"; +import { ReadonlyDate } from "readonly-date"; +import { Stream } from "xstream"; + +import { Adaptor } from "./adaptor"; +import { adaptorForVersion } from "./adaptorforversion"; +import { Client } from "./client"; +import { tendermintInstances } from "./config.spec"; +import { buildQuery } from "./requests"; +import * as responses from "./responses"; +import { HttpClient, RpcClient, WebsocketClient } from "./rpcclients"; +import { TxBytes } from "./types"; + +function pendingWithoutTendermint(): void { + if (!process.env.TENDERMINT_ENABLED) { + pending("Set TENDERMINT_ENABLED to enable tendermint-based tests"); + } +} + +async function tendermintSearchIndexUpdated(): Promise { + // Tendermint needs some time before a committed transaction is found in search + return sleep(50); +} + +function buildKvTx(k: string, v: string): TxBytes { + return toAscii(`${k}=${v}`) as TxBytes; +} + +function randomString(): string { + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return Array.from({ length: 12 }) + .map(() => alphabet[Math.floor(Math.random() * alphabet.length)]) + .join(""); +} + +function defaultTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor): void { + it("can connect to tendermint with known version", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + expect(await client.abciInfo()).toBeTruthy(); + client.disconnect(); + }); + + it("can auto-discover tendermint version and connect", async () => { + pendingWithoutTendermint(); + const client = await Client.detectVersion(rpcFactory()); + const info = await client.abciInfo(); + expect(info).toBeTruthy(); + client.disconnect(); + }); + + it("can post a transaction", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + const tx = buildKvTx(randomString(), randomString()); + + const response = await client.broadcastTxCommit({ tx: tx }); + expect(response.height).toBeGreaterThan(2); + expect(response.hash).toBeTruthy(); + // verify success + expect(response.checkTx.code).toBeFalsy(); + expect(response.deliverTx).toBeTruthy(); + if (response.deliverTx) { + expect(response.deliverTx.code).toBeFalsy(); + } + + client.disconnect(); + }); + + it("gets the same tx hash from backend as calculated locally", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + const tx = buildKvTx(randomString(), randomString()); + const calculatedTxHash = adaptor.hashTx(tx); + + const response = await client.broadcastTxCommit({ tx: tx }); + expect(response.hash).toEqual(calculatedTxHash); + + client.disconnect(); + }); + + it("can query the state", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + + const key = randomString(); + const value = randomString(); + await client.broadcastTxCommit({ tx: buildKvTx(key, value) }); + + const binKey = toAscii(key); + const binValue = toAscii(value); + const queryParams = { path: "/key", data: binKey, prove: true }; + const response = await client.abciQuery(queryParams); + expect(response.key).toEqual(binKey); + expect(response.value).toEqual(binValue); + expect(response.code).toBeFalsy(); + + client.disconnect(); + }); + + it("can call a bunch of methods", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + + expect(await client.block()).toBeTruthy(); + expect(await client.blockchain(2, 4)).toBeTruthy(); + expect(await client.blockResults(3)).toBeTruthy(); + expect(await client.commit(4)).toBeTruthy(); + expect(await client.genesis()).toBeTruthy(); + expect(await client.health()).toBeNull(); + expect(await client.validators()).toBeTruthy(); + + client.disconnect(); + }); + + it("can call status", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + + const status = await client.status(); + expect(status.nodeInfo.other.size).toBeGreaterThanOrEqual(2); + expect(status.nodeInfo.other.get("tx_index")).toEqual("on"); + expect(status.validatorInfo.pubkey).toBeTruthy(); + expect(status.validatorInfo.votingPower).toBeGreaterThan(0); + expect(status.syncInfo.catchingUp).toEqual(false); + expect(status.syncInfo.latestBlockHeight).toBeGreaterThanOrEqual(1); + + client.disconnect(); + }); + + it("can query a tx properly", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + + const find = randomString(); + const me = randomString(); + const tx = buildKvTx(find, me); + + const txRes = await client.broadcastTxCommit({ tx: tx }); + expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); + expect(txRes.height).toBeTruthy(); + const height: number = txRes.height || 0; // || 0 for type system + expect(txRes.hash.length).not.toEqual(0); + const hash = txRes.hash; + + await tendermintSearchIndexUpdated(); + + // find by hash - does it match? + const r = await client.tx({ hash: hash, prove: true }); + // both values come from rpc, so same type (Buffer/Uint8Array) + expect(r.hash).toEqual(hash); + // force the type when comparing to locally generated value + expect(r.tx).toEqual(tx); + expect(r.height).toEqual(height); + expect(r.proof).toBeTruthy(); + + // txSearch - you must enable the indexer when running + // tendermint, else you get empty results + const query = buildQuery({ tags: [{ key: "app.key", value: find }] }); + + // eslint-disable-next-line @typescript-eslint/camelcase + const s = await client.txSearch({ query: query, page: 1, per_page: 30 }); + // should find the tx + expect(s.totalCount).toEqual(1); + // should return same info as querying directly, + // except without the proof + expect(s.txs[0]).toEqual({ ...r, proof: undefined }); + + // ensure txSearchAll works as well + const sall = await client.txSearchAll({ query: query }); + // should find the tx + expect(sall.totalCount).toEqual(1); + // should return same info as querying directly, + // except without the proof + expect(sall.txs[0]).toEqual({ ...r, proof: undefined }); + + // and let's query the block itself to see this transaction + const block = await client.block(height); + expect(block.blockMeta.header.numTxs).toEqual(1); + expect(block.block.txs.length).toEqual(1); + expect(block.block.txs[0]).toEqual(tx); + + client.disconnect(); + }); + + it("can paginate over txSearch results", async () => { + pendingWithoutTendermint(); + const client = new Client(rpcFactory(), adaptor); + + const find = randomString(); + const query = buildQuery({ tags: [{ key: "app.key", value: find }] }); + + async function sendTx(): Promise { + const me = randomString(); + const tx = buildKvTx(find, me); + + const txRes = await client.broadcastTxCommit({ tx: tx }); + expect(responses.broadcastTxCommitSuccess(txRes)).toEqual(true); + expect(txRes.height).toBeTruthy(); + expect(txRes.hash.length).not.toEqual(0); + } + + // send 3 txs + await sendTx(); + await sendTx(); + await sendTx(); + + await tendermintSearchIndexUpdated(); + + // expect one page of results + // eslint-disable-next-line @typescript-eslint/camelcase + const s1 = await client.txSearch({ query: query, page: 1, per_page: 2 }); + expect(s1.totalCount).toEqual(3); + expect(s1.txs.length).toEqual(2); + + // second page + // eslint-disable-next-line @typescript-eslint/camelcase + const s2 = await client.txSearch({ query: query, page: 2, per_page: 2 }); + expect(s2.totalCount).toEqual(3); + expect(s2.txs.length).toEqual(1); + + // and all together now + // eslint-disable-next-line @typescript-eslint/camelcase + const sall = await client.txSearchAll({ query: query, per_page: 2 }); + expect(sall.totalCount).toEqual(3); + expect(sall.txs.length).toEqual(3); + // make sure there are in order from lowest to highest height + const [tx1, tx2, tx3] = sall.txs; + expect(tx2.height).toEqual(tx1.height + 1); + expect(tx3.height).toEqual(tx2.height + 1); + + client.disconnect(); + }); +} + +function websocketTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, appCreator: string): void { + it("can subscribe to block header events", (done) => { + pendingWithoutTendermint(); + + const testStart = ReadonlyDate.now(); + + (async () => { + const events: responses.NewBlockHeaderEvent[] = []; + const client = new Client(rpcFactory(), adaptor); + const stream = client.subscribeNewBlockHeader(); + expect(stream).toBeTruthy(); + const subscription = stream.subscribe({ + next: (event) => { + expect(event.chainId).toMatch(/^[-a-zA-Z0-9]{3,30}$/); + expect(event.height).toBeGreaterThan(0); + // seems that tendermint just guarantees within the last second for timestamp + expect(event.time.getTime()).toBeGreaterThan(testStart - 1000); + // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance + expect(event.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); + expect(event.numTxs).toEqual(0); + expect(event.lastBlockId).toBeTruthy(); + expect(event.totalTxs).toBeGreaterThan(0); + + // merkle roots for proofs + expect(event.appHash).toBeTruthy(); + expect(event.consensusHash).toBeTruthy(); + expect(event.dataHash).toBeTruthy(); + expect(event.evidenceHash).toBeTruthy(); + expect(event.lastCommitHash).toBeTruthy(); + expect(event.lastResultsHash).toBeTruthy(); + expect(event.validatorsHash).toBeTruthy(); + + events.push(event); + + if (events.length === 2) { + subscription.unsubscribe(); + expect(events.length).toEqual(2); + expect(events[1].chainId).toEqual(events[0].chainId); + expect(events[1].height).toEqual(events[0].height + 1); + expect(events[1].time.getTime()).toBeGreaterThan(events[0].time.getTime()); + expect(events[1].totalTxs).toEqual(events[0].totalTxs); + + expect(events[1].appHash).toEqual(events[0].appHash); + expect(events[1].consensusHash).toEqual(events[0].consensusHash); + expect(events[1].dataHash).toEqual(events[0].dataHash); + expect(events[1].evidenceHash).toEqual(events[0].evidenceHash); + expect(events[1].lastCommitHash).not.toEqual(events[0].lastCommitHash); + expect(events[1].lastResultsHash).not.toEqual(events[0].lastResultsHash); + expect(events[1].validatorsHash).toEqual(events[0].validatorsHash); + + client.disconnect(); + done(); + } + }, + error: done.fail, + complete: () => done.fail("Stream completed before we are done"), + }); + })().catch(done.fail); + }); + + it("can subscribe to block events", async () => { + pendingWithoutTendermint(); + + const testStart = ReadonlyDate.now(); + + const transactionData1 = buildKvTx(randomString(), randomString()); + const transactionData2 = buildKvTx(randomString(), randomString()); + + const events: responses.NewBlockEvent[] = []; + const client = new Client(rpcFactory(), adaptor); + const stream = client.subscribeNewBlock(); + const subscription = stream.subscribe({ + next: (event) => { + expect(event.header.chainId).toMatch(/^[-a-zA-Z0-9]{3,30}$/); + expect(event.header.height).toBeGreaterThan(0); + // seems that tendermint just guarantees within the last second for timestamp + expect(event.header.time.getTime()).toBeGreaterThan(testStart - 1000); + // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance + expect(event.header.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); + expect(event.header.numTxs).toEqual(1); + expect(event.header.lastBlockId).toBeTruthy(); + expect(event.header.totalTxs).toBeGreaterThan(0); + + // merkle roots for proofs + expect(event.header.appHash).toBeTruthy(); + expect(event.header.consensusHash).toBeTruthy(); + expect(event.header.dataHash).toBeTruthy(); + expect(event.header.evidenceHash).toBeTruthy(); + expect(event.header.lastCommitHash).toBeTruthy(); + expect(event.header.lastResultsHash).toBeTruthy(); + expect(event.header.validatorsHash).toBeTruthy(); + + events.push(event); + + if (events.length === 2) { + subscription.unsubscribe(); + } + }, + error: fail, + }); + + await client.broadcastTxCommit({ tx: transactionData1 }); + await client.broadcastTxCommit({ tx: transactionData2 }); + + // wait for events to be processed + await sleep(100); + + expect(events.length).toEqual(2); + // Block header + expect(events[1].header.height).toEqual(events[0].header.height + 1); + expect(events[1].header.chainId).toEqual(events[0].header.chainId); + expect(events[1].header.time.getTime()).toBeGreaterThan(events[0].header.time.getTime()); + expect(events[1].header.totalTxs).toEqual(events[0].header.totalTxs + 1); + expect(events[1].header.appHash).not.toEqual(events[0].header.appHash); + expect(events[1].header.validatorsHash).toEqual(events[0].header.validatorsHash); + // Block body + expect(events[0].txs.length).toEqual(1); + expect(events[1].txs.length).toEqual(1); + expect(events[0].txs[0]).toEqual(transactionData1); + expect(events[1].txs[0]).toEqual(transactionData2); + + client.disconnect(); + }); + + it("can subscribe to transaction events", async () => { + pendingWithoutTendermint(); + + const events: responses.TxEvent[] = []; + const client = new Client(rpcFactory(), adaptor); + const stream = client.subscribeTx(); + const subscription = stream.subscribe({ + next: (event) => { + expect(event.height).toBeGreaterThan(0); + expect(event.index).toEqual(0); + expect(event.result).toBeTruthy(); + + events.push(event); + + if (events.length === 2) { + subscription.unsubscribe(); + } + }, + error: fail, + }); + + const transactionData1 = buildKvTx(randomString(), randomString()); + const transactionData2 = buildKvTx(randomString(), randomString()); + + await client.broadcastTxCommit({ tx: transactionData1 }); + await client.broadcastTxCommit({ tx: transactionData2 }); + + // wait for events to be processed + await sleep(100); + + expect(events.length).toEqual(2); + // Meta + expect(events[1].height).toEqual(events[0].height + 1); + if (events[1].result.tags && events[0].result.tags) { + expect(events[1].result.tags).not.toEqual(events[0].result.tags); + } + if (events[1].result.events && events[0].result.events) { + expect(events[1].result.events).not.toEqual(events[0].result.events); + } + // Content + expect(events[0].tx).toEqual(transactionData1); + expect(events[1].tx).toEqual(transactionData2); + + client.disconnect(); + }); + + it("can subscribe to transaction events filtered by creator", async () => { + pendingWithoutTendermint(); + + const transactionData1 = buildKvTx(randomString(), randomString()); + const transactionData2 = buildKvTx(randomString(), randomString()); + + const events: responses.TxEvent[] = []; + const client = new Client(rpcFactory(), adaptor); + const query = buildQuery({ tags: [{ key: "app.creator", value: appCreator }] }); + const stream = client.subscribeTx(query); + expect(stream).toBeTruthy(); + const subscription = stream.subscribe({ + next: (event) => { + expect(event.height).toBeGreaterThan(0); + expect(event.index).toEqual(0); + expect(event.result).toBeTruthy(); + events.push(event); + + if (events.length === 2) { + subscription.unsubscribe(); + } + }, + error: fail, + }); + + await client.broadcastTxCommit({ tx: transactionData1 }); + await client.broadcastTxCommit({ tx: transactionData2 }); + + // wait for events to be processed + await sleep(100); + + expect(events.length).toEqual(2); + // Meta + expect(events[1].height).toEqual(events[0].height + 1); + if (events[1].result.tags && events[0].result.tags) { + expect(events[1].result.tags).not.toEqual(events[0].result.tags); + } + if (events[1].result.events && events[0].result.events) { + expect(events[1].result.events).not.toEqual(events[0].result.events); + } + // Content + expect(events[0].tx).toEqual(transactionData1); + expect(events[1].tx).toEqual(transactionData2); + + client.disconnect(); + }); + + it("can unsubscribe and re-subscribe to the same stream", async () => { + pendingWithoutTendermint(); + + const client = new Client(rpcFactory(), adaptor); + const stream = client.subscribeNewBlockHeader(); + + const event1 = await firstEvent(stream); + expect(event1.height).toBeGreaterThanOrEqual(1); + expect(event1.time.getTime()).toBeGreaterThanOrEqual(1); + + // No sleep: producer will not be stopped in the meantime + + const event2 = await firstEvent(stream); + expect(event2.height).toBeGreaterThan(event1.height); + expect(event2.time.getTime()).toBeGreaterThan(event1.time.getTime()); + + // Very short sleep: just enough to schedule asynchronous producer stopping + await sleep(5); + + const event3 = await firstEvent(stream); + expect(event3.height).toBeGreaterThan(event2.height); + expect(event3.time.getTime()).toBeGreaterThan(event2.time.getTime()); + + // Proper sleep: enough to finish unsubscribing at over the network + await sleep(100); + + const event4 = await firstEvent(stream); + expect(event4.height).toBeGreaterThan(event3.height); + expect(event4.time.getTime()).toBeGreaterThan(event3.time.getTime()); + + client.disconnect(); + }); + + it("can subscribe twice", async () => { + pendingWithoutTendermint(); + + const client = new Client(rpcFactory(), adaptor); + const stream1 = client.subscribeNewBlockHeader(); + const stream2 = client.subscribeNewBlockHeader(); + + const events = await toListPromise(Stream.merge(stream1, stream2), 4); + + expect(new Set(events.map((e) => e.height)).size).toEqual(2); + + client.disconnect(); + }); +} + +for (const { url, version, appCreator } of tendermintInstances) { + describe(`Client ${version}`, () => { + it("can connect to a given url", async () => { + pendingWithoutTendermint(); + + // default connection + { + const client = await Client.connect(url); + const info = await client.abciInfo(); + expect(info).toBeTruthy(); + client.disconnect(); + } + + // http connection + { + const client = await Client.connect("http://" + url); + const info = await client.abciInfo(); + expect(info).toBeTruthy(); + client.disconnect(); + } + + // ws connection + { + const client = await Client.connect("ws://" + url); + const info = await client.abciInfo(); + expect(info).toBeTruthy(); + client.disconnect(); + } + }); + + describe("With HttpClient", () => { + const adaptor = adaptorForVersion(version); + defaultTestSuite(() => new HttpClient(url), adaptor); + }); + + describe("With WebsocketClient", () => { + // don't print out WebSocket errors if marked pending + const onError = process.env.TENDERMINT_ENABLED ? console.error : () => 0; + const factory = (): WebsocketClient => new WebsocketClient(url, onError); + const adaptor = adaptorForVersion(version); + defaultTestSuite(factory, adaptor); + websocketTestSuite(factory, adaptor, appCreator); + }); + }); +} diff --git a/packages/tendermint-rpc/src/client.ts b/packages/tendermint-rpc/src/client.ts new file mode 100644 index 0000000000..7cf9db2228 --- /dev/null +++ b/packages/tendermint-rpc/src/client.ts @@ -0,0 +1,253 @@ +import { Stream } from "xstream"; + +import { Adaptor, Decoder, Encoder, Params, Responses } from "./adaptor"; +import { adaptorForVersion } from "./adaptorforversion"; +import { createJsonRpcRequest } from "./jsonrpc"; +import * as requests from "./requests"; +import * as responses from "./responses"; +import { + HttpClient, + instanceOfRpcStreamingClient, + RpcClient, + SubscriptionEvent, + WebsocketClient, +} from "./rpcclients"; + +export class Client { + public static async connect(url: string): Promise { + const useHttp = url.startsWith("http://") || url.startsWith("https://"); + const client = useHttp ? new HttpClient(url) : new WebsocketClient(url); + return this.detectVersion(client); + } + + public static async detectVersion(client: RpcClient): Promise { + const req = createJsonRpcRequest(requests.Method.Status); + const response = await client.execute(req); + const result = response.result; + + if (!result || !result.node_info) { + throw new Error("Unrecognized format for status response"); + } + + const version = result.node_info.version; + if (typeof version !== "string") { + throw new Error("Unrecognized version format: must be string"); + } + + return new Client(client, adaptorForVersion(version)); + } + + private readonly client: RpcClient; + private readonly p: Params; + private readonly r: Responses; + + public constructor(client: RpcClient, adaptor: Adaptor) { + this.client = client; + this.p = adaptor.params; + this.r = adaptor.responses; + } + + public disconnect(): void { + this.client.disconnect(); + } + + public async abciInfo(): Promise { + const query: requests.AbciInfoRequest = { method: requests.Method.AbciInfo }; + return this.doCall(query, this.p.encodeAbciInfo, this.r.decodeAbciInfo); + } + + public async abciQuery(params: requests.AbciQueryParams): Promise { + const query: requests.AbciQueryRequest = { params: params, method: requests.Method.AbciQuery }; + return this.doCall(query, this.p.encodeAbciQuery, this.r.decodeAbciQuery); + } + + public async block(height?: number): Promise { + const query: requests.BlockRequest = { method: requests.Method.Block, params: { height: height } }; + return this.doCall(query, this.p.encodeBlock, this.r.decodeBlock); + } + + public async blockResults(height?: number): Promise { + const query: requests.BlockResultsRequest = { + method: requests.Method.BlockResults, + params: { height: height }, + }; + return this.doCall(query, this.p.encodeBlockResults, this.r.decodeBlockResults); + } + + public async blockchain(minHeight?: number, maxHeight?: number): Promise { + const query: requests.BlockchainRequest = { + method: requests.Method.Blockchain, + params: { + minHeight: minHeight, + maxHeight: maxHeight, + }, + }; + return this.doCall(query, this.p.encodeBlockchain, this.r.decodeBlockchain); + } + + /** + * Broadcast transaction to mempool and wait for response + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync + */ + public async broadcastTxSync( + params: requests.BroadcastTxParams, + ): Promise { + const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxSync }; + return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxSync); + } + + /** + * Broadcast transaction to mempool and do not wait for result + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async + */ + public async broadcastTxAsync( + params: requests.BroadcastTxParams, + ): Promise { + const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxAsync }; + return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxAsync); + } + + /** + * Broadcast transaction to mempool and wait for block + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit + */ + public async broadcastTxCommit( + params: requests.BroadcastTxParams, + ): Promise { + const query: requests.BroadcastTxRequest = { params: params, method: requests.Method.BroadcastTxCommit }; + return this.doCall(query, this.p.encodeBroadcastTx, this.r.decodeBroadcastTxCommit); + } + + public async commit(height?: number): Promise { + const query: requests.CommitRequest = { method: requests.Method.Commit, params: { height: height } }; + return this.doCall(query, this.p.encodeCommit, this.r.decodeCommit); + } + + public async genesis(): Promise { + const query: requests.GenesisRequest = { method: requests.Method.Genesis }; + return this.doCall(query, this.p.encodeGenesis, this.r.decodeGenesis); + } + + public async health(): Promise { + const query: requests.HealthRequest = { method: requests.Method.Health }; + return this.doCall(query, this.p.encodeHealth, this.r.decodeHealth); + } + + public async status(): Promise { + const query: requests.StatusRequest = { method: requests.Method.Status }; + return this.doCall(query, this.p.encodeStatus, this.r.decodeStatus); + } + + public subscribeNewBlock(): Stream { + const request: requests.SubscribeRequest = { + method: requests.Method.Subscribe, + query: { type: requests.SubscriptionEventType.NewBlock }, + }; + return this.subscribe(request, this.r.decodeNewBlockEvent); + } + + public subscribeNewBlockHeader(): Stream { + const request: requests.SubscribeRequest = { + method: requests.Method.Subscribe, + query: { type: requests.SubscriptionEventType.NewBlockHeader }, + }; + return this.subscribe(request, this.r.decodeNewBlockHeaderEvent); + } + + public subscribeTx(query?: requests.QueryString): Stream { + const request: requests.SubscribeRequest = { + method: requests.Method.Subscribe, + query: { + type: requests.SubscriptionEventType.Tx, + raw: query, + }, + }; + return this.subscribe(request, this.r.decodeTxEvent); + } + + /** + * Get a single transaction by hash + * + * @see https://docs.tendermint.com/master/rpc/#/Info/tx + */ + public async tx(params: requests.TxParams): Promise { + const query: requests.TxRequest = { params: params, method: requests.Method.Tx }; + return this.doCall(query, this.p.encodeTx, this.r.decodeTx); + } + + /** + * Search for transactions that are in a block + * + * @see https://docs.tendermint.com/master/rpc/#/Info/tx_search + */ + public async txSearch(params: requests.TxSearchParams): Promise { + const query: requests.TxSearchRequest = { params: params, method: requests.Method.TxSearch }; + const resp = await this.doCall(query, this.p.encodeTxSearch, this.r.decodeTxSearch); + return { + ...resp, + // make sure we sort by height, as tendermint may be sorting by string value of the height + txs: [...resp.txs].sort((a, b) => a.height - b.height), + }; + } + + // this should paginate through all txSearch options to ensure it returns all results. + // starts with page 1 or whatever was provided (eg. to start on page 7) + public async txSearchAll(params: requests.TxSearchParams): Promise { + let page = params.page || 1; + // tslint:disable-next-line:readonly-array + const txs: responses.TxResponse[] = []; + let done = false; + + while (!done) { + const resp = await this.txSearch({ ...params, page: page }); + txs.push(...resp.txs); + if (txs.length < resp.totalCount) { + page++; + } else { + done = true; + } + } + // make sure we sort by height, as tendermint may be sorting by string value of the height + // and the earlier items may be in a higher page than the later items + txs.sort((a, b) => a.height - b.height); + + return { + totalCount: txs.length, + txs: txs, + }; + } + + public async validators(height?: number): Promise { + const query: requests.ValidatorsRequest = { + method: requests.Method.Validators, + params: { height: height }, + }; + return this.doCall(query, this.p.encodeValidators, this.r.decodeValidators); + } + + // doCall is a helper to handle the encode/call/decode logic + private async doCall( + request: T, + encode: Encoder, + decode: Decoder, + ): Promise { + const req = encode(request); + const result = await this.client.execute(req); + return decode(result); + } + + private subscribe(request: requests.SubscribeRequest, decode: (e: SubscriptionEvent) => T): Stream { + if (!instanceOfRpcStreamingClient(this.client)) { + throw new Error("This RPC client type cannot subscribe to events"); + } + + const req = this.p.encodeSubscribe(request); + const eventStream = this.client.listen(req); + return eventStream.map((event) => { + return decode(event); + }); + } +} diff --git a/packages/tendermint-rpc/src/config.spec.ts b/packages/tendermint-rpc/src/config.spec.ts new file mode 100644 index 0000000000..f30aef5499 --- /dev/null +++ b/packages/tendermint-rpc/src/config.spec.ts @@ -0,0 +1,32 @@ +export interface TendermintInstance { + readonly url: string; + readonly version: string; + readonly appCreator: string; +} + +/** + * Tendermint instances to be tested. + * + * Testing different versions: as a convention, the minor version number is encoded + * in the port 111, e.g. Tendermint 0.21.0 runs on port 11121. To start + * a specific version use: + * TENDERMINT_VERSION=0.29.2 TENDERMINT_PORT=11129 ./scripts/tendermint/start.sh + * + * When more than 1 instances of tendermint are running, stop them manually: + * docker container ls | grep tendermint/tendermint + * docker container kill + */ +export const tendermintInstances: readonly TendermintInstance[] = [ + { + url: "localhost:11131", + version: "0.31.x", + appCreator: "Cosmoshi Netowoko", + }, + { + url: "localhost:11132", + version: "0.32.x", + appCreator: "Cosmoshi Netowoko", + }, +]; + +export const defaultInstance: TendermintInstance = tendermintInstances[0]; diff --git a/packages/tendermint-rpc/src/encodings.spec.ts b/packages/tendermint-rpc/src/encodings.spec.ts new file mode 100644 index 0000000000..f251ef16d0 --- /dev/null +++ b/packages/tendermint-rpc/src/encodings.spec.ts @@ -0,0 +1,91 @@ +import { ReadonlyDate } from "readonly-date"; + +import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "./encodings"; + +describe("encodings", () => { + describe("encodeString", () => { + it("works", () => { + expect(encodeString("")).toEqual(Uint8Array.from([0])); + const str = "hello iov"; + expect(encodeString(str)).toEqual( + Uint8Array.from([str.length, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x69, 0x6f, 0x76]), + ); + }); + }); + + describe("encodeInt", () => { + it("works", () => { + expect(encodeInt(0)).toEqual(Uint8Array.from([0])); + expect(encodeInt(1)).toEqual(Uint8Array.from([1])); + expect(encodeInt(127)).toEqual(Uint8Array.from([127])); + expect(encodeInt(128)).toEqual(Uint8Array.from([128, 1])); + expect(encodeInt(255)).toEqual(Uint8Array.from([255, 1])); + expect(encodeInt(256)).toEqual(Uint8Array.from([128, 2])); + }); + }); + + describe("encodeTime", () => { + it("works", () => { + const readonlyDateWithNanoseconds = new ReadonlyDate(1464109200); + // tslint:disable-next-line:no-object-mutation + (readonlyDateWithNanoseconds as any).nanoseconds = 666666; + expect(encodeTime(readonlyDateWithNanoseconds)).toEqual( + Uint8Array.from([0x08, 173, 174, 89, 0x10, 170, 220, 215, 95]), + ); + }); + }); + + describe("encodeBytes", () => { + it("works", () => { + expect(encodeBytes(Uint8Array.from([]))).toEqual(Uint8Array.from([])); + const uint8Array = Uint8Array.from([1, 2, 3, 4, 5, 6, 7]); + expect(encodeBytes(uint8Array)).toEqual(Uint8Array.from([uint8Array.length, 1, 2, 3, 4, 5, 6, 7])); + }); + }); + + describe("encodeVersion", () => { + it("works", () => { + const version = { + block: 666666, + app: 200, + }; + expect(encodeVersion(version)).toEqual(Uint8Array.from([0x08, 170, 216, 40, 0x10, 200, 1])); + }); + }); + + describe("encodeBlockId", () => { + it("works", () => { + const blockId = { + hash: Uint8Array.from([1, 2, 3, 4, 5, 6, 7]), + parts: { + total: 88, + hash: Uint8Array.from([8, 9, 10, 11, 12]), + }, + }; + expect(encodeBlockId(blockId)).toEqual( + Uint8Array.from([ + 0x0a, + blockId.hash.length, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0x12, + 9, + 0x08, + 88, + 0x12, + 5, + 8, + 9, + 10, + 11, + 12, + ]), + ); + }); + }); +}); diff --git a/packages/tendermint-rpc/src/encodings.ts b/packages/tendermint-rpc/src/encodings.ts new file mode 100644 index 0000000000..60b49da17f --- /dev/null +++ b/packages/tendermint-rpc/src/encodings.ts @@ -0,0 +1,231 @@ +import { fromBase64, fromHex, fromRfc3339, Int53, toBase64, toHex, toUtf8 } from "@iov/encoding"; +import { As } from "type-tagger"; + +import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses"; + +export type Base64String = string & As<"base64">; +export type HexString = string & As<"hex">; +export type IntegerString = string & As<"integer">; +export type DateTimeString = string & As<"datetime">; + +/** + * A runtime checker that ensures a given value is set (i.e. not undefined or null) + * + * This is used when you want to verify that data at runtime matches the expected type. + */ +export function assertSet(value: T): T { + if ((value as unknown) === undefined) { + throw new Error("Value must not be undefined"); + } + + if ((value as unknown) === null) { + throw new Error("Value must not be null"); + } + + return value; +} + +/** + * A runtime checker that ensures a given value is a boolean + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export function assertBoolean(value: boolean): boolean { + assertSet(value); + if (typeof (value as unknown) !== "boolean") { + throw new Error("Value must be a boolean"); + } + return value; +} + +/** + * A runtime checker that ensures a given value is a number + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export function assertNumber(value: number): number { + assertSet(value); + if (typeof (value as unknown) !== "number") { + throw new Error("Value must be a number"); + } + return value; +} + +/** + * A runtime checker that ensures a given value is an array + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export function assertArray(value: readonly T[]): readonly T[] { + assertSet(value); + if (!Array.isArray(value as unknown)) { + throw new Error("Value must be a an array"); + } + return value; +} + +/** + * A runtime checker that ensures a given value is an object in the sense of JSON + * (an unordered collection of key–value pairs where the keys are strings) + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export function assertObject(value: T): T { + assertSet(value); + if (typeof (value as unknown) !== "object") { + throw new Error("Value must be an object"); + } + + // Exclude special kind of objects like Array, Date or Uint8Array + // Object.prototype.toString() returns a specified value: + // http://www.ecma-international.org/ecma-262/7.0/index.html#sec-object.prototype.tostring + if (Object.prototype.toString.call(value) !== "[object Object]") { + throw new Error("Value must be a simple object"); + } + + return value; +} + +interface Lengther { + readonly length: number; +} + +/** + * Throws an error if value matches the empty value for the + * given type (array/string of length 0, number of value 0, ...) + * + * Otherwise returns the value. + * + * This implies assertSet + */ +export function assertNotEmpty(value: T): T { + assertSet(value); + + if (typeof value === "number" && value === 0) { + throw new Error("must provide a non-zero value"); + } else if (((value as any) as Lengther).length === 0) { + throw new Error("must provide a non-empty value"); + } + return value; +} + +// optional uses the value or provides a default +export function optional(value: T | null | undefined, fallback: T): T { + return value === undefined || value === null ? fallback : value; +} + +// may will run the transform if value is defined, otherwise returns undefined +export function may(transform: (val: T) => U, value: T | null | undefined): U | undefined { + return value === undefined || value === null ? undefined : transform(value); +} + +export function dictionaryToStringMap(obj: any): Map { + const out = new Map(); + for (const key of Object.keys(obj)) { + const value: unknown = obj[key]; + if (typeof value !== "string") { + throw new Error("Found dictionary value of type other than string"); + } + out.set(key, value); + } + return out; +} + +export class Integer { + public static parse(input: IntegerString | number): number { + const asInt = typeof input === "number" ? new Int53(input) : Int53.fromString(input); + return asInt.toNumber(); + } + + public static encode(num: number): IntegerString { + return new Int53(num).toString() as IntegerString; + } +} + +export class Base64 { + public static encode(data: Uint8Array): Base64String { + return toBase64(data) as Base64String; + } + + public static decode(base64String: Base64String): Uint8Array { + return fromBase64(base64String); + } +} + +export class DateTime { + public static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds { + const readonlyDate = fromRfc3339(dateTimeString); + const nanosecondsMatch = dateTimeString.match(/\.(\d+)Z$/); + const nanoseconds = nanosecondsMatch ? nanosecondsMatch[1].slice(3) : ""; + // tslint:disable-next-line:no-object-mutation + (readonlyDate as any).nanoseconds = parseInt(nanoseconds.padEnd(6, "0"), 10); + return readonlyDate as ReadonlyDateWithNanoseconds; + } +} + +export class Hex { + public static encode(data: Uint8Array): HexString { + return toHex(data) as HexString; + } + + public static decode(hexString: HexString): Uint8Array { + return fromHex(hexString); + } +} + +// Encodings needed for hashing block headers +// Several of these functions are inspired by https://github.com/nomic-io/js-tendermint/blob/tendermint-0.30/src/ + +// See https://github.com/tendermint/go-amino/blob/v0.15.0/encoder.go#L193-L195 +export function encodeString(s: string): Uint8Array { + const utf8 = toUtf8(s); + return Uint8Array.from([utf8.length, ...utf8]); +} + +// See https://github.com/tendermint/go-amino/blob/v0.15.0/encoder.go#L79-L87 +export function encodeInt(n: number): Uint8Array { + // tslint:disable-next-line:no-bitwise + return n >= 0x80 ? Uint8Array.from([(n & 0xff) | 0x80, ...encodeInt(n >> 7)]) : Uint8Array.from([n & 0xff]); +} + +// See https://github.com/tendermint/go-amino/blob/v0.15.0/encoder.go#L134-L178 +export function encodeTime(time: ReadonlyDateWithNanoseconds): Uint8Array { + const milliseconds = time.getTime(); + const seconds = Math.floor(milliseconds / 1000); + const secondsArray = seconds ? [0x08, ...encodeInt(seconds)] : new Uint8Array(); + const nanoseconds = (time.nanoseconds || 0) + (milliseconds % 1000) * 1e6; + const nanosecondsArray = nanoseconds ? [0x10, ...encodeInt(nanoseconds)] : new Uint8Array(); + return Uint8Array.from([...secondsArray, ...nanosecondsArray]); +} + +// See https://github.com/tendermint/go-amino/blob/v0.15.0/encoder.go#L180-L187 +export function encodeBytes(bytes: Uint8Array): Uint8Array { + // Since we're only dealing with short byte arrays we don't need a full VarBuffer implementation yet + if (bytes.length >= 0x80) throw new Error("Not implemented for byte arrays of length 128 or more"); + return bytes.length ? Uint8Array.from([bytes.length, ...bytes]) : new Uint8Array(); +} + +export function encodeVersion(version: Version): Uint8Array { + const blockArray = version.block ? Uint8Array.from([0x08, ...encodeInt(version.block)]) : new Uint8Array(); + const appArray = version.app ? Uint8Array.from([0x10, ...encodeInt(version.app)]) : new Uint8Array(); + return Uint8Array.from([...blockArray, ...appArray]); +} + +export function encodeBlockId(blockId: BlockId): Uint8Array { + return Uint8Array.from([ + 0x0a, + blockId.hash.length, + ...blockId.hash, + 0x12, + blockId.parts.hash.length + 4, + 0x08, + blockId.parts.total, + 0x12, + blockId.parts.hash.length, + ...blockId.parts.hash, + ]); +} diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts new file mode 100644 index 0000000000..f3b010efa4 --- /dev/null +++ b/packages/tendermint-rpc/src/index.ts @@ -0,0 +1,40 @@ +// exported to access version-specific hashing +export { v0_31 } from "./v0-31"; +export { v0_32 } from "./v0-32"; + +export { Client } from "./client"; +export { + AbciInfoRequest, + AbciQueryParams, + AbciQueryRequest, + BlockRequest, + BlockchainRequest, + BlockResultsRequest, + BroadcastTxRequest, + BroadcastTxParams, + CommitRequest, + GenesisRequest, + HealthRequest, + Method, + Request, + QueryString, + QueryTag, + StatusRequest, + SubscriptionEventType, + TxParams, + TxRequest, + TxSearchParams, + TxSearchRequest, + ValidatorsRequest, +} from "./requests"; +export * from "./responses"; +export { HttpClient, WebsocketClient } from "./rpcclients"; // TODO: Why do we export those outside of this package? +export { + IpPortString, + TxBytes, + TxHash, + ValidatorEd25519Pubkey, + ValidatorEd25519Signature, + ValidatorPubkey, + ValidatorSignature, +} from "./types"; diff --git a/packages/tendermint-rpc/src/jsonrpc.spec.ts b/packages/tendermint-rpc/src/jsonrpc.spec.ts new file mode 100644 index 0000000000..f061242fd2 --- /dev/null +++ b/packages/tendermint-rpc/src/jsonrpc.spec.ts @@ -0,0 +1,25 @@ +import { createJsonRpcRequest } from "./jsonrpc"; + +describe("jsonrpc", () => { + describe("createJsonRpcRequest", () => { + it("generates proper object with correct method", () => { + const request = createJsonRpcRequest("do_something"); + expect(request.jsonrpc).toEqual("2.0"); + expect(request.id).toMatch(/^[a-zA-Z0-9]{12}$/); + expect(request.method).toEqual("do_something"); + }); + + it("generates distinct IDs", () => { + const request1 = createJsonRpcRequest("foo"); + const request2 = createJsonRpcRequest("foo"); + expect(request2.id).not.toEqual(request1.id); + }); + + it("copies params", () => { + const params = { foo: "bar" }; + const request = createJsonRpcRequest("some_method", params); + expect(request.params).toEqual(params); + expect(request.params).not.toBe(params); + }); + }); +}); diff --git a/packages/tendermint-rpc/src/jsonrpc.ts b/packages/tendermint-rpc/src/jsonrpc.ts new file mode 100644 index 0000000000..45b2318203 --- /dev/null +++ b/packages/tendermint-rpc/src/jsonrpc.ts @@ -0,0 +1,25 @@ +import { JsonRpcRequest } from "@iov/jsonrpc"; + +const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +/** generates a random alphanumeric character */ +function randomChar(): string { + return alphabet[Math.floor(Math.random() * alphabet.length)]; +} + +function randomId(): string { + return Array.from({ length: 12 }) + .map(() => randomChar()) + .join(""); +} + +/** Creates a JSON-RPC request with random ID */ +export function createJsonRpcRequest(method: string, params?: {}): JsonRpcRequest { + const paramsCopy = params ? { ...params } : {}; + return { + jsonrpc: "2.0", + id: randomId(), + method: method, + params: paramsCopy, + }; +} diff --git a/packages/tendermint-rpc/src/requests.spec.ts b/packages/tendermint-rpc/src/requests.spec.ts new file mode 100644 index 0000000000..5277e0b4ec --- /dev/null +++ b/packages/tendermint-rpc/src/requests.spec.ts @@ -0,0 +1,41 @@ +import { buildQuery, QueryString } from "./requests"; + +describe("Requests", () => { + describe("buildQuery", () => { + it("works for no input", () => { + const query = buildQuery({}); + expect(query).toEqual(""); + }); + + it("works for one tags", () => { + const query = buildQuery({ tags: [{ key: "abc", value: "def" }] }); + expect(query).toEqual("abc='def'"); + }); + + it("works for two tags", () => { + const query = buildQuery({ + tags: [ + { key: "k", value: "9" }, + { key: "L", value: "7" }, + ], + }); + expect(query).toEqual("k='9' AND L='7'"); + }); + + it("works for raw input", () => { + const query = buildQuery({ raw: "aabbCCDD" as QueryString }); + expect(query).toEqual("aabbCCDD"); + }); + + it("works for mixed input", () => { + const query = buildQuery({ + tags: [ + { key: "k", value: "9" }, + { key: "L", value: "7" }, + ], + raw: "aabbCCDD" as QueryString, + }); + expect(query).toEqual("k='9' AND L='7' AND aabbCCDD"); + }); + }); +}); diff --git a/packages/tendermint-rpc/src/requests.ts b/packages/tendermint-rpc/src/requests.ts new file mode 100644 index 0000000000..54a26cd2ca --- /dev/null +++ b/packages/tendermint-rpc/src/requests.ts @@ -0,0 +1,179 @@ +import { As } from "type-tagger"; + +/** + * RPC methods as documented in https://docs.tendermint.com/master/rpc/ + * + * Enum raw value must match the spelling in the "shell" example call (snake_case) + */ +export enum Method { + AbciInfo = "abci_info", + AbciQuery = "abci_query", + Block = "block", + Blockchain = "blockchain", + BlockResults = "block_results", + BroadcastTxAsync = "broadcast_tx_async", + BroadcastTxSync = "broadcast_tx_sync", + BroadcastTxCommit = "broadcast_tx_commit", + Commit = "commit", + Genesis = "genesis", + Health = "health", + Status = "status", + Subscribe = "subscribe", + Tx = "tx", + TxSearch = "tx_search", + Validators = "validators", + Unsubscribe = "unsubscribe", +} + +export type Request = + | AbciInfoRequest + | AbciQueryRequest + | BlockRequest + | BlockchainRequest + | BlockResultsRequest + | BroadcastTxRequest + | CommitRequest + | GenesisRequest + | HealthRequest + | StatusRequest + | TxRequest + | TxSearchRequest + | ValidatorsRequest; + +/** + * Raw values must match the tendermint event name + * + * @see https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants + */ +export enum SubscriptionEventType { + NewBlock = "NewBlock", + NewBlockHeader = "NewBlockHeader", + Tx = "Tx", +} + +export interface AbciInfoRequest { + readonly method: Method.AbciInfo; +} + +export interface AbciQueryRequest { + readonly method: Method.AbciQuery; + readonly params: AbciQueryParams; +} +export interface AbciQueryParams { + readonly path: string; + readonly data: Uint8Array; + readonly height?: number; + /** + * A flag that defines if proofs are included in the response or not. + * + * Internally this is mapped to the old inverse name `trusted` for Tendermint < 0.26. + * Starting with Tendermint 0.26, the default value changed from true to false. + */ + readonly prove?: boolean; +} + +export interface BlockRequest { + readonly method: Method.Block; + readonly params: { + readonly height?: number; + }; +} + +export interface BlockchainRequest { + readonly method: Method.Blockchain; + readonly params: BlockchainRequestParams; +} +export interface BlockchainRequestParams { + readonly minHeight?: number; + readonly maxHeight?: number; +} + +export interface BlockResultsRequest { + readonly method: Method.BlockResults; + readonly params: { + readonly height?: number; + }; +} + +export interface BroadcastTxRequest { + readonly method: Method.BroadcastTxAsync | Method.BroadcastTxSync | Method.BroadcastTxCommit; + readonly params: BroadcastTxParams; +} +export interface BroadcastTxParams { + readonly tx: Uint8Array; +} + +export interface CommitRequest { + readonly method: Method.Commit; + readonly params: { + readonly height?: number; + }; +} + +export interface GenesisRequest { + readonly method: Method.Genesis; +} + +export interface HealthRequest { + readonly method: Method.Health; +} + +export interface StatusRequest { + readonly method: Method.Status; +} + +export interface SubscribeRequest { + readonly method: Method.Subscribe; + readonly query: { + readonly type: SubscriptionEventType; + readonly raw?: QueryString; + }; +} + +export type QueryString = string & As<"query">; + +export interface QueryTag { + readonly key: string; + readonly value: string; +} + +export interface TxRequest { + readonly method: Method.Tx; + readonly params: TxParams; +} +export interface TxParams { + readonly hash: Uint8Array; + readonly prove?: boolean; +} + +// TODO: clarify this type +export interface TxSearchRequest { + readonly method: Method.TxSearch; + readonly params: TxSearchParams; +} +export interface TxSearchParams { + readonly query: QueryString; + readonly prove?: boolean; + readonly page?: number; + readonly per_page?: number; +} + +export interface ValidatorsRequest { + readonly method: Method.Validators; + readonly params: { + readonly height?: number; + }; +} + +export interface BuildQueryComponents { + readonly tags?: readonly QueryTag[]; + readonly raw?: QueryString; +} + +export function buildQuery(components: BuildQueryComponents): QueryString { + const tags = components.tags ? components.tags : []; + const tagComponents = tags.map((tag) => `${tag.key}='${tag.value}'`); + const rawComponents = components.raw ? [components.raw] : []; + + return [...tagComponents, ...rawComponents].join(" AND ") as QueryString; +} diff --git a/packages/tendermint-rpc/src/responses.ts b/packages/tendermint-rpc/src/responses.ts new file mode 100644 index 0000000000..7d6e432ed5 --- /dev/null +++ b/packages/tendermint-rpc/src/responses.ts @@ -0,0 +1,331 @@ +import { ReadonlyDate } from "readonly-date"; + +import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "./types"; + +export type Response = + | AbciInfoResponse + | AbciQueryResponse + | BlockResponse + | BlockResultsResponse + | BlockchainResponse + | BroadcastTxAsyncResponse + | BroadcastTxSyncResponse + | BroadcastTxCommitResponse + | CommitResponse + | GenesisResponse + | HealthResponse + | StatusResponse + | TxResponse + | TxSearchResponse + | ValidatorsResponse; + +export interface AbciInfoResponse { + readonly data?: string; + readonly lastBlockHeight?: number; + readonly lastBlockAppHash?: Uint8Array; +} + +export interface AbciQueryResponse { + readonly key: Uint8Array; + readonly value: Uint8Array; + readonly height?: number; + readonly index?: number; + readonly code?: number; // non-falsy for errors + readonly log?: string; +} + +export interface BlockResponse { + readonly blockMeta: BlockMeta; + readonly block: Block; +} + +export interface BlockResultsResponse { + readonly height: number; + readonly results: readonly TxData[]; + readonly endBlock: { + readonly validatorUpdates: readonly Validator[]; + readonly consensusUpdates?: ConsensusParams; + readonly tags?: readonly Tag[]; + }; +} + +export interface BlockchainResponse { + readonly lastHeight: number; + readonly blockMetas: readonly BlockMeta[]; +} + +/** No data in here because RPC method BroadcastTxAsync "returns right away, with no response" */ +export interface BroadcastTxAsyncResponse {} + +export interface BroadcastTxSyncResponse extends TxData { + readonly hash: TxHash; +} + +/** + * Returns true iff transaction made it sucessfully into the transaction pool + */ +export function broadcastTxSyncSuccess(res: BroadcastTxSyncResponse): boolean { + // code must be 0 on success + return res.code === 0; +} + +export interface BroadcastTxCommitResponse { + readonly height?: number; + readonly hash: TxHash; + readonly checkTx: TxData; + readonly deliverTx?: TxData; +} + +/** + * Returns true iff transaction made it sucessfully into a block + * (i.e. sucess in `check_tx` and `deliver_tx` field) + */ +export function broadcastTxCommitSuccess(res: BroadcastTxCommitResponse): boolean { + // code must be 0 on success + // deliverTx may be present but empty on failure + return res.checkTx.code === 0 && !!res.deliverTx && res.deliverTx.code === 0; +} + +export interface CommitResponse { + readonly header: Header; + readonly commit: Commit; + readonly canonical: boolean; +} + +export interface GenesisResponse { + readonly genesisTime: ReadonlyDate; + readonly chainId: string; + readonly consensusParams: ConsensusParams; + readonly validators: readonly Validator[]; + readonly appHash: Uint8Array; + readonly appState: {} | undefined; +} + +export type HealthResponse = null; + +export interface StatusResponse { + readonly nodeInfo: NodeInfo; + readonly syncInfo: SyncInfo; + readonly validatorInfo: Validator; +} + +/** + * A transaction from RPC calls like search. + * + * Try to keep this compatible to TxEvent + */ +export interface TxResponse { + readonly tx: TxBytes; + readonly hash: TxHash; + readonly height: number; + readonly index: number; + readonly result: TxData; + readonly proof?: TxProof; +} + +export interface TxSearchResponse { + readonly txs: readonly TxResponse[]; + readonly totalCount: number; +} + +export interface ValidatorsResponse { + readonly blockHeight: number; + readonly results: readonly Validator[]; +} + +// Events + +export interface NewBlockEvent extends Block {} + +export interface NewBlockHeaderEvent extends Header {} + +export interface TxEvent { + readonly tx: TxBytes; + readonly hash: TxHash; + readonly height: number; + readonly index: number; + readonly result: TxData; +} + +export const getTxEventHeight = (event: TxEvent): number => event.height; +export const getHeaderEventHeight = (event: NewBlockHeaderEvent): number => event.height; +export const getBlockEventHeight = (event: NewBlockEvent): number => event.header.height; + +// Helper items used above + +export interface Tag { + readonly key: Uint8Array; + readonly value: Uint8Array; +} + +export interface Event { + readonly type: string; + readonly attributes: readonly Tag[]; +} + +export interface TxData { + readonly code: number; + readonly log?: string; + readonly data?: Uint8Array; + readonly tags?: readonly Tag[]; + readonly events?: readonly Event[]; + // readonly fees?: any; +} + +export interface TxProof { + readonly data: Uint8Array; + readonly rootHash: Uint8Array; + readonly proof: { + readonly total: number; + readonly index: number; + /** Optional because does not exist in Tendermint 0.25.x */ + readonly leafHash?: Uint8Array; + readonly aunts: readonly Uint8Array[]; + }; +} + +export interface BlockMeta { + readonly blockId: BlockId; + readonly header: Header; +} + +export interface BlockId { + readonly hash: Uint8Array; + readonly parts: { + readonly total: number; + readonly hash: Uint8Array; + }; +} + +export interface Block { + readonly header: Header; + readonly lastCommit: Commit; + readonly txs: readonly Uint8Array[]; + readonly evidence?: readonly Evidence[]; +} + +export interface Evidence { + readonly type: string; + readonly validator: Validator; + readonly height: number; + readonly time: number; + readonly totalVotingPower: number; +} + +export interface Commit { + readonly blockId: BlockId; + readonly precommits: readonly Vote[]; +} + +/** + * raw values from https://github.com/tendermint/tendermint/blob/dfa9a9a30a666132425b29454e90a472aa579a48/types/vote.go#L44 + */ +export enum VoteType { + PREVOTE = 1, + PRECOMMIT = 2, +} + +export interface Vote { + readonly type: VoteType; + readonly validatorAddress: Uint8Array; + readonly validatorIndex: number; + readonly height: number; + readonly round: number; + readonly timestamp: ReadonlyDate; + readonly blockId: BlockId; + readonly signature: ValidatorSignature; +} + +export interface Version { + readonly block: number; + readonly app: number; +} + +export interface ReadonlyDateWithNanoseconds extends ReadonlyDate { + /* Nanoseconds after the time stored in a vanilla ReadonlyDate (millisecond granularity) */ + readonly nanoseconds?: number; +} + +// https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/blockchain.md +export interface Header { + // basic block info + readonly version: Version; + readonly chainId: string; + readonly height: number; + readonly time: ReadonlyDateWithNanoseconds; + readonly numTxs: number; + readonly totalTxs: number; + + // prev block info + readonly lastBlockId: BlockId; + + // hashes of block data + readonly lastCommitHash: Uint8Array; + readonly dataHash: Uint8Array; // empty when number of transaction is 0 + + // hashes from the app output from the prev block + readonly validatorsHash: Uint8Array; + readonly nextValidatorsHash: Uint8Array; + readonly consensusHash: Uint8Array; + readonly appHash: Uint8Array; + readonly lastResultsHash: Uint8Array; + + // consensus info + readonly evidenceHash: Uint8Array; + readonly proposerAddress: Uint8Array; +} + +export interface NodeInfo { + readonly id: Uint8Array; + readonly listenAddr: IpPortString; + readonly network: string; + readonly version: string; + readonly channels: string; // ??? + readonly moniker: string; + readonly other: Map; + readonly protocolVersion: { + readonly p2p: number; + readonly block: number; + readonly app: number; + }; +} + +export interface SyncInfo { + readonly latestBlockHash: Uint8Array; + readonly latestAppHash: Uint8Array; + readonly latestBlockHeight: number; + readonly latestBlockTime: ReadonlyDate; + readonly catchingUp: boolean; +} + +// this is in status +export interface Validator { + readonly address?: Uint8Array; + readonly pubkey: ValidatorPubkey; + readonly votingPower: number; + readonly accum?: number; + readonly name?: string; +} + +export interface ConsensusParams { + readonly block: BlockParams; + readonly evidence: EvidenceParams; +} + +export interface BlockParams { + readonly maxBytes: number; + readonly maxGas: number; +} + +export interface TxSizeParams { + readonly maxBytes: number; + readonly maxGas: number; +} + +export interface BlockGossipParams { + readonly blockPartSizeBytes: number; +} + +export interface EvidenceParams { + readonly maxAge: number; +} diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts new file mode 100644 index 0000000000..0b5a707ea8 --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts @@ -0,0 +1,33 @@ +import { defaultInstance } from "../config.spec"; +import { createJsonRpcRequest } from "../jsonrpc"; +import { Method } from "../requests"; +import { HttpClient } from "./httpclient"; + +function pendingWithoutTendermint(): void { + if (!process.env.TENDERMINT_ENABLED) { + pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + } +} + +describe("HttpClient", () => { + const tendermintUrl = defaultInstance.url; + + it("can make a simple call", async () => { + pendingWithoutTendermint(); + const client = new HttpClient(tendermintUrl); + + const healthResponse = await client.execute(createJsonRpcRequest(Method.Health)); + expect(healthResponse.result).toEqual({}); + + const statusResponse = await client.execute(createJsonRpcRequest(Method.Status)); + expect(statusResponse.result).toBeTruthy(); + expect(statusResponse.result.node_info).toBeTruthy(); + + await client + .execute(createJsonRpcRequest("no-such-method")) + .then(() => fail("must not resolve")) + .catch((error) => expect(error).toBeTruthy()); + + client.disconnect(); + }); +}); diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.ts new file mode 100644 index 0000000000..e909fc221d --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.ts @@ -0,0 +1,57 @@ +import { + isJsonRpcErrorResponse, + JsonRpcRequest, + JsonRpcSuccessResponse, + parseJsonRpcResponse, +} from "@iov/jsonrpc"; +import axios from "axios"; + +import { hasProtocol, RpcClient } from "./rpcclient"; + +// Global symbols in some environments +// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch +declare const fetch: any | undefined; + +function filterBadStatus(res: any): any { + if (res.status >= 400) { + throw new Error(`Bad status on response: ${res.status}`); + } + return res; +} + +/** + * Helper to work around missing CORS support in Tendermint (https://github.com/tendermint/tendermint/pull/2800) + * + * For some reason, fetch does not complain about missing server-side CORS support. + */ +async function http(method: "POST", url: string, request?: any): Promise { + if (typeof fetch !== "undefined") { + const body = request ? JSON.stringify(request) : undefined; + return fetch(url, { method: method, body: body }) + .then(filterBadStatus) + .then((res: any) => res.json()); + } else { + return axios.request({ url: url, method: method, data: request }).then((res) => res.data); + } +} + +export class HttpClient implements RpcClient { + protected readonly url: string; + + public constructor(url = "http://localhost:46657") { + // accept host.name:port and assume http protocol + this.url = hasProtocol(url) ? url : "http://" + url; + } + + public disconnect(): void { + // nothing to be done + } + + public async execute(request: JsonRpcRequest): Promise { + const response = parseJsonRpcResponse(await http("POST", this.url, request)); + if (isJsonRpcErrorResponse(response)) { + throw new Error(JSON.stringify(response.error)); + } + return response; + } +} diff --git a/packages/tendermint-rpc/src/rpcclients/index.ts b/packages/tendermint-rpc/src/rpcclients/index.ts new file mode 100644 index 0000000000..fc8aba58c2 --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/index.ts @@ -0,0 +1,5 @@ +// This folder contains Tendermint-specific RPC clients + +export { instanceOfRpcStreamingClient, RpcClient, RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; +export { HttpClient } from "./httpclient"; +export { WebsocketClient } from "./websocketclient"; diff --git a/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts new file mode 100644 index 0000000000..751f4b5b8e --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts @@ -0,0 +1,44 @@ +import { defaultInstance } from "../config.spec"; +import { createJsonRpcRequest } from "../jsonrpc"; +import { Method } from "../requests"; +import { HttpClient } from "./httpclient"; +import { instanceOfRpcStreamingClient } from "./rpcclient"; +import { WebsocketClient } from "./websocketclient"; + +function pendingWithoutTendermint(): void { + if (!process.env.TENDERMINT_ENABLED) { + pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + } +} + +describe("RpcClient", () => { + const tendermintUrl = defaultInstance.url; + + it("has working instanceOfRpcStreamingClient()", async () => { + pendingWithoutTendermint(); + + const httpClient = new HttpClient(tendermintUrl); + const wsClient = new WebsocketClient(tendermintUrl); + + expect(instanceOfRpcStreamingClient(httpClient)).toEqual(false); + expect(instanceOfRpcStreamingClient(wsClient)).toEqual(true); + + httpClient.disconnect(); + await wsClient.connected(); + wsClient.disconnect(); + }); + + it("should also work with trailing slashes", async () => { + pendingWithoutTendermint(); + + const statusRequest = createJsonRpcRequest(Method.Status); + + const httpClient = new HttpClient(tendermintUrl + "/"); + expect(await httpClient.execute(statusRequest)).toBeDefined(); + httpClient.disconnect(); + + const wsClient = new WebsocketClient(tendermintUrl + "/"); + expect(await wsClient.execute(statusRequest)).toBeDefined(); + wsClient.disconnect(); + }); +}); diff --git a/packages/tendermint-rpc/src/rpcclients/rpcclient.ts b/packages/tendermint-rpc/src/rpcclients/rpcclient.ts new file mode 100644 index 0000000000..8783026bcd --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/rpcclient.ts @@ -0,0 +1,36 @@ +import { JsonRpcRequest, JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import { Stream } from "xstream"; + +/** + * An event emitted from Tendermint after subscribing via RPC. + * + * These events are passed as the `result` of JSON-RPC responses, which is kind + * of hacky because it breaks the idea that exactly one JSON-RPC response belongs + * to each JSON-RPC request. But this is how subscriptions work in Tendermint. + */ +export interface SubscriptionEvent { + readonly query: string; + readonly data: { + readonly type: string; + readonly value: any; + }; +} + +export interface RpcClient { + readonly execute: (request: JsonRpcRequest) => Promise; + readonly disconnect: () => void; +} + +export interface RpcStreamingClient extends RpcClient { + readonly listen: (request: JsonRpcRequest) => Stream; +} + +export function instanceOfRpcStreamingClient(client: RpcClient): client is RpcStreamingClient { + return typeof (client as any).listen === "function"; +} + +// Helpers for all RPC clients + +export function hasProtocol(url: string): boolean { + return url.search("://") !== -1; +} diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts new file mode 100644 index 0000000000..df96a06aca --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts @@ -0,0 +1,210 @@ +import { toListPromise } from "@iov/stream"; +import { Stream } from "xstream"; + +import { defaultInstance } from "../config.spec"; +import { Integer } from "../encodings"; +import { createJsonRpcRequest } from "../jsonrpc"; +import { Method } from "../requests"; +import { SubscriptionEvent } from "./rpcclient"; +import { WebsocketClient } from "./websocketclient"; + +function pendingWithoutTendermint(): void { + if (!process.env.TENDERMINT_ENABLED) { + pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + } +} + +describe("WebsocketClient", () => { + const tendermintUrl = defaultInstance.url; + + it("can make a simple call", async () => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const healthResponse = await client.execute(createJsonRpcRequest(Method.Health)); + expect(healthResponse.result).toEqual({}); + + const statusResponse = await client.execute(createJsonRpcRequest(Method.Status)); + expect(statusResponse.result).toBeTruthy(); + expect(statusResponse.result.node_info).toBeTruthy(); + + await client + .execute(createJsonRpcRequest("no-such-method")) + .then(() => fail("must not resolve")) + .catch((error) => expect(error).toBeTruthy()); + + client.disconnect(); + }); + + it("can listen to events", (done) => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const query = "tm.event='NewBlockHeader'"; + const req = createJsonRpcRequest("subscribe", { query: query }); + const headers = client.listen(req); + + // tslint:disable-next-line:readonly-array + const events: SubscriptionEvent[] = []; + + const sub = headers.subscribe({ + error: done.fail, + complete: () => done.fail("subscription should not complete"), + next: (evt: SubscriptionEvent) => { + events.push(evt); + expect(evt.query).toEqual(query); + + if (events.length === 2) { + // make sure they are consequtive heights + const height = (i: number): number => Integer.parse(events[i].data.value.header.height); + expect(height(1)).toEqual(height(0) + 1); + + sub.unsubscribe(); + + // wait 1.5s and check we did not get more events + setTimeout(() => { + expect(events.length).toEqual(2); + + client.disconnect(); + done(); + }, 1500); + } + }, + }); + }); + + it("can listen to the same query twice", async () => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const newBlockHeaderQuery = "tm.event='NewBlockHeader'"; + + // we need two requests with unique IDs + const request1 = createJsonRpcRequest("subscribe", { query: newBlockHeaderQuery }); + const request2 = createJsonRpcRequest("subscribe", { query: newBlockHeaderQuery }); + const stream1 = client.listen(request1); + const stream2 = client.listen(request2); + + const eventHeights = await toListPromise( + Stream.merge(stream1, stream2).map((event) => { + // height is string or number, depending on Tendermint version. But we don't care in this case + return event.data.value.header.height; + }), + 4, + ); + expect(new Set(eventHeights).size).toEqual(2); + + client.disconnect(); + }); + + it("can execute commands while listening to events", (done) => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const query = "tm.event='NewBlockHeader'"; + const req = createJsonRpcRequest("subscribe", { query: query }); + const headers = client.listen(req); + + // tslint:disable-next-line:readonly-array + const events: SubscriptionEvent[] = []; + + const sub = headers.subscribe({ + error: done.fail, + complete: () => done.fail("subscription should not complete"), + next: (evt: SubscriptionEvent) => { + events.push(evt); + expect(evt.query).toEqual(query); + + if (events.length === 2) { + sub.unsubscribe(); + + // wait 1.5s and check we did not get more events + setTimeout(() => { + expect(events.length).toEqual(2); + + client.disconnect(); + done(); + }, 1500); + } + }, + }); + + client + .execute(createJsonRpcRequest(Method.Status)) + .then((startusResponse) => expect(startusResponse).toBeTruthy()) + .catch(done.fail); + }); + + it("can end event listening by disconnecting", (done) => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const query = "tm.event='NewBlockHeader'"; + const req = createJsonRpcRequest("subscribe", { query: query }); + const headers = client.listen(req); + + // tslint:disable-next-line:readonly-array + const receivedEvents: SubscriptionEvent[] = []; + + setTimeout(() => client.disconnect(), 1500); + + headers.subscribe({ + error: done.fail, + next: (event: SubscriptionEvent) => receivedEvents.push(event), + complete: () => { + expect(receivedEvents.length).toEqual(1); + done(); + }, + }); + }); + + it("fails when executing on a disconnected client", async () => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + // dummy command to ensure client is connected + await client.execute(createJsonRpcRequest(Method.Health)); + + client.disconnect(); + + await client + .execute(createJsonRpcRequest(Method.Health)) + .then(() => fail("must not resolve")) + .catch((error) => expect(error).toMatch(/socket has disconnected/i)); + }); + + it("fails when listening to a disconnected client", (done) => { + pendingWithoutTendermint(); + + // async and done does not work together with pending() in Jasmine 2.8 + (async () => { + const client = new WebsocketClient(tendermintUrl); + // dummy command to ensure client is connected + await client.execute(createJsonRpcRequest(Method.Health)); + + client.disconnect(); + + const query = "tm.event='NewBlockHeader'"; + const req = createJsonRpcRequest("subscribe", { query: query }); + expect(() => client.listen(req).subscribe({})).toThrowError(/socket has disconnected/i); + done(); + })().catch(done.fail); + }); + + it("cannot listen to simple requests", async () => { + pendingWithoutTendermint(); + + const client = new WebsocketClient(tendermintUrl); + + const req = createJsonRpcRequest(Method.Health); + expect(() => client.listen(req)).toThrowError(/request method must be "subscribe"/i); + + await client.connected(); + client.disconnect(); + }); +}); diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts new file mode 100644 index 0000000000..51d246e044 --- /dev/null +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts @@ -0,0 +1,212 @@ +/* tslint:disable:readonly-keyword readonly-array no-object-mutation */ +import { + isJsonRpcErrorResponse, + JsonRpcId, + JsonRpcRequest, + JsonRpcResponse, + JsonRpcSuccessResponse, + parseJsonRpcResponse, +} from "@iov/jsonrpc"; +import { ConnectionStatus, ReconnectingSocket, SocketWrapperMessageEvent } from "@iov/socket"; +import { firstEvent } from "@iov/stream"; +import { Listener, Producer, Stream, Subscription } from "xstream"; + +import { hasProtocol, RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; + +function defaultErrorHandler(error: any): never { + throw error; +} + +function toJsonRpcResponse(message: SocketWrapperMessageEvent): JsonRpcResponse { + // this should never happen, but I want an alert if it does + if (message.type !== "message") { + throw new Error(`Unexcepted message type on websocket: ${message.type}`); + } + + const jsonRpcEvent = parseJsonRpcResponse(JSON.parse(message.data)); + return jsonRpcEvent; +} + +class RpcEventProducer implements Producer { + private readonly request: JsonRpcRequest; + private readonly socket: ReconnectingSocket; + + private running = false; + private subscriptions: Subscription[] = []; + + public constructor(request: JsonRpcRequest, socket: ReconnectingSocket) { + this.request = request; + this.socket = socket; + } + + /** + * Implementation of Producer.start + */ + public start(listener: Listener): void { + if (this.running) { + throw Error("Already started. Please stop first before restarting."); + } + this.running = true; + + this.connectToClient(listener); + + this.socket.queueRequest(JSON.stringify(this.request)); + } + + /** + * Implementation of Producer.stop + * + * Called by the stream when the stream's last listener stopped listening + * or when the producer completed. + */ + public stop(): void { + this.running = false; + // Tell the server we are done in order to save resources. We cannot wait for the result. + // This may fail when socket connection is not open, thus ignore errors in queueRequest + const endRequest: JsonRpcRequest = { ...this.request, method: "unsubscribe" }; + try { + this.socket.queueRequest(JSON.stringify(endRequest)); + } catch (error) { + if (error instanceof Error && error.message.match(/socket has disconnected/i)) { + // ignore + } else { + throw error; + } + } + } + + protected connectToClient(listener: Listener): void { + const responseStream = this.socket.events.map(toJsonRpcResponse); + + // this should unsubscribe itself, so doesn't need to be removed explicitly + const idSubscription = responseStream + .filter((response) => response.id === this.request.id) + .subscribe({ + next: (response) => { + if (isJsonRpcErrorResponse(response)) { + this.closeSubscriptions(); + listener.error(JSON.stringify(response.error)); + } + idSubscription.unsubscribe(); + }, + }); + + // this will fire on a response (success or error) + // Tendermint adds an "#event" suffix for events that follow a previous subscription + // https://github.com/tendermint/tendermint/blob/v0.23.0/rpc/core/events.go#L107 + const idEventSubscription = responseStream + .filter((response) => response.id === `${this.request.id}#event`) + .subscribe({ + next: (response) => { + if (isJsonRpcErrorResponse(response)) { + this.closeSubscriptions(); + listener.error(JSON.stringify(response.error)); + } else { + listener.next(response.result as SubscriptionEvent); + } + }, + }); + + // this will fire in case the websocket disconnects cleanly + const nonResponseSubscription = responseStream.subscribe({ + error: (error) => { + this.closeSubscriptions(); + listener.error(error); + }, + complete: () => { + this.closeSubscriptions(); + listener.complete(); + }, + }); + + this.subscriptions.push(idSubscription, idEventSubscription, nonResponseSubscription); + } + + protected closeSubscriptions(): void { + for (const subscription of this.subscriptions) { + subscription.unsubscribe(); + } + // clear unused subscriptions + this.subscriptions = []; + } +} + +export class WebsocketClient implements RpcStreamingClient { + private readonly url: string; + private readonly socket: ReconnectingSocket; + /** Same events as in socket.events but in the format we need */ + private readonly jsonRpcResponseStream: Stream; + + // Lazily create streams and use the same stream when listening to the same query twice. + // + // Creating streams is cheap since producer is not started as long as nobody listens to events. Thus this + // map is never cleared and there is no need to do so. But unsubscribe all the subscriptions! + private readonly subscriptionStreams = new Map>(); + + public constructor(baseUrl = "ws://localhost:46657", onError: (err: any) => void = defaultErrorHandler) { + // accept host.name:port and assume ws protocol + // make sure we don't end up with ...//websocket + const path = baseUrl.endsWith("/") ? "websocket" : "/websocket"; + const cleanBaseUrl = hasProtocol(baseUrl) ? baseUrl : "ws://" + baseUrl; + this.url = cleanBaseUrl + path; + + this.socket = new ReconnectingSocket(this.url); + + const errorSubscription = this.socket.events.subscribe({ + error: (error) => { + onError(error); + errorSubscription.unsubscribe(); + }, + }); + + this.jsonRpcResponseStream = this.socket.events.map(toJsonRpcResponse); + + this.socket.connect(); + } + + public async execute(request: JsonRpcRequest): Promise { + const pendingResponse = this.responseForRequestId(request.id); + this.socket.queueRequest(JSON.stringify(request)); + + const response = await pendingResponse; + if (isJsonRpcErrorResponse(response)) { + throw new Error(JSON.stringify(response.error)); + } + return response; + } + + public listen(request: JsonRpcRequest): Stream { + if (request.method !== "subscribe") { + throw new Error(`Request method must be "subscribe" to start event listening`); + } + + const query = (request.params as any).query; + if (typeof query !== "string") { + throw new Error("request.params.query must be a string"); + } + + if (!this.subscriptionStreams.has(query)) { + const producer = new RpcEventProducer(request, this.socket); + const stream = Stream.create(producer); + this.subscriptionStreams.set(query, stream); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.subscriptionStreams.get(query)!; + } + + /** + * Resolves as soon as websocket is connected. execute() queues requests automatically, + * so this should be required for testing purposes only. + */ + public async connected(): Promise { + await this.socket.connectionStatus.waitFor(ConnectionStatus.Connected); + } + + public disconnect(): void { + this.socket.disconnect(); + } + + protected async responseForRequestId(id: JsonRpcId): Promise { + return firstEvent(this.jsonRpcResponseStream.filter((r) => r.id === id)); + } +} diff --git a/packages/tendermint-rpc/src/types.ts b/packages/tendermint-rpc/src/types.ts new file mode 100644 index 0000000000..29c3364143 --- /dev/null +++ b/packages/tendermint-rpc/src/types.ts @@ -0,0 +1,41 @@ +// Types in this file are exported outside of the @iov/tendermint-rpc package, +// e.g. as part of a request or response + +import { As } from "type-tagger"; + +/** + * Merkle root + */ +export type BlockHash = Uint8Array & As<"block-hash">; + +/** Raw transaction bytes */ +export type TxBytes = Uint8Array & As<"tx-bytes">; + +/** + * A raw tendermint transaction hash, currently 20 bytes + */ +export type TxHash = Uint8Array & As<"tx-hash">; + +export type IpPortString = string & As<"ipport">; + +export interface ValidatorEd25519Pubkey { + readonly algorithm: "ed25519"; + readonly data: Uint8Array; +} + +/** + * Union type for different possible pubkeys. + * Currently only Ed25519 supported. + */ +export type ValidatorPubkey = ValidatorEd25519Pubkey; + +export interface ValidatorEd25519Signature { + readonly algorithm: "ed25519"; + readonly data: Uint8Array; +} + +/** + * Union type for different possible voting signatures. + * Currently only Ed25519 supported. + */ +export type ValidatorSignature = ValidatorEd25519Signature; diff --git a/packages/tendermint-rpc/src/v0-31/hasher.spec.ts b/packages/tendermint-rpc/src/v0-31/hasher.spec.ts new file mode 100644 index 0000000000..e9b024b001 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-31/hasher.spec.ts @@ -0,0 +1,98 @@ +import { fromBase64, fromHex } from "@iov/encoding"; +import { ReadonlyDate } from "readonly-date"; + +import { ReadonlyDateWithNanoseconds } from "../responses"; +import { TxBytes } from "../types"; +import { hashBlock, hashTx } from "./hasher"; + +describe("Hasher", () => { + it("creates transaction hash equal to local test", () => { + // This was taken from a result from /tx_search of some random test transaction + // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" + const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); + const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg==") as TxBytes; + expect(hashTx(txData)).toEqual(txId); + }); + + it("creates block hash equal to local test for empty block", () => { + // This was taken from a result from /block of some random empty block + // curl "http://localhost:11131/block" + const blockId = fromHex("5B5D3F7E77A4BD6CB6067947E478BC3BD493DD24A981535F0ADEBDAAA0498480"); + const time = new ReadonlyDate("2019-09-19T10:41:24.898178746Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 178746; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-RRlV24", + height: 2195, + time: time as ReadonlyDateWithNanoseconds, + numTxs: 0, + totalTxs: 20, + + lastBlockId: { + hash: fromHex("1D38C4FE5C1D8C3CC1F47602BF107C9B269BA7DA3514DEDF958F5A33AB75C06B"), + parts: { + total: 1, + hash: fromHex("C441341B7D846DDA6AF72F83DF68C9AF93665FE5280B136CA29C7411D280DAEC"), + }, + }, + + lastCommitHash: fromHex("0C5EEF7AE1275337BFAA173F57799AA90830E74AFF3FB03D1F579DA37BCAEAB1"), + dataHash: fromHex(""), + + validatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), + nextValidatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("2800000000000000"), + lastResultsHash: fromHex(""), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("057B8C349E591579EDFCC0E5D5402E3076E99675"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); + + it("creates block hash equal to local test for block with a transaction", () => { + // This was taken from a result from /block of some random block with a transaction + // curl "http://localhost:11131/block?height=5940" + const blockId = fromHex("1C4777AFBBA49E15D031A830E62E7BE986823938732B872C02B8A3D16BD3163B"); + const time = new ReadonlyDate("2019-09-24T10:51:28.240847497Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 847497; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-lY9FO6", + height: 5940, + time: time as ReadonlyDateWithNanoseconds, + numTxs: 1, + totalTxs: 61, + + lastBlockId: { + hash: fromHex("D2983E6AEEFC55E0A46565CD2274CCD21CB013F5602B0C35A423A99D1120DB13"), + parts: { + total: 1, + hash: fromHex("AA55D7F92AD3A9CFDA8C5E45F95B03AEF9FB38AB984FD762E5CE20791324369D"), + }, + }, + + lastCommitHash: fromHex("5DBFFDBE41878AEB947176D3E0B0DC70850B0A61F8B709ED132FEA59664DFCE5"), + dataHash: fromHex("90FE1A62418F68B411915EEF6792B134693D9D0148432BA661D91213B0CCD15A"), + + validatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), + nextValidatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("7800000000000000"), + lastResultsHash: fromHex("6E340B9CFFB37A989CA544E6BB780A2C78901D3FB33738768511A30617AFA01D"), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("6BCBB90987613FE15D3DEFA4920E9F98425698FF"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); +}); diff --git a/packages/tendermint-rpc/src/v0-31/hasher.ts b/packages/tendermint-rpc/src/v0-31/hasher.ts new file mode 100644 index 0000000000..ac8b36d023 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-31/hasher.ts @@ -0,0 +1,71 @@ +import { Sha256 } from "@iov/crypto"; + +import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; + +// hash is sha256 +// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 +export function hashTx(tx: TxBytes): TxHash { + const hash = new Sha256(tx).digest(); + return hash as TxHash; +} + +function getSplitPoint(n: number): number { + if (n < 1) throw new Error("Cannot split an empty tree"); + const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); + return largestPowerOf2 < n ? largestPowerOf2 : largestPowerOf2 / 2; +} + +function hashLeaf(leaf: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([0])); + hash.update(leaf); + return hash.digest(); +} + +function hashInner(left: Uint8Array, right: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([1])); + hash.update(left); + hash.update(right); + return hash.digest(); +} + +// See https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/encoding.md#merkleroot +// Note: the hashes input may not actually be hashes, especially before a recursive call +function hashTree(hashes: readonly Uint8Array[]): Uint8Array { + switch (hashes.length) { + case 0: + throw new Error("Cannot hash empty tree"); + case 1: + return hashLeaf(hashes[0]); + default: { + const slicePoint = getSplitPoint(hashes.length); + const left = hashTree(hashes.slice(0, slicePoint)); + const right = hashTree(hashes.slice(slicePoint)); + return hashInner(left, right); + } + } +} + +export function hashBlock(header: Header): BlockHash { + const encodedFields: readonly Uint8Array[] = [ + encodeVersion(header.version), + encodeString(header.chainId), + encodeInt(header.height), + encodeTime(header.time), + encodeInt(header.numTxs), + encodeInt(header.totalTxs), + encodeBlockId(header.lastBlockId), + + encodeBytes(header.lastCommitHash), + encodeBytes(header.dataHash), + encodeBytes(header.validatorsHash), + encodeBytes(header.nextValidatorsHash), + encodeBytes(header.consensusHash), + encodeBytes(header.appHash), + encodeBytes(header.lastResultsHash), + encodeBytes(header.evidenceHash), + encodeBytes(header.proposerAddress), + ]; + return hashTree(encodedFields) as BlockHash; +} diff --git a/packages/tendermint-rpc/src/v0-31/index.ts b/packages/tendermint-rpc/src/v0-31/index.ts new file mode 100644 index 0000000000..cdb29496e6 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-31/index.ts @@ -0,0 +1,12 @@ +import { Adaptor } from "../adaptor"; +import { hashBlock, hashTx } from "./hasher"; +import { Params } from "./requests"; +import { Responses } from "./responses"; + +// tslint:disable-next-line:variable-name +export const v0_31: Adaptor = { + params: Params, + responses: Responses, + hashTx: hashTx, + hashBlock: hashBlock, +}; diff --git a/packages/tendermint-rpc/src/v0-31/requests.ts b/packages/tendermint-rpc/src/v0-31/requests.ts new file mode 100644 index 0000000000..f91d442339 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-31/requests.ts @@ -0,0 +1,142 @@ +import { toHex } from "@iov/encoding"; +import { JsonRpcRequest } from "@iov/jsonrpc"; + +import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; +import { createJsonRpcRequest } from "../jsonrpc"; +import * as requests from "../requests"; + +interface HeightParam { + readonly height?: number; +} +interface RpcHeightParam { + readonly height?: IntegerString; +} +function encodeHeightParam(param: HeightParam): RpcHeightParam { + return { + height: may(Integer.encode, param.height), + }; +} + +interface RpcBlockchainRequestParams { + readonly minHeight?: IntegerString; + readonly maxHeight?: IntegerString; +} +function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { + return { + minHeight: may(Integer.encode, param.minHeight), + maxHeight: may(Integer.encode, param.maxHeight), + }; +} + +interface RpcAbciQueryParams { + readonly path: string; + readonly data: HexString; + readonly height?: string; + readonly prove?: boolean; +} + +function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { + return { + path: assertNotEmpty(params.path), + data: toHex(params.data) as HexString, + height: may(Integer.encode, params.height), + prove: params.prove, + }; +} + +interface RpcBroadcastTxParams { + readonly tx: Base64String; +} +function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { + return { + tx: Base64.encode(assertNotEmpty(params.tx)), + }; +} + +interface RpcTxParams { + readonly hash: Base64String; + readonly prove?: boolean; +} +function encodeTxParams(params: requests.TxParams): RpcTxParams { + return { + hash: Base64.encode(assertNotEmpty(params.hash)), + prove: params.prove, + }; +} + +interface RpcTxSearchParams { + readonly query: requests.QueryString; + readonly prove?: boolean; + readonly page?: IntegerString; + readonly per_page?: IntegerString; +} +function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { + return { + query: params.query, + prove: params.prove, + page: may(Integer.encode, params.page), + // eslint-disable-next-line @typescript-eslint/camelcase + per_page: may(Integer.encode, params.per_page), + }; +} + +export class Params { + public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); + } + + public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); + } + + public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); + } + + public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { + const eventTag = { key: "tm.event", value: req.query.type }; + const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); + return createJsonRpcRequest("subscribe", { query: query }); + } + + public static encodeTx(req: requests.TxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxParams(req.params)); + } + + // TODO: encode params for query string??? + public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); + } + + public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } +} diff --git a/packages/tendermint-rpc/src/v0-31/responses.ts b/packages/tendermint-rpc/src/v0-31/responses.ts new file mode 100644 index 0000000000..f136e39cc2 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-31/responses.ts @@ -0,0 +1,773 @@ +import { fromHex } from "@iov/encoding"; +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; + +import { + assertArray, + assertBoolean, + assertNotEmpty, + assertNumber, + assertObject, + assertSet, + Base64, + Base64String, + DateTime, + DateTimeString, + dictionaryToStringMap, + Hex, + HexString, + Integer, + IntegerString, + may, + optional, +} from "../encodings"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; +import { hashTx } from "./hasher"; + +interface AbciInfoResult { + readonly response: RpcAbciInfoResponse; +} + +interface RpcAbciInfoResponse { + readonly data?: string; + readonly last_block_height?: IntegerString; + readonly last_block_app_hash?: Base64String; +} + +function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { + return { + data: data.data, + lastBlockHeight: may(Integer.parse, data.last_block_height), + lastBlockAppHash: may(Base64.decode, data.last_block_app_hash), + }; +} + +interface AbciQueryResult { + readonly response: RpcAbciQueryResponse; +} + +interface RpcAbciQueryResponse { + readonly key: Base64String; + readonly value?: Base64String; + readonly proof?: Base64String; + readonly height?: IntegerString; + readonly index?: IntegerString; + readonly code?: IntegerString; // only for errors + readonly log?: string; +} + +function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { + return { + key: Base64.decode(optional(data.key, "" as Base64String)), + value: Base64.decode(optional(data.value, "" as Base64String)), + // proof: may(Base64.decode, data.proof), + height: may(Integer.parse, data.height), + code: may(Integer.parse, data.code), + index: may(Integer.parse, data.index), + log: data.log, + }; +} +interface RpcTag { + readonly key: Base64String; + readonly value: Base64String; +} + +function decodeTag(tag: RpcTag): responses.Tag { + return { + key: Base64.decode(assertNotEmpty(tag.key)), + value: Base64.decode(assertNotEmpty(tag.value)), + }; +} + +function decodeTags(tags: readonly RpcTag[]): readonly responses.Tag[] { + return assertArray(tags).map(decodeTag); +} + +interface RpcTxData { + readonly code?: number; + readonly log?: string; + readonly data?: Base64String; + readonly tags?: readonly RpcTag[]; +} + +function decodeTxData(data: RpcTxData): responses.TxData { + return { + data: may(Base64.decode, data.data), + log: data.log, + code: Integer.parse(assertNumber(optional(data.code, 0))), + tags: may(decodeTags, data.tags), + }; +} + +// yes, a different format for status and dump consensus state +interface RpcPubkey { + readonly type: string; + readonly value: Base64String; +} + +function decodePubkey(data: RpcPubkey): ValidatorPubkey { + if (data.type === "tendermint/PubKeyEd25519") { + // go-amino special code + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data.value)), + }; + } + throw new Error(`unknown pubkey type: ${data.type}`); +} + +// for evidence, block results, etc. +interface RpcValidatorUpdate { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: Hex.decode(assertNotEmpty(data.address)), + }; +} + +interface RpcBlockParams { + readonly max_bytes: IntegerString; + readonly max_gas: IntegerString; +} + +/** + * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry + * + * > Add time_iota_ms to block's consensus parameters (not exposed to the application) + * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 + */ +function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { + return { + maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), + maxGas: Integer.parse(assertNotEmpty(data.max_gas)), + }; +} + +interface RpcEvidenceParams { + readonly max_age: IntegerString; +} + +function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { + return { + maxAge: Integer.parse(assertNotEmpty(data.max_age)), + }; +} + +/** + * Example data: + * { + * "block": { + * "max_bytes": "22020096", + * "max_gas": "-1", + * "time_iota_ms": "1000" + * }, + * "evidence": { + * "max_age": "100000" + * }, + * "validator": { + * "pub_key_types": [ + * "ed25519" + * ] + * } + * } + */ +interface RpcConsensusParams { + readonly block: RpcBlockParams; + readonly evidence: RpcEvidenceParams; +} + +function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { + return { + block: decodeBlockParams(assertObject(data.block)), + evidence: decodeEvidenceParams(assertObject(data.evidence)), + }; +} + +interface RpcBlockResultsResponse { + readonly height: IntegerString; + readonly results: { + readonly DeliverTx: readonly RpcTxData[]; + readonly EndBlock: { + readonly validator_updates?: readonly RpcValidatorUpdate[]; + readonly consensus_param_updates?: RpcConsensusParams; + readonly tags?: readonly RpcTag[]; + }; + }; +} + +function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { + const res = optional(data.results.DeliverTx, [] as readonly RpcTxData[]); + const end = data.results.EndBlock; + const validators = optional(end.validator_updates, [] as readonly RpcValidatorUpdate[]); + return { + height: Integer.parse(assertNotEmpty(data.height)), + results: assertArray(res).map(decodeTxData), + endBlock: { + validatorUpdates: assertArray(validators).map(decodeValidatorUpdate), + consensusUpdates: may(decodeConsensusParams, end.consensus_param_updates), + tags: may(decodeTags, end.tags), + }, + }; +} + +interface RpcBlockId { + readonly hash: HexString; + readonly parts: { + readonly total: IntegerString; + readonly hash: HexString; + }; +} + +function decodeBlockId(data: RpcBlockId): responses.BlockId { + return { + hash: fromHex(assertNotEmpty(data.hash)), + parts: { + total: Integer.parse(assertNotEmpty(data.parts.total)), + hash: fromHex(assertNotEmpty(data.parts.hash)), + }, + }; +} + +interface RpcBlockVersion { + readonly block: IntegerString; + readonly app: IntegerString; +} + +function decodeBlockVersion(data: RpcBlockVersion): responses.Version { + return { + block: Integer.parse(data.block), + app: Integer.parse(data.app), + }; +} + +interface RpcHeader { + readonly version: RpcBlockVersion; + readonly chain_id: string; + readonly height: IntegerString; + readonly time: DateTimeString; + readonly num_txs: IntegerString; + readonly total_txs: IntegerString; + + readonly last_block_id: RpcBlockId; + + readonly last_commit_hash: HexString; + readonly data_hash: HexString; + + readonly validators_hash: HexString; + readonly next_validators_hash: HexString; + readonly consensus_hash: HexString; + readonly app_hash: HexString; + readonly last_results_hash: HexString; + + readonly evidence_hash: HexString; + readonly proposer_address: HexString; +} + +function decodeHeader(data: RpcHeader): responses.Header { + return { + version: decodeBlockVersion(data.version), + chainId: assertNotEmpty(data.chain_id), + height: Integer.parse(assertNotEmpty(data.height)), + time: DateTime.decode(assertNotEmpty(data.time)), + numTxs: Integer.parse(assertNotEmpty(data.num_txs)), + totalTxs: Integer.parse(assertNotEmpty(data.total_txs)), + + lastBlockId: decodeBlockId(data.last_block_id), + + lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), + dataHash: fromHex(assertSet(data.data_hash)), + + validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), + nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), + consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), + appHash: fromHex(assertNotEmpty(data.app_hash)), + lastResultsHash: fromHex(assertSet(data.last_results_hash)), + + evidenceHash: fromHex(assertSet(data.evidence_hash)), + proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), + }; +} + +interface RpcBlockMeta { + readonly block_id: RpcBlockId; + readonly header: RpcHeader; +} + +function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { + return { + blockId: decodeBlockId(data.block_id), + header: decodeHeader(data.header), + }; +} + +interface RpcBlockchainResponse { + readonly last_height: IntegerString; + readonly block_metas: readonly RpcBlockMeta[]; +} + +function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { + return { + lastHeight: Integer.parse(assertNotEmpty(data.last_height)), + blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), + }; +} + +interface RpcBroadcastTxSyncResponse extends RpcTxData { + readonly hash: HexString; +} + +function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { + return { + ...decodeTxData(data), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + }; +} + +interface RpcBroadcastTxCommitResponse { + readonly height?: IntegerString; + readonly hash: HexString; + readonly check_tx: RpcTxData; + readonly deliver_tx?: RpcTxData; +} + +function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { + return { + height: may(Integer.parse, data.height), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + checkTx: decodeTxData(assertObject(data.check_tx)), + deliverTx: may(decodeTxData, data.deliver_tx), + }; +} + +type RpcSignature = Base64String; + +function decodeSignature(data: RpcSignature): ValidatorSignature { + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data)), + }; +} + +interface RpcVote { + readonly type: number; + readonly validator_address: HexString; + readonly validator_index: IntegerString; + readonly height: IntegerString; + readonly round: IntegerString; + readonly timestamp: DateTimeString; + readonly block_id: RpcBlockId; + readonly signature: RpcSignature; +} + +function decodeVote(data: RpcVote): responses.Vote { + return { + type: Integer.parse(assertNumber(data.type)), + validatorAddress: fromHex(assertNotEmpty(data.validator_address)), + validatorIndex: Integer.parse(assertNotEmpty(data.validator_index)), + height: Integer.parse(assertNotEmpty(data.height)), + round: Integer.parse(assertNotEmpty(data.round)), + timestamp: DateTime.decode(assertNotEmpty(data.timestamp)), + blockId: decodeBlockId(assertObject(data.block_id)), + signature: decodeSignature(assertNotEmpty(data.signature)), + }; +} + +interface RpcCommit { + readonly block_id: RpcBlockId; + readonly precommits: readonly RpcVote[]; +} + +function decodeCommit(data: RpcCommit): responses.Commit { + return { + blockId: decodeBlockId(assertObject(data.block_id)), + precommits: assertArray(data.precommits).map(decodeVote), + }; +} + +interface RpcCommitResponse { + readonly signed_header: { + readonly header: RpcHeader; + readonly commit: RpcCommit; + }; + readonly canonical: boolean; +} + +function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { + return { + canonical: assertBoolean(data.canonical), + header: decodeHeader(data.signed_header.header), + commit: decodeCommit(data.signed_header.commit), + }; +} + +interface RpcValidatorGenesis { + readonly pub_key: RpcPubkey; + readonly power: IntegerString; + readonly name?: string; +} + +function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.power)), + name: data.name, + }; +} + +interface RpcGenesisResponse { + readonly genesis_time: DateTimeString; + readonly chain_id: string; + readonly consensus_params: RpcConsensusParams; + readonly validators: readonly RpcValidatorGenesis[]; + readonly app_hash: HexString; + readonly app_state: {} | undefined; +} + +interface GenesisResult { + readonly genesis: RpcGenesisResponse; +} + +function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { + return { + genesisTime: DateTime.decode(assertNotEmpty(data.genesis_time)), + chainId: assertNotEmpty(data.chain_id), + consensusParams: decodeConsensusParams(data.consensus_params), + validators: assertArray(data.validators).map(decodeValidatorGenesis), + appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app + appState: data.app_state, + }; +} + +// this is in status +interface RpcValidatorInfo { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: fromHex(assertNotEmpty(data.address)), + }; +} + +interface RpcNodeInfo { + readonly id: HexString; + readonly listen_addr: IpPortString; + readonly network: string; + readonly version: string; + readonly channels: string; // ??? + readonly moniker: string; + readonly protocol_version: { + readonly p2p: IntegerString; + readonly block: IntegerString; + readonly app: IntegerString; + }; + /** + * Additional information. E.g. + * { + * "tx_index": "on", + * "rpc_address":"tcp://0.0.0.0:26657" + * } + */ + readonly other: object; +} + +function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { + return { + id: fromHex(assertNotEmpty(data.id)), + listenAddr: assertNotEmpty(data.listen_addr), + network: assertNotEmpty(data.network), + version: assertNotEmpty(data.version), + channels: assertNotEmpty(data.channels), + moniker: assertNotEmpty(data.moniker), + other: dictionaryToStringMap(data.other), + protocolVersion: { + app: Integer.parse(assertNotEmpty(data.protocol_version.app)), + block: Integer.parse(assertNotEmpty(data.protocol_version.block)), + p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), + }, + }; +} + +interface RpcSyncInfo { + readonly latest_block_hash: HexString; + readonly latest_app_hash: HexString; + readonly latest_block_height: IntegerString; + readonly latest_block_time: DateTimeString; + readonly catching_up: boolean; +} + +function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { + return { + latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), + latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), + latestBlockTime: DateTime.decode(assertNotEmpty(data.latest_block_time)), + latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), + catchingUp: assertBoolean(data.catching_up), + }; +} + +interface RpcStatusResponse { + readonly node_info: RpcNodeInfo; + readonly sync_info: RpcSyncInfo; + readonly validator_info: RpcValidatorInfo; +} + +function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { + return { + nodeInfo: decodeNodeInfo(data.node_info), + syncInfo: decodeSyncInfo(data.sync_info), + validatorInfo: decodeValidatorInfo(data.validator_info), + }; +} + +/** + * Example data: + * { + * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", + * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", + * "Proof": { + * "total": "1", + * "index": "0", + * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", + * "aunts": [] + * } + * } + */ +interface RpcTxProof { + readonly Data: Base64String; + readonly RootHash: HexString; + readonly Proof: { + readonly total: IntegerString; + readonly index: IntegerString; + readonly leaf_hash: Base64String; + readonly aunts: readonly Base64String[]; + }; +} + +function decodeTxProof(data: RpcTxProof): responses.TxProof { + return { + data: Base64.decode(assertNotEmpty(data.Data)), + rootHash: fromHex(assertNotEmpty(data.RootHash)), + proof: { + total: Integer.parse(assertNotEmpty(data.Proof.total)), + index: Integer.parse(assertNotEmpty(data.Proof.index)), + leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), + aunts: assertArray(data.Proof.aunts).map(Base64.decode), + }, + }; +} + +interface RpcTxResponse { + readonly tx: Base64String; + readonly tx_result: RpcTxData; + readonly height: IntegerString; + readonly index: number; + readonly hash: HexString; + readonly proof?: RpcTxProof; +} + +function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { + return { + tx: Base64.decode(assertNotEmpty(data.tx)) as TxBytes, + result: decodeTxData(assertObject(data.tx_result)), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + proof: may(decodeTxProof, data.proof), + }; +} + +interface RpcTxSearchResponse { + readonly txs: readonly RpcTxResponse[]; + readonly total_count: IntegerString; +} + +function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { + return { + totalCount: Integer.parse(assertNotEmpty(data.total_count)), + txs: assertArray(data.txs).map(decodeTxResponse), + }; +} + +interface RpcTxEvent { + readonly tx: Base64String; + readonly result: RpcTxData; + readonly height: IntegerString; + readonly index: number; +} + +function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { + const tx = Base64.decode(assertNotEmpty(data.tx)) as TxBytes; + return { + tx: tx, + hash: hashTx(tx), + result: decodeTxData(data.result), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + }; +} + +// for validators +interface RpcValidatorData extends RpcValidatorUpdate { + readonly accum?: IntegerString; +} + +function decodeValidatorData(data: RpcValidatorData): responses.Validator { + return { + ...decodeValidatorUpdate(data), + accum: may(Integer.parse, data.accum), + }; +} + +interface RpcValidatorsResponse { + readonly block_height: IntegerString; + readonly validators: readonly RpcValidatorData[]; +} + +function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { + return { + blockHeight: Integer.parse(assertNotEmpty(data.block_height)), + results: assertArray(data.validators).map(decodeValidatorData), + }; +} + +interface RpcEvidence { + readonly type: string; + readonly validator: RpcValidatorUpdate; + readonly height: IntegerString; + readonly time: IntegerString; + readonly totalVotingPower: IntegerString; +} + +function decodeEvidence(data: RpcEvidence): responses.Evidence { + return { + type: assertNotEmpty(data.type), + height: Integer.parse(assertNotEmpty(data.height)), + time: Integer.parse(assertNotEmpty(data.time)), + totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), + validator: decodeValidatorUpdate(data.validator), + }; +} + +function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { + return assertArray(ev).map(decodeEvidence); +} + +interface RpcBlock { + readonly header: RpcHeader; + readonly last_commit: RpcCommit; + readonly data: { + readonly txs?: readonly Base64String[]; + }; + readonly evidence?: { + readonly evidence?: readonly RpcEvidence[]; + }; +} + +function decodeBlock(data: RpcBlock): responses.Block { + return { + header: decodeHeader(assertObject(data.header)), + lastCommit: decodeCommit(assertObject(data.last_commit)), + txs: data.data.txs ? assertArray(data.data.txs).map(Base64.decode) : [], + evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), + }; +} + +interface RpcBlockResponse { + readonly block_meta: RpcBlockMeta; + readonly block: RpcBlock; +} + +function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { + return { + blockMeta: decodeBlockMeta(data.block_meta), + block: decodeBlock(data.block), + }; +} + +export class Responses { + public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { + return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); + } + + public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { + return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); + } + + public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { + return decodeBlockResponse(response.result as RpcBlockResponse); + } + + public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { + return decodeBlockResults(response.result as RpcBlockResultsResponse); + } + + public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { + return decodeBlockchain(response.result as RpcBlockchainResponse); + } + + public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { + return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); + } + + public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { + return this.decodeBroadcastTxSync(response); + } + + public static decodeBroadcastTxCommit( + response: JsonRpcSuccessResponse, + ): responses.BroadcastTxCommitResponse { + return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); + } + + public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { + return decodeCommitResponse(response.result as RpcCommitResponse); + } + + public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { + return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); + } + + public static decodeHealth(): responses.HealthResponse { + return null; + } + + public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { + return decodeStatus(response.result as RpcStatusResponse); + } + + public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { + return decodeBlock(event.data.value.block as RpcBlock); + } + + public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { + return decodeHeader(event.data.value.header as RpcHeader); + } + + public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { + return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); + } + + public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { + return decodeTxResponse(response.result as RpcTxResponse); + } + + public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { + return decodeTxSearch(response.result as RpcTxSearchResponse); + } + + public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { + return decodeValidators(response.result as RpcValidatorsResponse); + } +} diff --git a/packages/tendermint-rpc/src/v0-32/hasher.spec.ts b/packages/tendermint-rpc/src/v0-32/hasher.spec.ts new file mode 100644 index 0000000000..e9b024b001 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-32/hasher.spec.ts @@ -0,0 +1,98 @@ +import { fromBase64, fromHex } from "@iov/encoding"; +import { ReadonlyDate } from "readonly-date"; + +import { ReadonlyDateWithNanoseconds } from "../responses"; +import { TxBytes } from "../types"; +import { hashBlock, hashTx } from "./hasher"; + +describe("Hasher", () => { + it("creates transaction hash equal to local test", () => { + // This was taken from a result from /tx_search of some random test transaction + // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" + const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); + const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg==") as TxBytes; + expect(hashTx(txData)).toEqual(txId); + }); + + it("creates block hash equal to local test for empty block", () => { + // This was taken from a result from /block of some random empty block + // curl "http://localhost:11131/block" + const blockId = fromHex("5B5D3F7E77A4BD6CB6067947E478BC3BD493DD24A981535F0ADEBDAAA0498480"); + const time = new ReadonlyDate("2019-09-19T10:41:24.898178746Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 178746; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-RRlV24", + height: 2195, + time: time as ReadonlyDateWithNanoseconds, + numTxs: 0, + totalTxs: 20, + + lastBlockId: { + hash: fromHex("1D38C4FE5C1D8C3CC1F47602BF107C9B269BA7DA3514DEDF958F5A33AB75C06B"), + parts: { + total: 1, + hash: fromHex("C441341B7D846DDA6AF72F83DF68C9AF93665FE5280B136CA29C7411D280DAEC"), + }, + }, + + lastCommitHash: fromHex("0C5EEF7AE1275337BFAA173F57799AA90830E74AFF3FB03D1F579DA37BCAEAB1"), + dataHash: fromHex(""), + + validatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), + nextValidatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("2800000000000000"), + lastResultsHash: fromHex(""), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("057B8C349E591579EDFCC0E5D5402E3076E99675"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); + + it("creates block hash equal to local test for block with a transaction", () => { + // This was taken from a result from /block of some random block with a transaction + // curl "http://localhost:11131/block?height=5940" + const blockId = fromHex("1C4777AFBBA49E15D031A830E62E7BE986823938732B872C02B8A3D16BD3163B"); + const time = new ReadonlyDate("2019-09-24T10:51:28.240847497Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 847497; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-lY9FO6", + height: 5940, + time: time as ReadonlyDateWithNanoseconds, + numTxs: 1, + totalTxs: 61, + + lastBlockId: { + hash: fromHex("D2983E6AEEFC55E0A46565CD2274CCD21CB013F5602B0C35A423A99D1120DB13"), + parts: { + total: 1, + hash: fromHex("AA55D7F92AD3A9CFDA8C5E45F95B03AEF9FB38AB984FD762E5CE20791324369D"), + }, + }, + + lastCommitHash: fromHex("5DBFFDBE41878AEB947176D3E0B0DC70850B0A61F8B709ED132FEA59664DFCE5"), + dataHash: fromHex("90FE1A62418F68B411915EEF6792B134693D9D0148432BA661D91213B0CCD15A"), + + validatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), + nextValidatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("7800000000000000"), + lastResultsHash: fromHex("6E340B9CFFB37A989CA544E6BB780A2C78901D3FB33738768511A30617AFA01D"), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("6BCBB90987613FE15D3DEFA4920E9F98425698FF"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); +}); diff --git a/packages/tendermint-rpc/src/v0-32/hasher.ts b/packages/tendermint-rpc/src/v0-32/hasher.ts new file mode 100644 index 0000000000..ac8b36d023 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-32/hasher.ts @@ -0,0 +1,71 @@ +import { Sha256 } from "@iov/crypto"; + +import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; + +// hash is sha256 +// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 +export function hashTx(tx: TxBytes): TxHash { + const hash = new Sha256(tx).digest(); + return hash as TxHash; +} + +function getSplitPoint(n: number): number { + if (n < 1) throw new Error("Cannot split an empty tree"); + const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); + return largestPowerOf2 < n ? largestPowerOf2 : largestPowerOf2 / 2; +} + +function hashLeaf(leaf: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([0])); + hash.update(leaf); + return hash.digest(); +} + +function hashInner(left: Uint8Array, right: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([1])); + hash.update(left); + hash.update(right); + return hash.digest(); +} + +// See https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/encoding.md#merkleroot +// Note: the hashes input may not actually be hashes, especially before a recursive call +function hashTree(hashes: readonly Uint8Array[]): Uint8Array { + switch (hashes.length) { + case 0: + throw new Error("Cannot hash empty tree"); + case 1: + return hashLeaf(hashes[0]); + default: { + const slicePoint = getSplitPoint(hashes.length); + const left = hashTree(hashes.slice(0, slicePoint)); + const right = hashTree(hashes.slice(slicePoint)); + return hashInner(left, right); + } + } +} + +export function hashBlock(header: Header): BlockHash { + const encodedFields: readonly Uint8Array[] = [ + encodeVersion(header.version), + encodeString(header.chainId), + encodeInt(header.height), + encodeTime(header.time), + encodeInt(header.numTxs), + encodeInt(header.totalTxs), + encodeBlockId(header.lastBlockId), + + encodeBytes(header.lastCommitHash), + encodeBytes(header.dataHash), + encodeBytes(header.validatorsHash), + encodeBytes(header.nextValidatorsHash), + encodeBytes(header.consensusHash), + encodeBytes(header.appHash), + encodeBytes(header.lastResultsHash), + encodeBytes(header.evidenceHash), + encodeBytes(header.proposerAddress), + ]; + return hashTree(encodedFields) as BlockHash; +} diff --git a/packages/tendermint-rpc/src/v0-32/index.ts b/packages/tendermint-rpc/src/v0-32/index.ts new file mode 100644 index 0000000000..184887c076 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-32/index.ts @@ -0,0 +1,12 @@ +import { Adaptor } from "../adaptor"; +import { hashBlock, hashTx } from "./hasher"; +import { Params } from "./requests"; +import { Responses } from "./responses"; + +// tslint:disable-next-line:variable-name +export const v0_32: Adaptor = { + params: Params, + responses: Responses, + hashTx: hashTx, + hashBlock: hashBlock, +}; diff --git a/packages/tendermint-rpc/src/v0-32/requests.ts b/packages/tendermint-rpc/src/v0-32/requests.ts new file mode 100644 index 0000000000..f91d442339 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-32/requests.ts @@ -0,0 +1,142 @@ +import { toHex } from "@iov/encoding"; +import { JsonRpcRequest } from "@iov/jsonrpc"; + +import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; +import { createJsonRpcRequest } from "../jsonrpc"; +import * as requests from "../requests"; + +interface HeightParam { + readonly height?: number; +} +interface RpcHeightParam { + readonly height?: IntegerString; +} +function encodeHeightParam(param: HeightParam): RpcHeightParam { + return { + height: may(Integer.encode, param.height), + }; +} + +interface RpcBlockchainRequestParams { + readonly minHeight?: IntegerString; + readonly maxHeight?: IntegerString; +} +function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { + return { + minHeight: may(Integer.encode, param.minHeight), + maxHeight: may(Integer.encode, param.maxHeight), + }; +} + +interface RpcAbciQueryParams { + readonly path: string; + readonly data: HexString; + readonly height?: string; + readonly prove?: boolean; +} + +function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { + return { + path: assertNotEmpty(params.path), + data: toHex(params.data) as HexString, + height: may(Integer.encode, params.height), + prove: params.prove, + }; +} + +interface RpcBroadcastTxParams { + readonly tx: Base64String; +} +function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { + return { + tx: Base64.encode(assertNotEmpty(params.tx)), + }; +} + +interface RpcTxParams { + readonly hash: Base64String; + readonly prove?: boolean; +} +function encodeTxParams(params: requests.TxParams): RpcTxParams { + return { + hash: Base64.encode(assertNotEmpty(params.hash)), + prove: params.prove, + }; +} + +interface RpcTxSearchParams { + readonly query: requests.QueryString; + readonly prove?: boolean; + readonly page?: IntegerString; + readonly per_page?: IntegerString; +} +function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { + return { + query: params.query, + prove: params.prove, + page: may(Integer.encode, params.page), + // eslint-disable-next-line @typescript-eslint/camelcase + per_page: may(Integer.encode, params.per_page), + }; +} + +export class Params { + public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); + } + + public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); + } + + public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); + } + + public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { + const eventTag = { key: "tm.event", value: req.query.type }; + const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); + return createJsonRpcRequest("subscribe", { query: query }); + } + + public static encodeTx(req: requests.TxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxParams(req.params)); + } + + // TODO: encode params for query string??? + public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); + } + + public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } +} diff --git a/packages/tendermint-rpc/src/v0-32/responses.ts b/packages/tendermint-rpc/src/v0-32/responses.ts new file mode 100644 index 0000000000..6f6cc23972 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-32/responses.ts @@ -0,0 +1,790 @@ +import { fromHex } from "@iov/encoding"; +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; + +import { + assertArray, + assertBoolean, + assertNotEmpty, + assertNumber, + assertObject, + assertSet, + Base64, + Base64String, + DateTime, + DateTimeString, + dictionaryToStringMap, + Hex, + HexString, + Integer, + IntegerString, + may, + optional, +} from "../encodings"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; +import { hashTx } from "./hasher"; + +interface AbciInfoResult { + readonly response: RpcAbciInfoResponse; +} + +interface RpcAbciInfoResponse { + readonly data?: string; + readonly last_block_height?: IntegerString; + readonly last_block_app_hash?: Base64String; +} + +function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { + return { + data: data.data, + lastBlockHeight: may(Integer.parse, data.last_block_height), + lastBlockAppHash: may(Base64.decode, data.last_block_app_hash), + }; +} + +interface AbciQueryResult { + readonly response: RpcAbciQueryResponse; +} + +interface RpcAbciQueryResponse { + readonly key: Base64String; + readonly value?: Base64String; + readonly proof?: Base64String; + readonly height?: IntegerString; + readonly index?: IntegerString; + readonly code?: IntegerString; // only for errors + readonly log?: string; +} + +function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { + return { + key: Base64.decode(optional(data.key, "" as Base64String)), + value: Base64.decode(optional(data.value, "" as Base64String)), + // proof: may(Base64.decode, data.proof), + height: may(Integer.parse, data.height), + code: may(Integer.parse, data.code), + index: may(Integer.parse, data.index), + log: data.log, + }; +} + +interface RpcTag { + readonly key: Base64String; + readonly value: Base64String; +} + +function decodeTag(tag: RpcTag): responses.Tag { + return { + key: Base64.decode(assertNotEmpty(tag.key)), + value: Base64.decode(assertNotEmpty(tag.value)), + }; +} + +function decodeTags(tags: readonly RpcTag[]): readonly responses.Tag[] { + return assertArray(tags).map(decodeTag); +} + +interface RpcEvent { + readonly type: string; + readonly attributes: readonly RpcTag[]; +} + +function decodeEvent(event: RpcEvent): responses.Event { + return { + type: event.type, + attributes: decodeTags(event.attributes), + }; +} + +function decodeEvents(events: readonly RpcEvent[]): readonly responses.Event[] { + return assertArray(events).map(decodeEvent); +} + +interface RpcTxData { + readonly code?: number; + readonly log?: string; + readonly data?: Base64String; + readonly events?: readonly RpcEvent[]; +} + +function decodeTxData(data: RpcTxData): responses.TxData { + return { + data: may(Base64.decode, data.data), + log: data.log, + code: Integer.parse(assertNumber(optional(data.code, 0))), + events: may(decodeEvents, data.events), + }; +} + +// yes, a different format for status and dump consensus state +interface RpcPubkey { + readonly type: string; + readonly value: Base64String; +} + +function decodePubkey(data: RpcPubkey): ValidatorPubkey { + if (data.type === "tendermint/PubKeyEd25519") { + // go-amino special code + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data.value)), + }; + } + throw new Error(`unknown pubkey type: ${data.type}`); +} + +// for evidence, block results, etc. +interface RpcValidatorUpdate { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: Hex.decode(assertNotEmpty(data.address)), + }; +} + +interface RpcBlockParams { + readonly max_bytes: IntegerString; + readonly max_gas: IntegerString; +} + +/** + * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry + * + * > Add time_iota_ms to block's consensus parameters (not exposed to the application) + * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 + */ +function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { + return { + maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), + maxGas: Integer.parse(assertNotEmpty(data.max_gas)), + }; +} + +interface RpcEvidenceParams { + readonly max_age: IntegerString; +} + +function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { + return { + maxAge: Integer.parse(assertNotEmpty(data.max_age)), + }; +} + +/** + * Example data: + * { + * "block": { + * "max_bytes": "22020096", + * "max_gas": "-1", + * "time_iota_ms": "1000" + * }, + * "evidence": { + * "max_age": "100000" + * }, + * "validator": { + * "pub_key_types": [ + * "ed25519" + * ] + * } + * } + */ +interface RpcConsensusParams { + readonly block: RpcBlockParams; + readonly evidence: RpcEvidenceParams; +} + +function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { + return { + block: decodeBlockParams(assertObject(data.block)), + evidence: decodeEvidenceParams(assertObject(data.evidence)), + }; +} + +interface RpcBlockResultsResponse { + readonly height: IntegerString; + readonly results: { + readonly deliver_tx: readonly RpcTxData[]; + readonly end_block: { + readonly validator_updates?: readonly RpcValidatorUpdate[]; + readonly consensus_param_updates?: RpcConsensusParams; + readonly tags?: readonly RpcTag[]; + }; + }; +} + +function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { + const res = optional(data.results.deliver_tx, [] as readonly RpcTxData[]); + const end = data.results.end_block; + const validators = optional(end.validator_updates, [] as readonly RpcValidatorUpdate[]); + return { + height: Integer.parse(assertNotEmpty(data.height)), + results: assertArray(res).map(decodeTxData), + endBlock: { + validatorUpdates: assertArray(validators).map(decodeValidatorUpdate), + consensusUpdates: may(decodeConsensusParams, end.consensus_param_updates), + tags: may(decodeTags, end.tags), + }, + }; +} + +interface RpcBlockId { + readonly hash: HexString; + readonly parts: { + readonly total: IntegerString; + readonly hash: HexString; + }; +} + +function decodeBlockId(data: RpcBlockId): responses.BlockId { + return { + hash: fromHex(assertNotEmpty(data.hash)), + parts: { + total: Integer.parse(assertNotEmpty(data.parts.total)), + hash: fromHex(assertNotEmpty(data.parts.hash)), + }, + }; +} + +interface RpcBlockVersion { + readonly block: IntegerString; + readonly app: IntegerString; +} + +function decodeBlockVersion(data: RpcBlockVersion): responses.Version { + return { + block: Integer.parse(data.block), + app: Integer.parse(data.app), + }; +} + +interface RpcHeader { + readonly version: RpcBlockVersion; + readonly chain_id: string; + readonly height: IntegerString; + readonly time: DateTimeString; + readonly num_txs: IntegerString; + readonly total_txs: IntegerString; + + readonly last_block_id: RpcBlockId; + + readonly last_commit_hash: HexString; + readonly data_hash: HexString; + + readonly validators_hash: HexString; + readonly next_validators_hash: HexString; + readonly consensus_hash: HexString; + readonly app_hash: HexString; + readonly last_results_hash: HexString; + + readonly evidence_hash: HexString; + readonly proposer_address: HexString; +} + +function decodeHeader(data: RpcHeader): responses.Header { + return { + version: decodeBlockVersion(data.version), + chainId: assertNotEmpty(data.chain_id), + height: Integer.parse(assertNotEmpty(data.height)), + time: DateTime.decode(assertNotEmpty(data.time)), + numTxs: Integer.parse(assertNotEmpty(data.num_txs)), + totalTxs: Integer.parse(assertNotEmpty(data.total_txs)), + + lastBlockId: decodeBlockId(data.last_block_id), + + lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), + dataHash: fromHex(assertSet(data.data_hash)), + + validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), + nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), + consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), + appHash: fromHex(assertNotEmpty(data.app_hash)), + lastResultsHash: fromHex(assertSet(data.last_results_hash)), + + evidenceHash: fromHex(assertSet(data.evidence_hash)), + proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), + }; +} + +interface RpcBlockMeta { + readonly block_id: RpcBlockId; + readonly header: RpcHeader; +} + +function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { + return { + blockId: decodeBlockId(data.block_id), + header: decodeHeader(data.header), + }; +} + +interface RpcBlockchainResponse { + readonly last_height: IntegerString; + readonly block_metas: readonly RpcBlockMeta[]; +} + +function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { + return { + lastHeight: Integer.parse(assertNotEmpty(data.last_height)), + blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), + }; +} + +interface RpcBroadcastTxSyncResponse extends RpcTxData { + readonly hash: HexString; +} + +function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { + return { + ...decodeTxData(data), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + }; +} + +interface RpcBroadcastTxCommitResponse { + readonly height?: IntegerString; + readonly hash: HexString; + readonly check_tx: RpcTxData; + readonly deliver_tx?: RpcTxData; +} + +function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { + return { + height: may(Integer.parse, data.height), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + checkTx: decodeTxData(assertObject(data.check_tx)), + deliverTx: may(decodeTxData, data.deliver_tx), + }; +} + +type RpcSignature = Base64String; + +function decodeSignature(data: RpcSignature): ValidatorSignature { + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data)), + }; +} + +interface RpcVote { + readonly type: number; + readonly validator_address: HexString; + readonly validator_index: IntegerString; + readonly height: IntegerString; + readonly round: IntegerString; + readonly timestamp: DateTimeString; + readonly block_id: RpcBlockId; + readonly signature: RpcSignature; +} + +function decodeVote(data: RpcVote): responses.Vote { + return { + type: Integer.parse(assertNumber(data.type)), + validatorAddress: fromHex(assertNotEmpty(data.validator_address)), + validatorIndex: Integer.parse(assertNotEmpty(data.validator_index)), + height: Integer.parse(assertNotEmpty(data.height)), + round: Integer.parse(assertNotEmpty(data.round)), + timestamp: DateTime.decode(assertNotEmpty(data.timestamp)), + blockId: decodeBlockId(assertObject(data.block_id)), + signature: decodeSignature(assertNotEmpty(data.signature)), + }; +} + +interface RpcCommit { + readonly block_id: RpcBlockId; + readonly precommits: readonly RpcVote[]; +} + +function decodeCommit(data: RpcCommit): responses.Commit { + return { + blockId: decodeBlockId(assertObject(data.block_id)), + precommits: assertArray(data.precommits).map(decodeVote), + }; +} + +interface RpcCommitResponse { + readonly signed_header: { + readonly header: RpcHeader; + readonly commit: RpcCommit; + }; + readonly canonical: boolean; +} + +function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { + return { + canonical: assertBoolean(data.canonical), + header: decodeHeader(data.signed_header.header), + commit: decodeCommit(data.signed_header.commit), + }; +} + +interface RpcValidatorGenesis { + readonly pub_key: RpcPubkey; + readonly power: IntegerString; + readonly name?: string; +} + +function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.power)), + name: data.name, + }; +} + +interface RpcGenesisResponse { + readonly genesis_time: DateTimeString; + readonly chain_id: string; + readonly consensus_params: RpcConsensusParams; + readonly validators: readonly RpcValidatorGenesis[]; + readonly app_hash: HexString; + readonly app_state: {} | undefined; +} + +interface GenesisResult { + readonly genesis: RpcGenesisResponse; +} + +function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { + return { + genesisTime: DateTime.decode(assertNotEmpty(data.genesis_time)), + chainId: assertNotEmpty(data.chain_id), + consensusParams: decodeConsensusParams(data.consensus_params), + validators: assertArray(data.validators).map(decodeValidatorGenesis), + appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app + appState: data.app_state, + }; +} + +// this is in status +interface RpcValidatorInfo { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: fromHex(assertNotEmpty(data.address)), + }; +} + +interface RpcNodeInfo { + readonly id: HexString; + readonly listen_addr: IpPortString; + readonly network: string; + readonly version: string; + readonly channels: string; // ??? + readonly moniker: string; + readonly protocol_version: { + readonly p2p: IntegerString; + readonly block: IntegerString; + readonly app: IntegerString; + }; + /** + * Additional information. E.g. + * { + * "tx_index": "on", + * "rpc_address":"tcp://0.0.0.0:26657" + * } + */ + readonly other: object; +} + +function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { + return { + id: fromHex(assertNotEmpty(data.id)), + listenAddr: assertNotEmpty(data.listen_addr), + network: assertNotEmpty(data.network), + version: assertNotEmpty(data.version), + channels: assertNotEmpty(data.channels), + moniker: assertNotEmpty(data.moniker), + other: dictionaryToStringMap(data.other), + protocolVersion: { + app: Integer.parse(assertNotEmpty(data.protocol_version.app)), + block: Integer.parse(assertNotEmpty(data.protocol_version.block)), + p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), + }, + }; +} + +interface RpcSyncInfo { + readonly latest_block_hash: HexString; + readonly latest_app_hash: HexString; + readonly latest_block_height: IntegerString; + readonly latest_block_time: DateTimeString; + readonly catching_up: boolean; +} + +function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { + return { + latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), + latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), + latestBlockTime: DateTime.decode(assertNotEmpty(data.latest_block_time)), + latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), + catchingUp: assertBoolean(data.catching_up), + }; +} + +interface RpcStatusResponse { + readonly node_info: RpcNodeInfo; + readonly sync_info: RpcSyncInfo; + readonly validator_info: RpcValidatorInfo; +} + +function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { + return { + nodeInfo: decodeNodeInfo(data.node_info), + syncInfo: decodeSyncInfo(data.sync_info), + validatorInfo: decodeValidatorInfo(data.validator_info), + }; +} + +/** + * Example data: + * { + * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", + * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", + * "Proof": { + * "total": "1", + * "index": "0", + * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", + * "aunts": [] + * } + * } + */ +interface RpcTxProof { + readonly Data: Base64String; + readonly RootHash: HexString; + readonly Proof: { + readonly total: IntegerString; + readonly index: IntegerString; + readonly leaf_hash: Base64String; + readonly aunts: readonly Base64String[]; + }; +} + +function decodeTxProof(data: RpcTxProof): responses.TxProof { + return { + data: Base64.decode(assertNotEmpty(data.Data)), + rootHash: fromHex(assertNotEmpty(data.RootHash)), + proof: { + total: Integer.parse(assertNotEmpty(data.Proof.total)), + index: Integer.parse(assertNotEmpty(data.Proof.index)), + leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), + aunts: assertArray(data.Proof.aunts).map(Base64.decode), + }, + }; +} + +interface RpcTxResponse { + readonly tx: Base64String; + readonly tx_result: RpcTxData; + readonly height: IntegerString; + readonly index: number; + readonly hash: HexString; + readonly proof?: RpcTxProof; +} + +function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { + return { + tx: Base64.decode(assertNotEmpty(data.tx)) as TxBytes, + result: decodeTxData(assertObject(data.tx_result)), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + proof: may(decodeTxProof, data.proof), + }; +} + +interface RpcTxSearchResponse { + readonly txs: readonly RpcTxResponse[]; + readonly total_count: IntegerString; +} + +function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { + return { + totalCount: Integer.parse(assertNotEmpty(data.total_count)), + txs: assertArray(data.txs).map(decodeTxResponse), + }; +} + +interface RpcTxEvent { + readonly tx: Base64String; + readonly result: RpcTxData; + readonly height: IntegerString; + readonly index: number; +} + +function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { + const tx = Base64.decode(assertNotEmpty(data.tx)) as TxBytes; + return { + tx: tx, + hash: hashTx(tx), + result: decodeTxData(data.result), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + }; +} + +// for validators +interface RpcValidatorData extends RpcValidatorUpdate { + readonly accum?: IntegerString; +} + +function decodeValidatorData(data: RpcValidatorData): responses.Validator { + return { + ...decodeValidatorUpdate(data), + accum: may(Integer.parse, data.accum), + }; +} + +interface RpcValidatorsResponse { + readonly block_height: IntegerString; + readonly validators: readonly RpcValidatorData[]; +} + +function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { + return { + blockHeight: Integer.parse(assertNotEmpty(data.block_height)), + results: assertArray(data.validators).map(decodeValidatorData), + }; +} + +interface RpcEvidence { + readonly type: string; + readonly validator: RpcValidatorUpdate; + readonly height: IntegerString; + readonly time: IntegerString; + readonly totalVotingPower: IntegerString; +} + +function decodeEvidence(data: RpcEvidence): responses.Evidence { + return { + type: assertNotEmpty(data.type), + height: Integer.parse(assertNotEmpty(data.height)), + time: Integer.parse(assertNotEmpty(data.time)), + totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), + validator: decodeValidatorUpdate(data.validator), + }; +} + +function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { + return assertArray(ev).map(decodeEvidence); +} + +interface RpcBlock { + readonly header: RpcHeader; + readonly last_commit: RpcCommit; + readonly data: { + readonly txs?: readonly Base64String[]; + }; + readonly evidence?: { + readonly evidence?: readonly RpcEvidence[]; + }; +} + +function decodeBlock(data: RpcBlock): responses.Block { + return { + header: decodeHeader(assertObject(data.header)), + lastCommit: decodeCommit(assertObject(data.last_commit)), + txs: data.data.txs ? assertArray(data.data.txs).map(Base64.decode) : [], + evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), + }; +} + +interface RpcBlockResponse { + readonly block_meta: RpcBlockMeta; + readonly block: RpcBlock; +} + +function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { + return { + blockMeta: decodeBlockMeta(data.block_meta), + block: decodeBlock(data.block), + }; +} + +export class Responses { + public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { + return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); + } + + public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { + return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); + } + + public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { + return decodeBlockResponse(response.result as RpcBlockResponse); + } + + public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { + return decodeBlockResults(response.result as RpcBlockResultsResponse); + } + + public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { + return decodeBlockchain(response.result as RpcBlockchainResponse); + } + + public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { + return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); + } + + public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { + return this.decodeBroadcastTxSync(response); + } + + public static decodeBroadcastTxCommit( + response: JsonRpcSuccessResponse, + ): responses.BroadcastTxCommitResponse { + return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); + } + + public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { + return decodeCommitResponse(response.result as RpcCommitResponse); + } + + public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { + return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); + } + + public static decodeHealth(): responses.HealthResponse { + return null; + } + + public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { + return decodeStatus(response.result as RpcStatusResponse); + } + + public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { + return decodeBlock(event.data.value.block as RpcBlock); + } + + public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { + return decodeHeader(event.data.value.header as RpcHeader); + } + + public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { + return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); + } + + public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { + return decodeTxResponse(response.result as RpcTxResponse); + } + + public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { + return decodeTxSearch(response.result as RpcTxSearchResponse); + } + + public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { + return decodeValidators(response.result as RpcValidatorsResponse); + } +} diff --git a/packages/tendermint-rpc/tsconfig.json b/packages/tendermint-rpc/tsconfig.json new file mode 100644 index 0000000000..167e8c0226 --- /dev/null +++ b/packages/tendermint-rpc/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/tendermint-rpc/tslint.json b/packages/tendermint-rpc/tslint.json new file mode 100644 index 0000000000..0946f20963 --- /dev/null +++ b/packages/tendermint-rpc/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tslint.json" +} diff --git a/packages/tendermint-rpc/typedoc.js b/packages/tendermint-rpc/typedoc.js new file mode 100644 index 0000000000..e2387c7de4 --- /dev/null +++ b/packages/tendermint-rpc/typedoc.js @@ -0,0 +1,14 @@ +const packageJson = require("./package.json"); + +module.exports = { + src: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + target: "es6", + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/tendermint-rpc/types/adaptor.d.ts b/packages/tendermint-rpc/types/adaptor.d.ts new file mode 100644 index 0000000000..94fb19714b --- /dev/null +++ b/packages/tendermint-rpc/types/adaptor.d.ts @@ -0,0 +1,49 @@ +import { JsonRpcRequest, JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import * as requests from "./requests"; +import * as responses from "./responses"; +import { SubscriptionEvent } from "./rpcclients"; +import { BlockHash, TxBytes, TxHash } from "./types"; +export interface Adaptor { + readonly params: Params; + readonly responses: Responses; + readonly hashTx: (tx: TxBytes) => TxHash; + readonly hashBlock: (header: responses.Header) => BlockHash; +} +export declare type Encoder = (req: T) => JsonRpcRequest; +export declare type Decoder = (res: JsonRpcSuccessResponse) => T; +export interface Params { + readonly encodeAbciInfo: (req: requests.AbciInfoRequest) => JsonRpcRequest; + readonly encodeAbciQuery: (req: requests.AbciQueryRequest) => JsonRpcRequest; + readonly encodeBlock: (req: requests.BlockRequest) => JsonRpcRequest; + readonly encodeBlockchain: (req: requests.BlockchainRequest) => JsonRpcRequest; + readonly encodeBlockResults: (req: requests.BlockResultsRequest) => JsonRpcRequest; + readonly encodeBroadcastTx: (req: requests.BroadcastTxRequest) => JsonRpcRequest; + readonly encodeCommit: (req: requests.CommitRequest) => JsonRpcRequest; + readonly encodeGenesis: (req: requests.GenesisRequest) => JsonRpcRequest; + readonly encodeHealth: (req: requests.HealthRequest) => JsonRpcRequest; + readonly encodeStatus: (req: requests.StatusRequest) => JsonRpcRequest; + readonly encodeSubscribe: (req: requests.SubscribeRequest) => JsonRpcRequest; + readonly encodeTx: (req: requests.TxRequest) => JsonRpcRequest; + readonly encodeTxSearch: (req: requests.TxSearchRequest) => JsonRpcRequest; + readonly encodeValidators: (req: requests.ValidatorsRequest) => JsonRpcRequest; +} +export interface Responses { + readonly decodeAbciInfo: (response: JsonRpcSuccessResponse) => responses.AbciInfoResponse; + readonly decodeAbciQuery: (response: JsonRpcSuccessResponse) => responses.AbciQueryResponse; + readonly decodeBlock: (response: JsonRpcSuccessResponse) => responses.BlockResponse; + readonly decodeBlockResults: (response: JsonRpcSuccessResponse) => responses.BlockResultsResponse; + readonly decodeBlockchain: (response: JsonRpcSuccessResponse) => responses.BlockchainResponse; + readonly decodeBroadcastTxSync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxSyncResponse; + readonly decodeBroadcastTxAsync: (response: JsonRpcSuccessResponse) => responses.BroadcastTxAsyncResponse; + readonly decodeBroadcastTxCommit: (response: JsonRpcSuccessResponse) => responses.BroadcastTxCommitResponse; + readonly decodeCommit: (response: JsonRpcSuccessResponse) => responses.CommitResponse; + readonly decodeGenesis: (response: JsonRpcSuccessResponse) => responses.GenesisResponse; + readonly decodeHealth: (response: JsonRpcSuccessResponse) => responses.HealthResponse; + readonly decodeStatus: (response: JsonRpcSuccessResponse) => responses.StatusResponse; + readonly decodeTx: (response: JsonRpcSuccessResponse) => responses.TxResponse; + readonly decodeTxSearch: (response: JsonRpcSuccessResponse) => responses.TxSearchResponse; + readonly decodeValidators: (response: JsonRpcSuccessResponse) => responses.ValidatorsResponse; + readonly decodeNewBlockEvent: (response: SubscriptionEvent) => responses.NewBlockEvent; + readonly decodeNewBlockHeaderEvent: (response: SubscriptionEvent) => responses.NewBlockHeaderEvent; + readonly decodeTxEvent: (response: SubscriptionEvent) => responses.TxEvent; +} diff --git a/packages/tendermint-rpc/types/adaptorforversion.d.ts b/packages/tendermint-rpc/types/adaptorforversion.d.ts new file mode 100644 index 0000000000..caf42fd7dd --- /dev/null +++ b/packages/tendermint-rpc/types/adaptorforversion.d.ts @@ -0,0 +1,8 @@ +import { Adaptor } from "./adaptor"; +/** + * Returns an Adaptor implementation for a given tendermint version. + * Throws when version is not supported. + * + * @param version full Tendermint version string, e.g. "0.20.1" + */ +export declare function adaptorForVersion(version: string): Adaptor; diff --git a/packages/tendermint-rpc/types/client.d.ts b/packages/tendermint-rpc/types/client.d.ts new file mode 100644 index 0000000000..550db05048 --- /dev/null +++ b/packages/tendermint-rpc/types/client.d.ts @@ -0,0 +1,60 @@ +import { Stream } from "xstream"; +import { Adaptor } from "./adaptor"; +import * as requests from "./requests"; +import * as responses from "./responses"; +import { RpcClient } from "./rpcclients"; +export declare class Client { + static connect(url: string): Promise; + static detectVersion(client: RpcClient): Promise; + private readonly client; + private readonly p; + private readonly r; + constructor(client: RpcClient, adaptor: Adaptor); + disconnect(): void; + abciInfo(): Promise; + abciQuery(params: requests.AbciQueryParams): Promise; + block(height?: number): Promise; + blockResults(height?: number): Promise; + blockchain(minHeight?: number, maxHeight?: number): Promise; + /** + * Broadcast transaction to mempool and wait for response + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync + */ + broadcastTxSync(params: requests.BroadcastTxParams): Promise; + /** + * Broadcast transaction to mempool and do not wait for result + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async + */ + broadcastTxAsync(params: requests.BroadcastTxParams): Promise; + /** + * Broadcast transaction to mempool and wait for block + * + * @see https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit + */ + broadcastTxCommit(params: requests.BroadcastTxParams): Promise; + commit(height?: number): Promise; + genesis(): Promise; + health(): Promise; + status(): Promise; + subscribeNewBlock(): Stream; + subscribeNewBlockHeader(): Stream; + subscribeTx(query?: requests.QueryString): Stream; + /** + * Get a single transaction by hash + * + * @see https://docs.tendermint.com/master/rpc/#/Info/tx + */ + tx(params: requests.TxParams): Promise; + /** + * Search for transactions that are in a block + * + * @see https://docs.tendermint.com/master/rpc/#/Info/tx_search + */ + txSearch(params: requests.TxSearchParams): Promise; + txSearchAll(params: requests.TxSearchParams): Promise; + validators(height?: number): Promise; + private doCall; + private subscribe; +} diff --git a/packages/tendermint-rpc/types/encodings.d.ts b/packages/tendermint-rpc/types/encodings.d.ts new file mode 100644 index 0000000000..e7faa62fb3 --- /dev/null +++ b/packages/tendermint-rpc/types/encodings.d.ts @@ -0,0 +1,74 @@ +import { As } from "type-tagger"; +import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses"; +export declare type Base64String = string & As<"base64">; +export declare type HexString = string & As<"hex">; +export declare type IntegerString = string & As<"integer">; +export declare type DateTimeString = string & As<"datetime">; +/** + * A runtime checker that ensures a given value is set (i.e. not undefined or null) + * + * This is used when you want to verify that data at runtime matches the expected type. + */ +export declare function assertSet(value: T): T; +/** + * A runtime checker that ensures a given value is a boolean + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export declare function assertBoolean(value: boolean): boolean; +/** + * A runtime checker that ensures a given value is a number + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export declare function assertNumber(value: number): number; +/** + * A runtime checker that ensures a given value is an array + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export declare function assertArray(value: readonly T[]): readonly T[]; +/** + * A runtime checker that ensures a given value is an object in the sense of JSON + * (an unordered collection of key–value pairs where the keys are strings) + * + * This is used when you want to verify that data at runtime matches the expected type. + * This implies assertSet. + */ +export declare function assertObject(value: T): T; +/** + * Throws an error if value matches the empty value for the + * given type (array/string of length 0, number of value 0, ...) + * + * Otherwise returns the value. + * + * This implies assertSet + */ +export declare function assertNotEmpty(value: T): T; +export declare function optional(value: T | null | undefined, fallback: T): T; +export declare function may(transform: (val: T) => U, value: T | null | undefined): U | undefined; +export declare function dictionaryToStringMap(obj: any): Map; +export declare class Integer { + static parse(input: IntegerString | number): number; + static encode(num: number): IntegerString; +} +export declare class Base64 { + static encode(data: Uint8Array): Base64String; + static decode(base64String: Base64String): Uint8Array; +} +export declare class DateTime { + static decode(dateTimeString: DateTimeString): ReadonlyDateWithNanoseconds; +} +export declare class Hex { + static encode(data: Uint8Array): HexString; + static decode(hexString: HexString): Uint8Array; +} +export declare function encodeString(s: string): Uint8Array; +export declare function encodeInt(n: number): Uint8Array; +export declare function encodeTime(time: ReadonlyDateWithNanoseconds): Uint8Array; +export declare function encodeBytes(bytes: Uint8Array): Uint8Array; +export declare function encodeVersion(version: Version): Uint8Array; +export declare function encodeBlockId(blockId: BlockId): Uint8Array; diff --git a/packages/tendermint-rpc/types/index.d.ts b/packages/tendermint-rpc/types/index.d.ts new file mode 100644 index 0000000000..547da1d293 --- /dev/null +++ b/packages/tendermint-rpc/types/index.d.ts @@ -0,0 +1,38 @@ +export { v0_31 } from "./v0-31"; +export { v0_32 } from "./v0-32"; +export { Client } from "./client"; +export { + AbciInfoRequest, + AbciQueryParams, + AbciQueryRequest, + BlockRequest, + BlockchainRequest, + BlockResultsRequest, + BroadcastTxRequest, + BroadcastTxParams, + CommitRequest, + GenesisRequest, + HealthRequest, + Method, + Request, + QueryString, + QueryTag, + StatusRequest, + SubscriptionEventType, + TxParams, + TxRequest, + TxSearchParams, + TxSearchRequest, + ValidatorsRequest, +} from "./requests"; +export * from "./responses"; +export { HttpClient, WebsocketClient } from "./rpcclients"; +export { + IpPortString, + TxBytes, + TxHash, + ValidatorEd25519Pubkey, + ValidatorEd25519Signature, + ValidatorPubkey, + ValidatorSignature, +} from "./types"; diff --git a/packages/tendermint-rpc/types/jsonrpc.d.ts b/packages/tendermint-rpc/types/jsonrpc.d.ts new file mode 100644 index 0000000000..c420a2f247 --- /dev/null +++ b/packages/tendermint-rpc/types/jsonrpc.d.ts @@ -0,0 +1,3 @@ +import { JsonRpcRequest } from "@iov/jsonrpc"; +/** Creates a JSON-RPC request with random ID */ +export declare function createJsonRpcRequest(method: string, params?: {}): JsonRpcRequest; diff --git a/packages/tendermint-rpc/types/requests.d.ts b/packages/tendermint-rpc/types/requests.d.ts new file mode 100644 index 0000000000..f17fa18dcb --- /dev/null +++ b/packages/tendermint-rpc/types/requests.d.ts @@ -0,0 +1,151 @@ +import { As } from "type-tagger"; +/** + * RPC methods as documented in https://docs.tendermint.com/master/rpc/ + * + * Enum raw value must match the spelling in the "shell" example call (snake_case) + */ +export declare enum Method { + AbciInfo = "abci_info", + AbciQuery = "abci_query", + Block = "block", + Blockchain = "blockchain", + BlockResults = "block_results", + BroadcastTxAsync = "broadcast_tx_async", + BroadcastTxSync = "broadcast_tx_sync", + BroadcastTxCommit = "broadcast_tx_commit", + Commit = "commit", + Genesis = "genesis", + Health = "health", + Status = "status", + Subscribe = "subscribe", + Tx = "tx", + TxSearch = "tx_search", + Validators = "validators", + Unsubscribe = "unsubscribe", +} +export declare type Request = + | AbciInfoRequest + | AbciQueryRequest + | BlockRequest + | BlockchainRequest + | BlockResultsRequest + | BroadcastTxRequest + | CommitRequest + | GenesisRequest + | HealthRequest + | StatusRequest + | TxRequest + | TxSearchRequest + | ValidatorsRequest; +/** + * Raw values must match the tendermint event name + * + * @see https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants + */ +export declare enum SubscriptionEventType { + NewBlock = "NewBlock", + NewBlockHeader = "NewBlockHeader", + Tx = "Tx", +} +export interface AbciInfoRequest { + readonly method: Method.AbciInfo; +} +export interface AbciQueryRequest { + readonly method: Method.AbciQuery; + readonly params: AbciQueryParams; +} +export interface AbciQueryParams { + readonly path: string; + readonly data: Uint8Array; + readonly height?: number; + /** + * A flag that defines if proofs are included in the response or not. + * + * Internally this is mapped to the old inverse name `trusted` for Tendermint < 0.26. + * Starting with Tendermint 0.26, the default value changed from true to false. + */ + readonly prove?: boolean; +} +export interface BlockRequest { + readonly method: Method.Block; + readonly params: { + readonly height?: number; + }; +} +export interface BlockchainRequest { + readonly method: Method.Blockchain; + readonly params: BlockchainRequestParams; +} +export interface BlockchainRequestParams { + readonly minHeight?: number; + readonly maxHeight?: number; +} +export interface BlockResultsRequest { + readonly method: Method.BlockResults; + readonly params: { + readonly height?: number; + }; +} +export interface BroadcastTxRequest { + readonly method: Method.BroadcastTxAsync | Method.BroadcastTxSync | Method.BroadcastTxCommit; + readonly params: BroadcastTxParams; +} +export interface BroadcastTxParams { + readonly tx: Uint8Array; +} +export interface CommitRequest { + readonly method: Method.Commit; + readonly params: { + readonly height?: number; + }; +} +export interface GenesisRequest { + readonly method: Method.Genesis; +} +export interface HealthRequest { + readonly method: Method.Health; +} +export interface StatusRequest { + readonly method: Method.Status; +} +export interface SubscribeRequest { + readonly method: Method.Subscribe; + readonly query: { + readonly type: SubscriptionEventType; + readonly raw?: QueryString; + }; +} +export declare type QueryString = string & As<"query">; +export interface QueryTag { + readonly key: string; + readonly value: string; +} +export interface TxRequest { + readonly method: Method.Tx; + readonly params: TxParams; +} +export interface TxParams { + readonly hash: Uint8Array; + readonly prove?: boolean; +} +export interface TxSearchRequest { + readonly method: Method.TxSearch; + readonly params: TxSearchParams; +} +export interface TxSearchParams { + readonly query: QueryString; + readonly prove?: boolean; + readonly page?: number; + readonly per_page?: number; +} +export interface ValidatorsRequest { + readonly method: Method.Validators; + readonly params: { + readonly height?: number; + }; +} +export interface BuildQueryComponents { + readonly tags?: readonly QueryTag[]; + readonly raw?: QueryString; +} +export declare function buildQuery(components: BuildQueryComponents): QueryString; diff --git a/packages/tendermint-rpc/types/responses.d.ts b/packages/tendermint-rpc/types/responses.d.ts new file mode 100644 index 0000000000..cf409ec429 --- /dev/null +++ b/packages/tendermint-rpc/types/responses.d.ts @@ -0,0 +1,262 @@ +import { ReadonlyDate } from "readonly-date"; +import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "./types"; +export declare type Response = + | AbciInfoResponse + | AbciQueryResponse + | BlockResponse + | BlockResultsResponse + | BlockchainResponse + | BroadcastTxAsyncResponse + | BroadcastTxSyncResponse + | BroadcastTxCommitResponse + | CommitResponse + | GenesisResponse + | HealthResponse + | StatusResponse + | TxResponse + | TxSearchResponse + | ValidatorsResponse; +export interface AbciInfoResponse { + readonly data?: string; + readonly lastBlockHeight?: number; + readonly lastBlockAppHash?: Uint8Array; +} +export interface AbciQueryResponse { + readonly key: Uint8Array; + readonly value: Uint8Array; + readonly height?: number; + readonly index?: number; + readonly code?: number; + readonly log?: string; +} +export interface BlockResponse { + readonly blockMeta: BlockMeta; + readonly block: Block; +} +export interface BlockResultsResponse { + readonly height: number; + readonly results: readonly TxData[]; + readonly endBlock: { + readonly validatorUpdates: readonly Validator[]; + readonly consensusUpdates?: ConsensusParams; + readonly tags?: readonly Tag[]; + }; +} +export interface BlockchainResponse { + readonly lastHeight: number; + readonly blockMetas: readonly BlockMeta[]; +} +/** No data in here because RPC method BroadcastTxAsync "returns right away, with no response" */ +export interface BroadcastTxAsyncResponse {} +export interface BroadcastTxSyncResponse extends TxData { + readonly hash: TxHash; +} +/** + * Returns true iff transaction made it sucessfully into the transaction pool + */ +export declare function broadcastTxSyncSuccess(res: BroadcastTxSyncResponse): boolean; +export interface BroadcastTxCommitResponse { + readonly height?: number; + readonly hash: TxHash; + readonly checkTx: TxData; + readonly deliverTx?: TxData; +} +/** + * Returns true iff transaction made it sucessfully into a block + * (i.e. sucess in `check_tx` and `deliver_tx` field) + */ +export declare function broadcastTxCommitSuccess(res: BroadcastTxCommitResponse): boolean; +export interface CommitResponse { + readonly header: Header; + readonly commit: Commit; + readonly canonical: boolean; +} +export interface GenesisResponse { + readonly genesisTime: ReadonlyDate; + readonly chainId: string; + readonly consensusParams: ConsensusParams; + readonly validators: readonly Validator[]; + readonly appHash: Uint8Array; + readonly appState: {} | undefined; +} +export declare type HealthResponse = null; +export interface StatusResponse { + readonly nodeInfo: NodeInfo; + readonly syncInfo: SyncInfo; + readonly validatorInfo: Validator; +} +/** + * A transaction from RPC calls like search. + * + * Try to keep this compatible to TxEvent + */ +export interface TxResponse { + readonly tx: TxBytes; + readonly hash: TxHash; + readonly height: number; + readonly index: number; + readonly result: TxData; + readonly proof?: TxProof; +} +export interface TxSearchResponse { + readonly txs: readonly TxResponse[]; + readonly totalCount: number; +} +export interface ValidatorsResponse { + readonly blockHeight: number; + readonly results: readonly Validator[]; +} +export interface NewBlockEvent extends Block {} +export interface NewBlockHeaderEvent extends Header {} +export interface TxEvent { + readonly tx: TxBytes; + readonly hash: TxHash; + readonly height: number; + readonly index: number; + readonly result: TxData; +} +export declare const getTxEventHeight: (event: TxEvent) => number; +export declare const getHeaderEventHeight: (event: NewBlockHeaderEvent) => number; +export declare const getBlockEventHeight: (event: NewBlockEvent) => number; +export interface Tag { + readonly key: Uint8Array; + readonly value: Uint8Array; +} +export interface Event { + readonly type: string; + readonly attributes: readonly Tag[]; +} +export interface TxData { + readonly code: number; + readonly log?: string; + readonly data?: Uint8Array; + readonly tags?: readonly Tag[]; + readonly events?: readonly Event[]; +} +export interface TxProof { + readonly data: Uint8Array; + readonly rootHash: Uint8Array; + readonly proof: { + readonly total: number; + readonly index: number; + /** Optional because does not exist in Tendermint 0.25.x */ + readonly leafHash?: Uint8Array; + readonly aunts: readonly Uint8Array[]; + }; +} +export interface BlockMeta { + readonly blockId: BlockId; + readonly header: Header; +} +export interface BlockId { + readonly hash: Uint8Array; + readonly parts: { + readonly total: number; + readonly hash: Uint8Array; + }; +} +export interface Block { + readonly header: Header; + readonly lastCommit: Commit; + readonly txs: readonly Uint8Array[]; + readonly evidence?: readonly Evidence[]; +} +export interface Evidence { + readonly type: string; + readonly validator: Validator; + readonly height: number; + readonly time: number; + readonly totalVotingPower: number; +} +export interface Commit { + readonly blockId: BlockId; + readonly precommits: readonly Vote[]; +} +/** + * raw values from https://github.com/tendermint/tendermint/blob/dfa9a9a30a666132425b29454e90a472aa579a48/types/vote.go#L44 + */ +export declare enum VoteType { + PREVOTE = 1, + PRECOMMIT = 2, +} +export interface Vote { + readonly type: VoteType; + readonly validatorAddress: Uint8Array; + readonly validatorIndex: number; + readonly height: number; + readonly round: number; + readonly timestamp: ReadonlyDate; + readonly blockId: BlockId; + readonly signature: ValidatorSignature; +} +export interface Version { + readonly block: number; + readonly app: number; +} +export interface ReadonlyDateWithNanoseconds extends ReadonlyDate { + readonly nanoseconds?: number; +} +export interface Header { + readonly version: Version; + readonly chainId: string; + readonly height: number; + readonly time: ReadonlyDateWithNanoseconds; + readonly numTxs: number; + readonly totalTxs: number; + readonly lastBlockId: BlockId; + readonly lastCommitHash: Uint8Array; + readonly dataHash: Uint8Array; + readonly validatorsHash: Uint8Array; + readonly nextValidatorsHash: Uint8Array; + readonly consensusHash: Uint8Array; + readonly appHash: Uint8Array; + readonly lastResultsHash: Uint8Array; + readonly evidenceHash: Uint8Array; + readonly proposerAddress: Uint8Array; +} +export interface NodeInfo { + readonly id: Uint8Array; + readonly listenAddr: IpPortString; + readonly network: string; + readonly version: string; + readonly channels: string; + readonly moniker: string; + readonly other: Map; + readonly protocolVersion: { + readonly p2p: number; + readonly block: number; + readonly app: number; + }; +} +export interface SyncInfo { + readonly latestBlockHash: Uint8Array; + readonly latestAppHash: Uint8Array; + readonly latestBlockHeight: number; + readonly latestBlockTime: ReadonlyDate; + readonly catchingUp: boolean; +} +export interface Validator { + readonly address?: Uint8Array; + readonly pubkey: ValidatorPubkey; + readonly votingPower: number; + readonly accum?: number; + readonly name?: string; +} +export interface ConsensusParams { + readonly block: BlockParams; + readonly evidence: EvidenceParams; +} +export interface BlockParams { + readonly maxBytes: number; + readonly maxGas: number; +} +export interface TxSizeParams { + readonly maxBytes: number; + readonly maxGas: number; +} +export interface BlockGossipParams { + readonly blockPartSizeBytes: number; +} +export interface EvidenceParams { + readonly maxAge: number; +} diff --git a/packages/tendermint-rpc/types/rpcclients/httpclient.d.ts b/packages/tendermint-rpc/types/rpcclients/httpclient.d.ts new file mode 100644 index 0000000000..09ee652b63 --- /dev/null +++ b/packages/tendermint-rpc/types/rpcclients/httpclient.d.ts @@ -0,0 +1,8 @@ +import { JsonRpcRequest, JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import { RpcClient } from "./rpcclient"; +export declare class HttpClient implements RpcClient { + protected readonly url: string; + constructor(url?: string); + disconnect(): void; + execute(request: JsonRpcRequest): Promise; +} diff --git a/packages/tendermint-rpc/types/rpcclients/index.d.ts b/packages/tendermint-rpc/types/rpcclients/index.d.ts new file mode 100644 index 0000000000..32e8777e66 --- /dev/null +++ b/packages/tendermint-rpc/types/rpcclients/index.d.ts @@ -0,0 +1,3 @@ +export { instanceOfRpcStreamingClient, RpcClient, RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; +export { HttpClient } from "./httpclient"; +export { WebsocketClient } from "./websocketclient"; diff --git a/packages/tendermint-rpc/types/rpcclients/rpcclient.d.ts b/packages/tendermint-rpc/types/rpcclients/rpcclient.d.ts new file mode 100644 index 0000000000..8ef11353bb --- /dev/null +++ b/packages/tendermint-rpc/types/rpcclients/rpcclient.d.ts @@ -0,0 +1,25 @@ +import { JsonRpcRequest, JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import { Stream } from "xstream"; +/** + * An event emitted from Tendermint after subscribing via RPC. + * + * These events are passed as the `result` of JSON-RPC responses, which is kind + * of hacky because it breaks the idea that exactly one JSON-RPC response belongs + * to each JSON-RPC request. But this is how subscriptions work in Tendermint. + */ +export interface SubscriptionEvent { + readonly query: string; + readonly data: { + readonly type: string; + readonly value: any; + }; +} +export interface RpcClient { + readonly execute: (request: JsonRpcRequest) => Promise; + readonly disconnect: () => void; +} +export interface RpcStreamingClient extends RpcClient { + readonly listen: (request: JsonRpcRequest) => Stream; +} +export declare function instanceOfRpcStreamingClient(client: RpcClient): client is RpcStreamingClient; +export declare function hasProtocol(url: string): boolean; diff --git a/packages/tendermint-rpc/types/rpcclients/websocketclient.d.ts b/packages/tendermint-rpc/types/rpcclients/websocketclient.d.ts new file mode 100644 index 0000000000..4caae6a0de --- /dev/null +++ b/packages/tendermint-rpc/types/rpcclients/websocketclient.d.ts @@ -0,0 +1,20 @@ +import { JsonRpcId, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import { Stream } from "xstream"; +import { RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; +export declare class WebsocketClient implements RpcStreamingClient { + private readonly url; + private readonly socket; + /** Same events as in socket.events but in the format we need */ + private readonly jsonRpcResponseStream; + private readonly subscriptionStreams; + constructor(baseUrl?: string, onError?: (err: any) => void); + execute(request: JsonRpcRequest): Promise; + listen(request: JsonRpcRequest): Stream; + /** + * Resolves as soon as websocket is connected. execute() queues requests automatically, + * so this should be required for testing purposes only. + */ + connected(): Promise; + disconnect(): void; + protected responseForRequestId(id: JsonRpcId): Promise; +} diff --git a/packages/tendermint-rpc/types/types.d.ts b/packages/tendermint-rpc/types/types.d.ts new file mode 100644 index 0000000000..5129a3e7e7 --- /dev/null +++ b/packages/tendermint-rpc/types/types.d.ts @@ -0,0 +1,30 @@ +import { As } from "type-tagger"; +/** + * Merkle root + */ +export declare type BlockHash = Uint8Array & As<"block-hash">; +/** Raw transaction bytes */ +export declare type TxBytes = Uint8Array & As<"tx-bytes">; +/** + * A raw tendermint transaction hash, currently 20 bytes + */ +export declare type TxHash = Uint8Array & As<"tx-hash">; +export declare type IpPortString = string & As<"ipport">; +export interface ValidatorEd25519Pubkey { + readonly algorithm: "ed25519"; + readonly data: Uint8Array; +} +/** + * Union type for different possible pubkeys. + * Currently only Ed25519 supported. + */ +export declare type ValidatorPubkey = ValidatorEd25519Pubkey; +export interface ValidatorEd25519Signature { + readonly algorithm: "ed25519"; + readonly data: Uint8Array; +} +/** + * Union type for different possible voting signatures. + * Currently only Ed25519 supported. + */ +export declare type ValidatorSignature = ValidatorEd25519Signature; diff --git a/packages/tendermint-rpc/types/v0-31/hasher.d.ts b/packages/tendermint-rpc/types/v0-31/hasher.d.ts new file mode 100644 index 0000000000..de2e7e9012 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-31/hasher.d.ts @@ -0,0 +1,4 @@ +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; +export declare function hashTx(tx: TxBytes): TxHash; +export declare function hashBlock(header: Header): BlockHash; diff --git a/packages/tendermint-rpc/types/v0-31/index.d.ts b/packages/tendermint-rpc/types/v0-31/index.d.ts new file mode 100644 index 0000000000..3d2d1e080c --- /dev/null +++ b/packages/tendermint-rpc/types/v0-31/index.d.ts @@ -0,0 +1,2 @@ +import { Adaptor } from "../adaptor"; +export declare const v0_31: Adaptor; diff --git a/packages/tendermint-rpc/types/v0-31/requests.d.ts b/packages/tendermint-rpc/types/v0-31/requests.d.ts new file mode 100644 index 0000000000..af972c4a92 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-31/requests.d.ts @@ -0,0 +1,18 @@ +import { JsonRpcRequest } from "@iov/jsonrpc"; +import * as requests from "../requests"; +export declare class Params { + static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest; + static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest; + static encodeBlock(req: requests.BlockRequest): JsonRpcRequest; + static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest; + static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest; + static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest; + static encodeCommit(req: requests.CommitRequest): JsonRpcRequest; + static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest; + static encodeHealth(req: requests.HealthRequest): JsonRpcRequest; + static encodeStatus(req: requests.StatusRequest): JsonRpcRequest; + static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest; + static encodeTx(req: requests.TxRequest): JsonRpcRequest; + static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest; + static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest; +} diff --git a/packages/tendermint-rpc/types/v0-31/responses.d.ts b/packages/tendermint-rpc/types/v0-31/responses.d.ts new file mode 100644 index 0000000000..05fad338b2 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-31/responses.d.ts @@ -0,0 +1,23 @@ +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +export declare class Responses { + static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse; + static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse; + static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse; + static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse; + static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse; + static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse; + static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse; + static decodeBroadcastTxCommit(response: JsonRpcSuccessResponse): responses.BroadcastTxCommitResponse; + static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse; + static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse; + static decodeHealth(): responses.HealthResponse; + static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse; + static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent; + static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent; + static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent; + static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse; + static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse; + static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse; +} diff --git a/packages/tendermint-rpc/types/v0-32/hasher.d.ts b/packages/tendermint-rpc/types/v0-32/hasher.d.ts new file mode 100644 index 0000000000..de2e7e9012 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-32/hasher.d.ts @@ -0,0 +1,4 @@ +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; +export declare function hashTx(tx: TxBytes): TxHash; +export declare function hashBlock(header: Header): BlockHash; diff --git a/packages/tendermint-rpc/types/v0-32/index.d.ts b/packages/tendermint-rpc/types/v0-32/index.d.ts new file mode 100644 index 0000000000..0e52753a89 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-32/index.d.ts @@ -0,0 +1,2 @@ +import { Adaptor } from "../adaptor"; +export declare const v0_32: Adaptor; diff --git a/packages/tendermint-rpc/types/v0-32/requests.d.ts b/packages/tendermint-rpc/types/v0-32/requests.d.ts new file mode 100644 index 0000000000..af972c4a92 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-32/requests.d.ts @@ -0,0 +1,18 @@ +import { JsonRpcRequest } from "@iov/jsonrpc"; +import * as requests from "../requests"; +export declare class Params { + static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest; + static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest; + static encodeBlock(req: requests.BlockRequest): JsonRpcRequest; + static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest; + static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest; + static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest; + static encodeCommit(req: requests.CommitRequest): JsonRpcRequest; + static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest; + static encodeHealth(req: requests.HealthRequest): JsonRpcRequest; + static encodeStatus(req: requests.StatusRequest): JsonRpcRequest; + static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest; + static encodeTx(req: requests.TxRequest): JsonRpcRequest; + static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest; + static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest; +} diff --git a/packages/tendermint-rpc/types/v0-32/responses.d.ts b/packages/tendermint-rpc/types/v0-32/responses.d.ts new file mode 100644 index 0000000000..05fad338b2 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-32/responses.d.ts @@ -0,0 +1,23 @@ +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +export declare class Responses { + static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse; + static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse; + static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse; + static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse; + static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse; + static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse; + static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse; + static decodeBroadcastTxCommit(response: JsonRpcSuccessResponse): responses.BroadcastTxCommitResponse; + static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse; + static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse; + static decodeHealth(): responses.HealthResponse; + static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse; + static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent; + static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent; + static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent; + static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse; + static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse; + static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse; +} diff --git a/packages/tendermint-rpc/webpack.web.config.js b/packages/tendermint-rpc/webpack.web.config.js new file mode 100644 index 0000000000..f68149af39 --- /dev/null +++ b/packages/tendermint-rpc/webpack.web.config.js @@ -0,0 +1,19 @@ +const glob = require("glob"); +const path = require("path"); +const webpack = require("webpack"); + +const target = "web"; +const distdir = path.join(__dirname, "dist", "web"); + +module.exports = [ + { + // bundle used for Karma tests + target: target, + entry: glob.sync("./build/**/*.spec.js"), + output: { + path: distdir, + filename: "tests.js", + }, + plugins: [new webpack.EnvironmentPlugin(["TENDERMINT_ENABLED"])], + }, +]; From ad8d980af285cb3780c73d52dd3839e3030c5ae4 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:09:44 +0100 Subject: [PATCH 02/20] tendermint-rpc: Update yarn.lock --- yarn.lock | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/yarn.lock b/yarn.lock index 1b65a92ca2..4ce4a3cc7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,6 +92,64 @@ unique-filename "^1.1.1" which "^1.3.1" +"@iov/crypto@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/crypto/-/crypto-2.3.2.tgz#0f65623d29d1df7e35620bbbeede70f1d0797ff9" + integrity sha512-inHHZa9kr+4K88ZhqOQC/LVlZBcZlE0Kkj97ynWGzaR4CCdCFa0KOkx2vef2CH0J6wbfC13e6b93/MuQyGiUAw== + dependencies: + "@iov/encoding" "^2.3.2" + bip39 "^3.0.2" + bn.js "^4.11.8" + elliptic "^6.4.0" + js-sha3 "^0.8.0" + libsodium-wrappers "^0.7.6" + pbkdf2 "^3.0.16" + ripemd160 "^2.0.2" + sha.js "^2.4.11" + type-tagger "^1.0.0" + unorm "^1.5.0" + +"@iov/encoding@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/encoding/-/encoding-2.3.2.tgz#4b37966af0345a6bc904bb58189dc1ea9d14ad9b" + integrity sha512-viioqo1flTkG4Oxb0PvoBXGozHq9fObAgAL4dRHJe9zmChE77EBX2Y5u0nabd2JwAhEbir56AtsrUe4dOrtd5w== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + bn.js "^4.11.8" + readonly-date "^1.0.0" + +"@iov/jsonrpc@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/jsonrpc/-/jsonrpc-2.3.2.tgz#5cdfa56333741073cc00f17d54efb9f526b9705a" + integrity sha512-fPryTYZ4na1F/K0AF4eRjf1mwg97s8N/AgvELHyqpm7Bq9zvV0/ZBPvzjV1mqmPMfONx91qLIkpDguwmwEb8NA== + dependencies: + "@iov/encoding" "^2.3.2" + "@iov/stream" "^2.3.2" + xstream "^11.10.0" + +"@iov/socket@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/socket/-/socket-2.3.2.tgz#adc8ef389bafc5380e1c7415fb21f9a890d79195" + integrity sha512-LMIVGhYNvEdctHwjprVYv5QnDxXLNNZ9ASU0IRlsjHsMNR1Tcy+FsdqqQ5PJlW1r/r92dHLCfV9IKXucg/h8rQ== + dependencies: + "@iov/stream" "^2.3.2" + isomorphic-ws "^4.0.1" + ws "^6.2.0" + xstream "^11.10.0" + +"@iov/stream@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/stream/-/stream-2.3.2.tgz#472063f3a4fcd1e97de0ae99189f98b94825afac" + integrity sha512-nOq5OKwK2rWnyzXpGTAKZToCU0jmTjEC05owVzmBfw3+7hmjlExrGGbLncjcMWzbvp2VVpHc2l01Tr+qpzr8Vw== + dependencies: + xstream "^11.10.0" + +"@iov/utils@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@iov/utils/-/utils-2.3.2.tgz#a499ec304b4febaeb3af309dedbb30e14a09c91e" + integrity sha512-mtdZ8zh/LGjwA72HofOc8JF3KN1Rc1jwaQATePLDwIIJRw0AJXx2GLRBBRjja41huuw9ND0E2mQWlYLtYsNnUA== + "@koa/cors@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.0.0.tgz#df021b4df2dadf1e2b04d7c8ddf93ba2d42519cb" @@ -4649,6 +4707,11 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -8078,6 +8141,13 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" +ws@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + ws@^7.1.2: version "7.3.0" resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" @@ -8095,6 +8165,13 @@ xmlhttprequest-ssl@~1.5.4: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xstream@^11.10.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.12.0.tgz#a85fded069cc8ac3aedc9538e5d94ab6d0ce845a" + integrity sha512-rceZqhyRPJdmDNh8hyFEnOacNrL4pTVkNZzoLvFqOVaIZHbM3bS15ycqI5V9eJXCRMfgEapwzcNzPjkIRUkv2Q== + dependencies: + symbol-observable "1.2.0" + xstream@^11.11.0: version "11.11.0" resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.11.0.tgz#2c963cf0a6cb3eafecb57eaeae16c9850235b422" From 8204aa9f8c73d9112daa9a2ef3efc9cb0b0484f1 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:20:26 +0100 Subject: [PATCH 03/20] root: Copy tendermint scripts from @iov-one --- scripts/tendermint/all_start.sh | 19 +++++++++++++++ scripts/tendermint/all_stop.sh | 16 ++++++++++++ scripts/tendermint/start.sh | 43 +++++++++++++++++++++++++++++++++ scripts/tendermint/stop.sh | 8 ++++++ 4 files changed, 86 insertions(+) create mode 100755 scripts/tendermint/all_start.sh create mode 100755 scripts/tendermint/all_stop.sh create mode 100755 scripts/tendermint/start.sh create mode 100755 scripts/tendermint/stop.sh diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh new file mode 100755 index 0000000000..c11964c859 --- /dev/null +++ b/scripts/tendermint/all_start.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +# Find latest patch releases at https://hub.docker.com/r/tendermint/tendermint/tags/ +declare -a TM_VERSIONS +TM_VERSIONS[31]=v0.31.8 +TM_VERSIONS[32]=v0.32.3 + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +for KEY in "${!TM_VERSIONS[@]}"; do + export TENDERMINT_VERSION="${TM_VERSIONS[$KEY]}" + export TENDERMINT_PORT="111$KEY" + export TENDERMINT_NAME="tendermint-$KEY" + + echo "Starting $TENDERMINT_NAME ($TENDERMINT_VERSION) on port $TENDERMINT_PORT ..." + "$SCRIPT_DIR/start.sh" +done diff --git a/scripts/tendermint/all_stop.sh b/scripts/tendermint/all_stop.sh new file mode 100755 index 0000000000..e82b4dab1e --- /dev/null +++ b/scripts/tendermint/all_stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +declare -a TM_VERSIONS +TM_VERSIONS[31]=v0.31.8 +TM_VERSIONS[32]=v0.32.3 + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +for KEY in "${!TM_VERSIONS[@]}"; do + export TENDERMINT_NAME="tendermint-$KEY" + + echo "Stopping $TENDERMINT_NAME ..." + "$SCRIPT_DIR/stop.sh" +done diff --git a/scripts/tendermint/start.sh b/scripts/tendermint/start.sh new file mode 100755 index 0000000000..e7bd33f89e --- /dev/null +++ b/scripts/tendermint/start.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +# Tendermint settings must be specified +# Choose version from https://hub.docker.com/r/tendermint/tendermint/tags/ +for SETTING in "TENDERMINT_VERSION" "TENDERMINT_PORT" "TENDERMINT_NAME" +do + if test -z "$(eval echo "\$$SETTING")" + then + echo "\$$SETTING must be set when running this script" + exit 1 + fi +done + +TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tendermint.XXXXXXXXX") +chmod 777 "${TMP_DIR}" +echo "Using temporary dir $TMP_DIR" +LOGFILE="$TMP_DIR/tendermint.log" + +docker run --rm \ + --user="$UID" \ + -v "${TMP_DIR}:/tendermint" \ + "tendermint/tendermint:${TENDERMINT_VERSION}" \ + init + +# make sure we allow cors origins, only possible by modifying the config file +# https://github.com/tendermint/tendermint/issues/3216 +sed -ie 's/cors_allowed_origins.*$/cors_allowed_origins = ["*"]/' "${TMP_DIR}/config/config.toml" + +# must enable tx index for search and subscribe +docker run --rm \ + --user="$UID" \ + --name "$TENDERMINT_NAME" \ + -p "${TENDERMINT_PORT}:26657" -v "${TMP_DIR}:/tendermint" \ + -e "TM_TX_INDEX_INDEX_ALL_TAGS=true" \ + "tendermint/tendermint:${TENDERMINT_VERSION}" node \ + --proxy_app=kvstore \ + --rpc.laddr=tcp://0.0.0.0:26657 \ + --log_level=state:info,rpc:info,*:error \ + > "$LOGFILE" & + +echo "Tendermint running and logging into $LOGFILE" diff --git a/scripts/tendermint/stop.sh b/scripts/tendermint/stop.sh new file mode 100755 index 0000000000..89692b8219 --- /dev/null +++ b/scripts/tendermint/stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck > /dev/null && shellcheck "$0" + +NAME=${TENDERMINT_NAME:-tendermint-25} + +echo "Killing container named '$NAME' ..." +docker container kill "$NAME" From b7d8e940f054b11eb75771e6c91ec965069396cc Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:29:44 +0100 Subject: [PATCH 04/20] tendermint-rpc: Replace @iov/encoding dependency --- packages/tendermint-rpc/package.json | 3 ++- packages/tendermint-rpc/src/client.spec.ts | 2 +- packages/tendermint-rpc/src/encodings.ts | 3 ++- packages/tendermint-rpc/src/v0-31/hasher.spec.ts | 2 +- packages/tendermint-rpc/src/v0-31/requests.ts | 2 +- packages/tendermint-rpc/src/v0-31/responses.ts | 2 +- packages/tendermint-rpc/src/v0-32/hasher.spec.ts | 2 +- packages/tendermint-rpc/src/v0-32/requests.ts | 2 +- packages/tendermint-rpc/src/v0-32/responses.ts | 2 +- 9 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index d44dd59da8..9d215abab9 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -38,8 +38,9 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/encoding": "^0.20.0", + "@cosmjs/math": "^0.20.0", "@iov/crypto": "^2.3.2", - "@iov/encoding": "^2.3.2", "@iov/jsonrpc": "^2.3.2", "@iov/socket": "^2.3.2", "axios": "^0.19.0", diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts index 0a5bd6e502..a900a81e21 100644 --- a/packages/tendermint-rpc/src/client.spec.ts +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -1,5 +1,5 @@ // tslint:disable:readonly-array -import { toAscii } from "@iov/encoding"; +import { toAscii } from "@cosmjs/encoding"; import { firstEvent, toListPromise } from "@iov/stream"; import { sleep } from "@iov/utils"; import { ReadonlyDate } from "readonly-date"; diff --git a/packages/tendermint-rpc/src/encodings.ts b/packages/tendermint-rpc/src/encodings.ts index 60b49da17f..ac8bc9252d 100644 --- a/packages/tendermint-rpc/src/encodings.ts +++ b/packages/tendermint-rpc/src/encodings.ts @@ -1,4 +1,5 @@ -import { fromBase64, fromHex, fromRfc3339, Int53, toBase64, toHex, toUtf8 } from "@iov/encoding"; +import { fromBase64, fromHex, fromRfc3339, toBase64, toHex, toUtf8 } from "@cosmjs/encoding"; +import { Int53 } from "@cosmjs/math"; import { As } from "type-tagger"; import { BlockId, ReadonlyDateWithNanoseconds, Version } from "./responses"; diff --git a/packages/tendermint-rpc/src/v0-31/hasher.spec.ts b/packages/tendermint-rpc/src/v0-31/hasher.spec.ts index e9b024b001..95b8aea44c 100644 --- a/packages/tendermint-rpc/src/v0-31/hasher.spec.ts +++ b/packages/tendermint-rpc/src/v0-31/hasher.spec.ts @@ -1,4 +1,4 @@ -import { fromBase64, fromHex } from "@iov/encoding"; +import { fromBase64, fromHex } from "@cosmjs/encoding"; import { ReadonlyDate } from "readonly-date"; import { ReadonlyDateWithNanoseconds } from "../responses"; diff --git a/packages/tendermint-rpc/src/v0-31/requests.ts b/packages/tendermint-rpc/src/v0-31/requests.ts index f91d442339..7526fe0ae3 100644 --- a/packages/tendermint-rpc/src/v0-31/requests.ts +++ b/packages/tendermint-rpc/src/v0-31/requests.ts @@ -1,4 +1,4 @@ -import { toHex } from "@iov/encoding"; +import { toHex } from "@cosmjs/encoding"; import { JsonRpcRequest } from "@iov/jsonrpc"; import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; diff --git a/packages/tendermint-rpc/src/v0-31/responses.ts b/packages/tendermint-rpc/src/v0-31/responses.ts index f136e39cc2..86ea149ae9 100644 --- a/packages/tendermint-rpc/src/v0-31/responses.ts +++ b/packages/tendermint-rpc/src/v0-31/responses.ts @@ -1,4 +1,4 @@ -import { fromHex } from "@iov/encoding"; +import { fromHex } from "@cosmjs/encoding"; import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; import { diff --git a/packages/tendermint-rpc/src/v0-32/hasher.spec.ts b/packages/tendermint-rpc/src/v0-32/hasher.spec.ts index e9b024b001..95b8aea44c 100644 --- a/packages/tendermint-rpc/src/v0-32/hasher.spec.ts +++ b/packages/tendermint-rpc/src/v0-32/hasher.spec.ts @@ -1,4 +1,4 @@ -import { fromBase64, fromHex } from "@iov/encoding"; +import { fromBase64, fromHex } from "@cosmjs/encoding"; import { ReadonlyDate } from "readonly-date"; import { ReadonlyDateWithNanoseconds } from "../responses"; diff --git a/packages/tendermint-rpc/src/v0-32/requests.ts b/packages/tendermint-rpc/src/v0-32/requests.ts index f91d442339..7526fe0ae3 100644 --- a/packages/tendermint-rpc/src/v0-32/requests.ts +++ b/packages/tendermint-rpc/src/v0-32/requests.ts @@ -1,4 +1,4 @@ -import { toHex } from "@iov/encoding"; +import { toHex } from "@cosmjs/encoding"; import { JsonRpcRequest } from "@iov/jsonrpc"; import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; diff --git a/packages/tendermint-rpc/src/v0-32/responses.ts b/packages/tendermint-rpc/src/v0-32/responses.ts index 6f6cc23972..2426b98a07 100644 --- a/packages/tendermint-rpc/src/v0-32/responses.ts +++ b/packages/tendermint-rpc/src/v0-32/responses.ts @@ -1,4 +1,4 @@ -import { fromHex } from "@iov/encoding"; +import { fromHex } from "@cosmjs/encoding"; import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; import { From 248bf39dcc42e6039e24c68563c4257faab2587b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:32:48 +0100 Subject: [PATCH 05/20] tendermint-rpc: Replace @iov/crypto dependency --- packages/tendermint-rpc/package.json | 2 +- packages/tendermint-rpc/src/v0-31/hasher.ts | 2 +- packages/tendermint-rpc/src/v0-32/hasher.ts | 2 +- yarn.lock | 17 ----------------- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index 9d215abab9..4cc3d8a840 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -38,9 +38,9 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/crypto": "^0.20.0", "@cosmjs/encoding": "^0.20.0", "@cosmjs/math": "^0.20.0", - "@iov/crypto": "^2.3.2", "@iov/jsonrpc": "^2.3.2", "@iov/socket": "^2.3.2", "axios": "^0.19.0", diff --git a/packages/tendermint-rpc/src/v0-31/hasher.ts b/packages/tendermint-rpc/src/v0-31/hasher.ts index ac8b36d023..eba2392964 100644 --- a/packages/tendermint-rpc/src/v0-31/hasher.ts +++ b/packages/tendermint-rpc/src/v0-31/hasher.ts @@ -1,4 +1,4 @@ -import { Sha256 } from "@iov/crypto"; +import { Sha256 } from "@cosmjs/crypto"; import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; import { Header } from "../responses"; diff --git a/packages/tendermint-rpc/src/v0-32/hasher.ts b/packages/tendermint-rpc/src/v0-32/hasher.ts index ac8b36d023..eba2392964 100644 --- a/packages/tendermint-rpc/src/v0-32/hasher.ts +++ b/packages/tendermint-rpc/src/v0-32/hasher.ts @@ -1,4 +1,4 @@ -import { Sha256 } from "@iov/crypto"; +import { Sha256 } from "@cosmjs/crypto"; import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; import { Header } from "../responses"; diff --git a/yarn.lock b/yarn.lock index 4ce4a3cc7c..22e4979d30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,23 +92,6 @@ unique-filename "^1.1.1" which "^1.3.1" -"@iov/crypto@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@iov/crypto/-/crypto-2.3.2.tgz#0f65623d29d1df7e35620bbbeede70f1d0797ff9" - integrity sha512-inHHZa9kr+4K88ZhqOQC/LVlZBcZlE0Kkj97ynWGzaR4CCdCFa0KOkx2vef2CH0J6wbfC13e6b93/MuQyGiUAw== - dependencies: - "@iov/encoding" "^2.3.2" - bip39 "^3.0.2" - bn.js "^4.11.8" - elliptic "^6.4.0" - js-sha3 "^0.8.0" - libsodium-wrappers "^0.7.6" - pbkdf2 "^3.0.16" - ripemd160 "^2.0.2" - sha.js "^2.4.11" - type-tagger "^1.0.0" - unorm "^1.5.0" - "@iov/encoding@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@iov/encoding/-/encoding-2.3.2.tgz#4b37966af0345a6bc904bb58189dc1ea9d14ad9b" From 65dbe10b686518e0c762239427b6df9d88c26bd1 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:34:16 +0100 Subject: [PATCH 06/20] tendermint-rpc: Replace @iov/utils dependency --- packages/tendermint-rpc/package.json | 2 +- packages/tendermint-rpc/src/client.spec.ts | 2 +- yarn.lock | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index 4cc3d8a840..ea114ab59e 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -49,6 +49,6 @@ "xstream": "^11.10.0" }, "devDependencies": { - "@iov/utils": "^2.3.2" + "@cosmjs/utils": "^0.20.0" } } diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts index a900a81e21..8f1fc6e29b 100644 --- a/packages/tendermint-rpc/src/client.spec.ts +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -1,7 +1,7 @@ // tslint:disable:readonly-array import { toAscii } from "@cosmjs/encoding"; +import { sleep } from "@cosmjs/utils"; import { firstEvent, toListPromise } from "@iov/stream"; -import { sleep } from "@iov/utils"; import { ReadonlyDate } from "readonly-date"; import { Stream } from "xstream"; diff --git a/yarn.lock b/yarn.lock index 22e4979d30..02e874ef63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -128,11 +128,6 @@ dependencies: xstream "^11.10.0" -"@iov/utils@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@iov/utils/-/utils-2.3.2.tgz#a499ec304b4febaeb3af309dedbb30e14a09c91e" - integrity sha512-mtdZ8zh/LGjwA72HofOc8JF3KN1Rc1jwaQATePLDwIIJRw0AJXx2GLRBBRjja41huuw9ND0E2mQWlYLtYsNnUA== - "@koa/cors@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.0.0.tgz#df021b4df2dadf1e2b04d7c8ddf93ba2d42519cb" From caf5ff7ce89df50eb8b89bade9afdff0185eea62 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:47:55 +0100 Subject: [PATCH 07/20] tendermint-rpc: Remove v0.31 support --- packages/tendermint-rpc/README.md | 6 +- .../tendermint-rpc/src/adaptorforversion.ts | 6 +- packages/tendermint-rpc/src/config.spec.ts | 5 - packages/tendermint-rpc/src/index.ts | 2 +- .../tendermint-rpc/src/v0-31/hasher.spec.ts | 98 --- packages/tendermint-rpc/src/v0-31/hasher.ts | 71 -- packages/tendermint-rpc/src/v0-31/index.ts | 12 - packages/tendermint-rpc/src/v0-31/requests.ts | 142 ---- .../tendermint-rpc/src/v0-31/responses.ts | 773 ------------------ packages/tendermint-rpc/types/index.d.ts | 1 - .../tendermint-rpc/types/v0-31/hasher.d.ts | 4 - .../tendermint-rpc/types/v0-31/index.d.ts | 2 - .../tendermint-rpc/types/v0-31/requests.d.ts | 18 - .../tendermint-rpc/types/v0-31/responses.d.ts | 23 - 14 files changed, 5 insertions(+), 1158 deletions(-) delete mode 100644 packages/tendermint-rpc/src/v0-31/hasher.spec.ts delete mode 100644 packages/tendermint-rpc/src/v0-31/hasher.ts delete mode 100644 packages/tendermint-rpc/src/v0-31/index.ts delete mode 100644 packages/tendermint-rpc/src/v0-31/requests.ts delete mode 100644 packages/tendermint-rpc/src/v0-31/responses.ts delete mode 100644 packages/tendermint-rpc/types/v0-31/hasher.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-31/index.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-31/requests.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-31/responses.d.ts diff --git a/packages/tendermint-rpc/README.md b/packages/tendermint-rpc/README.md index 0b7db50226..95b6126fea 100644 --- a/packages/tendermint-rpc/README.md +++ b/packages/tendermint-rpc/README.md @@ -16,9 +16,7 @@ automatically, and call: ```ts import { Client } from "@iov/tendermint-rpc"; -const client = await Client.connect( - "ws://rpc-private-a-x-exchangenet.iov.one:16657", -); +const client = await Client.connect("ws://rpc-private-a-x-exchangenet.iov.one:16657"); const genesis = await client.genesis(); const status = await client.status(); @@ -69,7 +67,7 @@ and another for decoding [Responses](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/responses.html). The Tendermint version-specific functionality is implemented in global objects (like e.g. -[v0_31](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/globals.html#v0_31)). +[v0_32](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/globals.html#v0_32)). This knowledge is mainly for those who want to add support for new versions, which should be added to the [auto-detect method](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/client.html#detectversion). diff --git a/packages/tendermint-rpc/src/adaptorforversion.ts b/packages/tendermint-rpc/src/adaptorforversion.ts index 5196b07720..da7c0e6da2 100644 --- a/packages/tendermint-rpc/src/adaptorforversion.ts +++ b/packages/tendermint-rpc/src/adaptorforversion.ts @@ -1,7 +1,7 @@ +/* eslint-disable @typescript-eslint/camelcase */ // This module exposes translators for multiple tendermint versions // Pick a version that matches the server to properly encode the data types import { Adaptor } from "./adaptor"; -import { v0_31 } from "./v0-31"; import { v0_32 } from "./v0-32"; /** @@ -11,9 +11,7 @@ import { v0_32 } from "./v0-32"; * @param version full Tendermint version string, e.g. "0.20.1" */ export function adaptorForVersion(version: string): Adaptor { - if (version.startsWith("0.31.")) { - return v0_31; - } else if (version.startsWith("0.32.")) { + if (version.startsWith("0.32.")) { return v0_32; } else { throw new Error(`Unsupported tendermint version: ${version}`); diff --git a/packages/tendermint-rpc/src/config.spec.ts b/packages/tendermint-rpc/src/config.spec.ts index f30aef5499..68b28804e6 100644 --- a/packages/tendermint-rpc/src/config.spec.ts +++ b/packages/tendermint-rpc/src/config.spec.ts @@ -17,11 +17,6 @@ export interface TendermintInstance { * docker container kill */ export const tendermintInstances: readonly TendermintInstance[] = [ - { - url: "localhost:11131", - version: "0.31.x", - appCreator: "Cosmoshi Netowoko", - }, { url: "localhost:11132", version: "0.32.x", diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index f3b010efa4..9fe8c2ac4b 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -1,5 +1,5 @@ +/* eslint-disable @typescript-eslint/camelcase */ // exported to access version-specific hashing -export { v0_31 } from "./v0-31"; export { v0_32 } from "./v0-32"; export { Client } from "./client"; diff --git a/packages/tendermint-rpc/src/v0-31/hasher.spec.ts b/packages/tendermint-rpc/src/v0-31/hasher.spec.ts deleted file mode 100644 index 95b8aea44c..0000000000 --- a/packages/tendermint-rpc/src/v0-31/hasher.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { fromBase64, fromHex } from "@cosmjs/encoding"; -import { ReadonlyDate } from "readonly-date"; - -import { ReadonlyDateWithNanoseconds } from "../responses"; -import { TxBytes } from "../types"; -import { hashBlock, hashTx } from "./hasher"; - -describe("Hasher", () => { - it("creates transaction hash equal to local test", () => { - // This was taken from a result from /tx_search of some random test transaction - // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" - const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); - const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg==") as TxBytes; - expect(hashTx(txData)).toEqual(txId); - }); - - it("creates block hash equal to local test for empty block", () => { - // This was taken from a result from /block of some random empty block - // curl "http://localhost:11131/block" - const blockId = fromHex("5B5D3F7E77A4BD6CB6067947E478BC3BD493DD24A981535F0ADEBDAAA0498480"); - const time = new ReadonlyDate("2019-09-19T10:41:24.898178746Z"); - // tslint:disable-next-line:no-object-mutation - (time as any).nanoseconds = 178746; - const blockData = { - version: { - block: 10, - app: 1, - }, - chainId: "test-chain-RRlV24", - height: 2195, - time: time as ReadonlyDateWithNanoseconds, - numTxs: 0, - totalTxs: 20, - - lastBlockId: { - hash: fromHex("1D38C4FE5C1D8C3CC1F47602BF107C9B269BA7DA3514DEDF958F5A33AB75C06B"), - parts: { - total: 1, - hash: fromHex("C441341B7D846DDA6AF72F83DF68C9AF93665FE5280B136CA29C7411D280DAEC"), - }, - }, - - lastCommitHash: fromHex("0C5EEF7AE1275337BFAA173F57799AA90830E74AFF3FB03D1F579DA37BCAEAB1"), - dataHash: fromHex(""), - - validatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), - nextValidatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), - consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), - appHash: fromHex("2800000000000000"), - lastResultsHash: fromHex(""), - - evidenceHash: fromHex(""), - proposerAddress: fromHex("057B8C349E591579EDFCC0E5D5402E3076E99675"), - }; - expect(hashBlock(blockData)).toEqual(blockId); - }); - - it("creates block hash equal to local test for block with a transaction", () => { - // This was taken from a result from /block of some random block with a transaction - // curl "http://localhost:11131/block?height=5940" - const blockId = fromHex("1C4777AFBBA49E15D031A830E62E7BE986823938732B872C02B8A3D16BD3163B"); - const time = new ReadonlyDate("2019-09-24T10:51:28.240847497Z"); - // tslint:disable-next-line:no-object-mutation - (time as any).nanoseconds = 847497; - const blockData = { - version: { - block: 10, - app: 1, - }, - chainId: "test-chain-lY9FO6", - height: 5940, - time: time as ReadonlyDateWithNanoseconds, - numTxs: 1, - totalTxs: 61, - - lastBlockId: { - hash: fromHex("D2983E6AEEFC55E0A46565CD2274CCD21CB013F5602B0C35A423A99D1120DB13"), - parts: { - total: 1, - hash: fromHex("AA55D7F92AD3A9CFDA8C5E45F95B03AEF9FB38AB984FD762E5CE20791324369D"), - }, - }, - - lastCommitHash: fromHex("5DBFFDBE41878AEB947176D3E0B0DC70850B0A61F8B709ED132FEA59664DFCE5"), - dataHash: fromHex("90FE1A62418F68B411915EEF6792B134693D9D0148432BA661D91213B0CCD15A"), - - validatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), - nextValidatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), - consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), - appHash: fromHex("7800000000000000"), - lastResultsHash: fromHex("6E340B9CFFB37A989CA544E6BB780A2C78901D3FB33738768511A30617AFA01D"), - - evidenceHash: fromHex(""), - proposerAddress: fromHex("6BCBB90987613FE15D3DEFA4920E9F98425698FF"), - }; - expect(hashBlock(blockData)).toEqual(blockId); - }); -}); diff --git a/packages/tendermint-rpc/src/v0-31/hasher.ts b/packages/tendermint-rpc/src/v0-31/hasher.ts deleted file mode 100644 index eba2392964..0000000000 --- a/packages/tendermint-rpc/src/v0-31/hasher.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Sha256 } from "@cosmjs/crypto"; - -import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; -import { Header } from "../responses"; -import { BlockHash, TxBytes, TxHash } from "../types"; - -// hash is sha256 -// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 -export function hashTx(tx: TxBytes): TxHash { - const hash = new Sha256(tx).digest(); - return hash as TxHash; -} - -function getSplitPoint(n: number): number { - if (n < 1) throw new Error("Cannot split an empty tree"); - const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); - return largestPowerOf2 < n ? largestPowerOf2 : largestPowerOf2 / 2; -} - -function hashLeaf(leaf: Uint8Array): Uint8Array { - const hash = new Sha256(Uint8Array.from([0])); - hash.update(leaf); - return hash.digest(); -} - -function hashInner(left: Uint8Array, right: Uint8Array): Uint8Array { - const hash = new Sha256(Uint8Array.from([1])); - hash.update(left); - hash.update(right); - return hash.digest(); -} - -// See https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/encoding.md#merkleroot -// Note: the hashes input may not actually be hashes, especially before a recursive call -function hashTree(hashes: readonly Uint8Array[]): Uint8Array { - switch (hashes.length) { - case 0: - throw new Error("Cannot hash empty tree"); - case 1: - return hashLeaf(hashes[0]); - default: { - const slicePoint = getSplitPoint(hashes.length); - const left = hashTree(hashes.slice(0, slicePoint)); - const right = hashTree(hashes.slice(slicePoint)); - return hashInner(left, right); - } - } -} - -export function hashBlock(header: Header): BlockHash { - const encodedFields: readonly Uint8Array[] = [ - encodeVersion(header.version), - encodeString(header.chainId), - encodeInt(header.height), - encodeTime(header.time), - encodeInt(header.numTxs), - encodeInt(header.totalTxs), - encodeBlockId(header.lastBlockId), - - encodeBytes(header.lastCommitHash), - encodeBytes(header.dataHash), - encodeBytes(header.validatorsHash), - encodeBytes(header.nextValidatorsHash), - encodeBytes(header.consensusHash), - encodeBytes(header.appHash), - encodeBytes(header.lastResultsHash), - encodeBytes(header.evidenceHash), - encodeBytes(header.proposerAddress), - ]; - return hashTree(encodedFields) as BlockHash; -} diff --git a/packages/tendermint-rpc/src/v0-31/index.ts b/packages/tendermint-rpc/src/v0-31/index.ts deleted file mode 100644 index cdb29496e6..0000000000 --- a/packages/tendermint-rpc/src/v0-31/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Adaptor } from "../adaptor"; -import { hashBlock, hashTx } from "./hasher"; -import { Params } from "./requests"; -import { Responses } from "./responses"; - -// tslint:disable-next-line:variable-name -export const v0_31: Adaptor = { - params: Params, - responses: Responses, - hashTx: hashTx, - hashBlock: hashBlock, -}; diff --git a/packages/tendermint-rpc/src/v0-31/requests.ts b/packages/tendermint-rpc/src/v0-31/requests.ts deleted file mode 100644 index 7526fe0ae3..0000000000 --- a/packages/tendermint-rpc/src/v0-31/requests.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { toHex } from "@cosmjs/encoding"; -import { JsonRpcRequest } from "@iov/jsonrpc"; - -import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; -import { createJsonRpcRequest } from "../jsonrpc"; -import * as requests from "../requests"; - -interface HeightParam { - readonly height?: number; -} -interface RpcHeightParam { - readonly height?: IntegerString; -} -function encodeHeightParam(param: HeightParam): RpcHeightParam { - return { - height: may(Integer.encode, param.height), - }; -} - -interface RpcBlockchainRequestParams { - readonly minHeight?: IntegerString; - readonly maxHeight?: IntegerString; -} -function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { - return { - minHeight: may(Integer.encode, param.minHeight), - maxHeight: may(Integer.encode, param.maxHeight), - }; -} - -interface RpcAbciQueryParams { - readonly path: string; - readonly data: HexString; - readonly height?: string; - readonly prove?: boolean; -} - -function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { - return { - path: assertNotEmpty(params.path), - data: toHex(params.data) as HexString, - height: may(Integer.encode, params.height), - prove: params.prove, - }; -} - -interface RpcBroadcastTxParams { - readonly tx: Base64String; -} -function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { - return { - tx: Base64.encode(assertNotEmpty(params.tx)), - }; -} - -interface RpcTxParams { - readonly hash: Base64String; - readonly prove?: boolean; -} -function encodeTxParams(params: requests.TxParams): RpcTxParams { - return { - hash: Base64.encode(assertNotEmpty(params.hash)), - prove: params.prove, - }; -} - -interface RpcTxSearchParams { - readonly query: requests.QueryString; - readonly prove?: boolean; - readonly page?: IntegerString; - readonly per_page?: IntegerString; -} -function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { - return { - query: params.query, - prove: params.prove, - page: may(Integer.encode, params.page), - // eslint-disable-next-line @typescript-eslint/camelcase - per_page: may(Integer.encode, params.per_page), - }; -} - -export class Params { - public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); - } - - public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); - } - - public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); - } - - public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { - const eventTag = { key: "tm.event", value: req.query.type }; - const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); - return createJsonRpcRequest("subscribe", { query: query }); - } - - public static encodeTx(req: requests.TxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxParams(req.params)); - } - - // TODO: encode params for query string??? - public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); - } - - public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } -} diff --git a/packages/tendermint-rpc/src/v0-31/responses.ts b/packages/tendermint-rpc/src/v0-31/responses.ts deleted file mode 100644 index 86ea149ae9..0000000000 --- a/packages/tendermint-rpc/src/v0-31/responses.ts +++ /dev/null @@ -1,773 +0,0 @@ -import { fromHex } from "@cosmjs/encoding"; -import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; - -import { - assertArray, - assertBoolean, - assertNotEmpty, - assertNumber, - assertObject, - assertSet, - Base64, - Base64String, - DateTime, - DateTimeString, - dictionaryToStringMap, - Hex, - HexString, - Integer, - IntegerString, - may, - optional, -} from "../encodings"; -import * as responses from "../responses"; -import { SubscriptionEvent } from "../rpcclients"; -import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; -import { hashTx } from "./hasher"; - -interface AbciInfoResult { - readonly response: RpcAbciInfoResponse; -} - -interface RpcAbciInfoResponse { - readonly data?: string; - readonly last_block_height?: IntegerString; - readonly last_block_app_hash?: Base64String; -} - -function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { - return { - data: data.data, - lastBlockHeight: may(Integer.parse, data.last_block_height), - lastBlockAppHash: may(Base64.decode, data.last_block_app_hash), - }; -} - -interface AbciQueryResult { - readonly response: RpcAbciQueryResponse; -} - -interface RpcAbciQueryResponse { - readonly key: Base64String; - readonly value?: Base64String; - readonly proof?: Base64String; - readonly height?: IntegerString; - readonly index?: IntegerString; - readonly code?: IntegerString; // only for errors - readonly log?: string; -} - -function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { - return { - key: Base64.decode(optional(data.key, "" as Base64String)), - value: Base64.decode(optional(data.value, "" as Base64String)), - // proof: may(Base64.decode, data.proof), - height: may(Integer.parse, data.height), - code: may(Integer.parse, data.code), - index: may(Integer.parse, data.index), - log: data.log, - }; -} -interface RpcTag { - readonly key: Base64String; - readonly value: Base64String; -} - -function decodeTag(tag: RpcTag): responses.Tag { - return { - key: Base64.decode(assertNotEmpty(tag.key)), - value: Base64.decode(assertNotEmpty(tag.value)), - }; -} - -function decodeTags(tags: readonly RpcTag[]): readonly responses.Tag[] { - return assertArray(tags).map(decodeTag); -} - -interface RpcTxData { - readonly code?: number; - readonly log?: string; - readonly data?: Base64String; - readonly tags?: readonly RpcTag[]; -} - -function decodeTxData(data: RpcTxData): responses.TxData { - return { - data: may(Base64.decode, data.data), - log: data.log, - code: Integer.parse(assertNumber(optional(data.code, 0))), - tags: may(decodeTags, data.tags), - }; -} - -// yes, a different format for status and dump consensus state -interface RpcPubkey { - readonly type: string; - readonly value: Base64String; -} - -function decodePubkey(data: RpcPubkey): ValidatorPubkey { - if (data.type === "tendermint/PubKeyEd25519") { - // go-amino special code - return { - algorithm: "ed25519", - data: Base64.decode(assertNotEmpty(data.value)), - }; - } - throw new Error(`unknown pubkey type: ${data.type}`); -} - -// for evidence, block results, etc. -interface RpcValidatorUpdate { - readonly address: HexString; - readonly pub_key: RpcPubkey; - readonly voting_power: IntegerString; -} - -function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: Hex.decode(assertNotEmpty(data.address)), - }; -} - -interface RpcBlockParams { - readonly max_bytes: IntegerString; - readonly max_gas: IntegerString; -} - -/** - * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry - * - * > Add time_iota_ms to block's consensus parameters (not exposed to the application) - * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 - */ -function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { - return { - maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), - maxGas: Integer.parse(assertNotEmpty(data.max_gas)), - }; -} - -interface RpcEvidenceParams { - readonly max_age: IntegerString; -} - -function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { - return { - maxAge: Integer.parse(assertNotEmpty(data.max_age)), - }; -} - -/** - * Example data: - * { - * "block": { - * "max_bytes": "22020096", - * "max_gas": "-1", - * "time_iota_ms": "1000" - * }, - * "evidence": { - * "max_age": "100000" - * }, - * "validator": { - * "pub_key_types": [ - * "ed25519" - * ] - * } - * } - */ -interface RpcConsensusParams { - readonly block: RpcBlockParams; - readonly evidence: RpcEvidenceParams; -} - -function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { - return { - block: decodeBlockParams(assertObject(data.block)), - evidence: decodeEvidenceParams(assertObject(data.evidence)), - }; -} - -interface RpcBlockResultsResponse { - readonly height: IntegerString; - readonly results: { - readonly DeliverTx: readonly RpcTxData[]; - readonly EndBlock: { - readonly validator_updates?: readonly RpcValidatorUpdate[]; - readonly consensus_param_updates?: RpcConsensusParams; - readonly tags?: readonly RpcTag[]; - }; - }; -} - -function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { - const res = optional(data.results.DeliverTx, [] as readonly RpcTxData[]); - const end = data.results.EndBlock; - const validators = optional(end.validator_updates, [] as readonly RpcValidatorUpdate[]); - return { - height: Integer.parse(assertNotEmpty(data.height)), - results: assertArray(res).map(decodeTxData), - endBlock: { - validatorUpdates: assertArray(validators).map(decodeValidatorUpdate), - consensusUpdates: may(decodeConsensusParams, end.consensus_param_updates), - tags: may(decodeTags, end.tags), - }, - }; -} - -interface RpcBlockId { - readonly hash: HexString; - readonly parts: { - readonly total: IntegerString; - readonly hash: HexString; - }; -} - -function decodeBlockId(data: RpcBlockId): responses.BlockId { - return { - hash: fromHex(assertNotEmpty(data.hash)), - parts: { - total: Integer.parse(assertNotEmpty(data.parts.total)), - hash: fromHex(assertNotEmpty(data.parts.hash)), - }, - }; -} - -interface RpcBlockVersion { - readonly block: IntegerString; - readonly app: IntegerString; -} - -function decodeBlockVersion(data: RpcBlockVersion): responses.Version { - return { - block: Integer.parse(data.block), - app: Integer.parse(data.app), - }; -} - -interface RpcHeader { - readonly version: RpcBlockVersion; - readonly chain_id: string; - readonly height: IntegerString; - readonly time: DateTimeString; - readonly num_txs: IntegerString; - readonly total_txs: IntegerString; - - readonly last_block_id: RpcBlockId; - - readonly last_commit_hash: HexString; - readonly data_hash: HexString; - - readonly validators_hash: HexString; - readonly next_validators_hash: HexString; - readonly consensus_hash: HexString; - readonly app_hash: HexString; - readonly last_results_hash: HexString; - - readonly evidence_hash: HexString; - readonly proposer_address: HexString; -} - -function decodeHeader(data: RpcHeader): responses.Header { - return { - version: decodeBlockVersion(data.version), - chainId: assertNotEmpty(data.chain_id), - height: Integer.parse(assertNotEmpty(data.height)), - time: DateTime.decode(assertNotEmpty(data.time)), - numTxs: Integer.parse(assertNotEmpty(data.num_txs)), - totalTxs: Integer.parse(assertNotEmpty(data.total_txs)), - - lastBlockId: decodeBlockId(data.last_block_id), - - lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), - dataHash: fromHex(assertSet(data.data_hash)), - - validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), - nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), - consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), - appHash: fromHex(assertNotEmpty(data.app_hash)), - lastResultsHash: fromHex(assertSet(data.last_results_hash)), - - evidenceHash: fromHex(assertSet(data.evidence_hash)), - proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), - }; -} - -interface RpcBlockMeta { - readonly block_id: RpcBlockId; - readonly header: RpcHeader; -} - -function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { - return { - blockId: decodeBlockId(data.block_id), - header: decodeHeader(data.header), - }; -} - -interface RpcBlockchainResponse { - readonly last_height: IntegerString; - readonly block_metas: readonly RpcBlockMeta[]; -} - -function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { - return { - lastHeight: Integer.parse(assertNotEmpty(data.last_height)), - blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), - }; -} - -interface RpcBroadcastTxSyncResponse extends RpcTxData { - readonly hash: HexString; -} - -function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { - return { - ...decodeTxData(data), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - }; -} - -interface RpcBroadcastTxCommitResponse { - readonly height?: IntegerString; - readonly hash: HexString; - readonly check_tx: RpcTxData; - readonly deliver_tx?: RpcTxData; -} - -function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { - return { - height: may(Integer.parse, data.height), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - checkTx: decodeTxData(assertObject(data.check_tx)), - deliverTx: may(decodeTxData, data.deliver_tx), - }; -} - -type RpcSignature = Base64String; - -function decodeSignature(data: RpcSignature): ValidatorSignature { - return { - algorithm: "ed25519", - data: Base64.decode(assertNotEmpty(data)), - }; -} - -interface RpcVote { - readonly type: number; - readonly validator_address: HexString; - readonly validator_index: IntegerString; - readonly height: IntegerString; - readonly round: IntegerString; - readonly timestamp: DateTimeString; - readonly block_id: RpcBlockId; - readonly signature: RpcSignature; -} - -function decodeVote(data: RpcVote): responses.Vote { - return { - type: Integer.parse(assertNumber(data.type)), - validatorAddress: fromHex(assertNotEmpty(data.validator_address)), - validatorIndex: Integer.parse(assertNotEmpty(data.validator_index)), - height: Integer.parse(assertNotEmpty(data.height)), - round: Integer.parse(assertNotEmpty(data.round)), - timestamp: DateTime.decode(assertNotEmpty(data.timestamp)), - blockId: decodeBlockId(assertObject(data.block_id)), - signature: decodeSignature(assertNotEmpty(data.signature)), - }; -} - -interface RpcCommit { - readonly block_id: RpcBlockId; - readonly precommits: readonly RpcVote[]; -} - -function decodeCommit(data: RpcCommit): responses.Commit { - return { - blockId: decodeBlockId(assertObject(data.block_id)), - precommits: assertArray(data.precommits).map(decodeVote), - }; -} - -interface RpcCommitResponse { - readonly signed_header: { - readonly header: RpcHeader; - readonly commit: RpcCommit; - }; - readonly canonical: boolean; -} - -function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { - return { - canonical: assertBoolean(data.canonical), - header: decodeHeader(data.signed_header.header), - commit: decodeCommit(data.signed_header.commit), - }; -} - -interface RpcValidatorGenesis { - readonly pub_key: RpcPubkey; - readonly power: IntegerString; - readonly name?: string; -} - -function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.power)), - name: data.name, - }; -} - -interface RpcGenesisResponse { - readonly genesis_time: DateTimeString; - readonly chain_id: string; - readonly consensus_params: RpcConsensusParams; - readonly validators: readonly RpcValidatorGenesis[]; - readonly app_hash: HexString; - readonly app_state: {} | undefined; -} - -interface GenesisResult { - readonly genesis: RpcGenesisResponse; -} - -function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { - return { - genesisTime: DateTime.decode(assertNotEmpty(data.genesis_time)), - chainId: assertNotEmpty(data.chain_id), - consensusParams: decodeConsensusParams(data.consensus_params), - validators: assertArray(data.validators).map(decodeValidatorGenesis), - appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app - appState: data.app_state, - }; -} - -// this is in status -interface RpcValidatorInfo { - readonly address: HexString; - readonly pub_key: RpcPubkey; - readonly voting_power: IntegerString; -} - -function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: fromHex(assertNotEmpty(data.address)), - }; -} - -interface RpcNodeInfo { - readonly id: HexString; - readonly listen_addr: IpPortString; - readonly network: string; - readonly version: string; - readonly channels: string; // ??? - readonly moniker: string; - readonly protocol_version: { - readonly p2p: IntegerString; - readonly block: IntegerString; - readonly app: IntegerString; - }; - /** - * Additional information. E.g. - * { - * "tx_index": "on", - * "rpc_address":"tcp://0.0.0.0:26657" - * } - */ - readonly other: object; -} - -function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { - return { - id: fromHex(assertNotEmpty(data.id)), - listenAddr: assertNotEmpty(data.listen_addr), - network: assertNotEmpty(data.network), - version: assertNotEmpty(data.version), - channels: assertNotEmpty(data.channels), - moniker: assertNotEmpty(data.moniker), - other: dictionaryToStringMap(data.other), - protocolVersion: { - app: Integer.parse(assertNotEmpty(data.protocol_version.app)), - block: Integer.parse(assertNotEmpty(data.protocol_version.block)), - p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), - }, - }; -} - -interface RpcSyncInfo { - readonly latest_block_hash: HexString; - readonly latest_app_hash: HexString; - readonly latest_block_height: IntegerString; - readonly latest_block_time: DateTimeString; - readonly catching_up: boolean; -} - -function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { - return { - latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), - latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), - latestBlockTime: DateTime.decode(assertNotEmpty(data.latest_block_time)), - latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), - catchingUp: assertBoolean(data.catching_up), - }; -} - -interface RpcStatusResponse { - readonly node_info: RpcNodeInfo; - readonly sync_info: RpcSyncInfo; - readonly validator_info: RpcValidatorInfo; -} - -function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { - return { - nodeInfo: decodeNodeInfo(data.node_info), - syncInfo: decodeSyncInfo(data.sync_info), - validatorInfo: decodeValidatorInfo(data.validator_info), - }; -} - -/** - * Example data: - * { - * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", - * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", - * "Proof": { - * "total": "1", - * "index": "0", - * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", - * "aunts": [] - * } - * } - */ -interface RpcTxProof { - readonly Data: Base64String; - readonly RootHash: HexString; - readonly Proof: { - readonly total: IntegerString; - readonly index: IntegerString; - readonly leaf_hash: Base64String; - readonly aunts: readonly Base64String[]; - }; -} - -function decodeTxProof(data: RpcTxProof): responses.TxProof { - return { - data: Base64.decode(assertNotEmpty(data.Data)), - rootHash: fromHex(assertNotEmpty(data.RootHash)), - proof: { - total: Integer.parse(assertNotEmpty(data.Proof.total)), - index: Integer.parse(assertNotEmpty(data.Proof.index)), - leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), - aunts: assertArray(data.Proof.aunts).map(Base64.decode), - }, - }; -} - -interface RpcTxResponse { - readonly tx: Base64String; - readonly tx_result: RpcTxData; - readonly height: IntegerString; - readonly index: number; - readonly hash: HexString; - readonly proof?: RpcTxProof; -} - -function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { - return { - tx: Base64.decode(assertNotEmpty(data.tx)) as TxBytes, - result: decodeTxData(assertObject(data.tx_result)), - height: Integer.parse(assertNotEmpty(data.height)), - index: Integer.parse(assertNumber(data.index)), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - proof: may(decodeTxProof, data.proof), - }; -} - -interface RpcTxSearchResponse { - readonly txs: readonly RpcTxResponse[]; - readonly total_count: IntegerString; -} - -function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { - return { - totalCount: Integer.parse(assertNotEmpty(data.total_count)), - txs: assertArray(data.txs).map(decodeTxResponse), - }; -} - -interface RpcTxEvent { - readonly tx: Base64String; - readonly result: RpcTxData; - readonly height: IntegerString; - readonly index: number; -} - -function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { - const tx = Base64.decode(assertNotEmpty(data.tx)) as TxBytes; - return { - tx: tx, - hash: hashTx(tx), - result: decodeTxData(data.result), - height: Integer.parse(assertNotEmpty(data.height)), - index: Integer.parse(assertNumber(data.index)), - }; -} - -// for validators -interface RpcValidatorData extends RpcValidatorUpdate { - readonly accum?: IntegerString; -} - -function decodeValidatorData(data: RpcValidatorData): responses.Validator { - return { - ...decodeValidatorUpdate(data), - accum: may(Integer.parse, data.accum), - }; -} - -interface RpcValidatorsResponse { - readonly block_height: IntegerString; - readonly validators: readonly RpcValidatorData[]; -} - -function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { - return { - blockHeight: Integer.parse(assertNotEmpty(data.block_height)), - results: assertArray(data.validators).map(decodeValidatorData), - }; -} - -interface RpcEvidence { - readonly type: string; - readonly validator: RpcValidatorUpdate; - readonly height: IntegerString; - readonly time: IntegerString; - readonly totalVotingPower: IntegerString; -} - -function decodeEvidence(data: RpcEvidence): responses.Evidence { - return { - type: assertNotEmpty(data.type), - height: Integer.parse(assertNotEmpty(data.height)), - time: Integer.parse(assertNotEmpty(data.time)), - totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), - validator: decodeValidatorUpdate(data.validator), - }; -} - -function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { - return assertArray(ev).map(decodeEvidence); -} - -interface RpcBlock { - readonly header: RpcHeader; - readonly last_commit: RpcCommit; - readonly data: { - readonly txs?: readonly Base64String[]; - }; - readonly evidence?: { - readonly evidence?: readonly RpcEvidence[]; - }; -} - -function decodeBlock(data: RpcBlock): responses.Block { - return { - header: decodeHeader(assertObject(data.header)), - lastCommit: decodeCommit(assertObject(data.last_commit)), - txs: data.data.txs ? assertArray(data.data.txs).map(Base64.decode) : [], - evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), - }; -} - -interface RpcBlockResponse { - readonly block_meta: RpcBlockMeta; - readonly block: RpcBlock; -} - -function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { - return { - blockMeta: decodeBlockMeta(data.block_meta), - block: decodeBlock(data.block), - }; -} - -export class Responses { - public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { - return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); - } - - public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { - return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); - } - - public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { - return decodeBlockResponse(response.result as RpcBlockResponse); - } - - public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { - return decodeBlockResults(response.result as RpcBlockResultsResponse); - } - - public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { - return decodeBlockchain(response.result as RpcBlockchainResponse); - } - - public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { - return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); - } - - public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { - return this.decodeBroadcastTxSync(response); - } - - public static decodeBroadcastTxCommit( - response: JsonRpcSuccessResponse, - ): responses.BroadcastTxCommitResponse { - return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); - } - - public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { - return decodeCommitResponse(response.result as RpcCommitResponse); - } - - public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { - return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); - } - - public static decodeHealth(): responses.HealthResponse { - return null; - } - - public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { - return decodeStatus(response.result as RpcStatusResponse); - } - - public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { - return decodeBlock(event.data.value.block as RpcBlock); - } - - public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { - return decodeHeader(event.data.value.header as RpcHeader); - } - - public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { - return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); - } - - public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { - return decodeTxResponse(response.result as RpcTxResponse); - } - - public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { - return decodeTxSearch(response.result as RpcTxSearchResponse); - } - - public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { - return decodeValidators(response.result as RpcValidatorsResponse); - } -} diff --git a/packages/tendermint-rpc/types/index.d.ts b/packages/tendermint-rpc/types/index.d.ts index 547da1d293..8bc19a71eb 100644 --- a/packages/tendermint-rpc/types/index.d.ts +++ b/packages/tendermint-rpc/types/index.d.ts @@ -1,4 +1,3 @@ -export { v0_31 } from "./v0-31"; export { v0_32 } from "./v0-32"; export { Client } from "./client"; export { diff --git a/packages/tendermint-rpc/types/v0-31/hasher.d.ts b/packages/tendermint-rpc/types/v0-31/hasher.d.ts deleted file mode 100644 index de2e7e9012..0000000000 --- a/packages/tendermint-rpc/types/v0-31/hasher.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Header } from "../responses"; -import { BlockHash, TxBytes, TxHash } from "../types"; -export declare function hashTx(tx: TxBytes): TxHash; -export declare function hashBlock(header: Header): BlockHash; diff --git a/packages/tendermint-rpc/types/v0-31/index.d.ts b/packages/tendermint-rpc/types/v0-31/index.d.ts deleted file mode 100644 index 3d2d1e080c..0000000000 --- a/packages/tendermint-rpc/types/v0-31/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { Adaptor } from "../adaptor"; -export declare const v0_31: Adaptor; diff --git a/packages/tendermint-rpc/types/v0-31/requests.d.ts b/packages/tendermint-rpc/types/v0-31/requests.d.ts deleted file mode 100644 index af972c4a92..0000000000 --- a/packages/tendermint-rpc/types/v0-31/requests.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { JsonRpcRequest } from "@iov/jsonrpc"; -import * as requests from "../requests"; -export declare class Params { - static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest; - static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest; - static encodeBlock(req: requests.BlockRequest): JsonRpcRequest; - static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest; - static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest; - static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest; - static encodeCommit(req: requests.CommitRequest): JsonRpcRequest; - static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest; - static encodeHealth(req: requests.HealthRequest): JsonRpcRequest; - static encodeStatus(req: requests.StatusRequest): JsonRpcRequest; - static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest; - static encodeTx(req: requests.TxRequest): JsonRpcRequest; - static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest; - static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest; -} diff --git a/packages/tendermint-rpc/types/v0-31/responses.d.ts b/packages/tendermint-rpc/types/v0-31/responses.d.ts deleted file mode 100644 index 05fad338b2..0000000000 --- a/packages/tendermint-rpc/types/v0-31/responses.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; -import * as responses from "../responses"; -import { SubscriptionEvent } from "../rpcclients"; -export declare class Responses { - static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse; - static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse; - static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse; - static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse; - static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse; - static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse; - static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse; - static decodeBroadcastTxCommit(response: JsonRpcSuccessResponse): responses.BroadcastTxCommitResponse; - static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse; - static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse; - static decodeHealth(): responses.HealthResponse; - static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse; - static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent; - static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent; - static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent; - static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse; - static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse; - static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse; -} From 497ceb1eefede46861b9a3aa86017a708550480b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Mon, 15 Jun 2020 16:49:49 +0100 Subject: [PATCH 08/20] tendermint-rpc: Remove tslint --- packages/tendermint-rpc/package.json | 2 +- packages/tendermint-rpc/tslint.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 packages/tendermint-rpc/tslint.json diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index ea114ab59e..d691eda8e5 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -22,7 +22,7 @@ }, "scripts": { "docs": "shx rm -rf docs && typedoc --options typedoc.js", - "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\" && tslint -t verbose --project .", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "test-node": "node jasmine-testrunner.js", diff --git a/packages/tendermint-rpc/tslint.json b/packages/tendermint-rpc/tslint.json deleted file mode 100644 index 0946f20963..0000000000 --- a/packages/tendermint-rpc/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tslint.json" -} From c458803091dd789bd29ac68a26da0afa5625cccb Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 23 Jun 2020 13:13:57 +0200 Subject: [PATCH 09/20] root: Update Tendermint scripts for v0.33 --- scripts/tendermint/all_start.sh | 3 +-- scripts/tendermint/all_stop.sh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/tendermint/all_start.sh b/scripts/tendermint/all_start.sh index c11964c859..cfb3e4072d 100755 --- a/scripts/tendermint/all_start.sh +++ b/scripts/tendermint/all_start.sh @@ -4,8 +4,7 @@ command -v shellcheck > /dev/null && shellcheck "$0" # Find latest patch releases at https://hub.docker.com/r/tendermint/tendermint/tags/ declare -a TM_VERSIONS -TM_VERSIONS[31]=v0.31.8 -TM_VERSIONS[32]=v0.32.3 +TM_VERSIONS[33]=v0.33.5 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" diff --git a/scripts/tendermint/all_stop.sh b/scripts/tendermint/all_stop.sh index e82b4dab1e..c6e735f4df 100755 --- a/scripts/tendermint/all_stop.sh +++ b/scripts/tendermint/all_stop.sh @@ -3,8 +3,7 @@ set -o errexit -o nounset -o pipefail command -v shellcheck > /dev/null && shellcheck "$0" declare -a TM_VERSIONS -TM_VERSIONS[31]=v0.31.8 -TM_VERSIONS[32]=v0.32.3 +TM_VERSIONS[33]=v0.33.5 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" From 1eab5cd30f7fb88bb19acbcb851313e5ad2d47e7 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 23 Jun 2020 13:19:02 +0200 Subject: [PATCH 10/20] tendermint-rpc: Update package name and version --- packages/tendermint-rpc/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index d691eda8e5..da9a59a06e 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -1,6 +1,6 @@ { - "name": "@iov/tendermint-rpc", - "version": "2.3.2", + "name": "@cosmjs/tendermint-rpc", + "version": "0.20.0", "description": "Codec to encode/decode bns transactions and state objects", "author": "IOV SAS ", "license": "Apache-2.0", From 605d2e9a898c06c62375fddaa52e35334f64a48d Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 23 Jun 2020 13:19:46 +0200 Subject: [PATCH 11/20] tendermint-rpc: :fire: Remove v0.32 codec --- .../tendermint-rpc/src/v0-32/hasher.spec.ts | 98 --- packages/tendermint-rpc/src/v0-32/hasher.ts | 71 -- packages/tendermint-rpc/src/v0-32/index.ts | 12 - packages/tendermint-rpc/src/v0-32/requests.ts | 142 ---- .../tendermint-rpc/src/v0-32/responses.ts | 790 ------------------ .../tendermint-rpc/types/v0-32/hasher.d.ts | 4 - .../tendermint-rpc/types/v0-32/index.d.ts | 2 - .../tendermint-rpc/types/v0-32/requests.d.ts | 18 - .../tendermint-rpc/types/v0-32/responses.d.ts | 23 - 9 files changed, 1160 deletions(-) delete mode 100644 packages/tendermint-rpc/src/v0-32/hasher.spec.ts delete mode 100644 packages/tendermint-rpc/src/v0-32/hasher.ts delete mode 100644 packages/tendermint-rpc/src/v0-32/index.ts delete mode 100644 packages/tendermint-rpc/src/v0-32/requests.ts delete mode 100644 packages/tendermint-rpc/src/v0-32/responses.ts delete mode 100644 packages/tendermint-rpc/types/v0-32/hasher.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-32/index.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-32/requests.d.ts delete mode 100644 packages/tendermint-rpc/types/v0-32/responses.d.ts diff --git a/packages/tendermint-rpc/src/v0-32/hasher.spec.ts b/packages/tendermint-rpc/src/v0-32/hasher.spec.ts deleted file mode 100644 index 95b8aea44c..0000000000 --- a/packages/tendermint-rpc/src/v0-32/hasher.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { fromBase64, fromHex } from "@cosmjs/encoding"; -import { ReadonlyDate } from "readonly-date"; - -import { ReadonlyDateWithNanoseconds } from "../responses"; -import { TxBytes } from "../types"; -import { hashBlock, hashTx } from "./hasher"; - -describe("Hasher", () => { - it("creates transaction hash equal to local test", () => { - // This was taken from a result from /tx_search of some random test transaction - // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" - const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); - const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg==") as TxBytes; - expect(hashTx(txData)).toEqual(txId); - }); - - it("creates block hash equal to local test for empty block", () => { - // This was taken from a result from /block of some random empty block - // curl "http://localhost:11131/block" - const blockId = fromHex("5B5D3F7E77A4BD6CB6067947E478BC3BD493DD24A981535F0ADEBDAAA0498480"); - const time = new ReadonlyDate("2019-09-19T10:41:24.898178746Z"); - // tslint:disable-next-line:no-object-mutation - (time as any).nanoseconds = 178746; - const blockData = { - version: { - block: 10, - app: 1, - }, - chainId: "test-chain-RRlV24", - height: 2195, - time: time as ReadonlyDateWithNanoseconds, - numTxs: 0, - totalTxs: 20, - - lastBlockId: { - hash: fromHex("1D38C4FE5C1D8C3CC1F47602BF107C9B269BA7DA3514DEDF958F5A33AB75C06B"), - parts: { - total: 1, - hash: fromHex("C441341B7D846DDA6AF72F83DF68C9AF93665FE5280B136CA29C7411D280DAEC"), - }, - }, - - lastCommitHash: fromHex("0C5EEF7AE1275337BFAA173F57799AA90830E74AFF3FB03D1F579DA37BCAEAB1"), - dataHash: fromHex(""), - - validatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), - nextValidatorsHash: fromHex("44D7D0BE3C70B58DA87696102E3A52E5C9FA98A717E56D02987DA8CAE86F03F4"), - consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), - appHash: fromHex("2800000000000000"), - lastResultsHash: fromHex(""), - - evidenceHash: fromHex(""), - proposerAddress: fromHex("057B8C349E591579EDFCC0E5D5402E3076E99675"), - }; - expect(hashBlock(blockData)).toEqual(blockId); - }); - - it("creates block hash equal to local test for block with a transaction", () => { - // This was taken from a result from /block of some random block with a transaction - // curl "http://localhost:11131/block?height=5940" - const blockId = fromHex("1C4777AFBBA49E15D031A830E62E7BE986823938732B872C02B8A3D16BD3163B"); - const time = new ReadonlyDate("2019-09-24T10:51:28.240847497Z"); - // tslint:disable-next-line:no-object-mutation - (time as any).nanoseconds = 847497; - const blockData = { - version: { - block: 10, - app: 1, - }, - chainId: "test-chain-lY9FO6", - height: 5940, - time: time as ReadonlyDateWithNanoseconds, - numTxs: 1, - totalTxs: 61, - - lastBlockId: { - hash: fromHex("D2983E6AEEFC55E0A46565CD2274CCD21CB013F5602B0C35A423A99D1120DB13"), - parts: { - total: 1, - hash: fromHex("AA55D7F92AD3A9CFDA8C5E45F95B03AEF9FB38AB984FD762E5CE20791324369D"), - }, - }, - - lastCommitHash: fromHex("5DBFFDBE41878AEB947176D3E0B0DC70850B0A61F8B709ED132FEA59664DFCE5"), - dataHash: fromHex("90FE1A62418F68B411915EEF6792B134693D9D0148432BA661D91213B0CCD15A"), - - validatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), - nextValidatorsHash: fromHex("0A4647900ED90CC605E851BBB4946D7B9D1830F293BC87F3CE16AEFF4E4C77E2"), - consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), - appHash: fromHex("7800000000000000"), - lastResultsHash: fromHex("6E340B9CFFB37A989CA544E6BB780A2C78901D3FB33738768511A30617AFA01D"), - - evidenceHash: fromHex(""), - proposerAddress: fromHex("6BCBB90987613FE15D3DEFA4920E9F98425698FF"), - }; - expect(hashBlock(blockData)).toEqual(blockId); - }); -}); diff --git a/packages/tendermint-rpc/src/v0-32/hasher.ts b/packages/tendermint-rpc/src/v0-32/hasher.ts deleted file mode 100644 index eba2392964..0000000000 --- a/packages/tendermint-rpc/src/v0-32/hasher.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Sha256 } from "@cosmjs/crypto"; - -import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; -import { Header } from "../responses"; -import { BlockHash, TxBytes, TxHash } from "../types"; - -// hash is sha256 -// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 -export function hashTx(tx: TxBytes): TxHash { - const hash = new Sha256(tx).digest(); - return hash as TxHash; -} - -function getSplitPoint(n: number): number { - if (n < 1) throw new Error("Cannot split an empty tree"); - const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); - return largestPowerOf2 < n ? largestPowerOf2 : largestPowerOf2 / 2; -} - -function hashLeaf(leaf: Uint8Array): Uint8Array { - const hash = new Sha256(Uint8Array.from([0])); - hash.update(leaf); - return hash.digest(); -} - -function hashInner(left: Uint8Array, right: Uint8Array): Uint8Array { - const hash = new Sha256(Uint8Array.from([1])); - hash.update(left); - hash.update(right); - return hash.digest(); -} - -// See https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/encoding.md#merkleroot -// Note: the hashes input may not actually be hashes, especially before a recursive call -function hashTree(hashes: readonly Uint8Array[]): Uint8Array { - switch (hashes.length) { - case 0: - throw new Error("Cannot hash empty tree"); - case 1: - return hashLeaf(hashes[0]); - default: { - const slicePoint = getSplitPoint(hashes.length); - const left = hashTree(hashes.slice(0, slicePoint)); - const right = hashTree(hashes.slice(slicePoint)); - return hashInner(left, right); - } - } -} - -export function hashBlock(header: Header): BlockHash { - const encodedFields: readonly Uint8Array[] = [ - encodeVersion(header.version), - encodeString(header.chainId), - encodeInt(header.height), - encodeTime(header.time), - encodeInt(header.numTxs), - encodeInt(header.totalTxs), - encodeBlockId(header.lastBlockId), - - encodeBytes(header.lastCommitHash), - encodeBytes(header.dataHash), - encodeBytes(header.validatorsHash), - encodeBytes(header.nextValidatorsHash), - encodeBytes(header.consensusHash), - encodeBytes(header.appHash), - encodeBytes(header.lastResultsHash), - encodeBytes(header.evidenceHash), - encodeBytes(header.proposerAddress), - ]; - return hashTree(encodedFields) as BlockHash; -} diff --git a/packages/tendermint-rpc/src/v0-32/index.ts b/packages/tendermint-rpc/src/v0-32/index.ts deleted file mode 100644 index 184887c076..0000000000 --- a/packages/tendermint-rpc/src/v0-32/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Adaptor } from "../adaptor"; -import { hashBlock, hashTx } from "./hasher"; -import { Params } from "./requests"; -import { Responses } from "./responses"; - -// tslint:disable-next-line:variable-name -export const v0_32: Adaptor = { - params: Params, - responses: Responses, - hashTx: hashTx, - hashBlock: hashBlock, -}; diff --git a/packages/tendermint-rpc/src/v0-32/requests.ts b/packages/tendermint-rpc/src/v0-32/requests.ts deleted file mode 100644 index 7526fe0ae3..0000000000 --- a/packages/tendermint-rpc/src/v0-32/requests.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { toHex } from "@cosmjs/encoding"; -import { JsonRpcRequest } from "@iov/jsonrpc"; - -import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; -import { createJsonRpcRequest } from "../jsonrpc"; -import * as requests from "../requests"; - -interface HeightParam { - readonly height?: number; -} -interface RpcHeightParam { - readonly height?: IntegerString; -} -function encodeHeightParam(param: HeightParam): RpcHeightParam { - return { - height: may(Integer.encode, param.height), - }; -} - -interface RpcBlockchainRequestParams { - readonly minHeight?: IntegerString; - readonly maxHeight?: IntegerString; -} -function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { - return { - minHeight: may(Integer.encode, param.minHeight), - maxHeight: may(Integer.encode, param.maxHeight), - }; -} - -interface RpcAbciQueryParams { - readonly path: string; - readonly data: HexString; - readonly height?: string; - readonly prove?: boolean; -} - -function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { - return { - path: assertNotEmpty(params.path), - data: toHex(params.data) as HexString, - height: may(Integer.encode, params.height), - prove: params.prove, - }; -} - -interface RpcBroadcastTxParams { - readonly tx: Base64String; -} -function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { - return { - tx: Base64.encode(assertNotEmpty(params.tx)), - }; -} - -interface RpcTxParams { - readonly hash: Base64String; - readonly prove?: boolean; -} -function encodeTxParams(params: requests.TxParams): RpcTxParams { - return { - hash: Base64.encode(assertNotEmpty(params.hash)), - prove: params.prove, - }; -} - -interface RpcTxSearchParams { - readonly query: requests.QueryString; - readonly prove?: boolean; - readonly page?: IntegerString; - readonly per_page?: IntegerString; -} -function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { - return { - query: params.query, - prove: params.prove, - page: may(Integer.encode, params.page), - // eslint-disable-next-line @typescript-eslint/camelcase - per_page: may(Integer.encode, params.per_page), - }; -} - -export class Params { - public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); - } - - public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); - } - - public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); - } - - public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } - - public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method); - } - - public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { - const eventTag = { key: "tm.event", value: req.query.type }; - const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); - return createJsonRpcRequest("subscribe", { query: query }); - } - - public static encodeTx(req: requests.TxRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxParams(req.params)); - } - - // TODO: encode params for query string??? - public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); - } - - public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { - return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); - } -} diff --git a/packages/tendermint-rpc/src/v0-32/responses.ts b/packages/tendermint-rpc/src/v0-32/responses.ts deleted file mode 100644 index 2426b98a07..0000000000 --- a/packages/tendermint-rpc/src/v0-32/responses.ts +++ /dev/null @@ -1,790 +0,0 @@ -import { fromHex } from "@cosmjs/encoding"; -import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; - -import { - assertArray, - assertBoolean, - assertNotEmpty, - assertNumber, - assertObject, - assertSet, - Base64, - Base64String, - DateTime, - DateTimeString, - dictionaryToStringMap, - Hex, - HexString, - Integer, - IntegerString, - may, - optional, -} from "../encodings"; -import * as responses from "../responses"; -import { SubscriptionEvent } from "../rpcclients"; -import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; -import { hashTx } from "./hasher"; - -interface AbciInfoResult { - readonly response: RpcAbciInfoResponse; -} - -interface RpcAbciInfoResponse { - readonly data?: string; - readonly last_block_height?: IntegerString; - readonly last_block_app_hash?: Base64String; -} - -function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { - return { - data: data.data, - lastBlockHeight: may(Integer.parse, data.last_block_height), - lastBlockAppHash: may(Base64.decode, data.last_block_app_hash), - }; -} - -interface AbciQueryResult { - readonly response: RpcAbciQueryResponse; -} - -interface RpcAbciQueryResponse { - readonly key: Base64String; - readonly value?: Base64String; - readonly proof?: Base64String; - readonly height?: IntegerString; - readonly index?: IntegerString; - readonly code?: IntegerString; // only for errors - readonly log?: string; -} - -function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { - return { - key: Base64.decode(optional(data.key, "" as Base64String)), - value: Base64.decode(optional(data.value, "" as Base64String)), - // proof: may(Base64.decode, data.proof), - height: may(Integer.parse, data.height), - code: may(Integer.parse, data.code), - index: may(Integer.parse, data.index), - log: data.log, - }; -} - -interface RpcTag { - readonly key: Base64String; - readonly value: Base64String; -} - -function decodeTag(tag: RpcTag): responses.Tag { - return { - key: Base64.decode(assertNotEmpty(tag.key)), - value: Base64.decode(assertNotEmpty(tag.value)), - }; -} - -function decodeTags(tags: readonly RpcTag[]): readonly responses.Tag[] { - return assertArray(tags).map(decodeTag); -} - -interface RpcEvent { - readonly type: string; - readonly attributes: readonly RpcTag[]; -} - -function decodeEvent(event: RpcEvent): responses.Event { - return { - type: event.type, - attributes: decodeTags(event.attributes), - }; -} - -function decodeEvents(events: readonly RpcEvent[]): readonly responses.Event[] { - return assertArray(events).map(decodeEvent); -} - -interface RpcTxData { - readonly code?: number; - readonly log?: string; - readonly data?: Base64String; - readonly events?: readonly RpcEvent[]; -} - -function decodeTxData(data: RpcTxData): responses.TxData { - return { - data: may(Base64.decode, data.data), - log: data.log, - code: Integer.parse(assertNumber(optional(data.code, 0))), - events: may(decodeEvents, data.events), - }; -} - -// yes, a different format for status and dump consensus state -interface RpcPubkey { - readonly type: string; - readonly value: Base64String; -} - -function decodePubkey(data: RpcPubkey): ValidatorPubkey { - if (data.type === "tendermint/PubKeyEd25519") { - // go-amino special code - return { - algorithm: "ed25519", - data: Base64.decode(assertNotEmpty(data.value)), - }; - } - throw new Error(`unknown pubkey type: ${data.type}`); -} - -// for evidence, block results, etc. -interface RpcValidatorUpdate { - readonly address: HexString; - readonly pub_key: RpcPubkey; - readonly voting_power: IntegerString; -} - -function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: Hex.decode(assertNotEmpty(data.address)), - }; -} - -interface RpcBlockParams { - readonly max_bytes: IntegerString; - readonly max_gas: IntegerString; -} - -/** - * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry - * - * > Add time_iota_ms to block's consensus parameters (not exposed to the application) - * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 - */ -function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { - return { - maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), - maxGas: Integer.parse(assertNotEmpty(data.max_gas)), - }; -} - -interface RpcEvidenceParams { - readonly max_age: IntegerString; -} - -function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { - return { - maxAge: Integer.parse(assertNotEmpty(data.max_age)), - }; -} - -/** - * Example data: - * { - * "block": { - * "max_bytes": "22020096", - * "max_gas": "-1", - * "time_iota_ms": "1000" - * }, - * "evidence": { - * "max_age": "100000" - * }, - * "validator": { - * "pub_key_types": [ - * "ed25519" - * ] - * } - * } - */ -interface RpcConsensusParams { - readonly block: RpcBlockParams; - readonly evidence: RpcEvidenceParams; -} - -function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { - return { - block: decodeBlockParams(assertObject(data.block)), - evidence: decodeEvidenceParams(assertObject(data.evidence)), - }; -} - -interface RpcBlockResultsResponse { - readonly height: IntegerString; - readonly results: { - readonly deliver_tx: readonly RpcTxData[]; - readonly end_block: { - readonly validator_updates?: readonly RpcValidatorUpdate[]; - readonly consensus_param_updates?: RpcConsensusParams; - readonly tags?: readonly RpcTag[]; - }; - }; -} - -function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { - const res = optional(data.results.deliver_tx, [] as readonly RpcTxData[]); - const end = data.results.end_block; - const validators = optional(end.validator_updates, [] as readonly RpcValidatorUpdate[]); - return { - height: Integer.parse(assertNotEmpty(data.height)), - results: assertArray(res).map(decodeTxData), - endBlock: { - validatorUpdates: assertArray(validators).map(decodeValidatorUpdate), - consensusUpdates: may(decodeConsensusParams, end.consensus_param_updates), - tags: may(decodeTags, end.tags), - }, - }; -} - -interface RpcBlockId { - readonly hash: HexString; - readonly parts: { - readonly total: IntegerString; - readonly hash: HexString; - }; -} - -function decodeBlockId(data: RpcBlockId): responses.BlockId { - return { - hash: fromHex(assertNotEmpty(data.hash)), - parts: { - total: Integer.parse(assertNotEmpty(data.parts.total)), - hash: fromHex(assertNotEmpty(data.parts.hash)), - }, - }; -} - -interface RpcBlockVersion { - readonly block: IntegerString; - readonly app: IntegerString; -} - -function decodeBlockVersion(data: RpcBlockVersion): responses.Version { - return { - block: Integer.parse(data.block), - app: Integer.parse(data.app), - }; -} - -interface RpcHeader { - readonly version: RpcBlockVersion; - readonly chain_id: string; - readonly height: IntegerString; - readonly time: DateTimeString; - readonly num_txs: IntegerString; - readonly total_txs: IntegerString; - - readonly last_block_id: RpcBlockId; - - readonly last_commit_hash: HexString; - readonly data_hash: HexString; - - readonly validators_hash: HexString; - readonly next_validators_hash: HexString; - readonly consensus_hash: HexString; - readonly app_hash: HexString; - readonly last_results_hash: HexString; - - readonly evidence_hash: HexString; - readonly proposer_address: HexString; -} - -function decodeHeader(data: RpcHeader): responses.Header { - return { - version: decodeBlockVersion(data.version), - chainId: assertNotEmpty(data.chain_id), - height: Integer.parse(assertNotEmpty(data.height)), - time: DateTime.decode(assertNotEmpty(data.time)), - numTxs: Integer.parse(assertNotEmpty(data.num_txs)), - totalTxs: Integer.parse(assertNotEmpty(data.total_txs)), - - lastBlockId: decodeBlockId(data.last_block_id), - - lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), - dataHash: fromHex(assertSet(data.data_hash)), - - validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), - nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), - consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), - appHash: fromHex(assertNotEmpty(data.app_hash)), - lastResultsHash: fromHex(assertSet(data.last_results_hash)), - - evidenceHash: fromHex(assertSet(data.evidence_hash)), - proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), - }; -} - -interface RpcBlockMeta { - readonly block_id: RpcBlockId; - readonly header: RpcHeader; -} - -function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { - return { - blockId: decodeBlockId(data.block_id), - header: decodeHeader(data.header), - }; -} - -interface RpcBlockchainResponse { - readonly last_height: IntegerString; - readonly block_metas: readonly RpcBlockMeta[]; -} - -function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { - return { - lastHeight: Integer.parse(assertNotEmpty(data.last_height)), - blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), - }; -} - -interface RpcBroadcastTxSyncResponse extends RpcTxData { - readonly hash: HexString; -} - -function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { - return { - ...decodeTxData(data), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - }; -} - -interface RpcBroadcastTxCommitResponse { - readonly height?: IntegerString; - readonly hash: HexString; - readonly check_tx: RpcTxData; - readonly deliver_tx?: RpcTxData; -} - -function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { - return { - height: may(Integer.parse, data.height), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - checkTx: decodeTxData(assertObject(data.check_tx)), - deliverTx: may(decodeTxData, data.deliver_tx), - }; -} - -type RpcSignature = Base64String; - -function decodeSignature(data: RpcSignature): ValidatorSignature { - return { - algorithm: "ed25519", - data: Base64.decode(assertNotEmpty(data)), - }; -} - -interface RpcVote { - readonly type: number; - readonly validator_address: HexString; - readonly validator_index: IntegerString; - readonly height: IntegerString; - readonly round: IntegerString; - readonly timestamp: DateTimeString; - readonly block_id: RpcBlockId; - readonly signature: RpcSignature; -} - -function decodeVote(data: RpcVote): responses.Vote { - return { - type: Integer.parse(assertNumber(data.type)), - validatorAddress: fromHex(assertNotEmpty(data.validator_address)), - validatorIndex: Integer.parse(assertNotEmpty(data.validator_index)), - height: Integer.parse(assertNotEmpty(data.height)), - round: Integer.parse(assertNotEmpty(data.round)), - timestamp: DateTime.decode(assertNotEmpty(data.timestamp)), - blockId: decodeBlockId(assertObject(data.block_id)), - signature: decodeSignature(assertNotEmpty(data.signature)), - }; -} - -interface RpcCommit { - readonly block_id: RpcBlockId; - readonly precommits: readonly RpcVote[]; -} - -function decodeCommit(data: RpcCommit): responses.Commit { - return { - blockId: decodeBlockId(assertObject(data.block_id)), - precommits: assertArray(data.precommits).map(decodeVote), - }; -} - -interface RpcCommitResponse { - readonly signed_header: { - readonly header: RpcHeader; - readonly commit: RpcCommit; - }; - readonly canonical: boolean; -} - -function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { - return { - canonical: assertBoolean(data.canonical), - header: decodeHeader(data.signed_header.header), - commit: decodeCommit(data.signed_header.commit), - }; -} - -interface RpcValidatorGenesis { - readonly pub_key: RpcPubkey; - readonly power: IntegerString; - readonly name?: string; -} - -function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.power)), - name: data.name, - }; -} - -interface RpcGenesisResponse { - readonly genesis_time: DateTimeString; - readonly chain_id: string; - readonly consensus_params: RpcConsensusParams; - readonly validators: readonly RpcValidatorGenesis[]; - readonly app_hash: HexString; - readonly app_state: {} | undefined; -} - -interface GenesisResult { - readonly genesis: RpcGenesisResponse; -} - -function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { - return { - genesisTime: DateTime.decode(assertNotEmpty(data.genesis_time)), - chainId: assertNotEmpty(data.chain_id), - consensusParams: decodeConsensusParams(data.consensus_params), - validators: assertArray(data.validators).map(decodeValidatorGenesis), - appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app - appState: data.app_state, - }; -} - -// this is in status -interface RpcValidatorInfo { - readonly address: HexString; - readonly pub_key: RpcPubkey; - readonly voting_power: IntegerString; -} - -function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { - return { - pubkey: decodePubkey(assertObject(data.pub_key)), - votingPower: Integer.parse(assertNotEmpty(data.voting_power)), - address: fromHex(assertNotEmpty(data.address)), - }; -} - -interface RpcNodeInfo { - readonly id: HexString; - readonly listen_addr: IpPortString; - readonly network: string; - readonly version: string; - readonly channels: string; // ??? - readonly moniker: string; - readonly protocol_version: { - readonly p2p: IntegerString; - readonly block: IntegerString; - readonly app: IntegerString; - }; - /** - * Additional information. E.g. - * { - * "tx_index": "on", - * "rpc_address":"tcp://0.0.0.0:26657" - * } - */ - readonly other: object; -} - -function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { - return { - id: fromHex(assertNotEmpty(data.id)), - listenAddr: assertNotEmpty(data.listen_addr), - network: assertNotEmpty(data.network), - version: assertNotEmpty(data.version), - channels: assertNotEmpty(data.channels), - moniker: assertNotEmpty(data.moniker), - other: dictionaryToStringMap(data.other), - protocolVersion: { - app: Integer.parse(assertNotEmpty(data.protocol_version.app)), - block: Integer.parse(assertNotEmpty(data.protocol_version.block)), - p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), - }, - }; -} - -interface RpcSyncInfo { - readonly latest_block_hash: HexString; - readonly latest_app_hash: HexString; - readonly latest_block_height: IntegerString; - readonly latest_block_time: DateTimeString; - readonly catching_up: boolean; -} - -function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { - return { - latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), - latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), - latestBlockTime: DateTime.decode(assertNotEmpty(data.latest_block_time)), - latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), - catchingUp: assertBoolean(data.catching_up), - }; -} - -interface RpcStatusResponse { - readonly node_info: RpcNodeInfo; - readonly sync_info: RpcSyncInfo; - readonly validator_info: RpcValidatorInfo; -} - -function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { - return { - nodeInfo: decodeNodeInfo(data.node_info), - syncInfo: decodeSyncInfo(data.sync_info), - validatorInfo: decodeValidatorInfo(data.validator_info), - }; -} - -/** - * Example data: - * { - * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", - * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", - * "Proof": { - * "total": "1", - * "index": "0", - * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", - * "aunts": [] - * } - * } - */ -interface RpcTxProof { - readonly Data: Base64String; - readonly RootHash: HexString; - readonly Proof: { - readonly total: IntegerString; - readonly index: IntegerString; - readonly leaf_hash: Base64String; - readonly aunts: readonly Base64String[]; - }; -} - -function decodeTxProof(data: RpcTxProof): responses.TxProof { - return { - data: Base64.decode(assertNotEmpty(data.Data)), - rootHash: fromHex(assertNotEmpty(data.RootHash)), - proof: { - total: Integer.parse(assertNotEmpty(data.Proof.total)), - index: Integer.parse(assertNotEmpty(data.Proof.index)), - leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), - aunts: assertArray(data.Proof.aunts).map(Base64.decode), - }, - }; -} - -interface RpcTxResponse { - readonly tx: Base64String; - readonly tx_result: RpcTxData; - readonly height: IntegerString; - readonly index: number; - readonly hash: HexString; - readonly proof?: RpcTxProof; -} - -function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { - return { - tx: Base64.decode(assertNotEmpty(data.tx)) as TxBytes, - result: decodeTxData(assertObject(data.tx_result)), - height: Integer.parse(assertNotEmpty(data.height)), - index: Integer.parse(assertNumber(data.index)), - hash: fromHex(assertNotEmpty(data.hash)) as TxHash, - proof: may(decodeTxProof, data.proof), - }; -} - -interface RpcTxSearchResponse { - readonly txs: readonly RpcTxResponse[]; - readonly total_count: IntegerString; -} - -function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { - return { - totalCount: Integer.parse(assertNotEmpty(data.total_count)), - txs: assertArray(data.txs).map(decodeTxResponse), - }; -} - -interface RpcTxEvent { - readonly tx: Base64String; - readonly result: RpcTxData; - readonly height: IntegerString; - readonly index: number; -} - -function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { - const tx = Base64.decode(assertNotEmpty(data.tx)) as TxBytes; - return { - tx: tx, - hash: hashTx(tx), - result: decodeTxData(data.result), - height: Integer.parse(assertNotEmpty(data.height)), - index: Integer.parse(assertNumber(data.index)), - }; -} - -// for validators -interface RpcValidatorData extends RpcValidatorUpdate { - readonly accum?: IntegerString; -} - -function decodeValidatorData(data: RpcValidatorData): responses.Validator { - return { - ...decodeValidatorUpdate(data), - accum: may(Integer.parse, data.accum), - }; -} - -interface RpcValidatorsResponse { - readonly block_height: IntegerString; - readonly validators: readonly RpcValidatorData[]; -} - -function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { - return { - blockHeight: Integer.parse(assertNotEmpty(data.block_height)), - results: assertArray(data.validators).map(decodeValidatorData), - }; -} - -interface RpcEvidence { - readonly type: string; - readonly validator: RpcValidatorUpdate; - readonly height: IntegerString; - readonly time: IntegerString; - readonly totalVotingPower: IntegerString; -} - -function decodeEvidence(data: RpcEvidence): responses.Evidence { - return { - type: assertNotEmpty(data.type), - height: Integer.parse(assertNotEmpty(data.height)), - time: Integer.parse(assertNotEmpty(data.time)), - totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), - validator: decodeValidatorUpdate(data.validator), - }; -} - -function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { - return assertArray(ev).map(decodeEvidence); -} - -interface RpcBlock { - readonly header: RpcHeader; - readonly last_commit: RpcCommit; - readonly data: { - readonly txs?: readonly Base64String[]; - }; - readonly evidence?: { - readonly evidence?: readonly RpcEvidence[]; - }; -} - -function decodeBlock(data: RpcBlock): responses.Block { - return { - header: decodeHeader(assertObject(data.header)), - lastCommit: decodeCommit(assertObject(data.last_commit)), - txs: data.data.txs ? assertArray(data.data.txs).map(Base64.decode) : [], - evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), - }; -} - -interface RpcBlockResponse { - readonly block_meta: RpcBlockMeta; - readonly block: RpcBlock; -} - -function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { - return { - blockMeta: decodeBlockMeta(data.block_meta), - block: decodeBlock(data.block), - }; -} - -export class Responses { - public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { - return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); - } - - public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { - return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); - } - - public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { - return decodeBlockResponse(response.result as RpcBlockResponse); - } - - public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { - return decodeBlockResults(response.result as RpcBlockResultsResponse); - } - - public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { - return decodeBlockchain(response.result as RpcBlockchainResponse); - } - - public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { - return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); - } - - public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { - return this.decodeBroadcastTxSync(response); - } - - public static decodeBroadcastTxCommit( - response: JsonRpcSuccessResponse, - ): responses.BroadcastTxCommitResponse { - return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); - } - - public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { - return decodeCommitResponse(response.result as RpcCommitResponse); - } - - public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { - return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); - } - - public static decodeHealth(): responses.HealthResponse { - return null; - } - - public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { - return decodeStatus(response.result as RpcStatusResponse); - } - - public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { - return decodeBlock(event.data.value.block as RpcBlock); - } - - public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { - return decodeHeader(event.data.value.header as RpcHeader); - } - - public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { - return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); - } - - public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { - return decodeTxResponse(response.result as RpcTxResponse); - } - - public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { - return decodeTxSearch(response.result as RpcTxSearchResponse); - } - - public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { - return decodeValidators(response.result as RpcValidatorsResponse); - } -} diff --git a/packages/tendermint-rpc/types/v0-32/hasher.d.ts b/packages/tendermint-rpc/types/v0-32/hasher.d.ts deleted file mode 100644 index de2e7e9012..0000000000 --- a/packages/tendermint-rpc/types/v0-32/hasher.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Header } from "../responses"; -import { BlockHash, TxBytes, TxHash } from "../types"; -export declare function hashTx(tx: TxBytes): TxHash; -export declare function hashBlock(header: Header): BlockHash; diff --git a/packages/tendermint-rpc/types/v0-32/index.d.ts b/packages/tendermint-rpc/types/v0-32/index.d.ts deleted file mode 100644 index 0e52753a89..0000000000 --- a/packages/tendermint-rpc/types/v0-32/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { Adaptor } from "../adaptor"; -export declare const v0_32: Adaptor; diff --git a/packages/tendermint-rpc/types/v0-32/requests.d.ts b/packages/tendermint-rpc/types/v0-32/requests.d.ts deleted file mode 100644 index af972c4a92..0000000000 --- a/packages/tendermint-rpc/types/v0-32/requests.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { JsonRpcRequest } from "@iov/jsonrpc"; -import * as requests from "../requests"; -export declare class Params { - static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest; - static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest; - static encodeBlock(req: requests.BlockRequest): JsonRpcRequest; - static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest; - static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest; - static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest; - static encodeCommit(req: requests.CommitRequest): JsonRpcRequest; - static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest; - static encodeHealth(req: requests.HealthRequest): JsonRpcRequest; - static encodeStatus(req: requests.StatusRequest): JsonRpcRequest; - static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest; - static encodeTx(req: requests.TxRequest): JsonRpcRequest; - static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest; - static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest; -} diff --git a/packages/tendermint-rpc/types/v0-32/responses.d.ts b/packages/tendermint-rpc/types/v0-32/responses.d.ts deleted file mode 100644 index 05fad338b2..0000000000 --- a/packages/tendermint-rpc/types/v0-32/responses.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; -import * as responses from "../responses"; -import { SubscriptionEvent } from "../rpcclients"; -export declare class Responses { - static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse; - static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse; - static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse; - static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse; - static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse; - static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse; - static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse; - static decodeBroadcastTxCommit(response: JsonRpcSuccessResponse): responses.BroadcastTxCommitResponse; - static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse; - static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse; - static decodeHealth(): responses.HealthResponse; - static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse; - static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent; - static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent; - static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent; - static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse; - static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse; - static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse; -} From 6f07e90587fca4f0916c6cbe1bda465b01d833ff Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 23 Jun 2020 13:24:16 +0200 Subject: [PATCH 12/20] tendermint-rpc: Update general files for v0.33 --- packages/tendermint-rpc/src/adaptorforversion.ts | 6 +++--- packages/tendermint-rpc/src/config.spec.ts | 4 ++-- packages/tendermint-rpc/src/index.ts | 2 +- packages/tendermint-rpc/types/index.d.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/tendermint-rpc/src/adaptorforversion.ts b/packages/tendermint-rpc/src/adaptorforversion.ts index da7c0e6da2..8688f134d1 100644 --- a/packages/tendermint-rpc/src/adaptorforversion.ts +++ b/packages/tendermint-rpc/src/adaptorforversion.ts @@ -2,7 +2,7 @@ // This module exposes translators for multiple tendermint versions // Pick a version that matches the server to properly encode the data types import { Adaptor } from "./adaptor"; -import { v0_32 } from "./v0-32"; +import { v0_33 } from "./v0-33"; /** * Returns an Adaptor implementation for a given tendermint version. @@ -11,8 +11,8 @@ import { v0_32 } from "./v0-32"; * @param version full Tendermint version string, e.g. "0.20.1" */ export function adaptorForVersion(version: string): Adaptor { - if (version.startsWith("0.32.")) { - return v0_32; + if (version.startsWith("0.33.")) { + return v0_33; } else { throw new Error(`Unsupported tendermint version: ${version}`); } diff --git a/packages/tendermint-rpc/src/config.spec.ts b/packages/tendermint-rpc/src/config.spec.ts index 68b28804e6..ba8c4bbf3e 100644 --- a/packages/tendermint-rpc/src/config.spec.ts +++ b/packages/tendermint-rpc/src/config.spec.ts @@ -18,8 +18,8 @@ export interface TendermintInstance { */ export const tendermintInstances: readonly TendermintInstance[] = [ { - url: "localhost:11132", - version: "0.32.x", + url: "localhost:11133", + version: "0.33.x", appCreator: "Cosmoshi Netowoko", }, ]; diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index 9fe8c2ac4b..19f2335062 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ // exported to access version-specific hashing -export { v0_32 } from "./v0-32"; +export { v0_33 } from "./v0-33"; export { Client } from "./client"; export { diff --git a/packages/tendermint-rpc/types/index.d.ts b/packages/tendermint-rpc/types/index.d.ts index 8bc19a71eb..0250c255fe 100644 --- a/packages/tendermint-rpc/types/index.d.ts +++ b/packages/tendermint-rpc/types/index.d.ts @@ -1,4 +1,4 @@ -export { v0_32 } from "./v0-32"; +export { v0_33 } from "./v0-33"; export { Client } from "./client"; export { AbciInfoRequest, From 9ba93ebc9e8ef036896a70012056d1308f41ac2e Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 23 Jun 2020 17:01:45 +0200 Subject: [PATCH 13/20] tendermint-rpc: Add v0.33 support (part 1) --- packages/tendermint-rpc/src/client.spec.ts | 7 - packages/tendermint-rpc/src/responses.ts | 18 +- .../tendermint-rpc/src/v0-33/hasher.spec.ts | 94 +++ packages/tendermint-rpc/src/v0-33/hasher.ts | 69 ++ packages/tendermint-rpc/src/v0-33/index.ts | 12 + packages/tendermint-rpc/src/v0-33/requests.ts | 142 ++++ .../tendermint-rpc/src/v0-33/responses.ts | 767 ++++++++++++++++++ packages/tendermint-rpc/types/responses.d.ts | 18 +- .../tendermint-rpc/types/v0-33/hasher.d.ts | 4 + .../tendermint-rpc/types/v0-33/index.d.ts | 2 + .../tendermint-rpc/types/v0-33/requests.d.ts | 18 + .../tendermint-rpc/types/v0-33/responses.d.ts | 23 + 12 files changed, 1147 insertions(+), 27 deletions(-) create mode 100644 packages/tendermint-rpc/src/v0-33/hasher.spec.ts create mode 100644 packages/tendermint-rpc/src/v0-33/hasher.ts create mode 100644 packages/tendermint-rpc/src/v0-33/index.ts create mode 100644 packages/tendermint-rpc/src/v0-33/requests.ts create mode 100644 packages/tendermint-rpc/src/v0-33/responses.ts create mode 100644 packages/tendermint-rpc/types/v0-33/hasher.d.ts create mode 100644 packages/tendermint-rpc/types/v0-33/index.d.ts create mode 100644 packages/tendermint-rpc/types/v0-33/requests.d.ts create mode 100644 packages/tendermint-rpc/types/v0-33/responses.d.ts diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts index 8f1fc6e29b..acccac915c 100644 --- a/packages/tendermint-rpc/src/client.spec.ts +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -179,7 +179,6 @@ function defaultTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor): void { // and let's query the block itself to see this transaction const block = await client.block(height); - expect(block.blockMeta.header.numTxs).toEqual(1); expect(block.block.txs.length).toEqual(1); expect(block.block.txs[0]).toEqual(tx); @@ -255,9 +254,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, appCr expect(event.time.getTime()).toBeGreaterThan(testStart - 1000); // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance expect(event.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); - expect(event.numTxs).toEqual(0); expect(event.lastBlockId).toBeTruthy(); - expect(event.totalTxs).toBeGreaterThan(0); // merkle roots for proofs expect(event.appHash).toBeTruthy(); @@ -276,7 +273,6 @@ function websocketTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, appCr expect(events[1].chainId).toEqual(events[0].chainId); expect(events[1].height).toEqual(events[0].height + 1); expect(events[1].time.getTime()).toBeGreaterThan(events[0].time.getTime()); - expect(events[1].totalTxs).toEqual(events[0].totalTxs); expect(events[1].appHash).toEqual(events[0].appHash); expect(events[1].consensusHash).toEqual(events[0].consensusHash); @@ -315,9 +311,7 @@ function websocketTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, appCr expect(event.header.time.getTime()).toBeGreaterThan(testStart - 1000); // Tendermint clock is sometimes ahead of test clock. Add 10ms tolerance expect(event.header.time.getTime()).toBeLessThanOrEqual(ReadonlyDate.now() + 10); - expect(event.header.numTxs).toEqual(1); expect(event.header.lastBlockId).toBeTruthy(); - expect(event.header.totalTxs).toBeGreaterThan(0); // merkle roots for proofs expect(event.header.appHash).toBeTruthy(); @@ -348,7 +342,6 @@ function websocketTestSuite(rpcFactory: () => RpcClient, adaptor: Adaptor, appCr expect(events[1].header.height).toEqual(events[0].header.height + 1); expect(events[1].header.chainId).toEqual(events[0].header.chainId); expect(events[1].header.time.getTime()).toBeGreaterThan(events[0].header.time.getTime()); - expect(events[1].header.totalTxs).toEqual(events[0].header.totalTxs + 1); expect(events[1].header.appHash).not.toEqual(events[0].header.appHash); expect(events[1].header.validatorsHash).toEqual(events[0].header.validatorsHash); // Block body diff --git a/packages/tendermint-rpc/src/responses.ts b/packages/tendermint-rpc/src/responses.ts index 7d6e432ed5..8c9f4443c1 100644 --- a/packages/tendermint-rpc/src/responses.ts +++ b/packages/tendermint-rpc/src/responses.ts @@ -35,18 +35,17 @@ export interface AbciQueryResponse { } export interface BlockResponse { - readonly blockMeta: BlockMeta; + readonly blockId: BlockId; readonly block: Block; } export interface BlockResultsResponse { readonly height: number; readonly results: readonly TxData[]; - readonly endBlock: { - readonly validatorUpdates: readonly Validator[]; - readonly consensusUpdates?: ConsensusParams; - readonly tags?: readonly Tag[]; - }; + readonly validatorUpdates: readonly Validator[]; + readonly consensusUpdates?: ConsensusParams; + readonly beginBlock?: readonly Tag[]; + readonly endBlock?: readonly Tag[]; } export interface BlockchainResponse { @@ -214,7 +213,7 @@ export interface Evidence { export interface Commit { readonly blockId: BlockId; - readonly precommits: readonly Vote[]; + readonly signatures: readonly ValidatorSignature[]; } /** @@ -253,8 +252,6 @@ export interface Header { readonly chainId: string; readonly height: number; readonly time: ReadonlyDateWithNanoseconds; - readonly numTxs: number; - readonly totalTxs: number; // prev block info readonly lastBlockId: BlockId; @@ -327,5 +324,6 @@ export interface BlockGossipParams { } export interface EvidenceParams { - readonly maxAge: number; + readonly maxAgeNumBlocks: number; + readonly maxAgeDuration: number; } diff --git a/packages/tendermint-rpc/src/v0-33/hasher.spec.ts b/packages/tendermint-rpc/src/v0-33/hasher.spec.ts new file mode 100644 index 0000000000..f78576b715 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-33/hasher.spec.ts @@ -0,0 +1,94 @@ +import { fromBase64, fromHex } from "@cosmjs/encoding"; +import { ReadonlyDate } from "readonly-date"; + +import { ReadonlyDateWithNanoseconds } from "../responses"; +import { TxBytes } from "../types"; +import { hashBlock, hashTx } from "./hasher"; + +describe("Hasher", () => { + it("creates transaction hash equal to local test", () => { + // This was taken from a result from /tx_search of some random test transaction + // curl "http://localhost:11127/tx_search?query=\"tx.hash='5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2'\"" + const txId = fromHex("5CB2CF94A1097A4BC19258BC2353C3E76102B6D528458BE45C855DC5563C1DB2"); + const txData = fromBase64("YUpxZDY2NURaUDMxPWd2TzBPdnNrVWFWYg==") as TxBytes; + expect(hashTx(txData)).toEqual(txId); + }); + + it("creates block hash equal to local test for empty block", () => { + // This was taken from a result from /block of some random empty block + // curl "http://localhost:11133/block" + const blockId = fromHex("153C484DCBC33633F0616BC019388C93DEA94F7880627976F2BFE83749E062F7"); + const time = new ReadonlyDate("2020-06-23T13:54:15.4638668Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 866800; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-2A5rwi", + height: 7795, + time: time as ReadonlyDateWithNanoseconds, + + lastBlockId: { + hash: fromHex("1EC48444E64E7B96585BA518613612E52B976E3DA2F2222B9CD4D1602656C96F"), + parts: { + total: 1, + hash: fromHex("D4E6F1B0EE08D0438C9BB8455D7D3F2FC1883C32D66F7C69C4A0F093B073F6D2"), + }, + }, + + lastCommitHash: fromHex("BA6A5EEA6687ACA8EE4FFE4F5D40EA073CB7397A5336309C3EC824805AF9723E"), + dataHash: fromHex(""), + + validatorsHash: fromHex("0BEEBC6AB3B7D4FE21E22B609CD4AEC7E121A42C07604FF1827651F0173745EB"), + nextValidatorsHash: fromHex("0BEEBC6AB3B7D4FE21E22B609CD4AEC7E121A42C07604FF1827651F0173745EB"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("8801000000000000"), + lastResultsHash: fromHex(""), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("614F305502F65C01114F9B8711D9A0AB0AC369F4"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); + + it("creates block hash equal to local test for block with a transaction", () => { + // This was taken from a result from /block of some random block with a transaction + // curl "http://localhost:11133/block?height=13575" + const blockId = fromHex("FF2995AF1F38B9A584077E53B5E144778718FB86539A51886A2C55F730403373"); + const time = new ReadonlyDate("2020-06-23T15:34:12.3232688Z"); + // tslint:disable-next-line:no-object-mutation + (time as any).nanoseconds = 268800; + const blockData = { + version: { + block: 10, + app: 1, + }, + chainId: "test-chain-2A5rwi", + height: 13575, + time: time as ReadonlyDateWithNanoseconds, + + lastBlockId: { + hash: fromHex("046D5441FC4D008FCDBF9F3DD5DC25CF00883763E44CF4FAF3923FB5FEA42D8F"), + parts: { + total: 1, + hash: fromHex("02E4715343625093C717638EAC67FB3A4B24CCC8DA610E0CB324D705E68FEF7B"), + }, + }, + + lastCommitHash: fromHex("AA2B807F3B0ACC866AB58D90C2D0FC70B6C860CFAC440590B4F590CDC178A207"), + dataHash: fromHex("56782879F526889734BA65375CD92A9152C7114B2C91B2D2AD8464FF69E884AA"), + + validatorsHash: fromHex("0BEEBC6AB3B7D4FE21E22B609CD4AEC7E121A42C07604FF1827651F0173745EB"), + nextValidatorsHash: fromHex("0BEEBC6AB3B7D4FE21E22B609CD4AEC7E121A42C07604FF1827651F0173745EB"), + consensusHash: fromHex("048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F"), + appHash: fromHex("CC02000000000000"), + lastResultsHash: fromHex("6E340B9CFFB37A989CA544E6BB780A2C78901D3FB33738768511A30617AFA01D"), + + evidenceHash: fromHex(""), + proposerAddress: fromHex("614F305502F65C01114F9B8711D9A0AB0AC369F4"), + }; + expect(hashBlock(blockData)).toEqual(blockId); + }); +}); diff --git a/packages/tendermint-rpc/src/v0-33/hasher.ts b/packages/tendermint-rpc/src/v0-33/hasher.ts new file mode 100644 index 0000000000..4401053b17 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-33/hasher.ts @@ -0,0 +1,69 @@ +import { Sha256 } from "@cosmjs/crypto"; + +import { encodeBlockId, encodeBytes, encodeInt, encodeString, encodeTime, encodeVersion } from "../encodings"; +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; + +// hash is sha256 +// https://github.com/tendermint/tendermint/blob/master/UPGRADING.md#v0260 +export function hashTx(tx: TxBytes): TxHash { + const hash = new Sha256(tx).digest(); + return hash as TxHash; +} + +function getSplitPoint(n: number): number { + if (n < 1) throw new Error("Cannot split an empty tree"); + const largestPowerOf2 = 2 ** Math.floor(Math.log2(n)); + return largestPowerOf2 < n ? largestPowerOf2 : largestPowerOf2 / 2; +} + +function hashLeaf(leaf: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([0])); + hash.update(leaf); + return hash.digest(); +} + +function hashInner(left: Uint8Array, right: Uint8Array): Uint8Array { + const hash = new Sha256(Uint8Array.from([1])); + hash.update(left); + hash.update(right); + return hash.digest(); +} + +// See https://github.com/tendermint/tendermint/blob/v0.31.8/docs/spec/blockchain/encoding.md#merkleroot +// Note: the hashes input may not actually be hashes, especially before a recursive call +function hashTree(hashes: readonly Uint8Array[]): Uint8Array { + switch (hashes.length) { + case 0: + throw new Error("Cannot hash empty tree"); + case 1: + return hashLeaf(hashes[0]); + default: { + const slicePoint = getSplitPoint(hashes.length); + const left = hashTree(hashes.slice(0, slicePoint)); + const right = hashTree(hashes.slice(slicePoint)); + return hashInner(left, right); + } + } +} + +export function hashBlock(header: Header): BlockHash { + const encodedFields: readonly Uint8Array[] = [ + encodeVersion(header.version), + encodeString(header.chainId), + encodeInt(header.height), + encodeTime(header.time), + encodeBlockId(header.lastBlockId), + + encodeBytes(header.lastCommitHash), + encodeBytes(header.dataHash), + encodeBytes(header.validatorsHash), + encodeBytes(header.nextValidatorsHash), + encodeBytes(header.consensusHash), + encodeBytes(header.appHash), + encodeBytes(header.lastResultsHash), + encodeBytes(header.evidenceHash), + encodeBytes(header.proposerAddress), + ]; + return hashTree(encodedFields) as BlockHash; +} diff --git a/packages/tendermint-rpc/src/v0-33/index.ts b/packages/tendermint-rpc/src/v0-33/index.ts new file mode 100644 index 0000000000..bedc804cfb --- /dev/null +++ b/packages/tendermint-rpc/src/v0-33/index.ts @@ -0,0 +1,12 @@ +import { Adaptor } from "../adaptor"; +import { hashBlock, hashTx } from "./hasher"; +import { Params } from "./requests"; +import { Responses } from "./responses"; + +// eslint-disable-next-line @typescript-eslint/camelcase +export const v0_33: Adaptor = { + params: Params, + responses: Responses, + hashTx: hashTx, + hashBlock: hashBlock, +}; diff --git a/packages/tendermint-rpc/src/v0-33/requests.ts b/packages/tendermint-rpc/src/v0-33/requests.ts new file mode 100644 index 0000000000..7526fe0ae3 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-33/requests.ts @@ -0,0 +1,142 @@ +import { toHex } from "@cosmjs/encoding"; +import { JsonRpcRequest } from "@iov/jsonrpc"; + +import { assertNotEmpty, Base64, Base64String, HexString, Integer, IntegerString, may } from "../encodings"; +import { createJsonRpcRequest } from "../jsonrpc"; +import * as requests from "../requests"; + +interface HeightParam { + readonly height?: number; +} +interface RpcHeightParam { + readonly height?: IntegerString; +} +function encodeHeightParam(param: HeightParam): RpcHeightParam { + return { + height: may(Integer.encode, param.height), + }; +} + +interface RpcBlockchainRequestParams { + readonly minHeight?: IntegerString; + readonly maxHeight?: IntegerString; +} +function encodeBlockchainRequestParams(param: requests.BlockchainRequestParams): RpcBlockchainRequestParams { + return { + minHeight: may(Integer.encode, param.minHeight), + maxHeight: may(Integer.encode, param.maxHeight), + }; +} + +interface RpcAbciQueryParams { + readonly path: string; + readonly data: HexString; + readonly height?: string; + readonly prove?: boolean; +} + +function encodeAbciQueryParams(params: requests.AbciQueryParams): RpcAbciQueryParams { + return { + path: assertNotEmpty(params.path), + data: toHex(params.data) as HexString, + height: may(Integer.encode, params.height), + prove: params.prove, + }; +} + +interface RpcBroadcastTxParams { + readonly tx: Base64String; +} +function encodeBroadcastTxParams(params: requests.BroadcastTxParams): RpcBroadcastTxParams { + return { + tx: Base64.encode(assertNotEmpty(params.tx)), + }; +} + +interface RpcTxParams { + readonly hash: Base64String; + readonly prove?: boolean; +} +function encodeTxParams(params: requests.TxParams): RpcTxParams { + return { + hash: Base64.encode(assertNotEmpty(params.hash)), + prove: params.prove, + }; +} + +interface RpcTxSearchParams { + readonly query: requests.QueryString; + readonly prove?: boolean; + readonly page?: IntegerString; + readonly per_page?: IntegerString; +} +function encodeTxSearchParams(params: requests.TxSearchParams): RpcTxSearchParams { + return { + query: params.query, + prove: params.prove, + page: may(Integer.encode, params.page), + // eslint-disable-next-line @typescript-eslint/camelcase + per_page: may(Integer.encode, params.per_page), + }; +} + +export class Params { + public static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeAbciQueryParams(req.params)); + } + + public static encodeBlock(req: requests.BlockRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBlockchainRequestParams(req.params)); + } + + public static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeBroadcastTxParams(req.params)); + } + + public static encodeCommit(req: requests.CommitRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } + + public static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeHealth(req: requests.HealthRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeStatus(req: requests.StatusRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method); + } + + public static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest { + const eventTag = { key: "tm.event", value: req.query.type }; + const query = requests.buildQuery({ tags: [eventTag], raw: req.query.raw }); + return createJsonRpcRequest("subscribe", { query: query }); + } + + public static encodeTx(req: requests.TxRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxParams(req.params)); + } + + // TODO: encode params for query string??? + public static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeTxSearchParams(req.params)); + } + + public static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest { + return createJsonRpcRequest(req.method, encodeHeightParam(req.params)); + } +} diff --git a/packages/tendermint-rpc/src/v0-33/responses.ts b/packages/tendermint-rpc/src/v0-33/responses.ts new file mode 100644 index 0000000000..35eef209e1 --- /dev/null +++ b/packages/tendermint-rpc/src/v0-33/responses.ts @@ -0,0 +1,767 @@ +import { fromHex } from "@cosmjs/encoding"; +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; + +import { + assertArray, + assertBoolean, + assertNotEmpty, + assertNumber, + assertObject, + assertSet, + Base64, + Base64String, + DateTime, + DateTimeString, + dictionaryToStringMap, + Hex, + HexString, + Integer, + IntegerString, + may, + optional, +} from "../encodings"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +import { IpPortString, TxBytes, TxHash, ValidatorPubkey, ValidatorSignature } from "../types"; +import { hashTx } from "./hasher"; + +interface AbciInfoResult { + readonly response: RpcAbciInfoResponse; +} + +interface RpcAbciInfoResponse { + readonly data?: string; + readonly last_block_height?: IntegerString; + readonly last_block_app_hash?: Base64String; +} + +function decodeAbciInfo(data: RpcAbciInfoResponse): responses.AbciInfoResponse { + return { + data: data.data, + lastBlockHeight: may(Integer.parse, data.last_block_height), + lastBlockAppHash: may(Base64.decode, data.last_block_app_hash), + }; +} + +interface AbciQueryResult { + readonly response: RpcAbciQueryResponse; +} + +interface RpcAbciQueryResponse { + readonly key: Base64String; + readonly value?: Base64String; + readonly proof?: Base64String; + readonly height?: IntegerString; + readonly index?: IntegerString; + readonly code?: IntegerString; // only for errors + readonly log?: string; +} + +function decodeAbciQuery(data: RpcAbciQueryResponse): responses.AbciQueryResponse { + return { + key: Base64.decode(optional(data.key, "" as Base64String)), + value: Base64.decode(optional(data.value, "" as Base64String)), + // proof: may(Base64.decode, data.proof), + height: may(Integer.parse, data.height), + code: may(Integer.parse, data.code), + index: may(Integer.parse, data.index), + log: data.log, + }; +} + +interface RpcTag { + readonly key: Base64String; + readonly value: Base64String; +} + +function decodeTag(tag: RpcTag): responses.Tag { + return { + key: Base64.decode(assertNotEmpty(tag.key)), + value: Base64.decode(assertNotEmpty(tag.value)), + }; +} + +function decodeTags(tags: readonly RpcTag[]): readonly responses.Tag[] { + return assertArray(tags).map(decodeTag); +} + +interface RpcEvent { + readonly type: string; + readonly attributes: readonly RpcTag[]; +} + +function decodeEvent(event: RpcEvent): responses.Event { + return { + type: event.type, + attributes: decodeTags(event.attributes), + }; +} + +function decodeEvents(events: readonly RpcEvent[]): readonly responses.Event[] { + return assertArray(events).map(decodeEvent); +} + +interface RpcTxData { + readonly code?: number; + readonly log?: string; + readonly data?: Base64String; + readonly events?: readonly RpcEvent[]; +} + +function decodeTxData(data: RpcTxData): responses.TxData { + return { + data: may(Base64.decode, data.data), + log: data.log, + code: Integer.parse(assertNumber(optional(data.code, 0))), + events: may(decodeEvents, data.events), + }; +} + +// yes, a different format for status and dump consensus state +interface RpcPubkey { + readonly type: string; + readonly value: Base64String; +} + +function decodePubkey(data: RpcPubkey): ValidatorPubkey { + if (data.type === "tendermint/PubKeyEd25519") { + // go-amino special code + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data.value)), + }; + } + throw new Error(`unknown pubkey type: ${data.type}`); +} + +// for evidence, block results, etc. +interface RpcValidatorUpdate { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorUpdate(data: RpcValidatorUpdate): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: Hex.decode(assertNotEmpty(data.address)), + }; +} + +interface RpcBlockParams { + readonly max_bytes: IntegerString; + readonly max_gas: IntegerString; +} + +/** + * Note: we do not parse block.time_iota_ms for now because of this CHANGELOG entry + * + * > Add time_iota_ms to block's consensus parameters (not exposed to the application) + * https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 + */ +function decodeBlockParams(data: RpcBlockParams): responses.BlockParams { + return { + maxBytes: Integer.parse(assertNotEmpty(data.max_bytes)), + maxGas: Integer.parse(assertNotEmpty(data.max_gas)), + }; +} + +interface RpcEvidenceParams { + readonly max_age_num_blocks: IntegerString; + readonly max_age_duration: IntegerString; +} + +function decodeEvidenceParams(data: RpcEvidenceParams): responses.EvidenceParams { + return { + maxAgeNumBlocks: Integer.parse(assertNotEmpty(data.max_age_num_blocks)), + maxAgeDuration: Integer.parse(assertNotEmpty(data.max_age_duration)), + }; +} + +/** + * Example data: + * { + * "block": { + * "max_bytes": "22020096", + * "max_gas": "-1", + * "time_iota_ms": "1000" + * }, + * "evidence": { + * "max_age_num_blocks": "100000", + * "max_age_duration": "172800000000000" + * }, + * "validator": { + * "pub_key_types": [ + * "ed25519" + * ] + * } + * } + */ +interface RpcConsensusParams { + readonly block: RpcBlockParams; + readonly evidence: RpcEvidenceParams; +} + +function decodeConsensusParams(data: RpcConsensusParams): responses.ConsensusParams { + return { + block: decodeBlockParams(assertObject(data.block)), + evidence: decodeEvidenceParams(assertObject(data.evidence)), + }; +} + +interface RpcBlockResultsResponse { + readonly height: IntegerString; + readonly txs_results: readonly RpcTxData[] | null; + readonly begin_block_events: null; + readonly end_block_events: null; + readonly validator_updates: null; + readonly consensus_param_updates: null; +} + +function decodeBlockResults(data: RpcBlockResultsResponse): responses.BlockResultsResponse { + const results = optional(data.txs_results, [] as readonly RpcTxData[]); + const validatorUpdates = optional(data.validator_updates, [] as readonly RpcValidatorUpdate[]); + return { + height: Integer.parse(assertNotEmpty(data.height)), + results: results.map(decodeTxData), + validatorUpdates: validatorUpdates.map(decodeValidatorUpdate), + consensusUpdates: may(decodeConsensusParams, data.consensus_param_updates), + beginBlock: may(decodeTags, data.begin_block_events), + endBlock: may(decodeTags, data.end_block_events), + }; +} + +interface RpcBlockId { + readonly hash: HexString; + readonly parts: { + readonly total: IntegerString; + readonly hash: HexString; + }; +} + +function decodeBlockId(data: RpcBlockId): responses.BlockId { + return { + hash: fromHex(assertNotEmpty(data.hash)), + parts: { + total: Integer.parse(assertNotEmpty(data.parts.total)), + hash: fromHex(assertNotEmpty(data.parts.hash)), + }, + }; +} + +interface RpcBlockVersion { + readonly block: IntegerString; + readonly app: IntegerString; +} + +function decodeBlockVersion(data: RpcBlockVersion): responses.Version { + return { + block: Integer.parse(data.block), + app: Integer.parse(data.app), + }; +} + +interface RpcHeader { + readonly version: RpcBlockVersion; + readonly chain_id: string; + readonly height: IntegerString; + readonly time: DateTimeString; + readonly num_txs: IntegerString; + readonly total_txs: IntegerString; + + readonly last_block_id: RpcBlockId; + + readonly last_commit_hash: HexString; + readonly data_hash: HexString; + + readonly validators_hash: HexString; + readonly next_validators_hash: HexString; + readonly consensus_hash: HexString; + readonly app_hash: HexString; + readonly last_results_hash: HexString; + + readonly evidence_hash: HexString; + readonly proposer_address: HexString; +} + +function decodeHeader(data: RpcHeader): responses.Header { + return { + version: decodeBlockVersion(data.version), + chainId: assertNotEmpty(data.chain_id), + height: Integer.parse(assertNotEmpty(data.height)), + time: DateTime.decode(assertNotEmpty(data.time)), + + lastBlockId: decodeBlockId(data.last_block_id), + + lastCommitHash: fromHex(assertNotEmpty(data.last_commit_hash)), + dataHash: fromHex(assertSet(data.data_hash)), + + validatorsHash: fromHex(assertNotEmpty(data.validators_hash)), + nextValidatorsHash: fromHex(assertNotEmpty(data.next_validators_hash)), + consensusHash: fromHex(assertNotEmpty(data.consensus_hash)), + appHash: fromHex(assertNotEmpty(data.app_hash)), + lastResultsHash: fromHex(assertSet(data.last_results_hash)), + + evidenceHash: fromHex(assertSet(data.evidence_hash)), + proposerAddress: fromHex(assertNotEmpty(data.proposer_address)), + }; +} + +interface RpcBlockMeta { + readonly block_id: RpcBlockId; + readonly header: RpcHeader; +} + +function decodeBlockMeta(data: RpcBlockMeta): responses.BlockMeta { + return { + blockId: decodeBlockId(data.block_id), + header: decodeHeader(data.header), + }; +} + +interface RpcBlockchainResponse { + readonly last_height: IntegerString; + readonly block_metas: readonly RpcBlockMeta[]; +} + +function decodeBlockchain(data: RpcBlockchainResponse): responses.BlockchainResponse { + return { + lastHeight: Integer.parse(assertNotEmpty(data.last_height)), + blockMetas: assertArray(data.block_metas).map(decodeBlockMeta), + }; +} + +interface RpcBroadcastTxSyncResponse extends RpcTxData { + readonly hash: HexString; +} + +function decodeBroadcastTxSync(data: RpcBroadcastTxSyncResponse): responses.BroadcastTxSyncResponse { + return { + ...decodeTxData(data), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + }; +} + +interface RpcBroadcastTxCommitResponse { + readonly height?: IntegerString; + readonly hash: HexString; + readonly check_tx: RpcTxData; + readonly deliver_tx?: RpcTxData; +} + +function decodeBroadcastTxCommit(data: RpcBroadcastTxCommitResponse): responses.BroadcastTxCommitResponse { + return { + height: may(Integer.parse, data.height), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + checkTx: decodeTxData(assertObject(data.check_tx)), + deliverTx: may(decodeTxData, data.deliver_tx), + }; +} + +type RpcSignature = { + readonly block_id_flag: number; + readonly validator_address: HexString; + readonly timestamp: DateTimeString; + readonly signature: Base64String; +}; + +function decodeSignature(data: RpcSignature): ValidatorSignature { + return { + algorithm: "ed25519", + data: Base64.decode(assertNotEmpty(data.signature)), + }; +} + +interface RpcCommit { + readonly block_id: RpcBlockId; + readonly signatures: readonly RpcSignature[]; +} + +function decodeCommit(data: RpcCommit): responses.Commit { + return { + blockId: decodeBlockId(assertObject(data.block_id)), + signatures: assertArray(data.signatures).map(decodeSignature), + }; +} + +interface RpcCommitResponse { + readonly signed_header: { + readonly header: RpcHeader; + readonly commit: RpcCommit; + }; + readonly canonical: boolean; +} + +function decodeCommitResponse(data: RpcCommitResponse): responses.CommitResponse { + return { + canonical: assertBoolean(data.canonical), + header: decodeHeader(data.signed_header.header), + commit: decodeCommit(data.signed_header.commit), + }; +} + +interface RpcValidatorGenesis { + readonly pub_key: RpcPubkey; + readonly power: IntegerString; + readonly name?: string; +} + +function decodeValidatorGenesis(data: RpcValidatorGenesis): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.power)), + name: data.name, + }; +} + +interface RpcGenesisResponse { + readonly genesis_time: DateTimeString; + readonly chain_id: string; + readonly consensus_params: RpcConsensusParams; + readonly validators: readonly RpcValidatorGenesis[]; + readonly app_hash: HexString; + readonly app_state: {} | undefined; +} + +interface GenesisResult { + readonly genesis: RpcGenesisResponse; +} + +function decodeGenesis(data: RpcGenesisResponse): responses.GenesisResponse { + return { + genesisTime: DateTime.decode(assertNotEmpty(data.genesis_time)), + chainId: assertNotEmpty(data.chain_id), + consensusParams: decodeConsensusParams(data.consensus_params), + validators: assertArray(data.validators).map(decodeValidatorGenesis), + appHash: fromHex(assertSet(data.app_hash)), // empty string in kvstore app + appState: data.app_state, + }; +} + +// this is in status +interface RpcValidatorInfo { + readonly address: HexString; + readonly pub_key: RpcPubkey; + readonly voting_power: IntegerString; +} + +function decodeValidatorInfo(data: RpcValidatorInfo): responses.Validator { + return { + pubkey: decodePubkey(assertObject(data.pub_key)), + votingPower: Integer.parse(assertNotEmpty(data.voting_power)), + address: fromHex(assertNotEmpty(data.address)), + }; +} + +interface RpcNodeInfo { + readonly id: HexString; + readonly listen_addr: IpPortString; + readonly network: string; + readonly version: string; + readonly channels: string; // ??? + readonly moniker: string; + readonly protocol_version: { + readonly p2p: IntegerString; + readonly block: IntegerString; + readonly app: IntegerString; + }; + /** + * Additional information. E.g. + * { + * "tx_index": "on", + * "rpc_address":"tcp://0.0.0.0:26657" + * } + */ + readonly other: object; +} + +function decodeNodeInfo(data: RpcNodeInfo): responses.NodeInfo { + return { + id: fromHex(assertNotEmpty(data.id)), + listenAddr: assertNotEmpty(data.listen_addr), + network: assertNotEmpty(data.network), + version: assertNotEmpty(data.version), + channels: assertNotEmpty(data.channels), + moniker: assertNotEmpty(data.moniker), + other: dictionaryToStringMap(data.other), + protocolVersion: { + app: Integer.parse(assertNotEmpty(data.protocol_version.app)), + block: Integer.parse(assertNotEmpty(data.protocol_version.block)), + p2p: Integer.parse(assertNotEmpty(data.protocol_version.p2p)), + }, + }; +} + +interface RpcSyncInfo { + readonly latest_block_hash: HexString; + readonly latest_app_hash: HexString; + readonly latest_block_height: IntegerString; + readonly latest_block_time: DateTimeString; + readonly catching_up: boolean; +} + +function decodeSyncInfo(data: RpcSyncInfo): responses.SyncInfo { + return { + latestBlockHash: fromHex(assertNotEmpty(data.latest_block_hash)), + latestAppHash: fromHex(assertNotEmpty(data.latest_app_hash)), + latestBlockTime: DateTime.decode(assertNotEmpty(data.latest_block_time)), + latestBlockHeight: Integer.parse(assertNotEmpty(data.latest_block_height)), + catchingUp: assertBoolean(data.catching_up), + }; +} + +interface RpcStatusResponse { + readonly node_info: RpcNodeInfo; + readonly sync_info: RpcSyncInfo; + readonly validator_info: RpcValidatorInfo; +} + +function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { + return { + nodeInfo: decodeNodeInfo(data.node_info), + syncInfo: decodeSyncInfo(data.sync_info), + validatorInfo: decodeValidatorInfo(data.validator_info), + }; +} + +/** + * Example data: + * { + * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", + * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", + * "Proof": { + * "total": "1", + * "index": "0", + * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", + * "aunts": [] + * } + * } + */ +interface RpcTxProof { + readonly Data: Base64String; + readonly RootHash: HexString; + readonly Proof: { + readonly total: IntegerString; + readonly index: IntegerString; + readonly leaf_hash: Base64String; + readonly aunts: readonly Base64String[]; + }; +} + +function decodeTxProof(data: RpcTxProof): responses.TxProof { + return { + data: Base64.decode(assertNotEmpty(data.Data)), + rootHash: fromHex(assertNotEmpty(data.RootHash)), + proof: { + total: Integer.parse(assertNotEmpty(data.Proof.total)), + index: Integer.parse(assertNotEmpty(data.Proof.index)), + leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), + aunts: assertArray(data.Proof.aunts).map(Base64.decode), + }, + }; +} + +interface RpcTxResponse { + readonly tx: Base64String; + readonly tx_result: RpcTxData; + readonly height: IntegerString; + readonly index: number; + readonly hash: HexString; + readonly proof?: RpcTxProof; +} + +function decodeTxResponse(data: RpcTxResponse): responses.TxResponse { + return { + tx: Base64.decode(assertNotEmpty(data.tx)) as TxBytes, + result: decodeTxData(assertObject(data.tx_result)), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + hash: fromHex(assertNotEmpty(data.hash)) as TxHash, + proof: may(decodeTxProof, data.proof), + }; +} + +interface RpcTxSearchResponse { + readonly txs: readonly RpcTxResponse[]; + readonly total_count: IntegerString; +} + +function decodeTxSearch(data: RpcTxSearchResponse): responses.TxSearchResponse { + return { + totalCount: Integer.parse(assertNotEmpty(data.total_count)), + txs: assertArray(data.txs).map(decodeTxResponse), + }; +} + +interface RpcTxEvent { + readonly tx: Base64String; + readonly result: RpcTxData; + readonly height: IntegerString; + readonly index: number; +} + +function decodeTxEvent(data: RpcTxEvent): responses.TxEvent { + const tx = Base64.decode(assertNotEmpty(data.tx)) as TxBytes; + return { + tx: tx, + hash: hashTx(tx), + result: decodeTxData(data.result), + height: Integer.parse(assertNotEmpty(data.height)), + index: Integer.parse(assertNumber(data.index)), + }; +} + +// for validators +interface RpcValidatorData extends RpcValidatorUpdate { + readonly accum?: IntegerString; +} + +function decodeValidatorData(data: RpcValidatorData): responses.Validator { + return { + ...decodeValidatorUpdate(data), + accum: may(Integer.parse, data.accum), + }; +} + +interface RpcValidatorsResponse { + readonly block_height: IntegerString; + readonly validators: readonly RpcValidatorData[]; +} + +function decodeValidators(data: RpcValidatorsResponse): responses.ValidatorsResponse { + return { + blockHeight: Integer.parse(assertNotEmpty(data.block_height)), + results: assertArray(data.validators).map(decodeValidatorData), + }; +} + +interface RpcEvidence { + readonly type: string; + readonly validator: RpcValidatorUpdate; + readonly height: IntegerString; + readonly time: IntegerString; + readonly totalVotingPower: IntegerString; +} + +function decodeEvidence(data: RpcEvidence): responses.Evidence { + return { + type: assertNotEmpty(data.type), + height: Integer.parse(assertNotEmpty(data.height)), + time: Integer.parse(assertNotEmpty(data.time)), + totalVotingPower: Integer.parse(assertNotEmpty(data.totalVotingPower)), + validator: decodeValidatorUpdate(data.validator), + }; +} + +function decodeEvidences(ev: readonly RpcEvidence[]): readonly responses.Evidence[] { + return assertArray(ev).map(decodeEvidence); +} + +interface RpcBlock { + readonly header: RpcHeader; + readonly last_commit: RpcCommit; + readonly data: { + readonly txs?: readonly Base64String[]; + }; + readonly evidence?: { + readonly evidence?: readonly RpcEvidence[]; + }; +} + +function decodeBlock(data: RpcBlock): responses.Block { + return { + header: decodeHeader(assertObject(data.header)), + lastCommit: decodeCommit(assertObject(data.last_commit)), + txs: data.data.txs ? assertArray(data.data.txs).map(Base64.decode) : [], + evidence: data.evidence && may(decodeEvidences, data.evidence.evidence), + }; +} + +interface RpcBlockResponse { + readonly block_id: RpcBlockId; + readonly block: RpcBlock; +} + +function decodeBlockResponse(data: RpcBlockResponse): responses.BlockResponse { + return { + blockId: decodeBlockId(data.block_id), + block: decodeBlock(data.block), + }; +} + +export class Responses { + public static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse { + return decodeAbciInfo(assertObject((response.result as AbciInfoResult).response)); + } + + public static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse { + return decodeAbciQuery(assertObject((response.result as AbciQueryResult).response)); + } + + public static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse { + return decodeBlockResponse(response.result as RpcBlockResponse); + } + + public static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse { + return decodeBlockResults(response.result as RpcBlockResultsResponse); + } + + public static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse { + return decodeBlockchain(response.result as RpcBlockchainResponse); + } + + public static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse { + return decodeBroadcastTxSync(response.result as RpcBroadcastTxSyncResponse); + } + + public static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse { + return this.decodeBroadcastTxSync(response); + } + + public static decodeBroadcastTxCommit( + response: JsonRpcSuccessResponse, + ): responses.BroadcastTxCommitResponse { + return decodeBroadcastTxCommit(response.result as RpcBroadcastTxCommitResponse); + } + + public static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse { + return decodeCommitResponse(response.result as RpcCommitResponse); + } + + public static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse { + return decodeGenesis(assertObject((response.result as GenesisResult).genesis)); + } + + public static decodeHealth(): responses.HealthResponse { + return null; + } + + public static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse { + return decodeStatus(response.result as RpcStatusResponse); + } + + public static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent { + return decodeBlock(event.data.value.block as RpcBlock); + } + + public static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent { + return decodeHeader(event.data.value.header as RpcHeader); + } + + public static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent { + return decodeTxEvent(event.data.value.TxResult as RpcTxEvent); + } + + public static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse { + return decodeTxResponse(response.result as RpcTxResponse); + } + + public static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse { + return decodeTxSearch(response.result as RpcTxSearchResponse); + } + + public static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse { + return decodeValidators(response.result as RpcValidatorsResponse); + } +} diff --git a/packages/tendermint-rpc/types/responses.d.ts b/packages/tendermint-rpc/types/responses.d.ts index cf409ec429..6565e6bad4 100644 --- a/packages/tendermint-rpc/types/responses.d.ts +++ b/packages/tendermint-rpc/types/responses.d.ts @@ -30,17 +30,16 @@ export interface AbciQueryResponse { readonly log?: string; } export interface BlockResponse { - readonly blockMeta: BlockMeta; + readonly blockId: BlockId; readonly block: Block; } export interface BlockResultsResponse { readonly height: number; readonly results: readonly TxData[]; - readonly endBlock: { - readonly validatorUpdates: readonly Validator[]; - readonly consensusUpdates?: ConsensusParams; - readonly tags?: readonly Tag[]; - }; + readonly validatorUpdates: readonly Validator[]; + readonly consensusUpdates?: ConsensusParams; + readonly beginBlock?: readonly Tag[]; + readonly endBlock?: readonly Tag[]; } export interface BlockchainResponse { readonly lastHeight: number; @@ -170,7 +169,7 @@ export interface Evidence { } export interface Commit { readonly blockId: BlockId; - readonly precommits: readonly Vote[]; + readonly signatures: readonly ValidatorSignature[]; } /** * raw values from https://github.com/tendermint/tendermint/blob/dfa9a9a30a666132425b29454e90a472aa579a48/types/vote.go#L44 @@ -201,8 +200,6 @@ export interface Header { readonly chainId: string; readonly height: number; readonly time: ReadonlyDateWithNanoseconds; - readonly numTxs: number; - readonly totalTxs: number; readonly lastBlockId: BlockId; readonly lastCommitHash: Uint8Array; readonly dataHash: Uint8Array; @@ -258,5 +255,6 @@ export interface BlockGossipParams { readonly blockPartSizeBytes: number; } export interface EvidenceParams { - readonly maxAge: number; + readonly maxAgeNumBlocks: number; + readonly maxAgeDuration: number; } diff --git a/packages/tendermint-rpc/types/v0-33/hasher.d.ts b/packages/tendermint-rpc/types/v0-33/hasher.d.ts new file mode 100644 index 0000000000..de2e7e9012 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-33/hasher.d.ts @@ -0,0 +1,4 @@ +import { Header } from "../responses"; +import { BlockHash, TxBytes, TxHash } from "../types"; +export declare function hashTx(tx: TxBytes): TxHash; +export declare function hashBlock(header: Header): BlockHash; diff --git a/packages/tendermint-rpc/types/v0-33/index.d.ts b/packages/tendermint-rpc/types/v0-33/index.d.ts new file mode 100644 index 0000000000..f5b2b014a6 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-33/index.d.ts @@ -0,0 +1,2 @@ +import { Adaptor } from "../adaptor"; +export declare const v0_33: Adaptor; diff --git a/packages/tendermint-rpc/types/v0-33/requests.d.ts b/packages/tendermint-rpc/types/v0-33/requests.d.ts new file mode 100644 index 0000000000..af972c4a92 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-33/requests.d.ts @@ -0,0 +1,18 @@ +import { JsonRpcRequest } from "@iov/jsonrpc"; +import * as requests from "../requests"; +export declare class Params { + static encodeAbciInfo(req: requests.AbciInfoRequest): JsonRpcRequest; + static encodeAbciQuery(req: requests.AbciQueryRequest): JsonRpcRequest; + static encodeBlock(req: requests.BlockRequest): JsonRpcRequest; + static encodeBlockchain(req: requests.BlockchainRequest): JsonRpcRequest; + static encodeBlockResults(req: requests.BlockResultsRequest): JsonRpcRequest; + static encodeBroadcastTx(req: requests.BroadcastTxRequest): JsonRpcRequest; + static encodeCommit(req: requests.CommitRequest): JsonRpcRequest; + static encodeGenesis(req: requests.GenesisRequest): JsonRpcRequest; + static encodeHealth(req: requests.HealthRequest): JsonRpcRequest; + static encodeStatus(req: requests.StatusRequest): JsonRpcRequest; + static encodeSubscribe(req: requests.SubscribeRequest): JsonRpcRequest; + static encodeTx(req: requests.TxRequest): JsonRpcRequest; + static encodeTxSearch(req: requests.TxSearchRequest): JsonRpcRequest; + static encodeValidators(req: requests.ValidatorsRequest): JsonRpcRequest; +} diff --git a/packages/tendermint-rpc/types/v0-33/responses.d.ts b/packages/tendermint-rpc/types/v0-33/responses.d.ts new file mode 100644 index 0000000000..05fad338b2 --- /dev/null +++ b/packages/tendermint-rpc/types/v0-33/responses.d.ts @@ -0,0 +1,23 @@ +import { JsonRpcSuccessResponse } from "@iov/jsonrpc"; +import * as responses from "../responses"; +import { SubscriptionEvent } from "../rpcclients"; +export declare class Responses { + static decodeAbciInfo(response: JsonRpcSuccessResponse): responses.AbciInfoResponse; + static decodeAbciQuery(response: JsonRpcSuccessResponse): responses.AbciQueryResponse; + static decodeBlock(response: JsonRpcSuccessResponse): responses.BlockResponse; + static decodeBlockResults(response: JsonRpcSuccessResponse): responses.BlockResultsResponse; + static decodeBlockchain(response: JsonRpcSuccessResponse): responses.BlockchainResponse; + static decodeBroadcastTxSync(response: JsonRpcSuccessResponse): responses.BroadcastTxSyncResponse; + static decodeBroadcastTxAsync(response: JsonRpcSuccessResponse): responses.BroadcastTxAsyncResponse; + static decodeBroadcastTxCommit(response: JsonRpcSuccessResponse): responses.BroadcastTxCommitResponse; + static decodeCommit(response: JsonRpcSuccessResponse): responses.CommitResponse; + static decodeGenesis(response: JsonRpcSuccessResponse): responses.GenesisResponse; + static decodeHealth(): responses.HealthResponse; + static decodeStatus(response: JsonRpcSuccessResponse): responses.StatusResponse; + static decodeNewBlockEvent(event: SubscriptionEvent): responses.NewBlockEvent; + static decodeNewBlockHeaderEvent(event: SubscriptionEvent): responses.NewBlockHeaderEvent; + static decodeTxEvent(event: SubscriptionEvent): responses.TxEvent; + static decodeTx(response: JsonRpcSuccessResponse): responses.TxResponse; + static decodeTxSearch(response: JsonRpcSuccessResponse): responses.TxSearchResponse; + static decodeValidators(response: JsonRpcSuccessResponse): responses.ValidatorsResponse; +} From 66e38860f77d1d733cc18b6715ddddd4b2bf8ba3 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 12:45:46 +0200 Subject: [PATCH 14/20] tendermint-rpc: Add v0.33 support (part 2) --- packages/tendermint-rpc/src/client.spec.ts | 2 +- packages/tendermint-rpc/src/jsonrpc.spec.ts | 2 +- packages/tendermint-rpc/src/jsonrpc.ts | 19 ++++++++------- .../src/rpcclients/websocketclient.ts | 4 ++-- .../tendermint-rpc/src/v0-33/responses.ts | 24 +++++++++---------- scripts/tendermint/start.sh | 2 +- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts index acccac915c..c6116f27b7 100644 --- a/packages/tendermint-rpc/src/client.spec.ts +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -22,7 +22,7 @@ function pendingWithoutTendermint(): void { async function tendermintSearchIndexUpdated(): Promise { // Tendermint needs some time before a committed transaction is found in search - return sleep(50); + return sleep(75); } function buildKvTx(k: string, v: string): TxBytes { diff --git a/packages/tendermint-rpc/src/jsonrpc.spec.ts b/packages/tendermint-rpc/src/jsonrpc.spec.ts index f061242fd2..557c70c0db 100644 --- a/packages/tendermint-rpc/src/jsonrpc.spec.ts +++ b/packages/tendermint-rpc/src/jsonrpc.spec.ts @@ -5,7 +5,7 @@ describe("jsonrpc", () => { it("generates proper object with correct method", () => { const request = createJsonRpcRequest("do_something"); expect(request.jsonrpc).toEqual("2.0"); - expect(request.id).toMatch(/^[a-zA-Z0-9]{12}$/); + expect(request.id.toString()).toMatch(/^[0-9]{10,12}$/); expect(request.method).toEqual("do_something"); }); diff --git a/packages/tendermint-rpc/src/jsonrpc.ts b/packages/tendermint-rpc/src/jsonrpc.ts index 45b2318203..0b944bd3c2 100644 --- a/packages/tendermint-rpc/src/jsonrpc.ts +++ b/packages/tendermint-rpc/src/jsonrpc.ts @@ -1,16 +1,19 @@ import { JsonRpcRequest } from "@iov/jsonrpc"; -const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const numbers = "0123456789"; -/** generates a random alphanumeric character */ -function randomChar(): string { - return alphabet[Math.floor(Math.random() * alphabet.length)]; +/** generates a random numeric character */ +function randomNumericChar(): string { + return numbers[Math.floor(Math.random() * numbers.length)]; } -function randomId(): string { - return Array.from({ length: 12 }) - .map(() => randomChar()) - .join(""); +function randomId(): number { + return parseInt( + Array.from({ length: 12 }) + .map(() => randomNumericChar()) + .join(""), + 10, + ); } /** Creates a JSON-RPC request with random ID */ diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts index 51d246e044..e22595c314 100644 --- a/packages/tendermint-rpc/src/rpcclients/websocketclient.ts +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts @@ -95,7 +95,7 @@ class RpcEventProducer implements Producer { // Tendermint adds an "#event" suffix for events that follow a previous subscription // https://github.com/tendermint/tendermint/blob/v0.23.0/rpc/core/events.go#L107 const idEventSubscription = responseStream - .filter((response) => response.id === `${this.request.id}#event`) + .filter((response) => response.id === this.request.id) .subscribe({ next: (response) => { if (isJsonRpcErrorResponse(response)) { @@ -191,7 +191,7 @@ export class WebsocketClient implements RpcStreamingClient { this.subscriptionStreams.set(query, stream); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.subscriptionStreams.get(query)!; + return this.subscriptionStreams.get(query)!.filter((response) => response.query !== undefined); } /** diff --git a/packages/tendermint-rpc/src/v0-33/responses.ts b/packages/tendermint-rpc/src/v0-33/responses.ts index 35eef209e1..90b2bed403 100644 --- a/packages/tendermint-rpc/src/v0-33/responses.ts +++ b/packages/tendermint-rpc/src/v0-33/responses.ts @@ -528,9 +528,9 @@ function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { /** * Example data: * { - * "RootHash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", - * "Data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", - * "Proof": { + * "root_hash": "10A1A17D5F818099B5CAB5B91733A3CC27C0DB6CE2D571AC27FB970C314308BB", + * "data": "ZVlERVhDV2lVNEUwPXhTUjc4Tmp2QkNVSg==", + * "proof": { * "total": "1", * "index": "0", * "leaf_hash": "EKGhfV+BgJm1yrW5FzOjzCfA22zi1XGsJ/uXDDFDCLs=", @@ -539,9 +539,9 @@ function decodeStatus(data: RpcStatusResponse): responses.StatusResponse { * } */ interface RpcTxProof { - readonly Data: Base64String; - readonly RootHash: HexString; - readonly Proof: { + readonly data: Base64String; + readonly root_hash: HexString; + readonly proof: { readonly total: IntegerString; readonly index: IntegerString; readonly leaf_hash: Base64String; @@ -551,13 +551,13 @@ interface RpcTxProof { function decodeTxProof(data: RpcTxProof): responses.TxProof { return { - data: Base64.decode(assertNotEmpty(data.Data)), - rootHash: fromHex(assertNotEmpty(data.RootHash)), + data: Base64.decode(assertNotEmpty(data.data)), + rootHash: fromHex(assertNotEmpty(data.root_hash)), proof: { - total: Integer.parse(assertNotEmpty(data.Proof.total)), - index: Integer.parse(assertNotEmpty(data.Proof.index)), - leafHash: Base64.decode(assertNotEmpty(data.Proof.leaf_hash)), - aunts: assertArray(data.Proof.aunts).map(Base64.decode), + total: Integer.parse(assertNotEmpty(data.proof.total)), + index: Integer.parse(assertNotEmpty(data.proof.index)), + leafHash: Base64.decode(assertNotEmpty(data.proof.leaf_hash)), + aunts: assertArray(data.proof.aunts).map(Base64.decode), }, }; } diff --git a/scripts/tendermint/start.sh b/scripts/tendermint/start.sh index e7bd33f89e..16ac73c0fb 100755 --- a/scripts/tendermint/start.sh +++ b/scripts/tendermint/start.sh @@ -33,7 +33,7 @@ docker run --rm \ --user="$UID" \ --name "$TENDERMINT_NAME" \ -p "${TENDERMINT_PORT}:26657" -v "${TMP_DIR}:/tendermint" \ - -e "TM_TX_INDEX_INDEX_ALL_TAGS=true" \ + -e "TM_TX_INDEX_INDEX_ALL_KEYS=true" \ "tendermint/tendermint:${TENDERMINT_VERSION}" node \ --proxy_app=kvstore \ --rpc.laddr=tcp://0.0.0.0:26657 \ From 3825af984c9214fbe113ede95726f092311bddc1 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 12:58:10 +0200 Subject: [PATCH 15/20] tendermint-rpc: Remove nonces --- packages/tendermint-rpc/nonces/1552895838 | 0 packages/tendermint-rpc/nonces/1553085893 | 0 packages/tendermint-rpc/nonces/1554721221 | 0 packages/tendermint-rpc/nonces/1554724217 | 0 packages/tendermint-rpc/nonces/1554737147 | 0 packages/tendermint-rpc/nonces/1555314694 | 0 packages/tendermint-rpc/nonces/1556008552 | 0 packages/tendermint-rpc/nonces/1556028926 | 0 packages/tendermint-rpc/nonces/1556095341 | 0 packages/tendermint-rpc/nonces/1556616100 | 0 packages/tendermint-rpc/nonces/1557811966 | 0 packages/tendermint-rpc/nonces/1558346811 | 0 packages/tendermint-rpc/nonces/1558456815 | 0 packages/tendermint-rpc/nonces/1558460837 | 0 packages/tendermint-rpc/nonces/1559802671 | 0 packages/tendermint-rpc/nonces/1561970534 | 0 packages/tendermint-rpc/nonces/1562080432 | 0 packages/tendermint-rpc/nonces/1563468776 | 0 packages/tendermint-rpc/nonces/1563887488 | 0 packages/tendermint-rpc/nonces/1563960408 | 0 packages/tendermint-rpc/nonces/1563981076 | 0 packages/tendermint-rpc/nonces/1564503008 | 0 packages/tendermint-rpc/nonces/1564651088 | 0 packages/tendermint-rpc/nonces/1565101189 | 0 packages/tendermint-rpc/nonces/1565595547 | 0 packages/tendermint-rpc/nonces/1565876849 | 0 packages/tendermint-rpc/nonces/1566487600 | 0 packages/tendermint-rpc/nonces/1567435567 | 0 packages/tendermint-rpc/nonces/1567608963 | 0 packages/tendermint-rpc/nonces/1567694160 | 0 packages/tendermint-rpc/nonces/1568039925 | 0 packages/tendermint-rpc/nonces/1568116477 | 0 packages/tendermint-rpc/nonces/1568786866 | 0 packages/tendermint-rpc/nonces/1568910632 | 0 packages/tendermint-rpc/nonces/1569319493 | 0 packages/tendermint-rpc/nonces/1569487848 | 0 packages/tendermint-rpc/nonces/1569929617 | 0 packages/tendermint-rpc/nonces/1570527883 | 0 packages/tendermint-rpc/nonces/1573026590 | 0 packages/tendermint-rpc/nonces/1574869843 | 0 packages/tendermint-rpc/nonces/1576569788 | 0 packages/tendermint-rpc/nonces/1576595306 | 0 packages/tendermint-rpc/nonces/1576678551 | 0 packages/tendermint-rpc/nonces/1576746493 | 0 packages/tendermint-rpc/nonces/1576760285 | 0 packages/tendermint-rpc/nonces/1576767119 | 0 packages/tendermint-rpc/nonces/1579019908 | 0 packages/tendermint-rpc/nonces/1581606289 | 0 packages/tendermint-rpc/nonces/1581681020 | 0 packages/tendermint-rpc/nonces/1584038020 | 0 packages/tendermint-rpc/nonces/1588011428 | 0 packages/tendermint-rpc/nonces/1591293896 | 0 52 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/tendermint-rpc/nonces/1552895838 delete mode 100644 packages/tendermint-rpc/nonces/1553085893 delete mode 100644 packages/tendermint-rpc/nonces/1554721221 delete mode 100644 packages/tendermint-rpc/nonces/1554724217 delete mode 100644 packages/tendermint-rpc/nonces/1554737147 delete mode 100644 packages/tendermint-rpc/nonces/1555314694 delete mode 100644 packages/tendermint-rpc/nonces/1556008552 delete mode 100644 packages/tendermint-rpc/nonces/1556028926 delete mode 100644 packages/tendermint-rpc/nonces/1556095341 delete mode 100644 packages/tendermint-rpc/nonces/1556616100 delete mode 100644 packages/tendermint-rpc/nonces/1557811966 delete mode 100644 packages/tendermint-rpc/nonces/1558346811 delete mode 100644 packages/tendermint-rpc/nonces/1558456815 delete mode 100644 packages/tendermint-rpc/nonces/1558460837 delete mode 100644 packages/tendermint-rpc/nonces/1559802671 delete mode 100644 packages/tendermint-rpc/nonces/1561970534 delete mode 100644 packages/tendermint-rpc/nonces/1562080432 delete mode 100644 packages/tendermint-rpc/nonces/1563468776 delete mode 100644 packages/tendermint-rpc/nonces/1563887488 delete mode 100644 packages/tendermint-rpc/nonces/1563960408 delete mode 100644 packages/tendermint-rpc/nonces/1563981076 delete mode 100644 packages/tendermint-rpc/nonces/1564503008 delete mode 100644 packages/tendermint-rpc/nonces/1564651088 delete mode 100644 packages/tendermint-rpc/nonces/1565101189 delete mode 100644 packages/tendermint-rpc/nonces/1565595547 delete mode 100644 packages/tendermint-rpc/nonces/1565876849 delete mode 100644 packages/tendermint-rpc/nonces/1566487600 delete mode 100644 packages/tendermint-rpc/nonces/1567435567 delete mode 100644 packages/tendermint-rpc/nonces/1567608963 delete mode 100644 packages/tendermint-rpc/nonces/1567694160 delete mode 100644 packages/tendermint-rpc/nonces/1568039925 delete mode 100644 packages/tendermint-rpc/nonces/1568116477 delete mode 100644 packages/tendermint-rpc/nonces/1568786866 delete mode 100644 packages/tendermint-rpc/nonces/1568910632 delete mode 100644 packages/tendermint-rpc/nonces/1569319493 delete mode 100644 packages/tendermint-rpc/nonces/1569487848 delete mode 100644 packages/tendermint-rpc/nonces/1569929617 delete mode 100644 packages/tendermint-rpc/nonces/1570527883 delete mode 100644 packages/tendermint-rpc/nonces/1573026590 delete mode 100644 packages/tendermint-rpc/nonces/1574869843 delete mode 100644 packages/tendermint-rpc/nonces/1576569788 delete mode 100644 packages/tendermint-rpc/nonces/1576595306 delete mode 100644 packages/tendermint-rpc/nonces/1576678551 delete mode 100644 packages/tendermint-rpc/nonces/1576746493 delete mode 100644 packages/tendermint-rpc/nonces/1576760285 delete mode 100644 packages/tendermint-rpc/nonces/1576767119 delete mode 100644 packages/tendermint-rpc/nonces/1579019908 delete mode 100644 packages/tendermint-rpc/nonces/1581606289 delete mode 100644 packages/tendermint-rpc/nonces/1581681020 delete mode 100644 packages/tendermint-rpc/nonces/1584038020 delete mode 100644 packages/tendermint-rpc/nonces/1588011428 delete mode 100644 packages/tendermint-rpc/nonces/1591293896 diff --git a/packages/tendermint-rpc/nonces/1552895838 b/packages/tendermint-rpc/nonces/1552895838 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1553085893 b/packages/tendermint-rpc/nonces/1553085893 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1554721221 b/packages/tendermint-rpc/nonces/1554721221 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1554724217 b/packages/tendermint-rpc/nonces/1554724217 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1554737147 b/packages/tendermint-rpc/nonces/1554737147 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1555314694 b/packages/tendermint-rpc/nonces/1555314694 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1556008552 b/packages/tendermint-rpc/nonces/1556008552 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1556028926 b/packages/tendermint-rpc/nonces/1556028926 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1556095341 b/packages/tendermint-rpc/nonces/1556095341 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1556616100 b/packages/tendermint-rpc/nonces/1556616100 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1557811966 b/packages/tendermint-rpc/nonces/1557811966 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1558346811 b/packages/tendermint-rpc/nonces/1558346811 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1558456815 b/packages/tendermint-rpc/nonces/1558456815 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1558460837 b/packages/tendermint-rpc/nonces/1558460837 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1559802671 b/packages/tendermint-rpc/nonces/1559802671 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1561970534 b/packages/tendermint-rpc/nonces/1561970534 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1562080432 b/packages/tendermint-rpc/nonces/1562080432 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1563468776 b/packages/tendermint-rpc/nonces/1563468776 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1563887488 b/packages/tendermint-rpc/nonces/1563887488 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1563960408 b/packages/tendermint-rpc/nonces/1563960408 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1563981076 b/packages/tendermint-rpc/nonces/1563981076 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1564503008 b/packages/tendermint-rpc/nonces/1564503008 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1564651088 b/packages/tendermint-rpc/nonces/1564651088 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1565101189 b/packages/tendermint-rpc/nonces/1565101189 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1565595547 b/packages/tendermint-rpc/nonces/1565595547 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1565876849 b/packages/tendermint-rpc/nonces/1565876849 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1566487600 b/packages/tendermint-rpc/nonces/1566487600 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1567435567 b/packages/tendermint-rpc/nonces/1567435567 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1567608963 b/packages/tendermint-rpc/nonces/1567608963 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1567694160 b/packages/tendermint-rpc/nonces/1567694160 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1568039925 b/packages/tendermint-rpc/nonces/1568039925 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1568116477 b/packages/tendermint-rpc/nonces/1568116477 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1568786866 b/packages/tendermint-rpc/nonces/1568786866 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1568910632 b/packages/tendermint-rpc/nonces/1568910632 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1569319493 b/packages/tendermint-rpc/nonces/1569319493 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1569487848 b/packages/tendermint-rpc/nonces/1569487848 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1569929617 b/packages/tendermint-rpc/nonces/1569929617 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1570527883 b/packages/tendermint-rpc/nonces/1570527883 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1573026590 b/packages/tendermint-rpc/nonces/1573026590 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1574869843 b/packages/tendermint-rpc/nonces/1574869843 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576569788 b/packages/tendermint-rpc/nonces/1576569788 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576595306 b/packages/tendermint-rpc/nonces/1576595306 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576678551 b/packages/tendermint-rpc/nonces/1576678551 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576746493 b/packages/tendermint-rpc/nonces/1576746493 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576760285 b/packages/tendermint-rpc/nonces/1576760285 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1576767119 b/packages/tendermint-rpc/nonces/1576767119 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1579019908 b/packages/tendermint-rpc/nonces/1579019908 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1581606289 b/packages/tendermint-rpc/nonces/1581606289 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1581681020 b/packages/tendermint-rpc/nonces/1581681020 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1584038020 b/packages/tendermint-rpc/nonces/1584038020 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1588011428 b/packages/tendermint-rpc/nonces/1588011428 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/tendermint-rpc/nonces/1591293896 b/packages/tendermint-rpc/nonces/1591293896 deleted file mode 100644 index e69de29bb2..0000000000 From cff8e0eef459c185a750a31127a50150c95c095a Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 13:01:40 +0200 Subject: [PATCH 16/20] tendermint-rpc: Remove tslint comments --- packages/tendermint-rpc/src/client.spec.ts | 1 - packages/tendermint-rpc/src/client.ts | 1 - packages/tendermint-rpc/src/encodings.spec.ts | 1 - packages/tendermint-rpc/src/encodings.ts | 3 +-- packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts | 3 --- packages/tendermint-rpc/src/rpcclients/websocketclient.ts | 1 - packages/tendermint-rpc/src/v0-33/hasher.spec.ts | 2 -- 7 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/tendermint-rpc/src/client.spec.ts b/packages/tendermint-rpc/src/client.spec.ts index c6116f27b7..2e7e4557a3 100644 --- a/packages/tendermint-rpc/src/client.spec.ts +++ b/packages/tendermint-rpc/src/client.spec.ts @@ -1,4 +1,3 @@ -// tslint:disable:readonly-array import { toAscii } from "@cosmjs/encoding"; import { sleep } from "@cosmjs/utils"; import { firstEvent, toListPromise } from "@iov/stream"; diff --git a/packages/tendermint-rpc/src/client.ts b/packages/tendermint-rpc/src/client.ts index 7cf9db2228..23fd42ec63 100644 --- a/packages/tendermint-rpc/src/client.ts +++ b/packages/tendermint-rpc/src/client.ts @@ -197,7 +197,6 @@ export class Client { // starts with page 1 or whatever was provided (eg. to start on page 7) public async txSearchAll(params: requests.TxSearchParams): Promise { let page = params.page || 1; - // tslint:disable-next-line:readonly-array const txs: responses.TxResponse[] = []; let done = false; diff --git a/packages/tendermint-rpc/src/encodings.spec.ts b/packages/tendermint-rpc/src/encodings.spec.ts index f251ef16d0..afea0600d3 100644 --- a/packages/tendermint-rpc/src/encodings.spec.ts +++ b/packages/tendermint-rpc/src/encodings.spec.ts @@ -27,7 +27,6 @@ describe("encodings", () => { describe("encodeTime", () => { it("works", () => { const readonlyDateWithNanoseconds = new ReadonlyDate(1464109200); - // tslint:disable-next-line:no-object-mutation (readonlyDateWithNanoseconds as any).nanoseconds = 666666; expect(encodeTime(readonlyDateWithNanoseconds)).toEqual( Uint8Array.from([0x08, 173, 174, 89, 0x10, 170, 220, 215, 95]), diff --git a/packages/tendermint-rpc/src/encodings.ts b/packages/tendermint-rpc/src/encodings.ts index ac8bc9252d..b57ba1c9eb 100644 --- a/packages/tendermint-rpc/src/encodings.ts +++ b/packages/tendermint-rpc/src/encodings.ts @@ -162,7 +162,6 @@ export class DateTime { const readonlyDate = fromRfc3339(dateTimeString); const nanosecondsMatch = dateTimeString.match(/\.(\d+)Z$/); const nanoseconds = nanosecondsMatch ? nanosecondsMatch[1].slice(3) : ""; - // tslint:disable-next-line:no-object-mutation (readonlyDate as any).nanoseconds = parseInt(nanoseconds.padEnd(6, "0"), 10); return readonlyDate as ReadonlyDateWithNanoseconds; } @@ -189,7 +188,7 @@ export function encodeString(s: string): Uint8Array { // See https://github.com/tendermint/go-amino/blob/v0.15.0/encoder.go#L79-L87 export function encodeInt(n: number): Uint8Array { - // tslint:disable-next-line:no-bitwise + // eslint-disable-next-line no-bitwise return n >= 0x80 ? Uint8Array.from([(n & 0xff) | 0x80, ...encodeInt(n >> 7)]) : Uint8Array.from([n & 0xff]); } diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts index df96a06aca..9008e2c618 100644 --- a/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts @@ -46,7 +46,6 @@ describe("WebsocketClient", () => { const req = createJsonRpcRequest("subscribe", { query: query }); const headers = client.listen(req); - // tslint:disable-next-line:readonly-array const events: SubscriptionEvent[] = []; const sub = headers.subscribe({ @@ -109,7 +108,6 @@ describe("WebsocketClient", () => { const req = createJsonRpcRequest("subscribe", { query: query }); const headers = client.listen(req); - // tslint:disable-next-line:readonly-array const events: SubscriptionEvent[] = []; const sub = headers.subscribe({ @@ -148,7 +146,6 @@ describe("WebsocketClient", () => { const req = createJsonRpcRequest("subscribe", { query: query }); const headers = client.listen(req); - // tslint:disable-next-line:readonly-array const receivedEvents: SubscriptionEvent[] = []; setTimeout(() => client.disconnect(), 1500); diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts index e22595c314..9d84f8174c 100644 --- a/packages/tendermint-rpc/src/rpcclients/websocketclient.ts +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.ts @@ -1,4 +1,3 @@ -/* tslint:disable:readonly-keyword readonly-array no-object-mutation */ import { isJsonRpcErrorResponse, JsonRpcId, diff --git a/packages/tendermint-rpc/src/v0-33/hasher.spec.ts b/packages/tendermint-rpc/src/v0-33/hasher.spec.ts index f78576b715..96d9feb087 100644 --- a/packages/tendermint-rpc/src/v0-33/hasher.spec.ts +++ b/packages/tendermint-rpc/src/v0-33/hasher.spec.ts @@ -19,7 +19,6 @@ describe("Hasher", () => { // curl "http://localhost:11133/block" const blockId = fromHex("153C484DCBC33633F0616BC019388C93DEA94F7880627976F2BFE83749E062F7"); const time = new ReadonlyDate("2020-06-23T13:54:15.4638668Z"); - // tslint:disable-next-line:no-object-mutation (time as any).nanoseconds = 866800; const blockData = { version: { @@ -58,7 +57,6 @@ describe("Hasher", () => { // curl "http://localhost:11133/block?height=13575" const blockId = fromHex("FF2995AF1F38B9A584077E53B5E144778718FB86539A51886A2C55F730403373"); const time = new ReadonlyDate("2020-06-23T15:34:12.3232688Z"); - // tslint:disable-next-line:no-object-mutation (time as any).nanoseconds = 268800; const blockData = { version: { From 2c2423292f50735700f3fef308e928d5454bb31a Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 13:19:39 +0200 Subject: [PATCH 17/20] tendermint-rpc: Update package.json author/description --- packages/tendermint-rpc/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index da9a59a06e..94f9932c49 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -1,8 +1,8 @@ { "name": "@cosmjs/tendermint-rpc", "version": "0.20.0", - "description": "Codec to encode/decode bns transactions and state objects", - "author": "IOV SAS ", + "description": "Tendermint RPC clients", + "author": "Ethan Frey ", "license": "Apache-2.0", "main": "build/index.js", "types": "types/index.d.ts", From c0539e651711ddf396cdb4e61e3525e168ddcfd7 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 13:27:11 +0200 Subject: [PATCH 18/20] tendermint-rpc: Update README --- packages/tendermint-rpc/README.md | 81 +++---------------------------- 1 file changed, 6 insertions(+), 75 deletions(-) diff --git a/packages/tendermint-rpc/README.md b/packages/tendermint-rpc/README.md index 95b6126fea..31b423b6aa 100644 --- a/packages/tendermint-rpc/README.md +++ b/packages/tendermint-rpc/README.md @@ -1,84 +1,15 @@ -# @iov/tendermint-rpc +# @cosmjs/tendermint-rpc -[![npm version](https://img.shields.io/npm/v/@iov/tendermint-rpc.svg)](https://www.npmjs.com/package/@iov/tendermint-rpc) +[![npm version](https://img.shields.io/npm/v/@cosmjs/tendermint-rpc.svg)](https://www.npmjs.com/package/@cosmjs/tendermint-rpc) This package provides a type-safe wrapper around [Tendermint RPC](https://docs.tendermint.com/master/rpc/). Notably, all binary -data is passed in and out as Uint8Array, and this module is reponsible for the +data is passed in and out as `Uint8Array`, and this module is reponsible for the hex/base64 encoding/decoding depending on the field and version of Tendermint. Also handles converting numbers to and from strings. -## Getting started - -The simplest possible use of the module is to assume it does everything -automatically, and call: - -```ts -import { Client } from "@iov/tendermint-rpc"; - -const client = await Client.connect("ws://rpc-private-a-x-exchangenet.iov.one:16657"); - -const genesis = await client.genesis(); -const status = await client.status(); -``` - -A query to the ABCI application looks like this: - -```ts -const results = await client.abciQuery({ - path: "/tokens?prefix", - data: new Uint8Array([]), -}); -``` - -## Supported Tendermint versions - -| IOV-Core version | Supported Tendermint versions | -| ---------------- | ----------------------------- | -| 1.1 | 0.31.x – 0.32.x | -| 1.0 | 0.31.x | -| 0.15 – 0.17 | 0.29.x – 0.31.x | -| 0.12 – 0.14 | 0.25.x, 0.27.x – 0.29.x | -| 0.11 | 0.25.x, 0.27.x | -| 0.9 – 0.10 | 0.20.x, 0.21.x, 0.25.x | -| 0.1 – 0.8 | 0.20.x, 0.21.x | - -Support for Tendermint versions is determined by demand for IOV's own products. -Please let us know if you need support for other versions of Tendermint or need -long term support for one specific Tendermint version. - -## Code Overview - -The main entry point is the -[Client](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/client.html). - -The connection to the blockchain is defined by a flexible -[RpcClient](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/interfaces/rpcclient.html) -interface, with implementations for HTTP (POST) and WebSockets. You can add your -own connection type or just wrap one with custom retry rules, error handling, -etc. The RPC client is just responsible for sending JSON-RPC requests and -returning the responses. - -The actual domain knowledge is embedded in the -[Adaptor](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/interfaces/adaptor.html), -which defines a class for encoding -[Params](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/params.html) -and another for decoding -[Responses](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/responses.html). -The Tendermint version-specific functionality is implemented in global objects -(like e.g. -[v0_32](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/globals.html#v0_32)). -This knowledge is mainly for those who want to add support for new versions, -which should be added to the -[auto-detect method](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/classes/client.html#detectversion). - -## API Documentation - -[https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/](https://iov-one.github.io/iov-core-docs/latest/iov-tendermint-rpc/) - ## License -This package is part of the IOV-Core repository, licensed under the Apache -License 2.0 (see -[NOTICE](https://github.com/iov-one/iov-core/blob/master/NOTICE) and -[LICENSE](https://github.com/iov-one/iov-core/blob/master/LICENSE)). +This package is part of the cosmjs repository, licensed under the Apache License +2.0 (see [NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and +[LICENSE](https://github.com/CosmWasm/cosmjs/blob/master/LICENSE)). From bb35c86d35f9d6997caaa5432cc516a5f31cad4b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 13:27:21 +0200 Subject: [PATCH 19/20] root: Update NOTICE for tendermint --- NOTICE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NOTICE b/NOTICE index 865070d5e5..db3598d9d4 100644 --- a/NOTICE +++ b/NOTICE @@ -17,6 +17,8 @@ on 2020-06-05. The code in packages/encoding and packages/math was forked from https://github.com/iov-one/iov-core/tree/v2.3.2/packages/iov-encoding on 2020-06-05. +The code in packages/tendermint-rpc and scripts/tendermint was forked from the folders packages/iov-tendermint-rpc and scripts/tendermint respectively of https://github.com/iov-one/iov-core at tag v2.5.0 on 2020-06-15. + Copyright 2018-2020 IOV SAS Copyright 2020 Confio UO Copyright 2020 Simon Warta From cefd80160166bf4ab4ad3cd77d817fbdcf9fb561 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 24 Jun 2020 15:10:32 +0200 Subject: [PATCH 20/20] tendermint-rpc: Align package.json --- packages/tendermint-rpc/package.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/tendermint-rpc/package.json b/packages/tendermint-rpc/package.json index 94f9932c49..71ec1ea031 100644 --- a/packages/tendermint-rpc/package.json +++ b/packages/tendermint-rpc/package.json @@ -2,7 +2,11 @@ "name": "@cosmjs/tendermint-rpc", "version": "0.20.0", "description": "Tendermint RPC clients", - "author": "Ethan Frey ", + "contributors": [ + "IOV SAS ", + "Confio UO ", + "Will Clark " + ], "license": "Apache-2.0", "main": "build/index.js", "types": "types/index.d.ts", @@ -15,7 +19,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/iov-one/iov-core/tree/master/packages/iov-tendermint-rpc" + "url": "https://github.com/CosmWasm/cosmjs/tree/master/packages/tendermint-rpc" }, "publishConfig": { "access": "public" @@ -23,6 +27,7 @@ "scripts": { "docs": "shx rm -rf docs && typedoc --options typedoc.js", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "test-node": "node jasmine-testrunner.js",