@ -0,0 +1,139 @@ | |||||
version: 2 | |||||
defaults: &defaults | |||||
working_directory: /go/src/github.com/tendermint/abci | |||||
docker: | |||||
- image: circleci/golang:1.10.0 | |||||
environment: | |||||
GOBIN: /tmp/workspace/bin | |||||
jobs: | |||||
setup_dependencies: | |||||
<<: *defaults | |||||
steps: | |||||
- run: mkdir -p /tmp/workspace/bin | |||||
- run: mkdir -p /tmp/workspace/profiles | |||||
- checkout | |||||
- restore_cache: | |||||
keys: | |||||
- v1-pkg-cache | |||||
- run: | |||||
name: tools | |||||
command: | | |||||
export PATH="$GOBIN:$PATH" | |||||
make get_tools | |||||
- run: | |||||
name: dependencies | |||||
command: | | |||||
export PATH="$GOBIN:$PATH" | |||||
make get_vendor_deps | |||||
- run: | |||||
name: binaries | |||||
command: | | |||||
export PATH="$GOBIN:$PATH" | |||||
make install | |||||
- persist_to_workspace: | |||||
root: /tmp/workspace | |||||
paths: | |||||
- bin | |||||
- profiles | |||||
- save_cache: | |||||
key: v1-pkg-cache | |||||
paths: | |||||
- /go/pkg | |||||
- save_cache: | |||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} | |||||
paths: | |||||
- /go/src/github.com/tendermint/abci | |||||
test_apps: | |||||
<<: *defaults | |||||
steps: | |||||
- attach_workspace: | |||||
at: /tmp/workspace | |||||
- restore_cache: | |||||
key: v1-pkg-cache | |||||
- restore_cache: | |||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} | |||||
- run: | |||||
name: Run apps tests | |||||
command: | | |||||
export PATH="$GOBIN:$PATH" | |||||
bash tests/test_app/test.sh | |||||
# XXX: if this test fails, fix it and update the docs at: | |||||
# https://github.com/tendermint/tendermint/blob/develop/docs/abci-cli.rst | |||||
test_cli: | |||||
<<: *defaults | |||||
steps: | |||||
- attach_workspace: | |||||
at: /tmp/workspace | |||||
- restore_cache: | |||||
key: v1-pkg-cache | |||||
- restore_cache: | |||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} | |||||
- run: | |||||
name: Run cli tests | |||||
command: | | |||||
export PATH="$GOBIN:$PATH" | |||||
bash tests/test_cli/test.sh | |||||
test_cover: | |||||
<<: *defaults | |||||
parallelism: 4 | |||||
steps: | |||||
- attach_workspace: | |||||
at: /tmp/workspace | |||||
- restore_cache: | |||||
key: v1-pkg-cache | |||||
- restore_cache: | |||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} | |||||
- run: | |||||
name: Run test cover | |||||
command: | | |||||
for pkg in $(go list github.com/tendermint/abci/... | grep -v /vendor/ | circleci tests split --split-by=timings); do | |||||
id=$(basename "$pkg") | |||||
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | |||||
done | |||||
- persist_to_workspace: | |||||
root: /tmp/workspace | |||||
paths: | |||||
- "profiles/*" | |||||
upload_coverage: | |||||
<<: *defaults | |||||
steps: | |||||
- attach_workspace: | |||||
at: /tmp/workspace | |||||
- restore_cache: | |||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} | |||||
- run: | |||||
name: gather | |||||
command: | | |||||
set -ex | |||||
echo "mode: atomic" > coverage.txt | |||||
for prof in $(ls /tmp/workspace/profiles/); do | |||||
tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt | |||||
done | |||||
- run: | |||||
name: upload | |||||
command: bash <(curl -s https://codecov.io/bash) -f coverage.txt | |||||
workflows: | |||||
version: 2 | |||||
test-suite: | |||||
jobs: | |||||
- setup_dependencies | |||||
- test_cover: | |||||
requires: | |||||
- setup_dependencies | |||||
- test_apps: | |||||
requires: | |||||
- setup_dependencies | |||||
- test_cli: | |||||
requires: | |||||
- setup_dependencies | |||||
- upload_coverage: | |||||
requires: | |||||
- test_cover |
@ -0,0 +1,19 @@ | |||||
# top-most EditorConfig file | |||||
root = true | |||||
# Unix-style newlines with a newline ending every file | |||||
[*] | |||||
charset = utf-8 | |||||
end_of_line = lf | |||||
insert_final_newline = true | |||||
trim_trailing_whitespace = true | |||||
[Makefile] | |||||
indent_style = tab | |||||
[*.sh] | |||||
indent_style = tab | |||||
[*.proto] | |||||
indent_style = space | |||||
indent_size = 2 |
@ -0,0 +1,7 @@ | |||||
vendor | |||||
.glide | |||||
types/types.pb.go | |||||
*.sw[op] | |||||
abci-cli | |||||
coverage.txt | |||||
profile.out |
@ -0,0 +1,352 @@ | |||||
# Changelog | |||||
## 0.12.0 | |||||
*2018-06-12* | |||||
BREAKING CHANGES: | |||||
- [abci-cli] Change rpc port from 46658 to 26658. | |||||
- [examples] Change rpc port from 46658 to 26658. | |||||
## 0.11.0 | |||||
*June 6, 2018* | |||||
BREAKING CHANGES: | |||||
- [example/dummy] Remove. See example/kvstore | |||||
- [types] Upgrade many messages: | |||||
- RequestInitChain takes all fields from a Genesis file | |||||
- RequestBeginBlock provides a list of all validators and whether or not | |||||
they signed | |||||
- Header: remove some fields, add proposer | |||||
- BlockID, PartSetHeader: remove | |||||
- Validator: includes address | |||||
- PubKey: new message with `type` and `data` | |||||
- Evidence: add type and more fields | |||||
FEATURES: | |||||
- [types] Add some fields | |||||
- ResponseInitChain includes ConsensusParams and Validators | |||||
- ResponseBeginBlock includes tags | |||||
- ResponseEndBlock includes tags | |||||
## 0.10.3 (April 9, 2018) | |||||
IMPROVEMENTS: | |||||
- Update tmlibs dep | |||||
## 0.10.2 (March 23, 2018) | |||||
Hot fix to remove `omitempty` from `fee` and to actually run `make | |||||
protoc` | |||||
## 0.10.1 (March 22, 2018) | |||||
FEATURES: | |||||
- [types] ResponseCheckTx and ResponseDeliverTx are now the same. | |||||
- [example] `dummy` is duplicated as `kvstore`. | |||||
IMPROVEMENTS: | |||||
- glide -> Godep | |||||
- remove pkg/errors | |||||
- improve specification.rst | |||||
## 0.10.0 (February 20, 2018) | |||||
BREAKING CHANGES: | |||||
- [types] Socket messages are length prefixed with real protobuf Varint instead of `<len of len><big endian len>` | |||||
- [types] Drop gogo custom type magic with data.Bytes | |||||
- [types] Use `[(gogoproto.nullable)=false]` to prefer value over pointer for the types | |||||
- [types] Field re-ordering ... | |||||
- [types] KVPair: replace with common.KVPair. Add common KI64Pair too (for fees). | |||||
- [types] CheckTx/DeliverTx: updates for tags, gas, fees | |||||
- [types] Commit: Remove code and log from Commit | |||||
- [types] SetOption: Remove code | |||||
- [example/dummy] remove dependence on IAVL | |||||
- [types] IsOk/IsErr: methods removed | |||||
FEATURES: | |||||
- [types] SetOption/Query/CheckTx/DeliverTx: Add `info string` field to responses | |||||
- [types] RequestInitChain.AppStateBytes for app's genesis state | |||||
IMPROVEMENTS: | |||||
- [all] remove go-wire and go-crypto dependencies :) | |||||
## 0.9.0 (December 28, 2017) | |||||
BREAKING CHANGES: | |||||
- [types] Id -> ID | |||||
- [types] ResponseEndBlock: renamed Diffs field to ValidatorUpdates | |||||
- [types] changed protobuf field indices for Request and Response oneof types | |||||
FEATURES: | |||||
- [types] ResponseEndBlock: added ConsensusParamUpdates | |||||
BUG FIXES: | |||||
- [cmd] fix console and batch commands to use a single persistent connection | |||||
## 0.8.0 (December 6, 2017) | |||||
BREAKING CHANGES: | |||||
- [client] all XxxSync methods now return (ResponseXxx, error) | |||||
- [types] all methods on Application interface now take RequestXxx and return (ResponseXxx, error). | |||||
- Except `CheckTx`/`DeliverTx`, which takes a `tx []byte` argument. | |||||
- Except `Commit`, which takes no arguments. | |||||
- [types] removed Result and ResultQuery | |||||
- [types] removed CodeType - only `0 == OK` is defined here, everything else is left to convention at the application level | |||||
- [types] switched to using `gogo/protobuf` for code generation | |||||
- [types] use `customtype` feature of `gogo/protobuf` to replace `[]byte` with `data.Bytes` in all generated types :) | |||||
- this eliminates the need for additional types like ResultQuery | |||||
- [types] `pubKey` -> `pub_key` | |||||
- [types] `uint64` -> `int32` for `Header.num_txs` and `PartSetHeader.total` | |||||
- [types] `uint64` -> `int64` for everything else | |||||
- [types] ResponseSetOption includes error code | |||||
- [abci-cli] codes are printed as their number instead of a message, except for `code == 0`, which is still printed as `OK` | |||||
FEATURES: | |||||
- [types] ResponseDeliverTx: added `tags` field | |||||
- [types] ResponseCheckTx: added `gas` and `fee` fields | |||||
- [types] RequestBeginBlock: added `absent_validators` and `byzantine_validators` fields | |||||
- [dummy] DeliverTx returns an owner tag and a key tag | |||||
- [abci-cli] added `log_level` flag to control the logger | |||||
- [abci-cli] introduce `abci-cli test` command for simple testing of ABCI server implementations via Counter application | |||||
## 0.7.1 (November 14, 2017) | |||||
IMPROVEMENTS: | |||||
- [cli] added version command | |||||
BUG FIXES: | |||||
- [server] fix "Connection error module=abci-server error=EOF" | |||||
## 0.7.0 (October 27, 2017) | |||||
BREAKING CHANGES: | |||||
- [cli] consolidate example apps under a single `abci-cli` binary | |||||
IMPROVEMENTS: | |||||
- [cli] use spf13/cobra instead of urfave/cli | |||||
- [dummy] use iavl instead of merkleeyes, and add support for historical queries | |||||
BUG FIXES: | |||||
- [client] fix deadlock on StopForError | |||||
## 0.6.0 (September 22, 2017) | |||||
BREAKING CHANGES: | |||||
- [types/client] app.BeginBlock takes RequestBeginBlock | |||||
- [types/client] app.InitChain takes RequestInitChain | |||||
- [types/client] app.Info takes RequestInfo | |||||
IMPROVEMENTS: | |||||
- various linting | |||||
## 0.5.0 (May 18, 2017) | |||||
BREAKING CHANGES: | |||||
- `NewSocketClient` and `NewGRPCClient` no longer start the client automatically, and don't return errors. The caller is responsible for running `client.Start()` and checking the error. | |||||
- `NewSocketServer` and `NewGRPCServer` no longer start the server automatically, and don't return errors. The caller is responsible for running `server.Start()` and checking the error. | |||||
FEATURES: | |||||
- [types] new method `func (res Result) IsSameCode(compare Result) bool` checks whether two results have the same code | |||||
- [types] new methods `func (r *ResponseCheckTx) Result() Result` and `func (r *ResponseDeliverTx) Result() Result` to convert from protobuf types (for control over json serialization) | |||||
- [types] new method `func (r *ResponseQuery) Result() *ResultQuery` and struct `ResultQuery` to convert from protobuf types (for control over json serializtion) | |||||
IMPROVEMENTS: | |||||
- Update imports for new `tmlibs` repository | |||||
- Use the new logger | |||||
- [abci-cli] Add flags to the query command for `path`, `height`, and `prove` | |||||
- [types] use `data.Bytes` and `json` tags in the `Result` struct | |||||
BUG FIXES: | |||||
## 0.4.1 (April 18, 2017) | |||||
IMPROVEMENTS: | |||||
- Update dependencies | |||||
## 0.4.0 (March 6, 2017) | |||||
BREAKING CHANGES: | |||||
- Query takes RequestQuery and returns ResponseQuery. The request is split into `data` and `path`, | |||||
can specify a height to query the state from, and whether or not the response should come with a proof. | |||||
The response returns the corresponding key-value pair, with proof if requested. | |||||
``` | |||||
message RequestQuery{ | |||||
bytes data = 1; | |||||
string path = 2; | |||||
uint64 height = 3; | |||||
bool prove = 4; | |||||
} | |||||
message ResponseQuery{ | |||||
CodeType code = 1; | |||||
int64 index = 2; | |||||
bytes key = 3; | |||||
bytes value = 4; | |||||
bytes proof = 5; | |||||
uint64 height = 6; | |||||
string log = 7; | |||||
} | |||||
``` | |||||
IMPROVEMENTS: | |||||
- Updates to Makefile | |||||
- Various cleanup | |||||
- BaseApplication can be embedded by new apps to avoid implementing empty methods | |||||
- Drop BlockchainAware and make BeginBlock/EndBlock part of the `type Application interface` | |||||
## 0.3.0 (January 12, 2017) | |||||
BREAKING CHANGES: | |||||
- TMSP is now ABCI (Application/Asynchronous/A BlockChain Interface or Atomic BroadCast Interface) | |||||
- AppendTx is now DeliverTx (conforms to the literature) | |||||
- BeginBlock takes a Header: | |||||
``` | |||||
message RequestBeginBlock{ | |||||
bytes hash = 1; | |||||
Header header = 2; | |||||
} | |||||
``` | |||||
- Info returns a ResponseInfo, containing last block height and app hash: | |||||
``` | |||||
message ResponseInfo { | |||||
string data = 1; | |||||
string version = 2; | |||||
uint64 last_block_height = 3; | |||||
bytes last_block_app_hash = 4; | |||||
} | |||||
``` | |||||
- EndBlock returns a ResponseEndBlock, containing the changed validators: | |||||
``` | |||||
message ResponseEndBlock{ | |||||
repeated Validator diffs = 4; | |||||
} | |||||
``` | |||||
- Hex strings are 0x-prefixed in the CLI | |||||
- Query on the Dummy app now uses hex-strings | |||||
FEATURES: | |||||
- New app, PersistentDummy, uses Info/BeginBlock to recover from failures and supports validator set changes | |||||
- New message types for blockchain data: | |||||
``` | |||||
//---------------------------------------- | |||||
// Blockchain Types | |||||
message Header { | |||||
string chain_id = 1; | |||||
uint64 height = 2; | |||||
uint64 time = 3; | |||||
uint64 num_txs = 4; | |||||
BlockID last_block_id = 5; | |||||
bytes last_commit_hash = 6; | |||||
bytes data_hash = 7; | |||||
bytes validators_hash = 8; | |||||
bytes app_hash = 9; | |||||
} | |||||
message BlockID { | |||||
bytes hash = 1; | |||||
PartSetHeader parts = 2; | |||||
} | |||||
message PartSetHeader { | |||||
uint64 total = 1; | |||||
bytes hash = 2; | |||||
} | |||||
message Validator { | |||||
bytes pubKey = 1; | |||||
uint64 power = 2; | |||||
} | |||||
``` | |||||
- Add support for Query to Counter app | |||||
IMPROVEMENT: | |||||
- Don't exit the tmsp-cli console on bad args | |||||
BUG FIXES: | |||||
- Fix parsing in the Counter app to handle invalid transactions | |||||
## 0.2.1 (September 12, 2016) | |||||
IMPROVEMENTS | |||||
- Better error handling in console | |||||
## 0.2.0 (July 23, 2016) | |||||
BREAKING CHANGES: | |||||
- Use `oneof` types in protobuf | |||||
FEATURES: | |||||
- GRPC support | |||||
## PreHistory | |||||
##### Mar 26h, 2016 | |||||
* Introduce BeginBlock | |||||
##### Mar 6th, 2016 | |||||
* Added InitChain, EndBlock | |||||
##### Feb 14th, 2016 | |||||
* s/GetHash/Commit/g | |||||
* Document Protobuf request/response fields | |||||
##### Jan 23th, 2016 | |||||
* Added CheckTx/Query ABCI message types | |||||
* Added Result/Log fields to DeliverTx/CheckTx/SetOption | |||||
* Removed Listener messages | |||||
* Removed Code from ResponseSetOption and ResponseGetHash | |||||
* Made examples BigEndian | |||||
##### Jan 12th, 2016 | |||||
* Added "RetCodeBadNonce = 0x06" return code | |||||
##### Jan 8th, 2016 | |||||
* Tendermint/ABCI now comes to consensus on the order first before DeliverTx. | |||||
@ -0,0 +1,23 @@ | |||||
FROM golang:latest | |||||
RUN mkdir -p /go/src/github.com/tendermint/abci | |||||
WORKDIR /go/src/github.com/tendermint/abci | |||||
COPY Makefile /go/src/github.com/tendermint/abci/ | |||||
# see make protoc for details on ldconfig | |||||
RUN make get_protoc && ldconfig | |||||
# killall is used in tests | |||||
RUN apt-get update && apt-get install -y \ | |||||
psmisc \ | |||||
&& rm -rf /var/lib/apt/lists/* | |||||
COPY Gopkg.toml /go/src/github.com/tendermint/abci/ | |||||
COPY Gopkg.lock /go/src/github.com/tendermint/abci/ | |||||
RUN make get_tools | |||||
# see https://github.com/golang/dep/issues/1312 | |||||
RUN dep ensure -vendor-only | |||||
COPY . /go/src/github.com/tendermint/abci |
@ -0,0 +1,213 @@ | |||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. | |||||
[[projects]] | |||||
name = "github.com/davecgh/go-spew" | |||||
packages = ["spew"] | |||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76" | |||||
version = "v1.1.0" | |||||
[[projects]] | |||||
name = "github.com/go-kit/kit" | |||||
packages = [ | |||||
"log", | |||||
"log/level", | |||||
"log/term" | |||||
] | |||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71" | |||||
version = "v0.6.0" | |||||
[[projects]] | |||||
name = "github.com/go-logfmt/logfmt" | |||||
packages = ["."] | |||||
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
name = "github.com/go-stack/stack" | |||||
packages = ["."] | |||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" | |||||
version = "v1.7.0" | |||||
[[projects]] | |||||
name = "github.com/gogo/protobuf" | |||||
packages = [ | |||||
"gogoproto", | |||||
"jsonpb", | |||||
"proto", | |||||
"protoc-gen-gogo/descriptor", | |||||
"sortkeys", | |||||
"types" | |||||
] | |||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
name = "github.com/golang/protobuf" | |||||
packages = [ | |||||
"proto", | |||||
"ptypes", | |||||
"ptypes/any", | |||||
"ptypes/duration", | |||||
"ptypes/timestamp" | |||||
] | |||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/golang/snappy" | |||||
packages = ["."] | |||||
revision = "553a641470496b2327abcac10b36396bd98e45c9" | |||||
[[projects]] | |||||
name = "github.com/inconshreveable/mousetrap" | |||||
packages = ["."] | |||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" | |||||
version = "v1.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/jmhodges/levigo" | |||||
packages = ["."] | |||||
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/kr/logfmt" | |||||
packages = ["."] | |||||
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" | |||||
[[projects]] | |||||
name = "github.com/pkg/errors" | |||||
packages = ["."] | |||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" | |||||
version = "v0.8.0" | |||||
[[projects]] | |||||
name = "github.com/pmezard/go-difflib" | |||||
packages = ["difflib"] | |||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
name = "github.com/spf13/cobra" | |||||
packages = ["."] | |||||
revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4" | |||||
version = "v0.0.2" | |||||
[[projects]] | |||||
name = "github.com/spf13/pflag" | |||||
packages = ["."] | |||||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" | |||||
version = "v1.0.0" | |||||
[[projects]] | |||||
name = "github.com/stretchr/testify" | |||||
packages = [ | |||||
"assert", | |||||
"require" | |||||
] | |||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" | |||||
version = "v1.2.1" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "github.com/syndtr/goleveldb" | |||||
packages = [ | |||||
"leveldb", | |||||
"leveldb/cache", | |||||
"leveldb/comparer", | |||||
"leveldb/errors", | |||||
"leveldb/filter", | |||||
"leveldb/iterator", | |||||
"leveldb/journal", | |||||
"leveldb/memdb", | |||||
"leveldb/opt", | |||||
"leveldb/storage", | |||||
"leveldb/table", | |||||
"leveldb/util" | |||||
] | |||||
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad" | |||||
[[projects]] | |||||
name = "github.com/tendermint/tmlibs" | |||||
packages = [ | |||||
"common", | |||||
"db", | |||||
"log" | |||||
] | |||||
revision = "2e24b64fc121dcdf1cabceab8dc2f7257675483c" | |||||
version = "v0.8.1" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "golang.org/x/net" | |||||
packages = [ | |||||
"context", | |||||
"http2", | |||||
"http2/hpack", | |||||
"idna", | |||||
"internal/timeseries", | |||||
"lex/httplex", | |||||
"trace" | |||||
] | |||||
revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41" | |||||
[[projects]] | |||||
name = "golang.org/x/text" | |||||
packages = [ | |||||
"collate", | |||||
"collate/build", | |||||
"internal/colltab", | |||||
"internal/gen", | |||||
"internal/tag", | |||||
"internal/triegen", | |||||
"internal/ucd", | |||||
"language", | |||||
"secure/bidirule", | |||||
"transform", | |||||
"unicode/bidi", | |||||
"unicode/cldr", | |||||
"unicode/norm", | |||||
"unicode/rangetable" | |||||
] | |||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" | |||||
version = "v0.3.0" | |||||
[[projects]] | |||||
branch = "master" | |||||
name = "google.golang.org/genproto" | |||||
packages = ["googleapis/rpc/status"] | |||||
revision = "ce84044298496ef4b54b4a0a0909ba593cc60e30" | |||||
[[projects]] | |||||
name = "google.golang.org/grpc" | |||||
packages = [ | |||||
".", | |||||
"balancer", | |||||
"codes", | |||||
"connectivity", | |||||
"credentials", | |||||
"grpclb/grpc_lb_v1/messages", | |||||
"grpclog", | |||||
"internal", | |||||
"keepalive", | |||||
"metadata", | |||||
"naming", | |||||
"peer", | |||||
"resolver", | |||||
"stats", | |||||
"status", | |||||
"tap", | |||||
"transport" | |||||
] | |||||
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" | |||||
version = "v1.7.5" | |||||
[solve-meta] | |||||
analyzer-name = "dep" | |||||
analyzer-version = 1 | |||||
inputs-digest = "e42d4a691fb0d0db9c717394e580dd00b36ba9e185541f99fc56689338470123" | |||||
solver-name = "gps-cdcl" | |||||
solver-version = 1 |
@ -0,0 +1,56 @@ | |||||
# Gopkg.toml example | |||||
# | |||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md | |||||
# for detailed Gopkg.toml documentation. | |||||
# | |||||
# required = ["github.com/user/thing/cmd/thing"] | |||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project" | |||||
# version = "1.0.0" | |||||
# | |||||
# [[constraint]] | |||||
# name = "github.com/user/project2" | |||||
# branch = "dev" | |||||
# source = "github.com/myfork/project2" | |||||
# | |||||
# [[override]] | |||||
# name = "github.com/x/y" | |||||
# version = "2.4.0" | |||||
# | |||||
# [prune] | |||||
# non-go = false | |||||
# go-tests = true | |||||
# unused-packages = true | |||||
# NOTE if not specified, dep automatically adds `^` to each version, | |||||
# meaning it will accept up to the next version for the first non-zero | |||||
# element in the version. | |||||
# | |||||
# So `version = "1.3.2"` means `1.3.2 <= version < 2.0.0`. | |||||
# Use `~` for only minor version bumps. | |||||
[[constraint]] | |||||
name = "github.com/gogo/protobuf" | |||||
version = "~1.0.0" | |||||
[[constraint]] | |||||
name = "github.com/spf13/cobra" | |||||
version = "~0.0.1" | |||||
[[constraint]] | |||||
name = "github.com/stretchr/testify" | |||||
version = "~1.2.1" | |||||
[[constraint]] | |||||
name = "github.com/tendermint/tmlibs" | |||||
version = "0.8.1" | |||||
[[constraint]] | |||||
name = "google.golang.org/grpc" | |||||
version = "~1.7.3" | |||||
[prune] | |||||
go-tests = true | |||||
unused-packages = true |
@ -0,0 +1,193 @@ | |||||
Tendermint ABCI | |||||
Copyright (C) 2015 Tendermint | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
https://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
https://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -0,0 +1,174 @@ | |||||
GOTOOLS = \ | |||||
github.com/mitchellh/gox \ | |||||
github.com/golang/dep/cmd/dep \ | |||||
gopkg.in/alecthomas/gometalinter.v2 \ | |||||
github.com/gogo/protobuf/protoc-gen-gogo \ | |||||
github.com/gogo/protobuf/gogoproto | |||||
GOTOOLS_CHECK = gox dep gometalinter.v2 protoc protoc-gen-gogo | |||||
PACKAGES=$(shell go list ./... | grep -v '/vendor/') | |||||
INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf | |||||
all: check get_vendor_deps protoc build test install metalinter | |||||
check: check_tools | |||||
######################################## | |||||
### Build | |||||
protoc: | |||||
## If you get the following error, | |||||
## "error while loading shared libraries: libprotobuf.so.14: cannot open shared object file: No such file or directory" | |||||
## See https://stackoverflow.com/a/25518702 | |||||
protoc $(INCLUDE) --gogo_out=plugins=grpc:. types/*.proto | |||||
@echo "--> adding nolint declarations to protobuf generated files" | |||||
@awk '/package types/ { print "//nolint: gas"; print; next }1' types/types.pb.go > types/types.pb.go.new | |||||
@mv types/types.pb.go.new types/types.pb.go | |||||
build: | |||||
@go build -i ./cmd/... | |||||
dist: | |||||
@bash scripts/dist.sh | |||||
@bash scripts/publish.sh | |||||
install: | |||||
@go install ./cmd/... | |||||
######################################## | |||||
### Tools & dependencies | |||||
check_tools: | |||||
@# https://stackoverflow.com/a/25668869 | |||||
@echo "Found tools: $(foreach tool,$(GOTOOLS_CHECK),\ | |||||
$(if $(shell which $(tool)),$(tool),$(error "No $(tool) in PATH")))" | |||||
get_tools: | |||||
@echo "--> Installing tools" | |||||
go get -u -v $(GOTOOLS) | |||||
@gometalinter.v2 --install | |||||
get_protoc: | |||||
@# https://github.com/google/protobuf/releases | |||||
curl -L https://github.com/google/protobuf/releases/download/v3.4.1/protobuf-cpp-3.4.1.tar.gz | tar xvz && \ | |||||
cd protobuf-3.4.1 && \ | |||||
DIST_LANG=cpp ./configure && \ | |||||
make && \ | |||||
make install && \ | |||||
cd .. && \ | |||||
rm -rf protobuf-3.4.1 | |||||
update_tools: | |||||
@echo "--> Updating tools" | |||||
@go get -u $(GOTOOLS) | |||||
get_vendor_deps: | |||||
@rm -rf vendor/ | |||||
@echo "--> Running dep ensure" | |||||
@dep ensure | |||||
######################################## | |||||
### Testing | |||||
test: | |||||
@find . -path ./vendor -prune -o -name "*.sock" -exec rm {} \; | |||||
@echo "==> Running go test" | |||||
@go test $(PACKAGES) | |||||
test_race: | |||||
@find . -path ./vendor -prune -o -name "*.sock" -exec rm {} \; | |||||
@echo "==> Running go test --race" | |||||
@go test -v -race $(PACKAGES) | |||||
### three tests tested by Jenkins | |||||
test_cover: | |||||
@ bash tests/test_cover.sh | |||||
test_apps: | |||||
# test the counter using a go test script | |||||
@ bash tests/test_app/test.sh | |||||
test_cli: | |||||
# test the cli against the examples in the tutorial at: | |||||
# http://tendermint.readthedocs.io/projects/tools/en/master/abci-cli.html | |||||
# | |||||
# XXX: if this test fails, fix it and update the docs at: | |||||
# https://github.com/tendermint/tendermint/blob/develop/docs/abci-cli.rst | |||||
@ bash tests/test_cli/test.sh | |||||
######################################## | |||||
### Formatting, linting, and vetting | |||||
fmt: | |||||
@go fmt ./... | |||||
metalinter: | |||||
@echo "==> Running linter" | |||||
gometalinter.v2 --vendor --deadline=600s --disable-all \ | |||||
--enable=maligned \ | |||||
--enable=deadcode \ | |||||
--enable=goconst \ | |||||
--enable=goimports \ | |||||
--enable=gosimple \ | |||||
--enable=ineffassign \ | |||||
--enable=megacheck \ | |||||
--enable=misspell \ | |||||
--enable=staticcheck \ | |||||
--enable=safesql \ | |||||
--enable=structcheck \ | |||||
--enable=unconvert \ | |||||
--enable=unused \ | |||||
--enable=varcheck \ | |||||
--enable=vetshadow \ | |||||
./... | |||||
#--enable=gas \ | |||||
#--enable=dupl \ | |||||
#--enable=errcheck \ | |||||
#--enable=gocyclo \ | |||||
#--enable=golint \ <== comments on anything exported | |||||
#--enable=gotype \ | |||||
#--enable=interfacer \ | |||||
#--enable=unparam \ | |||||
#--enable=vet \ | |||||
metalinter_all: | |||||
protoc $(INCLUDE) --lint_out=. types/*.proto | |||||
gometalinter.v2 --vendor --deadline=600s --enable-all --disable=lll ./... | |||||
######################################## | |||||
### Docker | |||||
DEVDOC_SAVE = docker commit `docker ps -a -n 1 -q` devdoc:local | |||||
docker_build: | |||||
docker build -t "tendermint/abci-dev" -f Dockerfile.develop . | |||||
docker_run: | |||||
docker run -it -v "$(CURDIR):/go/src/github.com/tendermint/abci" -w "/go/src/github.com/tendermint/abci" "tendermint/abci-dev" /bin/bash | |||||
docker_run_rm: | |||||
docker run -it --rm -v "$(CURDIR):/go/src/github.com/tendermint/abci" -w "/go/src/github.com/tendermint/abci" "tendermint/abci-dev" /bin/bash | |||||
devdoc_init: | |||||
docker run -it -v "$(CURDIR):/go/src/github.com/tendermint/abci" -w "/go/src/github.com/tendermint/abci" tendermint/devdoc echo | |||||
# TODO make this safer | |||||
$(call DEVDOC_SAVE) | |||||
devdoc: | |||||
docker run -it -v "$(CURDIR):/go/src/github.com/tendermint/abci" -w "/go/src/github.com/tendermint/abci" devdoc:local bash | |||||
devdoc_save: | |||||
# TODO make this safer | |||||
$(call DEVDOC_SAVE) | |||||
devdoc_clean: | |||||
docker rmi $$(docker images -f "dangling=true" -q) | |||||
# To avoid unintended conflicts with file names, always add to .PHONY | |||||
# unless there is a reason not to. | |||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html | |||||
.PHONY: check protoc build dist install check_tools get_tools get_protoc update_tools get_vendor_deps test test_race fmt metalinter metalinter_all docker_build docker_run docker_run_rm devdoc_init devdoc devdoc_save devdoc_clean |
@ -0,0 +1,168 @@ | |||||
# Application BlockChain Interface (ABCI) | |||||
[![CircleCI](https://circleci.com/gh/tendermint/abci.svg?style=svg)](https://circleci.com/gh/tendermint/abci) | |||||
Blockchains are systems for multi-master state machine replication. | |||||
**ABCI** is an interface that defines the boundary between the replication engine (the blockchain), | |||||
and the state machine (the application). | |||||
Using a socket protocol, a consensus engine running in one process | |||||
can manage an application state running in another. | |||||
Previously, the ABCI was referred to as TMSP. | |||||
The community has provided a number of addtional implementations, see the [Tendermint Ecosystem](https://tendermint.com/ecosystem) | |||||
## Specification | |||||
A detailed description of the ABCI methods and message types is contained in: | |||||
- [A prose specification](specification.md) | |||||
- [A protobuf file](https://github.com/tendermint/abci/blob/master/types/types.proto) | |||||
- [A Go interface](https://github.com/tendermint/abci/blob/master/types/application.go). | |||||
For more background information on ABCI, motivations, and tendermint, please visit [the documentation](http://tendermint.readthedocs.io/en/master/). | |||||
The two guides to focus on are the `Application Development Guide` and `Using ABCI-CLI`. | |||||
## Protocl Buffers | |||||
To compile the protobuf file, run: | |||||
``` | |||||
make protoc | |||||
``` | |||||
See `protoc --help` and [the Protocol Buffers site](https://developers.google.com/protocol-buffers) | |||||
for details on compiling for other languages. Note we also include a [GRPC](http://www.grpc.io/docs) | |||||
service definition. | |||||
## Install ABCI-CLI | |||||
The `abci-cli` is a simple tool for debugging ABCI servers and running some | |||||
example apps. To install it: | |||||
``` | |||||
go get github.com/tendermint/abci | |||||
cd $GOPATH/src/github.com/tendermint/abci | |||||
make get_vendor_deps | |||||
make install | |||||
``` | |||||
## Implementation | |||||
We provide three implementations of the ABCI in Go: | |||||
- Golang in-process | |||||
- ABCI-socket | |||||
- GRPC | |||||
Note the GRPC version is maintained primarily to simplify onboarding and prototyping and is not receiving the same | |||||
attention to security and performance as the others | |||||
### In Process | |||||
The simplest implementation just uses function calls within Go. | |||||
This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary. | |||||
See the [examples](#examples) below for more information. | |||||
### Socket (TSP) | |||||
ABCI is best implemented as a streaming protocol. | |||||
The socket implementation provides for asynchronous, ordered message passing over unix or tcp. | |||||
Messages are serialized using Protobuf3 and length-prefixed with a [signed Varint](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#signed-integers) | |||||
For example, if the Protobuf3 encoded ABCI message is `0xDEADBEEF` (4 bytes), the length-prefixed message is `0x08DEADBEEF`, since `0x08` is the signed varint | |||||
encoding of `4`. If the Protobuf3 encoded ABCI message is 65535 bytes long, the length-prefixed message would be like `0xFEFF07...`. | |||||
Note the benefit of using this `varint` encoding over the old version (where integers were encoded as `<len of len><big endian len>` is that | |||||
it is the standard way to encode integers in Protobuf. It is also generally shorter. | |||||
### GRPC | |||||
GRPC is an rpc framework native to Protocol Buffers with support in many languages. | |||||
Implementing the ABCI using GRPC can allow for faster prototyping, but is expected to be much slower than | |||||
the ordered, asynchronous socket protocol. The implementation has also not received as much testing or review. | |||||
Note the length-prefixing used in the socket implementation does not apply for GRPC. | |||||
## Usage | |||||
The `abci-cli` tool wraps an ABCI client and can be used for probing/testing an ABCI server. | |||||
For instance, `abci-cli test` will run a test sequence against a listening server running the Counter application (see below). | |||||
It can also be used to run some example applications. | |||||
See [the documentation](http://tendermint.readthedocs.io/en/master/) for more details. | |||||
### Examples | |||||
Check out the variety of example applications in the [example directory](example/). | |||||
It also contains the code refered to by the `counter` and `kvstore` apps; these apps come | |||||
built into the `abci-cli` binary. | |||||
#### Counter | |||||
The `abci-cli counter` application illustrates nonce checking in transactions. It's code looks like: | |||||
```golang | |||||
func cmdCounter(cmd *cobra.Command, args []string) error { | |||||
app := counter.NewCounterApplication(flagSerial) | |||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
// Start the listener | |||||
srv, err := server.NewServer(flagAddrC, flagAbci, app) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
srv.SetLogger(logger.With("module", "abci-server")) | |||||
if err := srv.Start(); err != nil { | |||||
return err | |||||
} | |||||
// Wait forever | |||||
cmn.TrapSignal(func() { | |||||
// Cleanup | |||||
srv.Stop() | |||||
}) | |||||
return nil | |||||
} | |||||
``` | |||||
and can be found in [this file](cmd/abci-cli/abci-cli.go). | |||||
#### kvstore | |||||
The `abci-cli kvstore` application, which illustrates a simple key-value Merkle tree | |||||
```golang | |||||
func cmdKVStore(cmd *cobra.Command, args []string) error { | |||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
// Create the application - in memory or persisted to disk | |||||
var app types.Application | |||||
if flagPersist == "" { | |||||
app = kvstore.NewKVStoreApplication() | |||||
} else { | |||||
app = kvstore.NewPersistentKVStoreApplication(flagPersist) | |||||
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) | |||||
} | |||||
// Start the listener | |||||
srv, err := server.NewServer(flagAddrD, flagAbci, app) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
srv.SetLogger(logger.With("module", "abci-server")) | |||||
if err := srv.Start(); err != nil { | |||||
return err | |||||
} | |||||
// Wait forever | |||||
cmn.TrapSignal(func() { | |||||
// Cleanup | |||||
srv.Stop() | |||||
}) | |||||
return nil | |||||
} | |||||
``` |
@ -0,0 +1,129 @@ | |||||
package abcicli | |||||
import ( | |||||
"fmt" | |||||
"sync" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
const ( | |||||
dialRetryIntervalSeconds = 3 | |||||
echoRetryIntervalSeconds = 1 | |||||
) | |||||
// Client defines an interface for an ABCI client. | |||||
// All `Async` methods return a `ReqRes` object. | |||||
// All `Sync` methods return the appropriate protobuf ResponseXxx struct and an error. | |||||
// Note these are client errors, eg. ABCI socket connectivity issues. | |||||
// Application-related errors are reflected in response via ABCI error codes and logs. | |||||
type Client interface { | |||||
cmn.Service | |||||
SetResponseCallback(Callback) | |||||
Error() error | |||||
FlushAsync() *ReqRes | |||||
EchoAsync(msg string) *ReqRes | |||||
InfoAsync(types.RequestInfo) *ReqRes | |||||
SetOptionAsync(types.RequestSetOption) *ReqRes | |||||
DeliverTxAsync(tx []byte) *ReqRes | |||||
CheckTxAsync(tx []byte) *ReqRes | |||||
QueryAsync(types.RequestQuery) *ReqRes | |||||
CommitAsync() *ReqRes | |||||
InitChainAsync(types.RequestInitChain) *ReqRes | |||||
BeginBlockAsync(types.RequestBeginBlock) *ReqRes | |||||
EndBlockAsync(types.RequestEndBlock) *ReqRes | |||||
FlushSync() error | |||||
EchoSync(msg string) (*types.ResponseEcho, error) | |||||
InfoSync(types.RequestInfo) (*types.ResponseInfo, error) | |||||
SetOptionSync(types.RequestSetOption) (*types.ResponseSetOption, error) | |||||
DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) | |||||
CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) | |||||
QuerySync(types.RequestQuery) (*types.ResponseQuery, error) | |||||
CommitSync() (*types.ResponseCommit, error) | |||||
InitChainSync(types.RequestInitChain) (*types.ResponseInitChain, error) | |||||
BeginBlockSync(types.RequestBeginBlock) (*types.ResponseBeginBlock, error) | |||||
EndBlockSync(types.RequestEndBlock) (*types.ResponseEndBlock, error) | |||||
} | |||||
//---------------------------------------- | |||||
// NewClient returns a new ABCI client of the specified transport type. | |||||
// It returns an error if the transport is not "socket" or "grpc" | |||||
func NewClient(addr, transport string, mustConnect bool) (client Client, err error) { | |||||
switch transport { | |||||
case "socket": | |||||
client = NewSocketClient(addr, mustConnect) | |||||
case "grpc": | |||||
client = NewGRPCClient(addr, mustConnect) | |||||
default: | |||||
err = fmt.Errorf("Unknown abci transport %s", transport) | |||||
} | |||||
return | |||||
} | |||||
//---------------------------------------- | |||||
type Callback func(*types.Request, *types.Response) | |||||
//---------------------------------------- | |||||
type ReqRes struct { | |||||
*types.Request | |||||
*sync.WaitGroup | |||||
*types.Response // Not set atomically, so be sure to use WaitGroup. | |||||
mtx sync.Mutex | |||||
done bool // Gets set to true once *after* WaitGroup.Done(). | |||||
cb func(*types.Response) // A single callback that may be set. | |||||
} | |||||
func NewReqRes(req *types.Request) *ReqRes { | |||||
return &ReqRes{ | |||||
Request: req, | |||||
WaitGroup: waitGroup1(), | |||||
Response: nil, | |||||
done: false, | |||||
cb: nil, | |||||
} | |||||
} | |||||
// Sets the callback for this ReqRes atomically. | |||||
// If reqRes is already done, calls cb immediately. | |||||
// NOTE: reqRes.cb should not change if reqRes.done. | |||||
// NOTE: only one callback is supported. | |||||
func (reqRes *ReqRes) SetCallback(cb func(res *types.Response)) { | |||||
reqRes.mtx.Lock() | |||||
if reqRes.done { | |||||
reqRes.mtx.Unlock() | |||||
cb(reqRes.Response) | |||||
return | |||||
} | |||||
defer reqRes.mtx.Unlock() | |||||
reqRes.cb = cb | |||||
} | |||||
func (reqRes *ReqRes) GetCallback() func(*types.Response) { | |||||
reqRes.mtx.Lock() | |||||
defer reqRes.mtx.Unlock() | |||||
return reqRes.cb | |||||
} | |||||
// NOTE: it should be safe to read reqRes.cb without locks after this. | |||||
func (reqRes *ReqRes) SetDone() { | |||||
reqRes.mtx.Lock() | |||||
reqRes.done = true | |||||
reqRes.mtx.Unlock() | |||||
} | |||||
func waitGroup1() (wg *sync.WaitGroup) { | |||||
wg = &sync.WaitGroup{} | |||||
wg.Add(1) | |||||
return | |||||
} |
@ -0,0 +1,301 @@ | |||||
package abcicli | |||||
import ( | |||||
"fmt" | |||||
"net" | |||||
"sync" | |||||
"time" | |||||
context "golang.org/x/net/context" | |||||
grpc "google.golang.org/grpc" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
var _ Client = (*grpcClient)(nil) | |||||
// A stripped copy of the remoteClient that makes | |||||
// synchronous calls using grpc | |||||
type grpcClient struct { | |||||
cmn.BaseService | |||||
mustConnect bool | |||||
client types.ABCIApplicationClient | |||||
mtx sync.Mutex | |||||
addr string | |||||
err error | |||||
resCb func(*types.Request, *types.Response) // listens to all callbacks | |||||
} | |||||
func NewGRPCClient(addr string, mustConnect bool) *grpcClient { | |||||
cli := &grpcClient{ | |||||
addr: addr, | |||||
mustConnect: mustConnect, | |||||
} | |||||
cli.BaseService = *cmn.NewBaseService(nil, "grpcClient", cli) | |||||
return cli | |||||
} | |||||
func dialerFunc(addr string, timeout time.Duration) (net.Conn, error) { | |||||
return cmn.Connect(addr) | |||||
} | |||||
func (cli *grpcClient) OnStart() error { | |||||
if err := cli.BaseService.OnStart(); err != nil { | |||||
return err | |||||
} | |||||
RETRY_LOOP: | |||||
for { | |||||
conn, err := grpc.Dial(cli.addr, grpc.WithInsecure(), grpc.WithDialer(dialerFunc)) | |||||
if err != nil { | |||||
if cli.mustConnect { | |||||
return err | |||||
} | |||||
cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr)) | |||||
time.Sleep(time.Second * dialRetryIntervalSeconds) | |||||
continue RETRY_LOOP | |||||
} | |||||
cli.Logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr) | |||||
client := types.NewABCIApplicationClient(conn) | |||||
ENSURE_CONNECTED: | |||||
for { | |||||
_, err := client.Echo(context.Background(), &types.RequestEcho{"hello"}, grpc.FailFast(true)) | |||||
if err == nil { | |||||
break ENSURE_CONNECTED | |||||
} | |||||
cli.Logger.Error("Echo failed", "err", err) | |||||
time.Sleep(time.Second * echoRetryIntervalSeconds) | |||||
} | |||||
cli.client = client | |||||
return nil | |||||
} | |||||
} | |||||
func (cli *grpcClient) OnStop() { | |||||
cli.BaseService.OnStop() | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
// TODO: how to close conn? its not a net.Conn and grpc doesn't expose a Close() | |||||
/*if cli.client.conn != nil { | |||||
cli.client.conn.Close() | |||||
}*/ | |||||
} | |||||
func (cli *grpcClient) StopForError(err error) { | |||||
cli.mtx.Lock() | |||||
if !cli.IsRunning() { | |||||
return | |||||
} | |||||
if cli.err == nil { | |||||
cli.err = err | |||||
} | |||||
cli.mtx.Unlock() | |||||
cli.Logger.Error(fmt.Sprintf("Stopping abci.grpcClient for error: %v", err.Error())) | |||||
cli.Stop() | |||||
} | |||||
func (cli *grpcClient) Error() error { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
return cli.err | |||||
} | |||||
// Set listener for all responses | |||||
// NOTE: callback may get internally generated flush responses. | |||||
func (cli *grpcClient) SetResponseCallback(resCb Callback) { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
cli.resCb = resCb | |||||
} | |||||
//---------------------------------------- | |||||
// GRPC calls are synchronous, but some callbacks expect to be called asynchronously | |||||
// (eg. the mempool expects to be able to lock to remove bad txs from cache). | |||||
// To accommodate, we finish each call in its own go-routine, | |||||
// which is expensive, but easy - if you want something better, use the socket protocol! | |||||
// maybe one day, if people really want it, we use grpc streams, | |||||
// but hopefully not :D | |||||
func (cli *grpcClient) EchoAsync(msg string) *ReqRes { | |||||
req := types.ToRequestEcho(msg) | |||||
res, err := cli.client.Echo(context.Background(), req.GetEcho(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_Echo{res}}) | |||||
} | |||||
func (cli *grpcClient) FlushAsync() *ReqRes { | |||||
req := types.ToRequestFlush() | |||||
res, err := cli.client.Flush(context.Background(), req.GetFlush(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_Flush{res}}) | |||||
} | |||||
func (cli *grpcClient) InfoAsync(params types.RequestInfo) *ReqRes { | |||||
req := types.ToRequestInfo(params) | |||||
res, err := cli.client.Info(context.Background(), req.GetInfo(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_Info{res}}) | |||||
} | |||||
func (cli *grpcClient) SetOptionAsync(params types.RequestSetOption) *ReqRes { | |||||
req := types.ToRequestSetOption(params) | |||||
res, err := cli.client.SetOption(context.Background(), req.GetSetOption(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_SetOption{res}}) | |||||
} | |||||
func (cli *grpcClient) DeliverTxAsync(tx []byte) *ReqRes { | |||||
req := types.ToRequestDeliverTx(tx) | |||||
res, err := cli.client.DeliverTx(context.Background(), req.GetDeliverTx(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_DeliverTx{res}}) | |||||
} | |||||
func (cli *grpcClient) CheckTxAsync(tx []byte) *ReqRes { | |||||
req := types.ToRequestCheckTx(tx) | |||||
res, err := cli.client.CheckTx(context.Background(), req.GetCheckTx(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_CheckTx{res}}) | |||||
} | |||||
func (cli *grpcClient) QueryAsync(params types.RequestQuery) *ReqRes { | |||||
req := types.ToRequestQuery(params) | |||||
res, err := cli.client.Query(context.Background(), req.GetQuery(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_Query{res}}) | |||||
} | |||||
func (cli *grpcClient) CommitAsync() *ReqRes { | |||||
req := types.ToRequestCommit() | |||||
res, err := cli.client.Commit(context.Background(), req.GetCommit(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_Commit{res}}) | |||||
} | |||||
func (cli *grpcClient) InitChainAsync(params types.RequestInitChain) *ReqRes { | |||||
req := types.ToRequestInitChain(params) | |||||
res, err := cli.client.InitChain(context.Background(), req.GetInitChain(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_InitChain{res}}) | |||||
} | |||||
func (cli *grpcClient) BeginBlockAsync(params types.RequestBeginBlock) *ReqRes { | |||||
req := types.ToRequestBeginBlock(params) | |||||
res, err := cli.client.BeginBlock(context.Background(), req.GetBeginBlock(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_BeginBlock{res}}) | |||||
} | |||||
func (cli *grpcClient) EndBlockAsync(params types.RequestEndBlock) *ReqRes { | |||||
req := types.ToRequestEndBlock(params) | |||||
res, err := cli.client.EndBlock(context.Background(), req.GetEndBlock(), grpc.FailFast(true)) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
} | |||||
return cli.finishAsyncCall(req, &types.Response{&types.Response_EndBlock{res}}) | |||||
} | |||||
func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response) *ReqRes { | |||||
reqres := NewReqRes(req) | |||||
reqres.Response = res // Set response | |||||
reqres.Done() // Release waiters | |||||
reqres.SetDone() // so reqRes.SetCallback will run the callback | |||||
// go routine for callbacks | |||||
go func() { | |||||
// Notify reqRes listener if set | |||||
if cb := reqres.GetCallback(); cb != nil { | |||||
cb(res) | |||||
} | |||||
// Notify client listener if set | |||||
if cli.resCb != nil { | |||||
cli.resCb(reqres.Request, res) | |||||
} | |||||
}() | |||||
return reqres | |||||
} | |||||
//---------------------------------------- | |||||
func (cli *grpcClient) FlushSync() error { | |||||
return nil | |||||
} | |||||
func (cli *grpcClient) EchoSync(msg string) (*types.ResponseEcho, error) { | |||||
reqres := cli.EchoAsync(msg) | |||||
// StopForError should already have been called if error is set | |||||
return reqres.Response.GetEcho(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) { | |||||
reqres := cli.InfoAsync(req) | |||||
return reqres.Response.GetInfo(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) { | |||||
reqres := cli.SetOptionAsync(req) | |||||
return reqres.Response.GetSetOption(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { | |||||
reqres := cli.DeliverTxAsync(tx) | |||||
return reqres.Response.GetDeliverTx(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { | |||||
reqres := cli.CheckTxAsync(tx) | |||||
return reqres.Response.GetCheckTx(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) { | |||||
reqres := cli.QueryAsync(req) | |||||
return reqres.Response.GetQuery(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) CommitSync() (*types.ResponseCommit, error) { | |||||
reqres := cli.CommitAsync() | |||||
return reqres.Response.GetCommit(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) InitChainSync(params types.RequestInitChain) (*types.ResponseInitChain, error) { | |||||
reqres := cli.InitChainAsync(params) | |||||
return reqres.Response.GetInitChain(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) BeginBlockSync(params types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { | |||||
reqres := cli.BeginBlockAsync(params) | |||||
return reqres.Response.GetBeginBlock(), cli.Error() | |||||
} | |||||
func (cli *grpcClient) EndBlockSync(params types.RequestEndBlock) (*types.ResponseEndBlock, error) { | |||||
reqres := cli.EndBlockAsync(params) | |||||
return reqres.Response.GetEndBlock(), cli.Error() | |||||
} |
@ -0,0 +1,230 @@ | |||||
package abcicli | |||||
import ( | |||||
"sync" | |||||
types "github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
var _ Client = (*localClient)(nil) | |||||
type localClient struct { | |||||
cmn.BaseService | |||||
mtx *sync.Mutex | |||||
types.Application | |||||
Callback | |||||
} | |||||
func NewLocalClient(mtx *sync.Mutex, app types.Application) *localClient { | |||||
if mtx == nil { | |||||
mtx = new(sync.Mutex) | |||||
} | |||||
cli := &localClient{ | |||||
mtx: mtx, | |||||
Application: app, | |||||
} | |||||
cli.BaseService = *cmn.NewBaseService(nil, "localClient", cli) | |||||
return cli | |||||
} | |||||
func (app *localClient) SetResponseCallback(cb Callback) { | |||||
app.mtx.Lock() | |||||
defer app.mtx.Unlock() | |||||
app.Callback = cb | |||||
} | |||||
// TODO: change types.Application to include Error()? | |||||
func (app *localClient) Error() error { | |||||
return nil | |||||
} | |||||
func (app *localClient) FlushAsync() *ReqRes { | |||||
// Do nothing | |||||
return newLocalReqRes(types.ToRequestFlush(), nil) | |||||
} | |||||
func (app *localClient) EchoAsync(msg string) *ReqRes { | |||||
return app.callback( | |||||
types.ToRequestEcho(msg), | |||||
types.ToResponseEcho(msg), | |||||
) | |||||
} | |||||
func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.Info(req) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestInfo(req), | |||||
types.ToResponseInfo(res), | |||||
) | |||||
} | |||||
func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.SetOption(req) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestSetOption(req), | |||||
types.ToResponseSetOption(res), | |||||
) | |||||
} | |||||
func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.DeliverTx(tx) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestDeliverTx(tx), | |||||
types.ToResponseDeliverTx(res), | |||||
) | |||||
} | |||||
func (app *localClient) CheckTxAsync(tx []byte) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.CheckTx(tx) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestCheckTx(tx), | |||||
types.ToResponseCheckTx(res), | |||||
) | |||||
} | |||||
func (app *localClient) QueryAsync(req types.RequestQuery) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.Query(req) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestQuery(req), | |||||
types.ToResponseQuery(res), | |||||
) | |||||
} | |||||
func (app *localClient) CommitAsync() *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.Commit() | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestCommit(), | |||||
types.ToResponseCommit(res), | |||||
) | |||||
} | |||||
func (app *localClient) InitChainAsync(req types.RequestInitChain) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.InitChain(req) | |||||
reqRes := app.callback( | |||||
types.ToRequestInitChain(req), | |||||
types.ToResponseInitChain(res), | |||||
) | |||||
app.mtx.Unlock() | |||||
return reqRes | |||||
} | |||||
func (app *localClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.BeginBlock(req) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestBeginBlock(req), | |||||
types.ToResponseBeginBlock(res), | |||||
) | |||||
} | |||||
func (app *localClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes { | |||||
app.mtx.Lock() | |||||
res := app.Application.EndBlock(req) | |||||
app.mtx.Unlock() | |||||
return app.callback( | |||||
types.ToRequestEndBlock(req), | |||||
types.ToResponseEndBlock(res), | |||||
) | |||||
} | |||||
//------------------------------------------------------- | |||||
func (app *localClient) FlushSync() error { | |||||
return nil | |||||
} | |||||
func (app *localClient) EchoSync(msg string) (*types.ResponseEcho, error) { | |||||
return &types.ResponseEcho{msg}, nil | |||||
} | |||||
func (app *localClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.Info(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.SetOption(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.DeliverTx(tx) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.CheckTx(tx) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.Query(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) CommitSync() (*types.ResponseCommit, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.Commit() | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.InitChain(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.BeginBlock(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
func (app *localClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) { | |||||
app.mtx.Lock() | |||||
res := app.Application.EndBlock(req) | |||||
app.mtx.Unlock() | |||||
return &res, nil | |||||
} | |||||
//------------------------------------------------------- | |||||
func (app *localClient) callback(req *types.Request, res *types.Response) *ReqRes { | |||||
app.Callback(req, res) | |||||
return newLocalReqRes(req, res) | |||||
} | |||||
func newLocalReqRes(req *types.Request, res *types.Response) *ReqRes { | |||||
reqRes := NewReqRes(req) | |||||
reqRes.Response = res | |||||
reqRes.SetDone() | |||||
return reqRes | |||||
} |
@ -0,0 +1,399 @@ | |||||
package abcicli | |||||
import ( | |||||
"bufio" | |||||
"container/list" | |||||
"errors" | |||||
"fmt" | |||||
"net" | |||||
"reflect" | |||||
"sync" | |||||
"time" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
const reqQueueSize = 256 // TODO make configurable | |||||
// const maxResponseSize = 1048576 // 1MB TODO make configurable | |||||
const flushThrottleMS = 20 // Don't wait longer than... | |||||
var _ Client = (*socketClient)(nil) | |||||
// This is goroutine-safe, but users should beware that | |||||
// the application in general is not meant to be interfaced | |||||
// with concurrent callers. | |||||
type socketClient struct { | |||||
cmn.BaseService | |||||
reqQueue chan *ReqRes | |||||
flushTimer *cmn.ThrottleTimer | |||||
mustConnect bool | |||||
mtx sync.Mutex | |||||
addr string | |||||
conn net.Conn | |||||
err error | |||||
reqSent *list.List | |||||
resCb func(*types.Request, *types.Response) // listens to all callbacks | |||||
} | |||||
func NewSocketClient(addr string, mustConnect bool) *socketClient { | |||||
cli := &socketClient{ | |||||
reqQueue: make(chan *ReqRes, reqQueueSize), | |||||
flushTimer: cmn.NewThrottleTimer("socketClient", flushThrottleMS), | |||||
mustConnect: mustConnect, | |||||
addr: addr, | |||||
reqSent: list.New(), | |||||
resCb: nil, | |||||
} | |||||
cli.BaseService = *cmn.NewBaseService(nil, "socketClient", cli) | |||||
return cli | |||||
} | |||||
func (cli *socketClient) OnStart() error { | |||||
if err := cli.BaseService.OnStart(); err != nil { | |||||
return err | |||||
} | |||||
var err error | |||||
var conn net.Conn | |||||
RETRY_LOOP: | |||||
for { | |||||
conn, err = cmn.Connect(cli.addr) | |||||
if err != nil { | |||||
if cli.mustConnect { | |||||
return err | |||||
} | |||||
cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr)) | |||||
time.Sleep(time.Second * dialRetryIntervalSeconds) | |||||
continue RETRY_LOOP | |||||
} | |||||
cli.conn = conn | |||||
go cli.sendRequestsRoutine(conn) | |||||
go cli.recvResponseRoutine(conn) | |||||
return nil | |||||
} | |||||
} | |||||
func (cli *socketClient) OnStop() { | |||||
cli.BaseService.OnStop() | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
if cli.conn != nil { | |||||
cli.conn.Close() | |||||
} | |||||
cli.flushQueue() | |||||
} | |||||
// Stop the client and set the error | |||||
func (cli *socketClient) StopForError(err error) { | |||||
if !cli.IsRunning() { | |||||
return | |||||
} | |||||
cli.mtx.Lock() | |||||
if cli.err == nil { | |||||
cli.err = err | |||||
} | |||||
cli.mtx.Unlock() | |||||
cli.Logger.Error(fmt.Sprintf("Stopping abci.socketClient for error: %v", err.Error())) | |||||
cli.Stop() | |||||
} | |||||
func (cli *socketClient) Error() error { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
return cli.err | |||||
} | |||||
// Set listener for all responses | |||||
// NOTE: callback may get internally generated flush responses. | |||||
func (cli *socketClient) SetResponseCallback(resCb Callback) { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
cli.resCb = resCb | |||||
} | |||||
//---------------------------------------- | |||||
func (cli *socketClient) sendRequestsRoutine(conn net.Conn) { | |||||
w := bufio.NewWriter(conn) | |||||
for { | |||||
select { | |||||
case <-cli.flushTimer.Ch: | |||||
select { | |||||
case cli.reqQueue <- NewReqRes(types.ToRequestFlush()): | |||||
default: | |||||
// Probably will fill the buffer, or retry later. | |||||
} | |||||
case <-cli.Quit(): | |||||
return | |||||
case reqres := <-cli.reqQueue: | |||||
cli.willSendReq(reqres) | |||||
err := types.WriteMessage(reqres.Request, w) | |||||
if err != nil { | |||||
cli.StopForError(fmt.Errorf("Error writing msg: %v", err)) | |||||
return | |||||
} | |||||
// cli.Logger.Debug("Sent request", "requestType", reflect.TypeOf(reqres.Request), "request", reqres.Request) | |||||
if _, ok := reqres.Request.Value.(*types.Request_Flush); ok { | |||||
err = w.Flush() | |||||
if err != nil { | |||||
cli.StopForError(fmt.Errorf("Error flushing writer: %v", err)) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func (cli *socketClient) recvResponseRoutine(conn net.Conn) { | |||||
r := bufio.NewReader(conn) // Buffer reads | |||||
for { | |||||
var res = &types.Response{} | |||||
err := types.ReadMessage(r, res) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
return | |||||
} | |||||
switch r := res.Value.(type) { | |||||
case *types.Response_Exception: | |||||
// XXX After setting cli.err, release waiters (e.g. reqres.Done()) | |||||
cli.StopForError(errors.New(r.Exception.Error)) | |||||
return | |||||
default: | |||||
// cli.Logger.Debug("Received response", "responseType", reflect.TypeOf(res), "response", res) | |||||
err := cli.didRecvResponse(res) | |||||
if err != nil { | |||||
cli.StopForError(err) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func (cli *socketClient) willSendReq(reqres *ReqRes) { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
cli.reqSent.PushBack(reqres) | |||||
} | |||||
func (cli *socketClient) didRecvResponse(res *types.Response) error { | |||||
cli.mtx.Lock() | |||||
defer cli.mtx.Unlock() | |||||
// Get the first ReqRes | |||||
next := cli.reqSent.Front() | |||||
if next == nil { | |||||
return fmt.Errorf("Unexpected result type %v when nothing expected", reflect.TypeOf(res.Value)) | |||||
} | |||||
reqres := next.Value.(*ReqRes) | |||||
if !resMatchesReq(reqres.Request, res) { | |||||
return fmt.Errorf("Unexpected result type %v when response to %v expected", | |||||
reflect.TypeOf(res.Value), reflect.TypeOf(reqres.Request.Value)) | |||||
} | |||||
reqres.Response = res // Set response | |||||
reqres.Done() // Release waiters | |||||
cli.reqSent.Remove(next) // Pop first item from linked list | |||||
// Notify reqRes listener if set | |||||
if cb := reqres.GetCallback(); cb != nil { | |||||
cb(res) | |||||
} | |||||
// Notify client listener if set | |||||
if cli.resCb != nil { | |||||
cli.resCb(reqres.Request, res) | |||||
} | |||||
return nil | |||||
} | |||||
//---------------------------------------- | |||||
func (cli *socketClient) EchoAsync(msg string) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestEcho(msg)) | |||||
} | |||||
func (cli *socketClient) FlushAsync() *ReqRes { | |||||
return cli.queueRequest(types.ToRequestFlush()) | |||||
} | |||||
func (cli *socketClient) InfoAsync(req types.RequestInfo) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestInfo(req)) | |||||
} | |||||
func (cli *socketClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestSetOption(req)) | |||||
} | |||||
func (cli *socketClient) DeliverTxAsync(tx []byte) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestDeliverTx(tx)) | |||||
} | |||||
func (cli *socketClient) CheckTxAsync(tx []byte) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestCheckTx(tx)) | |||||
} | |||||
func (cli *socketClient) QueryAsync(req types.RequestQuery) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestQuery(req)) | |||||
} | |||||
func (cli *socketClient) CommitAsync() *ReqRes { | |||||
return cli.queueRequest(types.ToRequestCommit()) | |||||
} | |||||
func (cli *socketClient) InitChainAsync(req types.RequestInitChain) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestInitChain(req)) | |||||
} | |||||
func (cli *socketClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestBeginBlock(req)) | |||||
} | |||||
func (cli *socketClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes { | |||||
return cli.queueRequest(types.ToRequestEndBlock(req)) | |||||
} | |||||
//---------------------------------------- | |||||
func (cli *socketClient) FlushSync() error { | |||||
reqRes := cli.queueRequest(types.ToRequestFlush()) | |||||
if err := cli.Error(); err != nil { | |||||
return err | |||||
} | |||||
reqRes.Wait() // NOTE: if we don't flush the queue, its possible to get stuck here | |||||
return cli.Error() | |||||
} | |||||
func (cli *socketClient) EchoSync(msg string) (*types.ResponseEcho, error) { | |||||
reqres := cli.queueRequest(types.ToRequestEcho(msg)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetEcho(), cli.Error() | |||||
} | |||||
func (cli *socketClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) { | |||||
reqres := cli.queueRequest(types.ToRequestInfo(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetInfo(), cli.Error() | |||||
} | |||||
func (cli *socketClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) { | |||||
reqres := cli.queueRequest(types.ToRequestSetOption(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetSetOption(), cli.Error() | |||||
} | |||||
func (cli *socketClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { | |||||
reqres := cli.queueRequest(types.ToRequestDeliverTx(tx)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetDeliverTx(), cli.Error() | |||||
} | |||||
func (cli *socketClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { | |||||
reqres := cli.queueRequest(types.ToRequestCheckTx(tx)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetCheckTx(), cli.Error() | |||||
} | |||||
func (cli *socketClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) { | |||||
reqres := cli.queueRequest(types.ToRequestQuery(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetQuery(), cli.Error() | |||||
} | |||||
func (cli *socketClient) CommitSync() (*types.ResponseCommit, error) { | |||||
reqres := cli.queueRequest(types.ToRequestCommit()) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetCommit(), cli.Error() | |||||
} | |||||
func (cli *socketClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) { | |||||
reqres := cli.queueRequest(types.ToRequestInitChain(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetInitChain(), cli.Error() | |||||
} | |||||
func (cli *socketClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { | |||||
reqres := cli.queueRequest(types.ToRequestBeginBlock(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetBeginBlock(), cli.Error() | |||||
} | |||||
func (cli *socketClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) { | |||||
reqres := cli.queueRequest(types.ToRequestEndBlock(req)) | |||||
cli.FlushSync() | |||||
return reqres.Response.GetEndBlock(), cli.Error() | |||||
} | |||||
//---------------------------------------- | |||||
func (cli *socketClient) queueRequest(req *types.Request) *ReqRes { | |||||
reqres := NewReqRes(req) | |||||
// TODO: set cli.err if reqQueue times out | |||||
cli.reqQueue <- reqres | |||||
// Maybe auto-flush, or unset auto-flush | |||||
switch req.Value.(type) { | |||||
case *types.Request_Flush: | |||||
cli.flushTimer.Unset() | |||||
default: | |||||
cli.flushTimer.Set() | |||||
} | |||||
return reqres | |||||
} | |||||
func (cli *socketClient) flushQueue() { | |||||
LOOP: | |||||
for { | |||||
select { | |||||
case reqres := <-cli.reqQueue: | |||||
reqres.Done() | |||||
default: | |||||
break LOOP | |||||
} | |||||
} | |||||
} | |||||
//---------------------------------------- | |||||
func resMatchesReq(req *types.Request, res *types.Response) (ok bool) { | |||||
switch req.Value.(type) { | |||||
case *types.Request_Echo: | |||||
_, ok = res.Value.(*types.Response_Echo) | |||||
case *types.Request_Flush: | |||||
_, ok = res.Value.(*types.Response_Flush) | |||||
case *types.Request_Info: | |||||
_, ok = res.Value.(*types.Response_Info) | |||||
case *types.Request_SetOption: | |||||
_, ok = res.Value.(*types.Response_SetOption) | |||||
case *types.Request_DeliverTx: | |||||
_, ok = res.Value.(*types.Response_DeliverTx) | |||||
case *types.Request_CheckTx: | |||||
_, ok = res.Value.(*types.Response_CheckTx) | |||||
case *types.Request_Commit: | |||||
_, ok = res.Value.(*types.Response_Commit) | |||||
case *types.Request_Query: | |||||
_, ok = res.Value.(*types.Response_Query) | |||||
case *types.Request_InitChain: | |||||
_, ok = res.Value.(*types.Response_InitChain) | |||||
case *types.Request_BeginBlock: | |||||
_, ok = res.Value.(*types.Response_BeginBlock) | |||||
case *types.Request_EndBlock: | |||||
_, ok = res.Value.(*types.Response_EndBlock) | |||||
} | |||||
return ok | |||||
} |
@ -0,0 +1,28 @@ | |||||
package abcicli_test | |||||
import ( | |||||
"errors" | |||||
"testing" | |||||
"time" | |||||
"github.com/tendermint/abci/client" | |||||
) | |||||
func TestSocketClientStopForErrorDeadlock(t *testing.T) { | |||||
c := abcicli.NewSocketClient(":80", false) | |||||
err := errors.New("foo-tendermint") | |||||
// See Issue https://github.com/tendermint/abci/issues/114 | |||||
doneChan := make(chan bool) | |||||
go func() { | |||||
defer close(doneChan) | |||||
c.StopForError(err) | |||||
c.StopForError(err) | |||||
}() | |||||
select { | |||||
case <-doneChan: | |||||
case <-time.After(time.Second * 4): | |||||
t.Fatalf("Test took too long, potential deadlock still exists") | |||||
} | |||||
} |
@ -0,0 +1,765 @@ | |||||
package main | |||||
import ( | |||||
"bufio" | |||||
"encoding/hex" | |||||
"errors" | |||||
"fmt" | |||||
"io" | |||||
"os" | |||||
"strings" | |||||
"github.com/spf13/cobra" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
"github.com/tendermint/tmlibs/log" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/example/counter" | |||||
"github.com/tendermint/abci/example/kvstore" | |||||
"github.com/tendermint/abci/server" | |||||
servertest "github.com/tendermint/abci/tests/server" | |||||
"github.com/tendermint/abci/types" | |||||
"github.com/tendermint/abci/version" | |||||
) | |||||
// client is a global variable so it can be reused by the console | |||||
var ( | |||||
client abcicli.Client | |||||
logger log.Logger | |||||
) | |||||
// flags | |||||
var ( | |||||
// global | |||||
flagAddress string | |||||
flagAbci string | |||||
flagVerbose bool // for the println output | |||||
flagLogLevel string // for the logger | |||||
// query | |||||
flagPath string | |||||
flagHeight int | |||||
flagProve bool | |||||
// counter | |||||
flagSerial bool | |||||
// kvstore | |||||
flagPersist string | |||||
) | |||||
var RootCmd = &cobra.Command{ | |||||
Use: "abci-cli", | |||||
Short: "the ABCI CLI tool wraps an ABCI client", | |||||
Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers", | |||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { | |||||
switch cmd.Use { | |||||
case "counter", "kvstore", "dummy": // for the examples apps, don't pre-run | |||||
return nil | |||||
case "version": // skip running for version command | |||||
return nil | |||||
} | |||||
if logger == nil { | |||||
allowLevel, err := log.AllowLevel(flagLogLevel) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), allowLevel) | |||||
} | |||||
if client == nil { | |||||
var err error | |||||
client, err = abcicli.NewClient(flagAddress, flagAbci, false) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
client.SetLogger(logger.With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
}, | |||||
} | |||||
// Structure for data passed to print response. | |||||
type response struct { | |||||
// generic abci response | |||||
Data []byte | |||||
Code uint32 | |||||
Info string | |||||
Log string | |||||
Query *queryResponse | |||||
} | |||||
type queryResponse struct { | |||||
Key []byte | |||||
Value []byte | |||||
Height int64 | |||||
Proof []byte | |||||
} | |||||
func Execute() error { | |||||
addGlobalFlags() | |||||
addCommands() | |||||
return RootCmd.Execute() | |||||
} | |||||
func addGlobalFlags() { | |||||
RootCmd.PersistentFlags().StringVarP(&flagAddress, "address", "", "tcp://0.0.0.0:26658", "address of application socket") | |||||
RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc") | |||||
RootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "print the command and results as if it were a console session") | |||||
RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level") | |||||
} | |||||
func addQueryFlags() { | |||||
queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with") | |||||
queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at") | |||||
queryCmd.PersistentFlags().BoolVarP(&flagProve, "prove", "", false, "whether or not to return a merkle proof of the query result") | |||||
} | |||||
func addCounterFlags() { | |||||
counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions") | |||||
} | |||||
func addDummyFlags() { | |||||
dummyCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database") | |||||
} | |||||
func addKVStoreFlags() { | |||||
kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database") | |||||
} | |||||
func addCommands() { | |||||
RootCmd.AddCommand(batchCmd) | |||||
RootCmd.AddCommand(consoleCmd) | |||||
RootCmd.AddCommand(echoCmd) | |||||
RootCmd.AddCommand(infoCmd) | |||||
RootCmd.AddCommand(setOptionCmd) | |||||
RootCmd.AddCommand(deliverTxCmd) | |||||
RootCmd.AddCommand(checkTxCmd) | |||||
RootCmd.AddCommand(commitCmd) | |||||
RootCmd.AddCommand(versionCmd) | |||||
RootCmd.AddCommand(testCmd) | |||||
addQueryFlags() | |||||
RootCmd.AddCommand(queryCmd) | |||||
// examples | |||||
addCounterFlags() | |||||
RootCmd.AddCommand(counterCmd) | |||||
// deprecated, left for backwards compatibility | |||||
addDummyFlags() | |||||
RootCmd.AddCommand(dummyCmd) | |||||
// replaces dummy, see issue #196 | |||||
addKVStoreFlags() | |||||
RootCmd.AddCommand(kvstoreCmd) | |||||
} | |||||
var batchCmd = &cobra.Command{ | |||||
Use: "batch", | |||||
Short: "run a batch of abci commands against an application", | |||||
Long: `run a batch of abci commands against an application | |||||
This command is run by piping in a file containing a series of commands | |||||
you'd like to run: | |||||
abci-cli batch < example.file | |||||
where example.file looks something like: | |||||
set_option serial on | |||||
check_tx 0x00 | |||||
check_tx 0xff | |||||
deliver_tx 0x00 | |||||
check_tx 0x00 | |||||
deliver_tx 0x01 | |||||
deliver_tx 0x04 | |||||
info | |||||
`, | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdBatch(cmd, args) | |||||
}, | |||||
} | |||||
var consoleCmd = &cobra.Command{ | |||||
Use: "console", | |||||
Short: "start an interactive ABCI console for multiple commands", | |||||
Long: `start an interactive ABCI console for multiple commands | |||||
This command opens an interactive console for running any of the other commands | |||||
without opening a new connection each time | |||||
`, | |||||
Args: cobra.ExactArgs(0), | |||||
ValidArgs: []string{"echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"}, | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdConsole(cmd, args) | |||||
}, | |||||
} | |||||
var echoCmd = &cobra.Command{ | |||||
Use: "echo", | |||||
Short: "have the application echo a message", | |||||
Long: "have the application echo a message", | |||||
Args: cobra.ExactArgs(1), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdEcho(cmd, args) | |||||
}, | |||||
} | |||||
var infoCmd = &cobra.Command{ | |||||
Use: "info", | |||||
Short: "get some info about the application", | |||||
Long: "get some info about the application", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdInfo(cmd, args) | |||||
}, | |||||
} | |||||
var setOptionCmd = &cobra.Command{ | |||||
Use: "set_option", | |||||
Short: "set an option on the application", | |||||
Long: "set an option on the application", | |||||
Args: cobra.ExactArgs(2), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdSetOption(cmd, args) | |||||
}, | |||||
} | |||||
var deliverTxCmd = &cobra.Command{ | |||||
Use: "deliver_tx", | |||||
Short: "deliver a new transaction to the application", | |||||
Long: "deliver a new transaction to the application", | |||||
Args: cobra.ExactArgs(1), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdDeliverTx(cmd, args) | |||||
}, | |||||
} | |||||
var checkTxCmd = &cobra.Command{ | |||||
Use: "check_tx", | |||||
Short: "validate a transaction", | |||||
Long: "validate a transaction", | |||||
Args: cobra.ExactArgs(1), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdCheckTx(cmd, args) | |||||
}, | |||||
} | |||||
var commitCmd = &cobra.Command{ | |||||
Use: "commit", | |||||
Short: "commit the application state and return the Merkle root hash", | |||||
Long: "commit the application state and return the Merkle root hash", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdCommit(cmd, args) | |||||
}, | |||||
} | |||||
var versionCmd = &cobra.Command{ | |||||
Use: "version", | |||||
Short: "print ABCI console version", | |||||
Long: "print ABCI console version", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
fmt.Println(version.Version) | |||||
return nil | |||||
}, | |||||
} | |||||
var queryCmd = &cobra.Command{ | |||||
Use: "query", | |||||
Short: "query the application state", | |||||
Long: "query the application state", | |||||
Args: cobra.ExactArgs(1), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdQuery(cmd, args) | |||||
}, | |||||
} | |||||
var counterCmd = &cobra.Command{ | |||||
Use: "counter", | |||||
Short: "ABCI demo example", | |||||
Long: "ABCI demo example", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdCounter(cmd, args) | |||||
}, | |||||
} | |||||
// deprecated, left for backwards compatibility | |||||
var dummyCmd = &cobra.Command{ | |||||
Use: "dummy", | |||||
Deprecated: "use: [abci-cli kvstore] instead", | |||||
Short: "ABCI demo example", | |||||
Long: "ABCI demo example", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdKVStore(cmd, args) | |||||
}, | |||||
} | |||||
var kvstoreCmd = &cobra.Command{ | |||||
Use: "kvstore", | |||||
Short: "ABCI demo example", | |||||
Long: "ABCI demo example", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdKVStore(cmd, args) | |||||
}, | |||||
} | |||||
var testCmd = &cobra.Command{ | |||||
Use: "test", | |||||
Short: "run integration tests", | |||||
Long: "run integration tests", | |||||
Args: cobra.ExactArgs(0), | |||||
RunE: func(cmd *cobra.Command, args []string) error { | |||||
return cmdTest(cmd, args) | |||||
}, | |||||
} | |||||
// Generates new Args array based off of previous call args to maintain flag persistence | |||||
func persistentArgs(line []byte) []string { | |||||
// generate the arguments to run from original os.Args | |||||
// to maintain flag arguments | |||||
args := os.Args | |||||
args = args[:len(args)-1] // remove the previous command argument | |||||
if len(line) > 0 { // prevents introduction of extra space leading to argument parse errors | |||||
args = append(args, strings.Split(string(line), " ")...) | |||||
} | |||||
return args | |||||
} | |||||
//-------------------------------------------------------------------------------- | |||||
func compose(fs []func() error) error { | |||||
if len(fs) == 0 { | |||||
return nil | |||||
} else { | |||||
err := fs[0]() | |||||
if err == nil { | |||||
return compose(fs[1:]) | |||||
} else { | |||||
return err | |||||
} | |||||
} | |||||
} | |||||
func cmdTest(cmd *cobra.Command, args []string) error { | |||||
return compose( | |||||
[]func() error{ | |||||
func() error { return servertest.InitChain(client) }, | |||||
func() error { return servertest.SetOption(client, "serial", "on") }, | |||||
func() error { return servertest.Commit(client, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil) }, | |||||
func() error { return servertest.Commit(client, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeOK, nil) }, | |||||
func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x01}, code.CodeTypeOK, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x02}, code.CodeTypeOK, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x03}, code.CodeTypeOK, nil) }, | |||||
func() error { return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x04}, code.CodeTypeOK, nil) }, | |||||
func() error { | |||||
return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil) | |||||
}, | |||||
func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) }, | |||||
}) | |||||
} | |||||
func cmdBatch(cmd *cobra.Command, args []string) error { | |||||
bufReader := bufio.NewReader(os.Stdin) | |||||
for { | |||||
line, more, err := bufReader.ReadLine() | |||||
if more { | |||||
return errors.New("Input line is too long") | |||||
} else if err == io.EOF { | |||||
break | |||||
} else if len(line) == 0 { | |||||
continue | |||||
} else if err != nil { | |||||
return err | |||||
} | |||||
cmdArgs := persistentArgs(line) | |||||
if err := muxOnCommands(cmd, cmdArgs); err != nil { | |||||
return err | |||||
} | |||||
fmt.Println() | |||||
} | |||||
return nil | |||||
} | |||||
func cmdConsole(cmd *cobra.Command, args []string) error { | |||||
for { | |||||
fmt.Printf("> ") | |||||
bufReader := bufio.NewReader(os.Stdin) | |||||
line, more, err := bufReader.ReadLine() | |||||
if more { | |||||
return errors.New("Input is too long") | |||||
} else if err != nil { | |||||
return err | |||||
} | |||||
pArgs := persistentArgs(line) | |||||
if err := muxOnCommands(cmd, pArgs); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func muxOnCommands(cmd *cobra.Command, pArgs []string) error { | |||||
if len(pArgs) < 2 { | |||||
return errors.New("expecting persistent args of the form: abci-cli [command] <...>") | |||||
} | |||||
// TODO: this parsing is fragile | |||||
args := []string{} | |||||
for i := 0; i < len(pArgs); i++ { | |||||
arg := pArgs[i] | |||||
// check for flags | |||||
if strings.HasPrefix(arg, "-") { | |||||
// if it has an equal, we can just skip | |||||
if strings.Contains(arg, "=") { | |||||
continue | |||||
} | |||||
// if its a boolean, we can just skip | |||||
_, err := cmd.Flags().GetBool(strings.TrimLeft(arg, "-")) | |||||
if err == nil { | |||||
continue | |||||
} | |||||
// otherwise, we need to skip the next one too | |||||
i += 1 | |||||
continue | |||||
} | |||||
// append the actual arg | |||||
args = append(args, arg) | |||||
} | |||||
var subCommand string | |||||
var actualArgs []string | |||||
if len(args) > 1 { | |||||
subCommand = args[1] | |||||
} | |||||
if len(args) > 2 { | |||||
actualArgs = args[2:] | |||||
} | |||||
cmd.Use = subCommand // for later print statements ... | |||||
switch strings.ToLower(subCommand) { | |||||
case "check_tx": | |||||
return cmdCheckTx(cmd, actualArgs) | |||||
case "commit": | |||||
return cmdCommit(cmd, actualArgs) | |||||
case "deliver_tx": | |||||
return cmdDeliverTx(cmd, actualArgs) | |||||
case "echo": | |||||
return cmdEcho(cmd, actualArgs) | |||||
case "info": | |||||
return cmdInfo(cmd, actualArgs) | |||||
case "query": | |||||
return cmdQuery(cmd, actualArgs) | |||||
case "set_option": | |||||
return cmdSetOption(cmd, actualArgs) | |||||
default: | |||||
return cmdUnimplemented(cmd, pArgs) | |||||
} | |||||
} | |||||
func cmdUnimplemented(cmd *cobra.Command, args []string) error { | |||||
// TODO: Print out all the sub-commands available | |||||
msg := "unimplemented command" | |||||
if err := cmd.Help(); err != nil { | |||||
msg = err.Error() | |||||
} | |||||
if len(args) > 0 { | |||||
msg += fmt.Sprintf(" args: [%s]", strings.Join(args, " ")) | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Code: codeBad, | |||||
Log: msg, | |||||
}) | |||||
return nil | |||||
} | |||||
// Have the application echo a message | |||||
func cmdEcho(cmd *cobra.Command, args []string) error { | |||||
msg := "" | |||||
if len(args) > 0 { | |||||
msg = args[0] | |||||
} | |||||
res, err := client.EchoSync(msg) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Data: []byte(res.Message), | |||||
}) | |||||
return nil | |||||
} | |||||
// Get some info from the application | |||||
func cmdInfo(cmd *cobra.Command, args []string) error { | |||||
var version string | |||||
if len(args) == 1 { | |||||
version = args[0] | |||||
} | |||||
res, err := client.InfoSync(types.RequestInfo{version}) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Data: []byte(res.Data), | |||||
}) | |||||
return nil | |||||
} | |||||
const codeBad uint32 = 10 | |||||
// Set an option on the application | |||||
func cmdSetOption(cmd *cobra.Command, args []string) error { | |||||
if len(args) < 2 { | |||||
printResponse(cmd, args, response{ | |||||
Code: codeBad, | |||||
Log: "want at least arguments of the form: <key> <value>", | |||||
}) | |||||
return nil | |||||
} | |||||
key, val := args[0], args[1] | |||||
_, err := client.SetOptionSync(types.RequestSetOption{key, val}) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{Log: "OK (SetOption doesn't return anything.)"}) // NOTE: Nothing to show... | |||||
return nil | |||||
} | |||||
// Append a new tx to application | |||||
func cmdDeliverTx(cmd *cobra.Command, args []string) error { | |||||
if len(args) == 0 { | |||||
printResponse(cmd, args, response{ | |||||
Code: codeBad, | |||||
Log: "want the tx", | |||||
}) | |||||
return nil | |||||
} | |||||
txBytes, err := stringOrHexToBytes(args[0]) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
res, err := client.DeliverTxSync(txBytes) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Code: res.Code, | |||||
Data: res.Data, | |||||
Info: res.Info, | |||||
Log: res.Log, | |||||
}) | |||||
return nil | |||||
} | |||||
// Validate a tx | |||||
func cmdCheckTx(cmd *cobra.Command, args []string) error { | |||||
if len(args) == 0 { | |||||
printResponse(cmd, args, response{ | |||||
Code: codeBad, | |||||
Info: "want the tx", | |||||
}) | |||||
return nil | |||||
} | |||||
txBytes, err := stringOrHexToBytes(args[0]) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
res, err := client.CheckTxSync(txBytes) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Code: res.Code, | |||||
Data: res.Data, | |||||
Info: res.Info, | |||||
Log: res.Log, | |||||
}) | |||||
return nil | |||||
} | |||||
// Get application Merkle root hash | |||||
func cmdCommit(cmd *cobra.Command, args []string) error { | |||||
res, err := client.CommitSync() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Data: res.Data, | |||||
}) | |||||
return nil | |||||
} | |||||
// Query application state | |||||
func cmdQuery(cmd *cobra.Command, args []string) error { | |||||
if len(args) == 0 { | |||||
printResponse(cmd, args, response{ | |||||
Code: codeBad, | |||||
Info: "want the query", | |||||
Log: "", | |||||
}) | |||||
return nil | |||||
} | |||||
queryBytes, err := stringOrHexToBytes(args[0]) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
resQuery, err := client.QuerySync(types.RequestQuery{ | |||||
Data: queryBytes, | |||||
Path: flagPath, | |||||
Height: int64(flagHeight), | |||||
Prove: flagProve, | |||||
}) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
printResponse(cmd, args, response{ | |||||
Code: resQuery.Code, | |||||
Info: resQuery.Info, | |||||
Log: resQuery.Log, | |||||
Query: &queryResponse{ | |||||
Key: resQuery.Key, | |||||
Value: resQuery.Value, | |||||
Height: resQuery.Height, | |||||
Proof: resQuery.Proof, | |||||
}, | |||||
}) | |||||
return nil | |||||
} | |||||
func cmdCounter(cmd *cobra.Command, args []string) error { | |||||
app := counter.NewCounterApplication(flagSerial) | |||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
// Start the listener | |||||
srv, err := server.NewServer(flagAddress, flagAbci, app) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
srv.SetLogger(logger.With("module", "abci-server")) | |||||
if err := srv.Start(); err != nil { | |||||
return err | |||||
} | |||||
// Wait forever | |||||
cmn.TrapSignal(func() { | |||||
// Cleanup | |||||
srv.Stop() | |||||
}) | |||||
return nil | |||||
} | |||||
func cmdKVStore(cmd *cobra.Command, args []string) error { | |||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
// Create the application - in memory or persisted to disk | |||||
var app types.Application | |||||
if flagPersist == "" { | |||||
app = kvstore.NewKVStoreApplication() | |||||
} else { | |||||
app = kvstore.NewPersistentKVStoreApplication(flagPersist) | |||||
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) | |||||
} | |||||
// Start the listener | |||||
srv, err := server.NewServer(flagAddress, flagAbci, app) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
srv.SetLogger(logger.With("module", "abci-server")) | |||||
if err := srv.Start(); err != nil { | |||||
return err | |||||
} | |||||
// Wait forever | |||||
cmn.TrapSignal(func() { | |||||
// Cleanup | |||||
srv.Stop() | |||||
}) | |||||
return nil | |||||
} | |||||
//-------------------------------------------------------------------------------- | |||||
func printResponse(cmd *cobra.Command, args []string, rsp response) { | |||||
if flagVerbose { | |||||
fmt.Println(">", cmd.Use, strings.Join(args, " ")) | |||||
} | |||||
// Always print the status code. | |||||
if rsp.Code == types.CodeTypeOK { | |||||
fmt.Printf("-> code: OK\n") | |||||
} else { | |||||
fmt.Printf("-> code: %d\n", rsp.Code) | |||||
} | |||||
if len(rsp.Data) != 0 { | |||||
// Do no print this line when using the commit command | |||||
// because the string comes out as gibberish | |||||
if cmd.Use != "commit" { | |||||
fmt.Printf("-> data: %s\n", rsp.Data) | |||||
} | |||||
fmt.Printf("-> data.hex: 0x%X\n", rsp.Data) | |||||
} | |||||
if rsp.Log != "" { | |||||
fmt.Printf("-> log: %s\n", rsp.Log) | |||||
} | |||||
if rsp.Query != nil { | |||||
fmt.Printf("-> height: %d\n", rsp.Query.Height) | |||||
if rsp.Query.Key != nil { | |||||
fmt.Printf("-> key: %s\n", rsp.Query.Key) | |||||
fmt.Printf("-> key.hex: %X\n", rsp.Query.Key) | |||||
} | |||||
if rsp.Query.Value != nil { | |||||
fmt.Printf("-> value: %s\n", rsp.Query.Value) | |||||
fmt.Printf("-> value.hex: %X\n", rsp.Query.Value) | |||||
} | |||||
if rsp.Query.Proof != nil { | |||||
fmt.Printf("-> proof: %X\n", rsp.Query.Proof) | |||||
} | |||||
} | |||||
} | |||||
// NOTE: s is interpreted as a string unless prefixed with 0x | |||||
func stringOrHexToBytes(s string) ([]byte, error) { | |||||
if len(s) > 2 && strings.ToLower(s[:2]) == "0x" { | |||||
b, err := hex.DecodeString(s[2:]) | |||||
if err != nil { | |||||
err = fmt.Errorf("Error decoding hex argument: %s", err.Error()) | |||||
return nil, err | |||||
} | |||||
return b, nil | |||||
} | |||||
if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") { | |||||
err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s) | |||||
return nil, err | |||||
} | |||||
return []byte(s[1 : len(s)-1]), nil | |||||
} |
@ -0,0 +1,14 @@ | |||||
package main | |||||
import ( | |||||
"fmt" | |||||
"os" | |||||
) | |||||
func main() { | |||||
err := Execute() | |||||
if err != nil { | |||||
fmt.Print(err) | |||||
os.Exit(1) | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
package code | |||||
// Return codes for the examples | |||||
const ( | |||||
CodeTypeOK uint32 = 0 | |||||
CodeTypeEncodingError uint32 = 1 | |||||
CodeTypeBadNonce uint32 = 2 | |||||
CodeTypeUnauthorized uint32 = 3 | |||||
) |
@ -0,0 +1,104 @@ | |||||
package counter | |||||
import ( | |||||
"encoding/binary" | |||||
"fmt" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
type CounterApplication struct { | |||||
types.BaseApplication | |||||
hashCount int | |||||
txCount int | |||||
serial bool | |||||
} | |||||
func NewCounterApplication(serial bool) *CounterApplication { | |||||
return &CounterApplication{serial: serial} | |||||
} | |||||
func (app *CounterApplication) Info(req types.RequestInfo) types.ResponseInfo { | |||||
return types.ResponseInfo{Data: cmn.Fmt("{\"hashes\":%v,\"txs\":%v}", app.hashCount, app.txCount)} | |||||
} | |||||
func (app *CounterApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption { | |||||
key, value := req.Key, req.Value | |||||
if key == "serial" && value == "on" { | |||||
app.serial = true | |||||
} else { | |||||
/* | |||||
TODO Panic and have the ABCI server pass an exception. | |||||
The client can call SetOptionSync() and get an `error`. | |||||
return types.ResponseSetOption{ | |||||
Error: cmn.Fmt("Unknown key (%s) or value (%s)", key, value), | |||||
} | |||||
*/ | |||||
return types.ResponseSetOption{} | |||||
} | |||||
return types.ResponseSetOption{} | |||||
} | |||||
func (app *CounterApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { | |||||
if app.serial { | |||||
if len(tx) > 8 { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))} | |||||
} | |||||
tx8 := make([]byte, 8) | |||||
copy(tx8[len(tx8)-len(tx):], tx) | |||||
txValue := binary.BigEndian.Uint64(tx8) | |||||
if txValue != uint64(app.txCount) { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeBadNonce, | |||||
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)} | |||||
} | |||||
} | |||||
app.txCount++ | |||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK} | |||||
} | |||||
func (app *CounterApplication) CheckTx(tx []byte) types.ResponseCheckTx { | |||||
if app.serial { | |||||
if len(tx) > 8 { | |||||
return types.ResponseCheckTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))} | |||||
} | |||||
tx8 := make([]byte, 8) | |||||
copy(tx8[len(tx8)-len(tx):], tx) | |||||
txValue := binary.BigEndian.Uint64(tx8) | |||||
if txValue < uint64(app.txCount) { | |||||
return types.ResponseCheckTx{ | |||||
Code: code.CodeTypeBadNonce, | |||||
Log: fmt.Sprintf("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)} | |||||
} | |||||
} | |||||
return types.ResponseCheckTx{Code: code.CodeTypeOK} | |||||
} | |||||
func (app *CounterApplication) Commit() (resp types.ResponseCommit) { | |||||
app.hashCount++ | |||||
if app.txCount == 0 { | |||||
return types.ResponseCommit{} | |||||
} | |||||
hash := make([]byte, 8) | |||||
binary.BigEndian.PutUint64(hash, uint64(app.txCount)) | |||||
return types.ResponseCommit{Data: hash} | |||||
} | |||||
func (app *CounterApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery { | |||||
switch reqQuery.Path { | |||||
case "hash": | |||||
return types.ResponseQuery{Value: []byte(cmn.Fmt("%v", app.hashCount))} | |||||
case "tx": | |||||
return types.ResponseQuery{Value: []byte(cmn.Fmt("%v", app.txCount))} | |||||
default: | |||||
return types.ResponseQuery{Log: cmn.Fmt("Invalid query path. Expected hash or tx, got %v", reqQuery.Path)} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
package example | |||||
// so the go tool doesn't return errors about no buildable go files ... |
@ -0,0 +1,154 @@ | |||||
package example | |||||
import ( | |||||
"fmt" | |||||
"net" | |||||
"reflect" | |||||
"testing" | |||||
"time" | |||||
"google.golang.org/grpc" | |||||
"golang.org/x/net/context" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
"github.com/tendermint/tmlibs/log" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/example/kvstore" | |||||
abciserver "github.com/tendermint/abci/server" | |||||
"github.com/tendermint/abci/types" | |||||
) | |||||
func TestKVStore(t *testing.T) { | |||||
fmt.Println("### Testing KVStore") | |||||
testStream(t, kvstore.NewKVStoreApplication()) | |||||
} | |||||
func TestBaseApp(t *testing.T) { | |||||
fmt.Println("### Testing BaseApp") | |||||
testStream(t, types.NewBaseApplication()) | |||||
} | |||||
func TestGRPC(t *testing.T) { | |||||
fmt.Println("### Testing GRPC") | |||||
testGRPCSync(t, types.NewGRPCApplication(types.NewBaseApplication())) | |||||
} | |||||
func testStream(t *testing.T, app types.Application) { | |||||
numDeliverTxs := 200000 | |||||
// Start the listener | |||||
server := abciserver.NewSocketServer("unix://test.sock", app) | |||||
server.SetLogger(log.TestingLogger().With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
t.Fatalf("Error starting socket server: %v", err.Error()) | |||||
} | |||||
defer server.Stop() | |||||
// Connect to the socket | |||||
client := abcicli.NewSocketClient("unix://test.sock", false) | |||||
client.SetLogger(log.TestingLogger().With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
t.Fatalf("Error starting socket client: %v", err.Error()) | |||||
} | |||||
defer client.Stop() | |||||
done := make(chan struct{}) | |||||
counter := 0 | |||||
client.SetResponseCallback(func(req *types.Request, res *types.Response) { | |||||
// Process response | |||||
switch r := res.Value.(type) { | |||||
case *types.Response_DeliverTx: | |||||
counter++ | |||||
if r.DeliverTx.Code != code.CodeTypeOK { | |||||
t.Error("DeliverTx failed with ret_code", r.DeliverTx.Code) | |||||
} | |||||
if counter > numDeliverTxs { | |||||
t.Fatalf("Too many DeliverTx responses. Got %d, expected %d", counter, numDeliverTxs) | |||||
} | |||||
if counter == numDeliverTxs { | |||||
go func() { | |||||
time.Sleep(time.Second * 2) // Wait for a bit to allow counter overflow | |||||
close(done) | |||||
}() | |||||
return | |||||
} | |||||
case *types.Response_Flush: | |||||
// ignore | |||||
default: | |||||
t.Error("Unexpected response type", reflect.TypeOf(res.Value)) | |||||
} | |||||
}) | |||||
// Write requests | |||||
for counter := 0; counter < numDeliverTxs; counter++ { | |||||
// Send request | |||||
reqRes := client.DeliverTxAsync([]byte("test")) | |||||
_ = reqRes | |||||
// check err ? | |||||
// Sometimes send flush messages | |||||
if counter%123 == 0 { | |||||
client.FlushAsync() | |||||
// check err ? | |||||
} | |||||
} | |||||
// Send final flush message | |||||
client.FlushAsync() | |||||
<-done | |||||
} | |||||
//------------------------- | |||||
// test grpc | |||||
func dialerFunc(addr string, timeout time.Duration) (net.Conn, error) { | |||||
return cmn.Connect(addr) | |||||
} | |||||
func testGRPCSync(t *testing.T, app *types.GRPCApplication) { | |||||
numDeliverTxs := 2000 | |||||
// Start the listener | |||||
server := abciserver.NewGRPCServer("unix://test.sock", app) | |||||
server.SetLogger(log.TestingLogger().With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
t.Fatalf("Error starting GRPC server: %v", err.Error()) | |||||
} | |||||
defer server.Stop() | |||||
// Connect to the socket | |||||
conn, err := grpc.Dial("unix://test.sock", grpc.WithInsecure(), grpc.WithDialer(dialerFunc)) | |||||
if err != nil { | |||||
t.Fatalf("Error dialing GRPC server: %v", err.Error()) | |||||
} | |||||
defer conn.Close() | |||||
client := types.NewABCIApplicationClient(conn) | |||||
// Write requests | |||||
for counter := 0; counter < numDeliverTxs; counter++ { | |||||
// Send request | |||||
response, err := client.DeliverTx(context.Background(), &types.RequestDeliverTx{[]byte("test")}) | |||||
if err != nil { | |||||
t.Fatalf("Error in GRPC DeliverTx: %v", err.Error()) | |||||
} | |||||
counter++ | |||||
if response.Code != code.CodeTypeOK { | |||||
t.Error("DeliverTx failed with ret_code", response.Code) | |||||
} | |||||
if counter > numDeliverTxs { | |||||
t.Fatal("Too many DeliverTx responses") | |||||
} | |||||
t.Log("response", counter) | |||||
if counter == numDeliverTxs { | |||||
go func() { | |||||
time.Sleep(time.Second * 2) // Wait for a bit to allow counter overflow | |||||
}() | |||||
} | |||||
} | |||||
} |
@ -0,0 +1 @@ | |||||
node_modules |
@ -0,0 +1 @@ | |||||
This example has been moved here: https://github.com/tendermint/js-abci/tree/master/example |
@ -0,0 +1,31 @@ | |||||
# KVStore | |||||
There are two app's here: the KVStoreApplication and the PersistentKVStoreApplication. | |||||
## KVStoreApplication | |||||
The KVStoreApplication is a simple merkle key-value store. | |||||
Transactions of the form `key=value` are stored as key-value pairs in the tree. | |||||
Transactions without an `=` sign set the value to the key. | |||||
The app has no replay protection (other than what the mempool provides). | |||||
## PersistentKVStoreApplication | |||||
The PersistentKVStoreApplication wraps the KVStoreApplication | |||||
and provides two additional features: | |||||
1) persistence of state across app restarts (using Tendermint's ABCI-Handshake mechanism) | |||||
2) validator set changes | |||||
The state is persisted in leveldb along with the last block committed, | |||||
and the Handshake allows any necessary blocks to be replayed. | |||||
Validator set changes are effected using the following transaction format: | |||||
``` | |||||
val:pubkey1/power1,addr2/power2,addr3/power3" | |||||
``` | |||||
where `power1` is the new voting power for the validator with `pubkey1` (possibly a new one). | |||||
There is no sybil protection against new validators joining. | |||||
Validators can be removed by setting their power to `0`. | |||||
@ -0,0 +1,38 @@ | |||||
package kvstore | |||||
import ( | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
// RandVal creates one random validator, with a key derived | |||||
// from the input value | |||||
func RandVal(i int) types.Validator { | |||||
addr := cmn.RandBytes(20) | |||||
pubkey := cmn.RandBytes(32) | |||||
power := cmn.RandUint16() + 1 | |||||
v := types.Ed25519Validator(pubkey, int64(power)) | |||||
v.Address = addr | |||||
return v | |||||
} | |||||
// RandVals returns a list of cnt validators for initializing | |||||
// the application. Note that the keys are deterministically | |||||
// derived from the index in the array, while the power is | |||||
// random (Change this if not desired) | |||||
func RandVals(cnt int) []types.Validator { | |||||
res := make([]types.Validator, cnt) | |||||
for i := 0; i < cnt; i++ { | |||||
res[i] = RandVal(i) | |||||
} | |||||
return res | |||||
} | |||||
// InitKVStore initializes the kvstore app with some data, | |||||
// which allows tests to pass and is fine as long as you | |||||
// don't make any tx that modify the validator state | |||||
func InitKVStore(app *PersistentKVStoreApplication) { | |||||
app.InitChain(types.RequestInitChain{ | |||||
Validators: RandVals(1), | |||||
}) | |||||
} |
@ -0,0 +1,126 @@ | |||||
package kvstore | |||||
import ( | |||||
"bytes" | |||||
"encoding/binary" | |||||
"encoding/json" | |||||
"fmt" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
dbm "github.com/tendermint/tmlibs/db" | |||||
) | |||||
var ( | |||||
stateKey = []byte("stateKey") | |||||
kvPairPrefixKey = []byte("kvPairKey:") | |||||
) | |||||
type State struct { | |||||
db dbm.DB | |||||
Size int64 `json:"size"` | |||||
Height int64 `json:"height"` | |||||
AppHash []byte `json:"app_hash"` | |||||
} | |||||
func loadState(db dbm.DB) State { | |||||
stateBytes := db.Get(stateKey) | |||||
var state State | |||||
if len(stateBytes) != 0 { | |||||
err := json.Unmarshal(stateBytes, &state) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
} | |||||
state.db = db | |||||
return state | |||||
} | |||||
func saveState(state State) { | |||||
stateBytes, err := json.Marshal(state) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
state.db.Set(stateKey, stateBytes) | |||||
} | |||||
func prefixKey(key []byte) []byte { | |||||
return append(kvPairPrefixKey, key...) | |||||
} | |||||
//--------------------------------------------------- | |||||
var _ types.Application = (*KVStoreApplication)(nil) | |||||
type KVStoreApplication struct { | |||||
types.BaseApplication | |||||
state State | |||||
} | |||||
func NewKVStoreApplication() *KVStoreApplication { | |||||
state := loadState(dbm.NewMemDB()) | |||||
return &KVStoreApplication{state: state} | |||||
} | |||||
func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { | |||||
return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size)} | |||||
} | |||||
// tx is either "key=value" or just arbitrary bytes | |||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { | |||||
var key, value []byte | |||||
parts := bytes.Split(tx, []byte("=")) | |||||
if len(parts) == 2 { | |||||
key, value = parts[0], parts[1] | |||||
} else { | |||||
key, value = tx, tx | |||||
} | |||||
app.state.db.Set(prefixKey(key), value) | |||||
app.state.Size += 1 | |||||
tags := []cmn.KVPair{ | |||||
{[]byte("app.creator"), []byte("jae")}, | |||||
{[]byte("app.key"), key}, | |||||
} | |||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags} | |||||
} | |||||
func (app *KVStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx { | |||||
return types.ResponseCheckTx{Code: code.CodeTypeOK} | |||||
} | |||||
func (app *KVStoreApplication) Commit() types.ResponseCommit { | |||||
// Using a memdb - just return the big endian size of the db | |||||
appHash := make([]byte, 8) | |||||
binary.PutVarint(appHash, app.state.Size) | |||||
app.state.AppHash = appHash | |||||
app.state.Height += 1 | |||||
saveState(app.state) | |||||
return types.ResponseCommit{Data: appHash} | |||||
} | |||||
func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { | |||||
if reqQuery.Prove { | |||||
value := app.state.db.Get(prefixKey(reqQuery.Data)) | |||||
resQuery.Index = -1 // TODO make Proof return index | |||||
resQuery.Key = reqQuery.Data | |||||
resQuery.Value = value | |||||
if value != nil { | |||||
resQuery.Log = "exists" | |||||
} else { | |||||
resQuery.Log = "does not exist" | |||||
} | |||||
return | |||||
} else { | |||||
value := app.state.db.Get(prefixKey(reqQuery.Data)) | |||||
resQuery.Value = value | |||||
if value != nil { | |||||
resQuery.Log = "exists" | |||||
} else { | |||||
resQuery.Log = "does not exist" | |||||
} | |||||
return | |||||
} | |||||
} |
@ -0,0 +1,310 @@ | |||||
package kvstore | |||||
import ( | |||||
"bytes" | |||||
"io/ioutil" | |||||
"sort" | |||||
"testing" | |||||
"github.com/stretchr/testify/require" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
"github.com/tendermint/tmlibs/log" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/example/code" | |||||
abciserver "github.com/tendermint/abci/server" | |||||
"github.com/tendermint/abci/types" | |||||
) | |||||
func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) { | |||||
ar := app.DeliverTx(tx) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// repeating tx doesn't raise error | |||||
ar = app.DeliverTx(tx) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// make sure query is fine | |||||
resQuery := app.Query(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
}) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
// make sure proof is fine | |||||
resQuery = app.Query(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
Prove: true, | |||||
}) | |||||
require.EqualValues(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
} | |||||
func TestKVStoreKV(t *testing.T) { | |||||
kvstore := NewKVStoreApplication() | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testKVStore(t, kvstore, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testKVStore(t, kvstore, tx, key, value) | |||||
} | |||||
func TestPersistentKVStoreKV(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
kvstore := NewPersistentKVStoreApplication(dir) | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testKVStore(t, kvstore, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testKVStore(t, kvstore, tx, key, value) | |||||
} | |||||
func TestPersistentKVStoreInfo(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
kvstore := NewPersistentKVStoreApplication(dir) | |||||
InitKVStore(kvstore) | |||||
height := int64(0) | |||||
resInfo := kvstore.Info(types.RequestInfo{}) | |||||
if resInfo.LastBlockHeight != height { | |||||
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) | |||||
} | |||||
// make and apply block | |||||
height = int64(1) | |||||
hash := []byte("foo") | |||||
header := types.Header{ | |||||
Height: int64(height), | |||||
} | |||||
kvstore.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil}) | |||||
kvstore.EndBlock(types.RequestEndBlock{header.Height}) | |||||
kvstore.Commit() | |||||
resInfo = kvstore.Info(types.RequestInfo{}) | |||||
if resInfo.LastBlockHeight != height { | |||||
t.Fatalf("expected height of %d, got %d", height, resInfo.LastBlockHeight) | |||||
} | |||||
} | |||||
// add a validator, remove a validator, update a validator | |||||
func TestValUpdates(t *testing.T) { | |||||
dir, err := ioutil.TempDir("/tmp", "abci-kvstore-test") // TODO | |||||
if err != nil { | |||||
t.Fatal(err) | |||||
} | |||||
kvstore := NewPersistentKVStoreApplication(dir) | |||||
// init with some validators | |||||
total := 10 | |||||
nInit := 5 | |||||
vals := RandVals(total) | |||||
// iniitalize with the first nInit | |||||
kvstore.InitChain(types.RequestInitChain{ | |||||
Validators: vals[:nInit], | |||||
}) | |||||
vals1, vals2 := vals[:nInit], kvstore.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
var v1, v2, v3 types.Validator | |||||
// add some validators | |||||
v1, v2 = vals[nInit], vals[nInit+1] | |||||
diff := []types.Validator{v1, v2} | |||||
tx1 := MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
tx2 := MakeValSetChangeTx(v2.PubKey, v2.Power) | |||||
makeApplyBlock(t, kvstore, 1, diff, tx1, tx2) | |||||
vals1, vals2 = vals[:nInit+2], kvstore.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
// remove some validators | |||||
v1, v2, v3 = vals[nInit-2], vals[nInit-1], vals[nInit] | |||||
v1.Power = 0 | |||||
v2.Power = 0 | |||||
v3.Power = 0 | |||||
diff = []types.Validator{v1, v2, v3} | |||||
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
tx2 = MakeValSetChangeTx(v2.PubKey, v2.Power) | |||||
tx3 := MakeValSetChangeTx(v3.PubKey, v3.Power) | |||||
makeApplyBlock(t, kvstore, 2, diff, tx1, tx2, tx3) | |||||
vals1 = append(vals[:nInit-2], vals[nInit+1]) | |||||
vals2 = kvstore.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
// update some validators | |||||
v1 = vals[0] | |||||
if v1.Power == 5 { | |||||
v1.Power = 6 | |||||
} else { | |||||
v1.Power = 5 | |||||
} | |||||
diff = []types.Validator{v1} | |||||
tx1 = MakeValSetChangeTx(v1.PubKey, v1.Power) | |||||
makeApplyBlock(t, kvstore, 3, diff, tx1) | |||||
vals1 = append([]types.Validator{v1}, vals1[1:]...) | |||||
vals2 = kvstore.Validators() | |||||
valsEqual(t, vals1, vals2) | |||||
} | |||||
func makeApplyBlock(t *testing.T, kvstore types.Application, heightInt int, diff []types.Validator, txs ...[]byte) { | |||||
// make and apply block | |||||
height := int64(heightInt) | |||||
hash := []byte("foo") | |||||
header := types.Header{ | |||||
Height: height, | |||||
} | |||||
kvstore.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil}) | |||||
for _, tx := range txs { | |||||
if r := kvstore.DeliverTx(tx); r.IsErr() { | |||||
t.Fatal(r) | |||||
} | |||||
} | |||||
resEndBlock := kvstore.EndBlock(types.RequestEndBlock{header.Height}) | |||||
kvstore.Commit() | |||||
valsEqual(t, diff, resEndBlock.ValidatorUpdates) | |||||
} | |||||
// order doesn't matter | |||||
func valsEqual(t *testing.T, vals1, vals2 []types.Validator) { | |||||
if len(vals1) != len(vals2) { | |||||
t.Fatalf("vals dont match in len. got %d, expected %d", len(vals2), len(vals1)) | |||||
} | |||||
sort.Sort(types.Validators(vals1)) | |||||
sort.Sort(types.Validators(vals2)) | |||||
for i, v1 := range vals1 { | |||||
v2 := vals2[i] | |||||
if !bytes.Equal(v1.PubKey.Data, v2.PubKey.Data) || | |||||
v1.Power != v2.Power { | |||||
t.Fatalf("vals dont match at index %d. got %X/%d , expected %X/%d", i, v2.PubKey, v2.Power, v1.PubKey, v1.Power) | |||||
} | |||||
} | |||||
} | |||||
func makeSocketClientServer(app types.Application, name string) (abcicli.Client, cmn.Service, error) { | |||||
// Start the listener | |||||
socket := cmn.Fmt("unix://%s.sock", name) | |||||
logger := log.TestingLogger() | |||||
server := abciserver.NewSocketServer(socket, app) | |||||
server.SetLogger(logger.With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
// Connect to the socket | |||||
client := abcicli.NewSocketClient(socket, false) | |||||
client.SetLogger(logger.With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
server.Stop() | |||||
return nil, nil, err | |||||
} | |||||
return client, server, nil | |||||
} | |||||
func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, cmn.Service, error) { | |||||
// Start the listener | |||||
socket := cmn.Fmt("unix://%s.sock", name) | |||||
logger := log.TestingLogger() | |||||
gapp := types.NewGRPCApplication(app) | |||||
server := abciserver.NewGRPCServer(socket, gapp) | |||||
server.SetLogger(logger.With("module", "abci-server")) | |||||
if err := server.Start(); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
client := abcicli.NewGRPCClient(socket, true) | |||||
client.SetLogger(logger.With("module", "abci-client")) | |||||
if err := client.Start(); err != nil { | |||||
server.Stop() | |||||
return nil, nil, err | |||||
} | |||||
return client, server, nil | |||||
} | |||||
func TestClientServer(t *testing.T) { | |||||
// set up socket app | |||||
kvstore := NewKVStoreApplication() | |||||
client, server, err := makeSocketClientServer(kvstore, "kvstore-socket") | |||||
require.Nil(t, err) | |||||
defer server.Stop() | |||||
defer client.Stop() | |||||
runClientTests(t, client) | |||||
// set up grpc app | |||||
kvstore = NewKVStoreApplication() | |||||
gclient, gserver, err := makeGRPCClientServer(kvstore, "kvstore-grpc") | |||||
require.Nil(t, err) | |||||
defer gserver.Stop() | |||||
defer gclient.Stop() | |||||
runClientTests(t, gclient) | |||||
} | |||||
func runClientTests(t *testing.T, client abcicli.Client) { | |||||
// run some tests.... | |||||
key := "abc" | |||||
value := key | |||||
tx := []byte(key) | |||||
testClient(t, client, tx, key, value) | |||||
value = "def" | |||||
tx = []byte(key + "=" + value) | |||||
testClient(t, client, tx, key, value) | |||||
} | |||||
func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) { | |||||
ar, err := app.DeliverTxSync(tx) | |||||
require.NoError(t, err) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// repeating tx doesn't raise error | |||||
ar, err = app.DeliverTxSync(tx) | |||||
require.NoError(t, err) | |||||
require.False(t, ar.IsErr(), ar) | |||||
// make sure query is fine | |||||
resQuery, err := app.QuerySync(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
}) | |||||
require.Nil(t, err) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
// make sure proof is fine | |||||
resQuery, err = app.QuerySync(types.RequestQuery{ | |||||
Path: "/store", | |||||
Data: []byte(key), | |||||
Prove: true, | |||||
}) | |||||
require.Nil(t, err) | |||||
require.Equal(t, code.CodeTypeOK, resQuery.Code) | |||||
require.Equal(t, value, string(resQuery.Value)) | |||||
} |
@ -0,0 +1,200 @@ | |||||
package kvstore | |||||
import ( | |||||
"bytes" | |||||
"encoding/hex" | |||||
"fmt" | |||||
"strconv" | |||||
"strings" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
dbm "github.com/tendermint/tmlibs/db" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
const ( | |||||
ValidatorSetChangePrefix string = "val:" | |||||
) | |||||
//----------------------------------------- | |||||
var _ types.Application = (*PersistentKVStoreApplication)(nil) | |||||
type PersistentKVStoreApplication struct { | |||||
app *KVStoreApplication | |||||
// validator set | |||||
ValUpdates []types.Validator | |||||
logger log.Logger | |||||
} | |||||
func NewPersistentKVStoreApplication(dbDir string) *PersistentKVStoreApplication { | |||||
name := "kvstore" | |||||
db, err := dbm.NewGoLevelDB(name, dbDir) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
state := loadState(db) | |||||
return &PersistentKVStoreApplication{ | |||||
app: &KVStoreApplication{state: state}, | |||||
logger: log.NewNopLogger(), | |||||
} | |||||
} | |||||
func (app *PersistentKVStoreApplication) SetLogger(l log.Logger) { | |||||
app.logger = l | |||||
} | |||||
func (app *PersistentKVStoreApplication) Info(req types.RequestInfo) types.ResponseInfo { | |||||
res := app.app.Info(req) | |||||
res.LastBlockHeight = app.app.state.Height | |||||
res.LastBlockAppHash = app.app.state.AppHash | |||||
return res | |||||
} | |||||
func (app *PersistentKVStoreApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption { | |||||
return app.app.SetOption(req) | |||||
} | |||||
// tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes | |||||
func (app *PersistentKVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { | |||||
// if it starts with "val:", update the validator set | |||||
// format is "val:pubkey/power" | |||||
if isValidatorTx(tx) { | |||||
// update validators in the merkle tree | |||||
// and in app.ValUpdates | |||||
return app.execValidatorTx(tx) | |||||
} | |||||
// otherwise, update the key-value store | |||||
return app.app.DeliverTx(tx) | |||||
} | |||||
func (app *PersistentKVStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx { | |||||
return app.app.CheckTx(tx) | |||||
} | |||||
// Commit will panic if InitChain was not called | |||||
func (app *PersistentKVStoreApplication) Commit() types.ResponseCommit { | |||||
return app.app.Commit() | |||||
} | |||||
func (app *PersistentKVStoreApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery { | |||||
return app.app.Query(reqQuery) | |||||
} | |||||
// Save the validators in the merkle tree | |||||
func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain { | |||||
for _, v := range req.Validators { | |||||
r := app.updateValidator(v) | |||||
if r.IsErr() { | |||||
app.logger.Error("Error updating validators", "r", r) | |||||
} | |||||
} | |||||
return types.ResponseInitChain{} | |||||
} | |||||
// Track the block hash and header information | |||||
func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { | |||||
// reset valset changes | |||||
app.ValUpdates = make([]types.Validator, 0) | |||||
return types.ResponseBeginBlock{} | |||||
} | |||||
// Update the validator set | |||||
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { | |||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates} | |||||
} | |||||
//--------------------------------------------- | |||||
// update validators | |||||
func (app *PersistentKVStoreApplication) Validators() (validators []types.Validator) { | |||||
itr := app.app.state.db.Iterator(nil, nil) | |||||
for ; itr.Valid(); itr.Next() { | |||||
if isValidatorTx(itr.Key()) { | |||||
validator := new(types.Validator) | |||||
err := types.ReadMessage(bytes.NewBuffer(itr.Value()), validator) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
validators = append(validators, *validator) | |||||
} | |||||
} | |||||
return | |||||
} | |||||
func MakeValSetChangeTx(pubkey types.PubKey, power int64) []byte { | |||||
return []byte(cmn.Fmt("val:%X/%d", pubkey.Data, power)) | |||||
} | |||||
func isValidatorTx(tx []byte) bool { | |||||
return strings.HasPrefix(string(tx), ValidatorSetChangePrefix) | |||||
} | |||||
// format is "val:pubkey/power" | |||||
// pubkey is raw 32-byte ed25519 key | |||||
func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx { | |||||
tx = tx[len(ValidatorSetChangePrefix):] | |||||
//get the pubkey and power | |||||
pubKeyAndPower := strings.Split(string(tx), "/") | |||||
if len(pubKeyAndPower) != 2 { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Expected 'pubkey/power'. Got %v", pubKeyAndPower)} | |||||
} | |||||
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1] | |||||
// decode the pubkey | |||||
pubkey, err := hex.DecodeString(pubkeyS) | |||||
if err != nil { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Pubkey (%s) is invalid hex", pubkeyS)} | |||||
} | |||||
// decode the power | |||||
power, err := strconv.ParseInt(powerS, 10, 64) | |||||
if err != nil { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Power (%s) is not an int", powerS)} | |||||
} | |||||
// update | |||||
return app.updateValidator(types.Ed25519Validator(pubkey, int64(power))) | |||||
} | |||||
// add, update, or remove a validator | |||||
func (app *PersistentKVStoreApplication) updateValidator(v types.Validator) types.ResponseDeliverTx { | |||||
key := []byte("val:" + string(v.PubKey.Data)) | |||||
if v.Power == 0 { | |||||
// remove validator | |||||
if !app.app.state.db.Has(key) { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeUnauthorized, | |||||
Log: fmt.Sprintf("Cannot remove non-existent validator %X", key)} | |||||
} | |||||
app.app.state.db.Delete(key) | |||||
} else { | |||||
// add or update validator | |||||
value := bytes.NewBuffer(make([]byte, 0)) | |||||
if err := types.WriteMessage(&v, value); err != nil { | |||||
return types.ResponseDeliverTx{ | |||||
Code: code.CodeTypeEncodingError, | |||||
Log: fmt.Sprintf("Error encoding validator: %v", err)} | |||||
} | |||||
app.app.state.db.Set(key, value.Bytes()) | |||||
} | |||||
// we only update the changes array if we successfully updated the tree | |||||
app.ValUpdates = append(app.ValUpdates, v) | |||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK} | |||||
} |
@ -0,0 +1,50 @@ | |||||
from wire import decode_string | |||||
# map type_byte to message name | |||||
message_types = { | |||||
0x01: "echo", | |||||
0x02: "flush", | |||||
0x03: "info", | |||||
0x04: "set_option", | |||||
0x21: "deliver_tx", | |||||
0x22: "check_tx", | |||||
0x23: "commit", | |||||
0x24: "add_listener", | |||||
0x25: "rm_listener", | |||||
} | |||||
# return the decoded arguments of abci messages | |||||
class RequestDecoder(): | |||||
def __init__(self, reader): | |||||
self.reader = reader | |||||
def echo(self): | |||||
return decode_string(self.reader) | |||||
def flush(self): | |||||
return | |||||
def info(self): | |||||
return | |||||
def set_option(self): | |||||
return decode_string(self.reader), decode_string(self.reader) | |||||
def deliver_tx(self): | |||||
return decode_string(self.reader) | |||||
def check_tx(self): | |||||
return decode_string(self.reader) | |||||
def commit(self): | |||||
return | |||||
def add_listener(self): | |||||
# TODO | |||||
return | |||||
def rm_listener(self): | |||||
# TODO | |||||
return |
@ -0,0 +1,56 @@ | |||||
# Simple read() method around a bytearray | |||||
class BytesBuffer(): | |||||
def __init__(self, b): | |||||
self.buf = b | |||||
self.readCount = 0 | |||||
def count(self): | |||||
return self.readCount | |||||
def reset_count(self): | |||||
self.readCount = 0 | |||||
def size(self): | |||||
return len(self.buf) | |||||
def peek(self): | |||||
return self.buf[0] | |||||
def write(self, b): | |||||
# b should be castable to byte array | |||||
self.buf += bytearray(b) | |||||
def read(self, n): | |||||
if len(self.buf) < n: | |||||
print "reader err: buf less than n" | |||||
# TODO: exception | |||||
return | |||||
self.readCount += n | |||||
r = self.buf[:n] | |||||
self.buf = self.buf[n:] | |||||
return r | |||||
# Buffer bytes off a tcp connection and read them off in chunks | |||||
class ConnReader(): | |||||
def __init__(self, conn): | |||||
self.conn = conn | |||||
self.buf = bytearray() | |||||
# blocking | |||||
def read(self, n): | |||||
while n > len(self.buf): | |||||
moreBuf = self.conn.recv(1024) | |||||
if not moreBuf: | |||||
raise IOError("dead connection") | |||||
self.buf = self.buf + bytearray(moreBuf) | |||||
r = self.buf[:n] | |||||
self.buf = self.buf[n:] | |||||
return r |
@ -0,0 +1,202 @@ | |||||
import socket | |||||
import select | |||||
import sys | |||||
from wire import decode_varint, encode | |||||
from reader import BytesBuffer | |||||
from msg import RequestDecoder, message_types | |||||
# hold the asyncronous state of a connection | |||||
# ie. we may not get enough bytes on one read to decode the message | |||||
class Connection(): | |||||
def __init__(self, fd, app): | |||||
self.fd = fd | |||||
self.app = app | |||||
self.recBuf = BytesBuffer(bytearray()) | |||||
self.resBuf = BytesBuffer(bytearray()) | |||||
self.msgLength = 0 | |||||
self.decoder = RequestDecoder(self.recBuf) | |||||
self.inProgress = False # are we in the middle of a message | |||||
def recv(this): | |||||
data = this.fd.recv(1024) | |||||
if not data: # what about len(data) == 0 | |||||
raise IOError("dead connection") | |||||
this.recBuf.write(data) | |||||
# ABCI server responds to messges by calling methods on the app | |||||
class ABCIServer(): | |||||
def __init__(self, app, port=5410): | |||||
self.app = app | |||||
# map conn file descriptors to (app, reqBuf, resBuf, msgDecoder) | |||||
self.appMap = {} | |||||
self.port = port | |||||
self.listen_backlog = 10 | |||||
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |||||
self.listener.setblocking(0) | |||||
self.listener.bind(('', port)) | |||||
self.listener.listen(self.listen_backlog) | |||||
self.shutdown = False | |||||
self.read_list = [self.listener] | |||||
self.write_list = [] | |||||
def handle_new_connection(self, r): | |||||
new_fd, new_addr = r.accept() | |||||
new_fd.setblocking(0) # non-blocking | |||||
self.read_list.append(new_fd) | |||||
self.write_list.append(new_fd) | |||||
print 'new connection to', new_addr | |||||
self.appMap[new_fd] = Connection(new_fd, self.app) | |||||
def handle_conn_closed(self, r): | |||||
self.read_list.remove(r) | |||||
self.write_list.remove(r) | |||||
r.close() | |||||
print "connection closed" | |||||
def handle_recv(self, r): | |||||
# app, recBuf, resBuf, conn | |||||
conn = self.appMap[r] | |||||
while True: | |||||
try: | |||||
print "recv loop" | |||||
# check if we need more data first | |||||
if conn.inProgress: | |||||
if (conn.msgLength == 0 or conn.recBuf.size() < conn.msgLength): | |||||
conn.recv() | |||||
else: | |||||
if conn.recBuf.size() == 0: | |||||
conn.recv() | |||||
conn.inProgress = True | |||||
# see if we have enough to get the message length | |||||
if conn.msgLength == 0: | |||||
ll = conn.recBuf.peek() | |||||
if conn.recBuf.size() < 1 + ll: | |||||
# we don't have enough bytes to read the length yet | |||||
return | |||||
print "decoding msg length" | |||||
conn.msgLength = decode_varint(conn.recBuf) | |||||
# see if we have enough to decode the message | |||||
if conn.recBuf.size() < conn.msgLength: | |||||
return | |||||
# now we can decode the message | |||||
# first read the request type and get the particular msg | |||||
# decoder | |||||
typeByte = conn.recBuf.read(1) | |||||
typeByte = int(typeByte[0]) | |||||
resTypeByte = typeByte + 0x10 | |||||
req_type = message_types[typeByte] | |||||
if req_type == "flush": | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.fd.send(str(conn.resBuf.buf)) | |||||
conn.msgLength = 0 | |||||
conn.inProgress = False | |||||
conn.resBuf = BytesBuffer(bytearray()) | |||||
return | |||||
decoder = getattr(conn.decoder, req_type) | |||||
print "decoding args" | |||||
req_args = decoder() | |||||
print "got args", req_args | |||||
# done decoding message | |||||
conn.msgLength = 0 | |||||
conn.inProgress = False | |||||
req_f = getattr(conn.app, req_type) | |||||
if req_args is None: | |||||
res = req_f() | |||||
elif isinstance(req_args, tuple): | |||||
res = req_f(*req_args) | |||||
else: | |||||
res = req_f(req_args) | |||||
if isinstance(res, tuple): | |||||
res, ret_code = res | |||||
else: | |||||
ret_code = res | |||||
res = None | |||||
print "called", req_type, "ret code:", ret_code | |||||
if ret_code != 0: | |||||
print "non-zero retcode:", ret_code | |||||
if req_type in ("echo", "info"): # these dont return a ret code | |||||
enc = encode(res) | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(len(enc) + 1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.resBuf.write(enc) | |||||
else: | |||||
enc, encRet = encode(res), encode(ret_code) | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(len(enc) + len(encRet) + 1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.resBuf.write(encRet) | |||||
conn.resBuf.write(enc) | |||||
except TypeError as e: | |||||
print "TypeError on reading from connection:", e | |||||
self.handle_conn_closed(r) | |||||
return | |||||
except ValueError as e: | |||||
print "ValueError on reading from connection:", e | |||||
self.handle_conn_closed(r) | |||||
return | |||||
except IOError as e: | |||||
print "IOError on reading from connection:", e | |||||
self.handle_conn_closed(r) | |||||
return | |||||
except Exception as e: | |||||
# sys.exc_info()[0] # TODO better | |||||
print "error reading from connection", str(e) | |||||
self.handle_conn_closed(r) | |||||
return | |||||
def main_loop(self): | |||||
while not self.shutdown: | |||||
r_list, w_list, _ = select.select( | |||||
self.read_list, self.write_list, [], 2.5) | |||||
for r in r_list: | |||||
if (r == self.listener): | |||||
try: | |||||
self.handle_new_connection(r) | |||||
# undo adding to read list ... | |||||
except NameError as e: | |||||
print "Could not connect due to NameError:", e | |||||
except TypeError as e: | |||||
print "Could not connect due to TypeError:", e | |||||
except: | |||||
print "Could not connect due to unexpected error:", sys.exc_info()[0] | |||||
else: | |||||
self.handle_recv(r) | |||||
def handle_shutdown(self): | |||||
for r in self.read_list: | |||||
r.close() | |||||
for w in self.write_list: | |||||
try: | |||||
w.close() | |||||
except Exception as e: | |||||
print(e) # TODO: add logging | |||||
self.shutdown = True |
@ -0,0 +1,115 @@ | |||||
# the decoder works off a reader | |||||
# the encoder returns bytearray | |||||
def hex2bytes(h): | |||||
return bytearray(h.decode('hex')) | |||||
def bytes2hex(b): | |||||
if type(b) in (str, unicode): | |||||
return "".join([hex(ord(c))[2:].zfill(2) for c in b]) | |||||
else: | |||||
return bytes2hex(b.decode()) | |||||
# expects uvarint64 (no crazy big nums!) | |||||
def uvarint_size(i): | |||||
if i == 0: | |||||
return 0 | |||||
for j in xrange(1, 8): | |||||
if i < 1 << j * 8: | |||||
return j | |||||
return 8 | |||||
# expects i < 2**size | |||||
def encode_big_endian(i, size): | |||||
if size == 0: | |||||
return bytearray() | |||||
return encode_big_endian(i / 256, size - 1) + bytearray([i % 256]) | |||||
def decode_big_endian(reader, size): | |||||
if size == 0: | |||||
return 0 | |||||
firstByte = reader.read(1)[0] | |||||
return firstByte * (256 ** (size - 1)) + decode_big_endian(reader, size - 1) | |||||
# ints are max 16 bytes long | |||||
def encode_varint(i): | |||||
negate = False | |||||
if i < 0: | |||||
negate = True | |||||
i = -i | |||||
size = uvarint_size(i) | |||||
if size == 0: | |||||
return bytearray([0]) | |||||
big_end = encode_big_endian(i, size) | |||||
if negate: | |||||
size += 0xF0 | |||||
return bytearray([size]) + big_end | |||||
# returns the int and whats left of the byte array | |||||
def decode_varint(reader): | |||||
size = reader.read(1)[0] | |||||
if size == 0: | |||||
return 0 | |||||
negate = True if size > int(0xF0) else False | |||||
if negate: | |||||
size = size - 0xF0 | |||||
i = decode_big_endian(reader, size) | |||||
if negate: | |||||
i = i * (-1) | |||||
return i | |||||
def encode_string(s): | |||||
size = encode_varint(len(s)) | |||||
return size + bytearray(s) | |||||
def decode_string(reader): | |||||
length = decode_varint(reader) | |||||
return str(reader.read(length)) | |||||
def encode_list(s): | |||||
b = bytearray() | |||||
map(b.extend, map(encode, s)) | |||||
return encode_varint(len(s)) + b | |||||
def encode(s): | |||||
if s is None: | |||||
return bytearray() | |||||
if isinstance(s, int): | |||||
return encode_varint(s) | |||||
elif isinstance(s, str): | |||||
return encode_string(s) | |||||
elif isinstance(s, list): | |||||
return encode_list(s) | |||||
else: | |||||
print "UNSUPPORTED TYPE!", type(s), s | |||||
if __name__ == '__main__': | |||||
ns = [100, 100, 1000, 256] | |||||
ss = [2, 5, 5, 2] | |||||
bs = map(encode_big_endian, ns, ss) | |||||
ds = map(decode_big_endian, bs, ss) | |||||
print ns | |||||
print [i[0] for i in ds] | |||||
ss = ["abc", "hi there jim", "ok now what"] | |||||
e = map(encode_string, ss) | |||||
d = map(decode_string, e) | |||||
print ss | |||||
print [i[0] for i in d] |
@ -0,0 +1,82 @@ | |||||
import sys | |||||
from abci.wire import hex2bytes, decode_big_endian, encode_big_endian | |||||
from abci.server import ABCIServer | |||||
from abci.reader import BytesBuffer | |||||
class CounterApplication(): | |||||
def __init__(self): | |||||
sys.exit("The python example is out of date. Upgrading the Python examples is currently left as an exercise to you.") | |||||
self.hashCount = 0 | |||||
self.txCount = 0 | |||||
self.serial = False | |||||
def echo(self, msg): | |||||
return msg, 0 | |||||
def info(self): | |||||
return ["hashes:%d, txs:%d" % (self.hashCount, self.txCount)], 0 | |||||
def set_option(self, key, value): | |||||
if key == "serial" and value == "on": | |||||
self.serial = True | |||||
return 0 | |||||
def deliver_tx(self, txBytes): | |||||
if self.serial: | |||||
txByteArray = bytearray(txBytes) | |||||
if len(txBytes) >= 2 and txBytes[:2] == "0x": | |||||
txByteArray = hex2bytes(txBytes[2:]) | |||||
txValue = decode_big_endian( | |||||
BytesBuffer(txByteArray), len(txBytes)) | |||||
if txValue != self.txCount: | |||||
return None, 6 | |||||
self.txCount += 1 | |||||
return None, 0 | |||||
def check_tx(self, txBytes): | |||||
if self.serial: | |||||
txByteArray = bytearray(txBytes) | |||||
if len(txBytes) >= 2 and txBytes[:2] == "0x": | |||||
txByteArray = hex2bytes(txBytes[2:]) | |||||
txValue = decode_big_endian( | |||||
BytesBuffer(txByteArray), len(txBytes)) | |||||
if txValue < self.txCount: | |||||
return 6 | |||||
return 0 | |||||
def commit(self): | |||||
self.hashCount += 1 | |||||
if self.txCount == 0: | |||||
return "", 0 | |||||
h = encode_big_endian(self.txCount, 8) | |||||
h.reverse() | |||||
return str(h), 0 | |||||
def add_listener(self): | |||||
return 0 | |||||
def rm_listener(self): | |||||
return 0 | |||||
def event(self): | |||||
return | |||||
if __name__ == '__main__': | |||||
l = len(sys.argv) | |||||
if l == 1: | |||||
port = 26658 | |||||
elif l == 2: | |||||
port = int(sys.argv[1]) | |||||
else: | |||||
print "too many arguments" | |||||
quit() | |||||
print 'ABCI Demo APP (Python)' | |||||
app = CounterApplication() | |||||
server = ABCIServer(app, port) | |||||
server.main_loop() |
@ -0,0 +1,50 @@ | |||||
from .wire import decode_string | |||||
# map type_byte to message name | |||||
message_types = { | |||||
0x01: "echo", | |||||
0x02: "flush", | |||||
0x03: "info", | |||||
0x04: "set_option", | |||||
0x21: "deliver_tx", | |||||
0x22: "check_tx", | |||||
0x23: "commit", | |||||
0x24: "add_listener", | |||||
0x25: "rm_listener", | |||||
} | |||||
# return the decoded arguments of abci messages | |||||
class RequestDecoder(): | |||||
def __init__(self, reader): | |||||
self.reader = reader | |||||
def echo(self): | |||||
return decode_string(self.reader) | |||||
def flush(self): | |||||
return | |||||
def info(self): | |||||
return | |||||
def set_option(self): | |||||
return decode_string(self.reader), decode_string(self.reader) | |||||
def deliver_tx(self): | |||||
return decode_string(self.reader) | |||||
def check_tx(self): | |||||
return decode_string(self.reader) | |||||
def commit(self): | |||||
return | |||||
def add_listener(self): | |||||
# TODO | |||||
return | |||||
def rm_listener(self): | |||||
# TODO | |||||
return |
@ -0,0 +1,56 @@ | |||||
# Simple read() method around a bytearray | |||||
class BytesBuffer(): | |||||
def __init__(self, b): | |||||
self.buf = b | |||||
self.readCount = 0 | |||||
def count(self): | |||||
return self.readCount | |||||
def reset_count(self): | |||||
self.readCount = 0 | |||||
def size(self): | |||||
return len(self.buf) | |||||
def peek(self): | |||||
return self.buf[0] | |||||
def write(self, b): | |||||
# b should be castable to byte array | |||||
self.buf += bytearray(b) | |||||
def read(self, n): | |||||
if len(self.buf) < n: | |||||
print("reader err: buf less than n") | |||||
# TODO: exception | |||||
return | |||||
self.readCount += n | |||||
r = self.buf[:n] | |||||
self.buf = self.buf[n:] | |||||
return r | |||||
# Buffer bytes off a tcp connection and read them off in chunks | |||||
class ConnReader(): | |||||
def __init__(self, conn): | |||||
self.conn = conn | |||||
self.buf = bytearray() | |||||
# blocking | |||||
def read(self, n): | |||||
while n > len(self.buf): | |||||
moreBuf = self.conn.recv(1024) | |||||
if not moreBuf: | |||||
raise IOError("dead connection") | |||||
self.buf = self.buf + bytearray(moreBuf) | |||||
r = self.buf[:n] | |||||
self.buf = self.buf[n:] | |||||
return r |
@ -0,0 +1,196 @@ | |||||
import socket | |||||
import select | |||||
import sys | |||||
import logging | |||||
from .wire import decode_varint, encode | |||||
from .reader import BytesBuffer | |||||
from .msg import RequestDecoder, message_types | |||||
# hold the asyncronous state of a connection | |||||
# ie. we may not get enough bytes on one read to decode the message | |||||
logger = logging.getLogger(__name__) | |||||
class Connection(): | |||||
def __init__(self, fd, app): | |||||
self.fd = fd | |||||
self.app = app | |||||
self.recBuf = BytesBuffer(bytearray()) | |||||
self.resBuf = BytesBuffer(bytearray()) | |||||
self.msgLength = 0 | |||||
self.decoder = RequestDecoder(self.recBuf) | |||||
self.inProgress = False # are we in the middle of a message | |||||
def recv(this): | |||||
data = this.fd.recv(1024) | |||||
if not data: # what about len(data) == 0 | |||||
raise IOError("dead connection") | |||||
this.recBuf.write(data) | |||||
# ABCI server responds to messges by calling methods on the app | |||||
class ABCIServer(): | |||||
def __init__(self, app, port=5410): | |||||
self.app = app | |||||
# map conn file descriptors to (app, reqBuf, resBuf, msgDecoder) | |||||
self.appMap = {} | |||||
self.port = port | |||||
self.listen_backlog = 10 | |||||
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |||||
self.listener.setblocking(0) | |||||
self.listener.bind(('', port)) | |||||
self.listener.listen(self.listen_backlog) | |||||
self.shutdown = False | |||||
self.read_list = [self.listener] | |||||
self.write_list = [] | |||||
def handle_new_connection(self, r): | |||||
new_fd, new_addr = r.accept() | |||||
new_fd.setblocking(0) # non-blocking | |||||
self.read_list.append(new_fd) | |||||
self.write_list.append(new_fd) | |||||
print('new connection to', new_addr) | |||||
self.appMap[new_fd] = Connection(new_fd, self.app) | |||||
def handle_conn_closed(self, r): | |||||
self.read_list.remove(r) | |||||
self.write_list.remove(r) | |||||
r.close() | |||||
print("connection closed") | |||||
def handle_recv(self, r): | |||||
# app, recBuf, resBuf, conn | |||||
conn = self.appMap[r] | |||||
while True: | |||||
try: | |||||
print("recv loop") | |||||
# check if we need more data first | |||||
if conn.inProgress: | |||||
if (conn.msgLength == 0 or conn.recBuf.size() < conn.msgLength): | |||||
conn.recv() | |||||
else: | |||||
if conn.recBuf.size() == 0: | |||||
conn.recv() | |||||
conn.inProgress = True | |||||
# see if we have enough to get the message length | |||||
if conn.msgLength == 0: | |||||
ll = conn.recBuf.peek() | |||||
if conn.recBuf.size() < 1 + ll: | |||||
# we don't have enough bytes to read the length yet | |||||
return | |||||
print("decoding msg length") | |||||
conn.msgLength = decode_varint(conn.recBuf) | |||||
# see if we have enough to decode the message | |||||
if conn.recBuf.size() < conn.msgLength: | |||||
return | |||||
# now we can decode the message | |||||
# first read the request type and get the particular msg | |||||
# decoder | |||||
typeByte = conn.recBuf.read(1) | |||||
typeByte = int(typeByte[0]) | |||||
resTypeByte = typeByte + 0x10 | |||||
req_type = message_types[typeByte] | |||||
if req_type == "flush": | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.fd.send(conn.resBuf.buf) | |||||
conn.msgLength = 0 | |||||
conn.inProgress = False | |||||
conn.resBuf = BytesBuffer(bytearray()) | |||||
return | |||||
decoder = getattr(conn.decoder, req_type) | |||||
print("decoding args") | |||||
req_args = decoder() | |||||
print("got args", req_args) | |||||
# done decoding message | |||||
conn.msgLength = 0 | |||||
conn.inProgress = False | |||||
req_f = getattr(conn.app, req_type) | |||||
if req_args is None: | |||||
res = req_f() | |||||
elif isinstance(req_args, tuple): | |||||
res = req_f(*req_args) | |||||
else: | |||||
res = req_f(req_args) | |||||
if isinstance(res, tuple): | |||||
res, ret_code = res | |||||
else: | |||||
ret_code = res | |||||
res = None | |||||
print("called", req_type, "ret code:", ret_code, 'res:', res) | |||||
if ret_code != 0: | |||||
print("non-zero retcode:", ret_code) | |||||
if req_type in ("echo", "info"): # these dont return a ret code | |||||
enc = encode(res) | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(len(enc) + 1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.resBuf.write(enc) | |||||
else: | |||||
enc, encRet = encode(res), encode(ret_code) | |||||
# messages are length prefixed | |||||
conn.resBuf.write(encode(len(enc) + len(encRet) + 1)) | |||||
conn.resBuf.write([resTypeByte]) | |||||
conn.resBuf.write(encRet) | |||||
conn.resBuf.write(enc) | |||||
except IOError as e: | |||||
print("IOError on reading from connection:", e) | |||||
self.handle_conn_closed(r) | |||||
return | |||||
except Exception as e: | |||||
logger.exception("error reading from connection") | |||||
self.handle_conn_closed(r) | |||||
return | |||||
def main_loop(self): | |||||
while not self.shutdown: | |||||
r_list, w_list, _ = select.select( | |||||
self.read_list, self.write_list, [], 2.5) | |||||
for r in r_list: | |||||
if (r == self.listener): | |||||
try: | |||||
self.handle_new_connection(r) | |||||
# undo adding to read list ... | |||||
except NameError as e: | |||||
print("Could not connect due to NameError:", e) | |||||
except TypeError as e: | |||||
print("Could not connect due to TypeError:", e) | |||||
except: | |||||
print("Could not connect due to unexpected error:", sys.exc_info()[0]) | |||||
else: | |||||
self.handle_recv(r) | |||||
def handle_shutdown(self): | |||||
for r in self.read_list: | |||||
r.close() | |||||
for w in self.write_list: | |||||
try: | |||||
w.close() | |||||
except Exception as e: | |||||
print(e) # TODO: add logging | |||||
self.shutdown = True |
@ -0,0 +1,119 @@ | |||||
# the decoder works off a reader | |||||
# the encoder returns bytearray | |||||
def hex2bytes(h): | |||||
return bytearray(h.decode('hex')) | |||||
def bytes2hex(b): | |||||
if type(b) in (str, str): | |||||
return "".join([hex(ord(c))[2:].zfill(2) for c in b]) | |||||
else: | |||||
return bytes2hex(b.decode()) | |||||
# expects uvarint64 (no crazy big nums!) | |||||
def uvarint_size(i): | |||||
if i == 0: | |||||
return 0 | |||||
for j in range(1, 8): | |||||
if i < 1 << j * 8: | |||||
return j | |||||
return 8 | |||||
# expects i < 2**size | |||||
def encode_big_endian(i, size): | |||||
if size == 0: | |||||
return bytearray() | |||||
return encode_big_endian(i // 256, size - 1) + bytearray([i % 256]) | |||||
def decode_big_endian(reader, size): | |||||
if size == 0: | |||||
return 0 | |||||
firstByte = reader.read(1)[0] | |||||
return firstByte * (256 ** (size - 1)) + decode_big_endian(reader, size - 1) | |||||
# ints are max 16 bytes long | |||||
def encode_varint(i): | |||||
negate = False | |||||
if i < 0: | |||||
negate = True | |||||
i = -i | |||||
size = uvarint_size(i) | |||||
if size == 0: | |||||
return bytearray([0]) | |||||
big_end = encode_big_endian(i, size) | |||||
if negate: | |||||
size += 0xF0 | |||||
return bytearray([size]) + big_end | |||||
# returns the int and whats left of the byte array | |||||
def decode_varint(reader): | |||||
size = reader.read(1)[0] | |||||
if size == 0: | |||||
return 0 | |||||
negate = True if size > int(0xF0) else False | |||||
if negate: | |||||
size = size - 0xF0 | |||||
i = decode_big_endian(reader, size) | |||||
if negate: | |||||
i = i * (-1) | |||||
return i | |||||
def encode_string(s): | |||||
size = encode_varint(len(s)) | |||||
return size + bytearray(s, 'utf8') | |||||
def decode_string(reader): | |||||
length = decode_varint(reader) | |||||
raw_data = reader.read(length) | |||||
return raw_data.decode() | |||||
def encode_list(s): | |||||
b = bytearray() | |||||
list(map(b.extend, list(map(encode, s)))) | |||||
return encode_varint(len(s)) + b | |||||
def encode(s): | |||||
print('encoding', repr(s)) | |||||
if s is None: | |||||
return bytearray() | |||||
if isinstance(s, int): | |||||
return encode_varint(s) | |||||
elif isinstance(s, str): | |||||
return encode_string(s) | |||||
elif isinstance(s, list): | |||||
return encode_list(s) | |||||
elif isinstance(s, bytearray): | |||||
return encode_string(s) | |||||
else: | |||||
print("UNSUPPORTED TYPE!", type(s), s) | |||||
if __name__ == '__main__': | |||||
ns = [100, 100, 1000, 256] | |||||
ss = [2, 5, 5, 2] | |||||
bs = list(map(encode_big_endian, ns, ss)) | |||||
ds = list(map(decode_big_endian, bs, ss)) | |||||
print(ns) | |||||
print([i[0] for i in ds]) | |||||
ss = ["abc", "hi there jim", "ok now what"] | |||||
e = list(map(encode_string, ss)) | |||||
d = list(map(decode_string, e)) | |||||
print(ss) | |||||
print([i[0] for i in d]) |
@ -0,0 +1,82 @@ | |||||
import sys | |||||
from abci.wire import hex2bytes, decode_big_endian, encode_big_endian | |||||
from abci.server import ABCIServer | |||||
from abci.reader import BytesBuffer | |||||
class CounterApplication(): | |||||
def __init__(self): | |||||
sys.exit("The python example is out of date. Upgrading the Python examples is currently left as an exercise to you.") | |||||
self.hashCount = 0 | |||||
self.txCount = 0 | |||||
self.serial = False | |||||
def echo(self, msg): | |||||
return msg, 0 | |||||
def info(self): | |||||
return ["hashes:%d, txs:%d" % (self.hashCount, self.txCount)], 0 | |||||
def set_option(self, key, value): | |||||
if key == "serial" and value == "on": | |||||
self.serial = True | |||||
return 0 | |||||
def deliver_tx(self, txBytes): | |||||
if self.serial: | |||||
txByteArray = bytearray(txBytes) | |||||
if len(txBytes) >= 2 and txBytes[:2] == "0x": | |||||
txByteArray = hex2bytes(txBytes[2:]) | |||||
txValue = decode_big_endian( | |||||
BytesBuffer(txByteArray), len(txBytes)) | |||||
if txValue != self.txCount: | |||||
return None, 6 | |||||
self.txCount += 1 | |||||
return None, 0 | |||||
def check_tx(self, txBytes): | |||||
if self.serial: | |||||
txByteArray = bytearray(txBytes) | |||||
if len(txBytes) >= 2 and txBytes[:2] == "0x": | |||||
txByteArray = hex2bytes(txBytes[2:]) | |||||
txValue = decode_big_endian( | |||||
BytesBuffer(txByteArray), len(txBytes)) | |||||
if txValue < self.txCount: | |||||
return 6 | |||||
return 0 | |||||
def commit(self): | |||||
self.hashCount += 1 | |||||
if self.txCount == 0: | |||||
return "", 0 | |||||
h = encode_big_endian(self.txCount, 8) | |||||
h.reverse() | |||||
return h.decode(), 0 | |||||
def add_listener(self): | |||||
return 0 | |||||
def rm_listener(self): | |||||
return 0 | |||||
def event(self): | |||||
return | |||||
if __name__ == '__main__': | |||||
l = len(sys.argv) | |||||
if l == 1: | |||||
port = 26658 | |||||
elif l == 2: | |||||
port = int(sys.argv[1]) | |||||
else: | |||||
print("too many arguments") | |||||
quit() | |||||
print('ABCI Demo APP (Python)') | |||||
app = CounterApplication() | |||||
server = ABCIServer(app, port) | |||||
server.main_loop() |
@ -0,0 +1,12 @@ | |||||
FROM golang:1.9.2 | |||||
RUN apt-get update && apt-get install -y --no-install-recommends \ | |||||
zip \ | |||||
&& rm -rf /var/lib/apt/lists/* | |||||
# We want to ensure that release builds never have any cgo dependencies so we | |||||
# switch that off at the highest level. | |||||
ENV CGO_ENABLED 0 | |||||
RUN mkdir -p $GOPATH/src/github.com/tendermint/abci | |||||
WORKDIR $GOPATH/src/github.com/tendermint/abci |
@ -0,0 +1,52 @@ | |||||
#!/usr/bin/env bash | |||||
set -e | |||||
REPO_NAME="abci" | |||||
# Get the version from the environment, or try to figure it out. | |||||
if [ -z $VERSION ]; then | |||||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) | |||||
fi | |||||
if [ -z "$VERSION" ]; then | |||||
echo "Please specify a version." | |||||
exit 1 | |||||
fi | |||||
echo "==> Building version $VERSION..." | |||||
# Get the parent directory of where this script is. | |||||
SOURCE="${BASH_SOURCE[0]}" | |||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done | |||||
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" | |||||
# Change into that dir because we expect that. | |||||
cd "$DIR" | |||||
# Delete the old dir | |||||
echo "==> Removing old directory..." | |||||
rm -rf build/pkg | |||||
mkdir -p build/pkg | |||||
# Do a hermetic build inside a Docker container. | |||||
docker build -t tendermint/${REPO_NAME}-builder scripts/${REPO_NAME}-builder/ | |||||
docker run --rm -e "BUILD_TAGS=$BUILD_TAGS" -v "$(pwd)":/go/src/github.com/tendermint/${REPO_NAME} tendermint/${REPO_NAME}-builder ./scripts/dist_build.sh | |||||
# Add $REPO_NAME and $VERSION prefix to package name. | |||||
rm -rf ./build/dist | |||||
mkdir -p ./build/dist | |||||
for FILENAME in $(find ./build/pkg -mindepth 1 -maxdepth 1 -type f); do | |||||
FILENAME=$(basename "$FILENAME") | |||||
cp "./build/pkg/${FILENAME}" "./build/dist/${REPO_NAME}_${VERSION}_${FILENAME}" | |||||
done | |||||
# Make the checksums. | |||||
pushd ./build/dist | |||||
shasum -a256 ./* > "./${REPO_NAME}_${VERSION}_SHA256SUMS" | |||||
popd | |||||
# Done | |||||
echo | |||||
echo "==> Results:" | |||||
ls -hl ./build/dist | |||||
exit 0 |
@ -0,0 +1,53 @@ | |||||
#!/usr/bin/env bash | |||||
set -e | |||||
# Get the parent directory of where this script is. | |||||
SOURCE="${BASH_SOURCE[0]}" | |||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done | |||||
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" | |||||
# Change into that dir because we expect that. | |||||
cd "$DIR" | |||||
# Get the git commit | |||||
GIT_COMMIT="$(git rev-parse --short HEAD)" | |||||
GIT_DESCRIBE="$(git describe --tags --always)" | |||||
GIT_IMPORT="github.com/tendermint/abci/version" | |||||
# Determine the arch/os combos we're building for | |||||
XC_ARCH=${XC_ARCH:-"386 amd64 arm"} | |||||
XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"} | |||||
# Make sure build tools are available. | |||||
make get_tools | |||||
# Get VENDORED dependencies | |||||
make get_vendor_deps | |||||
BINARY="abci-cli" | |||||
# Build! | |||||
echo "==> Building..." | |||||
"$(which gox)" \ | |||||
-os="${XC_OS}" \ | |||||
-arch="${XC_ARCH}" \ | |||||
-osarch="!darwin/arm !solaris/amd64 !freebsd/amd64" \ | |||||
-ldflags "-X ${GIT_IMPORT}.GitCommit='${GIT_COMMIT}' -X ${GIT_IMPORT}.GitDescribe='${GIT_DESCRIBE}'" \ | |||||
-output "build/pkg/{{.OS}}_{{.Arch}}/$BINARY" \ | |||||
-tags="${BUILD_TAGS}" \ | |||||
github.com/tendermint/abci/cmd/$BINARY | |||||
# Zip all the files. | |||||
echo "==> Packaging..." | |||||
for PLATFORM in $(find ./build/pkg -mindepth 1 -maxdepth 1 -type d); do | |||||
OSARCH=$(basename "${PLATFORM}") | |||||
echo "--> ${OSARCH}" | |||||
pushd "$PLATFORM" >/dev/null 2>&1 | |||||
zip "../${OSARCH}.zip" ./* | |||||
popd >/dev/null 2>&1 | |||||
done | |||||
exit 0 |
@ -0,0 +1,7 @@ | |||||
#! /bin/bash | |||||
# Get the version from the environment, or try to figure it out. | |||||
if [ -z $VERSION ]; then | |||||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) | |||||
fi | |||||
aws s3 cp --recursive build/dist s3://tendermint/binaries/abci/v${VERSION} --acl public-read |
@ -0,0 +1,57 @@ | |||||
package server | |||||
import ( | |||||
"net" | |||||
"google.golang.org/grpc" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
type GRPCServer struct { | |||||
cmn.BaseService | |||||
proto string | |||||
addr string | |||||
listener net.Listener | |||||
server *grpc.Server | |||||
app types.ABCIApplicationServer | |||||
} | |||||
// NewGRPCServer returns a new gRPC ABCI server | |||||
func NewGRPCServer(protoAddr string, app types.ABCIApplicationServer) cmn.Service { | |||||
proto, addr := cmn.ProtocolAndAddress(protoAddr) | |||||
s := &GRPCServer{ | |||||
proto: proto, | |||||
addr: addr, | |||||
listener: nil, | |||||
app: app, | |||||
} | |||||
s.BaseService = *cmn.NewBaseService(nil, "ABCIServer", s) | |||||
return s | |||||
} | |||||
// OnStart starts the gRPC service | |||||
func (s *GRPCServer) OnStart() error { | |||||
if err := s.BaseService.OnStart(); err != nil { | |||||
return err | |||||
} | |||||
ln, err := net.Listen(s.proto, s.addr) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
s.Logger.Info("Listening", "proto", s.proto, "addr", s.addr) | |||||
s.listener = ln | |||||
s.server = grpc.NewServer() | |||||
types.RegisterABCIApplicationServer(s.server, s.app) | |||||
go s.server.Serve(s.listener) | |||||
return nil | |||||
} | |||||
// OnStop stops the gRPC server | |||||
func (s *GRPCServer) OnStop() { | |||||
s.BaseService.OnStop() | |||||
s.server.Stop() | |||||
} |
@ -0,0 +1,31 @@ | |||||
/* | |||||
Package server is used to start a new ABCI server. | |||||
It contains two server implementation: | |||||
* gRPC server | |||||
* socket server | |||||
*/ | |||||
package server | |||||
import ( | |||||
"fmt" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func NewServer(protoAddr, transport string, app types.Application) (cmn.Service, error) { | |||||
var s cmn.Service | |||||
var err error | |||||
switch transport { | |||||
case "socket": | |||||
s = NewSocketServer(protoAddr, app) | |||||
case "grpc": | |||||
s = NewGRPCServer(protoAddr, types.NewGRPCApplication(app)) | |||||
default: | |||||
err = fmt.Errorf("Unknown server type %s", transport) | |||||
} | |||||
return s, err | |||||
} |
@ -0,0 +1,226 @@ | |||||
package server | |||||
import ( | |||||
"bufio" | |||||
"fmt" | |||||
"io" | |||||
"net" | |||||
"sync" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
// var maxNumberConnections = 2 | |||||
type SocketServer struct { | |||||
cmn.BaseService | |||||
proto string | |||||
addr string | |||||
listener net.Listener | |||||
connsMtx sync.Mutex | |||||
conns map[int]net.Conn | |||||
nextConnID int | |||||
appMtx sync.Mutex | |||||
app types.Application | |||||
} | |||||
func NewSocketServer(protoAddr string, app types.Application) cmn.Service { | |||||
proto, addr := cmn.ProtocolAndAddress(protoAddr) | |||||
s := &SocketServer{ | |||||
proto: proto, | |||||
addr: addr, | |||||
listener: nil, | |||||
app: app, | |||||
conns: make(map[int]net.Conn), | |||||
} | |||||
s.BaseService = *cmn.NewBaseService(nil, "ABCIServer", s) | |||||
return s | |||||
} | |||||
func (s *SocketServer) OnStart() error { | |||||
if err := s.BaseService.OnStart(); err != nil { | |||||
return err | |||||
} | |||||
ln, err := net.Listen(s.proto, s.addr) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
s.listener = ln | |||||
go s.acceptConnectionsRoutine() | |||||
return nil | |||||
} | |||||
func (s *SocketServer) OnStop() { | |||||
s.BaseService.OnStop() | |||||
if err := s.listener.Close(); err != nil { | |||||
s.Logger.Error("Error closing listener", "err", err) | |||||
} | |||||
s.connsMtx.Lock() | |||||
defer s.connsMtx.Unlock() | |||||
for id, conn := range s.conns { | |||||
delete(s.conns, id) | |||||
if err := conn.Close(); err != nil { | |||||
s.Logger.Error("Error closing connection", "id", id, "conn", conn, "err", err) | |||||
} | |||||
} | |||||
} | |||||
func (s *SocketServer) addConn(conn net.Conn) int { | |||||
s.connsMtx.Lock() | |||||
defer s.connsMtx.Unlock() | |||||
connID := s.nextConnID | |||||
s.nextConnID++ | |||||
s.conns[connID] = conn | |||||
return connID | |||||
} | |||||
// deletes conn even if close errs | |||||
func (s *SocketServer) rmConn(connID int) error { | |||||
s.connsMtx.Lock() | |||||
defer s.connsMtx.Unlock() | |||||
conn, ok := s.conns[connID] | |||||
if !ok { | |||||
return fmt.Errorf("Connection %d does not exist", connID) | |||||
} | |||||
delete(s.conns, connID) | |||||
return conn.Close() | |||||
} | |||||
func (s *SocketServer) acceptConnectionsRoutine() { | |||||
for { | |||||
// Accept a connection | |||||
s.Logger.Info("Waiting for new connection...") | |||||
conn, err := s.listener.Accept() | |||||
if err != nil { | |||||
if !s.IsRunning() { | |||||
return // Ignore error from listener closing. | |||||
} | |||||
s.Logger.Error("Failed to accept connection: " + err.Error()) | |||||
continue | |||||
} | |||||
s.Logger.Info("Accepted a new connection") | |||||
connID := s.addConn(conn) | |||||
closeConn := make(chan error, 2) // Push to signal connection closed | |||||
responses := make(chan *types.Response, 1000) // A channel to buffer responses | |||||
// Read requests from conn and deal with them | |||||
go s.handleRequests(closeConn, conn, responses) | |||||
// Pull responses from 'responses' and write them to conn. | |||||
go s.handleResponses(closeConn, conn, responses) | |||||
// Wait until signal to close connection | |||||
go s.waitForClose(closeConn, connID) | |||||
} | |||||
} | |||||
func (s *SocketServer) waitForClose(closeConn chan error, connID int) { | |||||
err := <-closeConn | |||||
if err == io.EOF { | |||||
s.Logger.Error("Connection was closed by client") | |||||
} else if err != nil { | |||||
s.Logger.Error("Connection error", "error", err) | |||||
} else { | |||||
// never happens | |||||
s.Logger.Error("Connection was closed.") | |||||
} | |||||
// Close the connection | |||||
if err := s.rmConn(connID); err != nil { | |||||
s.Logger.Error("Error in closing connection", "error", err) | |||||
} | |||||
} | |||||
// Read requests from conn and deal with them | |||||
func (s *SocketServer) handleRequests(closeConn chan error, conn net.Conn, responses chan<- *types.Response) { | |||||
var count int | |||||
var bufReader = bufio.NewReader(conn) | |||||
for { | |||||
var req = &types.Request{} | |||||
err := types.ReadMessage(bufReader, req) | |||||
if err != nil { | |||||
if err == io.EOF { | |||||
closeConn <- err | |||||
} else { | |||||
closeConn <- fmt.Errorf("Error reading message: %v", err.Error()) | |||||
} | |||||
return | |||||
} | |||||
s.appMtx.Lock() | |||||
count++ | |||||
s.handleRequest(req, responses) | |||||
s.appMtx.Unlock() | |||||
} | |||||
} | |||||
func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types.Response) { | |||||
switch r := req.Value.(type) { | |||||
case *types.Request_Echo: | |||||
responses <- types.ToResponseEcho(r.Echo.Message) | |||||
case *types.Request_Flush: | |||||
responses <- types.ToResponseFlush() | |||||
case *types.Request_Info: | |||||
res := s.app.Info(*r.Info) | |||||
responses <- types.ToResponseInfo(res) | |||||
case *types.Request_SetOption: | |||||
res := s.app.SetOption(*r.SetOption) | |||||
responses <- types.ToResponseSetOption(res) | |||||
case *types.Request_DeliverTx: | |||||
res := s.app.DeliverTx(r.DeliverTx.Tx) | |||||
responses <- types.ToResponseDeliverTx(res) | |||||
case *types.Request_CheckTx: | |||||
res := s.app.CheckTx(r.CheckTx.Tx) | |||||
responses <- types.ToResponseCheckTx(res) | |||||
case *types.Request_Commit: | |||||
res := s.app.Commit() | |||||
responses <- types.ToResponseCommit(res) | |||||
case *types.Request_Query: | |||||
res := s.app.Query(*r.Query) | |||||
responses <- types.ToResponseQuery(res) | |||||
case *types.Request_InitChain: | |||||
res := s.app.InitChain(*r.InitChain) | |||||
responses <- types.ToResponseInitChain(res) | |||||
case *types.Request_BeginBlock: | |||||
res := s.app.BeginBlock(*r.BeginBlock) | |||||
responses <- types.ToResponseBeginBlock(res) | |||||
case *types.Request_EndBlock: | |||||
res := s.app.EndBlock(*r.EndBlock) | |||||
responses <- types.ToResponseEndBlock(res) | |||||
default: | |||||
responses <- types.ToResponseException("Unknown request") | |||||
} | |||||
} | |||||
// Pull responses from 'responses' and write them to conn. | |||||
func (s *SocketServer) handleResponses(closeConn chan error, conn net.Conn, responses <-chan *types.Response) { | |||||
var count int | |||||
var bufWriter = bufio.NewWriter(conn) | |||||
for { | |||||
var res = <-responses | |||||
err := types.WriteMessage(res, bufWriter) | |||||
if err != nil { | |||||
closeConn <- fmt.Errorf("Error writing message: %v", err.Error()) | |||||
return | |||||
} | |||||
if _, ok := res.Value.(*types.Response_Flush); ok { | |||||
err = bufWriter.Flush() | |||||
if err != nil { | |||||
closeConn <- fmt.Errorf("Error flushing write buffer: %v", err.Error()) | |||||
return | |||||
} | |||||
} | |||||
count++ | |||||
} | |||||
} |
@ -0,0 +1,324 @@ | |||||
# ABCI Specification | |||||
## Message Types | |||||
ABCI requests/responses are defined as simple Protobuf messages in [this | |||||
schema file](https://github.com/tendermint/abci/blob/master/types/types.proto). | |||||
TendermintCore sends the requests, and the ABCI application sends the | |||||
responses. Here, we provide an overview of the messages types and how | |||||
they are used by Tendermint. Then we describe each request-response pair | |||||
as a function with arguments and return values, and add some notes on | |||||
usage. | |||||
Some messages (`Echo, Info, InitChain, BeginBlock, EndBlock, Commit`), | |||||
don't return errors because an error would indicate a critical failure | |||||
in the application and there's nothing Tendermint can do. The problem | |||||
should be addressed and both Tendermint and the application restarted. | |||||
All other messages (`SetOption, Query, CheckTx, DeliverTx`) return an | |||||
application-specific response `Code uint32`, where only `0` is reserved | |||||
for `OK`. | |||||
Some messages (`SetOption, Query, CheckTx, DeliverTx`) return | |||||
non-deterministic data in the form of `Info` and `Log`. The `Log` is | |||||
intended for the literal output from the application's logger, while the | |||||
`Info` is any additional info that should be returned. | |||||
The first time a new blockchain is started, Tendermint calls | |||||
`InitChain`. From then on, the Block Execution Sequence that causes the | |||||
committed state to be updated is as follows: | |||||
`BeginBlock, [DeliverTx], EndBlock, Commit` | |||||
where one `DeliverTx` is called for each transaction in the block. | |||||
Cryptographic commitments to the results of DeliverTx, EndBlock, and | |||||
Commit are included in the header of the next block. | |||||
Tendermint opens three connections to the application to handle the | |||||
different message types: | |||||
- `Consensus Connection - InitChain, BeginBlock, DeliverTx, EndBlock, Commit` | |||||
- `Mempool Connection - CheckTx` | |||||
- `Info Connection - Info, SetOption, Query` | |||||
The `Flush` message is used on every connection, and the `Echo` message | |||||
is only used for debugging. | |||||
Note that messages may be sent concurrently across all connections -a | |||||
typical application will thus maintain a distinct state for each | |||||
connection. They may be referred to as the `DeliverTx state`, the | |||||
`CheckTx state`, and the `Commit state` respectively. | |||||
See below for more details on the message types and how they are used. | |||||
## Request/Response Messages | |||||
### Echo | |||||
- **Request**: | |||||
- `Message (string)`: A string to echo back | |||||
- **Response**: | |||||
- `Message (string)`: The input string | |||||
- **Usage**: | |||||
- Echo a string to test an abci client/server implementation | |||||
### Flush | |||||
- **Usage**: | |||||
- Signals that messages queued on the client should be flushed to | |||||
the server. It is called periodically by the client | |||||
implementation to ensure asynchronous requests are actually | |||||
sent, and is called immediately to make a synchronous request, | |||||
which returns when the Flush response comes back. | |||||
### Info | |||||
- **Request**: | |||||
- `Version (string)`: The Tendermint version | |||||
- **Response**: | |||||
- `Data (string)`: Some arbitrary information | |||||
- `Version (Version)`: Version information | |||||
- `LastBlockHeight (int64)`: Latest block for which the app has | |||||
called Commit | |||||
- `LastBlockAppHash ([]byte)`: Latest result of Commit | |||||
- **Usage**: | |||||
- Return information about the application state. | |||||
- Used to sync Tendermint with the application during a handshake | |||||
that happens on startup. | |||||
- Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to | |||||
be updated during `Commit`, ensuring that `Commit` is never | |||||
called twice for the same block height. | |||||
### SetOption | |||||
- **Request**: | |||||
- `Key (string)`: Key to set | |||||
- `Value (string)`: Value to set for key | |||||
- **Response**: | |||||
- `Code (uint32)`: Response code | |||||
- `Log (string)`: The output of the application's logger. May | |||||
be non-deterministic. | |||||
- `Info (string)`: Additional information. May | |||||
be non-deterministic. | |||||
- **Usage**: | |||||
- Set non-consensus critical application specific options. | |||||
- e.g. Key="min-fee", Value="100fermion" could set the minimum fee | |||||
required for CheckTx (but not DeliverTx - that would be | |||||
consensus critical). | |||||
### InitChain | |||||
- **Request**: | |||||
- `Validators ([]Validator)`: Initial genesis validators | |||||
- `AppStateBytes ([]byte)`: Serialized initial application state | |||||
- **Response**: | |||||
- `ConsensusParams (ConsensusParams)`: Initial | |||||
consensus-critical parameters. | |||||
- `Validators ([]Validator)`: Initial validator set. | |||||
- **Usage**: | |||||
- Called once upon genesis. | |||||
### Query | |||||
- **Request**: | |||||
- `Data ([]byte)`: Raw query bytes. Can be used with or in lieu | |||||
of Path. | |||||
- `Path (string)`: Path of request, like an HTTP GET path. Can be | |||||
used with or in liue of Data. | |||||
- Apps MUST interpret '/store' as a query by key on the | |||||
underlying store. The key SHOULD be specified in the Data field. | |||||
- Apps SHOULD allow queries over specific types like | |||||
'/accounts/...' or '/votes/...' | |||||
- `Height (int64)`: The block height for which you want the query | |||||
(default=0 returns data for the latest committed block). Note | |||||
that this is the height of the block containing the | |||||
application's Merkle root hash, which represents the state as it | |||||
was after committing the block at Height-1 | |||||
- `Prove (bool)`: Return Merkle proof with response if possible | |||||
- **Response**: | |||||
- `Code (uint32)`: Response code. | |||||
- `Log (string)`: The output of the application's logger. May | |||||
be non-deterministic. | |||||
- `Info (string)`: Additional information. May | |||||
be non-deterministic. | |||||
- `Index (int64)`: The index of the key in the tree. | |||||
- `Key ([]byte)`: The key of the matching data. | |||||
- `Value ([]byte)`: The value of the matching data. | |||||
- `Proof ([]byte)`: Proof for the data, if requested. | |||||
- `Height (int64)`: The block height from which data was derived. | |||||
Note that this is the height of the block containing the | |||||
application's Merkle root hash, which represents the state as it | |||||
was after committing the block at Height-1 | |||||
- **Usage**: | |||||
- Query for data from the application at current or past height. | |||||
- Optionally return Merkle proof. | |||||
### BeginBlock | |||||
- **Request**: | |||||
- `Hash ([]byte)`: The block's hash. This can be derived from the | |||||
block header. | |||||
- `Header (struct{})`: The block header | |||||
- `Validators ([]SigningValidator)`: List of validators in the current validator | |||||
set and whether or not they signed a vote in the LastCommit | |||||
- `ByzantineValidators ([]Evidence)`: List of evidence of | |||||
validators that acted maliciously | |||||
- **Response**: | |||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing | |||||
- **Usage**: | |||||
- Signals the beginning of a new block. Called prior to | |||||
any DeliverTxs. | |||||
- The header is expected to at least contain the Height. | |||||
- The `Validators` and `ByzantineValidators` can be used to | |||||
determine rewards and punishments for the validators. | |||||
### CheckTx | |||||
- **Request**: | |||||
- `Tx ([]byte)`: The request transaction bytes | |||||
- **Response**: | |||||
- `Code (uint32)`: Response code | |||||
- `Data ([]byte)`: Result bytes, if any. | |||||
- `Log (string)`: The output of the application's logger. May | |||||
be non-deterministic. | |||||
- `Info (string)`: Additional information. May | |||||
be non-deterministic. | |||||
- `GasWanted (int64)`: Amount of gas request for transaction. | |||||
- `GasUsed (int64)`: Amount of gas consumed by transaction. | |||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing | |||||
transactions (eg. by account). | |||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction. | |||||
- **Usage**: Validate a mempool transaction, prior to broadcasting | |||||
or proposing. CheckTx should perform stateful but light-weight | |||||
checks of the validity of the transaction (like checking signatures | |||||
and account balances), but need not execute in full (like running a | |||||
smart contract). | |||||
Tendermint runs CheckTx and DeliverTx concurrently with eachother, | |||||
though on distinct ABCI connections - the mempool connection and the | |||||
consensus connection, respectively. | |||||
The application should maintain a separate state to support CheckTx. | |||||
This state can be reset to the latest committed state during | |||||
`Commit`, where Tendermint ensures the mempool is locked and not | |||||
sending new `CheckTx`. After `Commit`, the mempool will rerun | |||||
CheckTx on all remaining transactions, throwing out any that are no | |||||
longer valid. | |||||
Keys and values in Tags must be UTF-8 encoded strings (e.g. | |||||
"account.owner": "Bob", "balance": "100.0", "date": "2018-01-02") | |||||
### DeliverTx | |||||
- **Request**: | |||||
- `Tx ([]byte)`: The request transaction bytes. | |||||
- **Response**: | |||||
- `Code (uint32)`: Response code. | |||||
- `Data ([]byte)`: Result bytes, if any. | |||||
- `Log (string)`: The output of the application's logger. May | |||||
be non-deterministic. | |||||
- `Info (string)`: Additional information. May | |||||
be non-deterministic. | |||||
- `GasWanted (int64)`: Amount of gas requested for transaction. | |||||
- `GasUsed (int64)`: Amount of gas consumed by transaction. | |||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing | |||||
transactions (eg. by account). | |||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction. | |||||
- **Usage**: | |||||
- Deliver a transaction to be executed in full by the application. | |||||
If the transaction is valid, returns CodeType.OK. | |||||
- Keys and values in Tags must be UTF-8 encoded strings (e.g. | |||||
"account.owner": "Bob", "balance": "100.0", | |||||
"time": "2018-01-02T12:30:00Z") | |||||
### EndBlock | |||||
- **Request**: | |||||
- `Height (int64)`: Height of the block just executed. | |||||
- **Response**: | |||||
- `ValidatorUpdates ([]Validator)`: Changes to validator set (set | |||||
voting power to 0 to remove). | |||||
- `ConsensusParamUpdates (ConsensusParams)`: Changes to | |||||
consensus-critical time, size, and other parameters. | |||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing | |||||
- **Usage**: | |||||
- Signals the end of a block. | |||||
- Called prior to each Commit, after all transactions. | |||||
- Validator set and consensus params are updated with the result. | |||||
- Validator pubkeys are expected to be go-wire encoded. | |||||
### Commit | |||||
- **Response**: | |||||
- `Data ([]byte)`: The Merkle root hash | |||||
- **Usage**: | |||||
- Persist the application state. | |||||
- Return a Merkle root hash of the application state. | |||||
- It's critical that all application instances return the | |||||
same hash. If not, they will not be able to agree on the next | |||||
block, because the hash is included in the next block! | |||||
## Data Messages | |||||
### Header | |||||
- **Fields**: | |||||
- `ChainID (string)`: ID of the blockchain | |||||
- `Height (int64)`: Height of the block in the chain | |||||
- `Time (int64)`: Unix time of the block | |||||
- `NumTxs (int32)`: Number of transactions in the block | |||||
- `TotalTxs (int64)`: Total number of transactions in the blockchain until | |||||
now | |||||
- `LastBlockHash ([]byte)`: Hash of the previous (parent) block | |||||
- `ValidatorsHash ([]byte)`: Hash of the validator set for this block | |||||
- `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the | |||||
Merkle root of the application state after executing the previous block's | |||||
transactions | |||||
- `Proposer (Validator)`: Original proposer for the block | |||||
- **Usage**: | |||||
- Provided in RequestBeginBlock | |||||
- Provides important context about the current state of the blockchain - | |||||
especially height and time. | |||||
- Provides the proposer of the current block, for use in proposer-based | |||||
reward mechanisms. | |||||
### Validator | |||||
- **Fields**: | |||||
- `Address ([]byte)`: Address of the validator (hash of the public key) | |||||
- `PubKey (PubKey)`: Public key of the validator | |||||
- `Power (int64)`: Voting power of the validator | |||||
- **Usage**: | |||||
- Provides all identifying information about the validator | |||||
### SigningValidator | |||||
- **Fields**: | |||||
- `Validator (Validator)`: A validator | |||||
- `SignedLastBlock (bool)`: Indicated whether or not the validator signed | |||||
the last block | |||||
- **Usage**: | |||||
- Indicates whether a validator signed the last block, allowing for rewards | |||||
based on validator availability | |||||
### PubKey | |||||
- **Fields**: | |||||
- `Type (string)`: Type of the public key. A simple string like `"ed25519"`. | |||||
In the future, may indicate a serialization algorithm to parse the `Data`, | |||||
for instance `"amino"`. | |||||
- `Data ([]byte)`: Public key data. For a simple public key, it's just the | |||||
raw bytes. If the `Type` indicates an encoding algorithm, this is the | |||||
encoded public key. | |||||
- **Usage**: | |||||
- A generic and extensible typed public key | |||||
### Evidence | |||||
- **Fields**: | |||||
- `Type (string)`: Type of the evidence. A hierarchical path like | |||||
"duplicate/vote". | |||||
- `Validator (Validator`: The offending validator | |||||
- `Height (int64)`: Height when the offense was committed | |||||
- `Time (int64)`: Unix time of the block at height `Height` | |||||
- `TotalVotingPower (int64)`: Total voting power of the validator set at | |||||
height `Height` |
@ -0,0 +1,294 @@ | |||||
ABCI Specification | |||||
================== | |||||
NOTE: this file has moved to `specification.md <./specification.md>`__. It is left to prevent link breakages for the forseable future. It can safely be deleted in a few months. | |||||
Message Types | |||||
~~~~~~~~~~~~~ | |||||
ABCI requests/responses are defined as simple Protobuf messages in `this | |||||
schema | |||||
file <https://github.com/tendermint/abci/blob/master/types/types.proto>`__. | |||||
TendermintCore sends the requests, and the ABCI application sends the | |||||
responses. Here, we provide an overview of the messages types and how they | |||||
are used by Tendermint. Then we describe each request-response pair as a | |||||
function with arguments and return values, and add some notes on usage. | |||||
Some messages (``Echo, Info, InitChain, BeginBlock, EndBlock, Commit``), don't | |||||
return errors because an error would indicate a critical failure in the | |||||
application and there's nothing Tendermint can do. The problem should be | |||||
addressed and both Tendermint and the application restarted. All other | |||||
messages (``SetOption, Query, CheckTx, DeliverTx``) return an | |||||
application-specific response ``Code uint32``, where only ``0`` is reserved for | |||||
``OK``. | |||||
Some messages (``SetOption, Query, CheckTx, DeliverTx``) return | |||||
non-deterministic data in the form of ``Info`` and ``Log``. The ``Log`` is | |||||
intended for the literal output from the application's logger, while the | |||||
``Info`` is any additional info that should be returned. | |||||
The first time a new blockchain is started, Tendermint calls ``InitChain``. | |||||
From then on, the Block Execution Sequence that causes the committed state to | |||||
be updated is as follows: | |||||
``BeginBlock, [DeliverTx], EndBlock, Commit`` | |||||
where one ``DeliverTx`` is called for each transaction in the block. | |||||
Cryptographic commitments to the results of DeliverTx, EndBlock, and | |||||
Commit are included in the header of the next block. | |||||
Tendermint opens three connections to the application to handle the different message | |||||
types: | |||||
- ``Consensus Connection - InitChain, BeginBlock, DeliverTx, EndBlock, Commit`` | |||||
- ``Mempool Connection - CheckTx`` | |||||
- ``Info Connection - Info, SetOption, Query`` | |||||
The ``Flush`` message is used on every connection, and the ``Echo`` message | |||||
is only used for debugging. | |||||
Note that messages may be sent concurrently across all connections - | |||||
a typical application will thus maintain a distinct state for each | |||||
connection. They may be referred to as the ``DeliverTx state``, the | |||||
``CheckTx state``, and the ``Commit state`` respectively. | |||||
See below for more details on the message types and how they are used. | |||||
Echo | |||||
^^^^ | |||||
- **Arguments**: | |||||
- ``Message (string)``: A string to echo back | |||||
- **Returns**: | |||||
- ``Message (string)``: The input string | |||||
- **Usage**: | |||||
- Echo a string to test an abci client/server implementation | |||||
Flush | |||||
^^^^^ | |||||
- **Usage**: | |||||
- Signals that messages queued on the client should be flushed to | |||||
the server. It is called periodically by the client implementation | |||||
to ensure asynchronous requests are actually sent, and is called | |||||
immediately to make a synchronous request, which returns when the | |||||
Flush response comes back. | |||||
Info | |||||
^^^^ | |||||
- **Arguments**: | |||||
- ``Version (string)``: The Tendermint version | |||||
- **Returns**: | |||||
- ``Data (string)``: Some arbitrary information | |||||
- ``Version (Version)``: Version information | |||||
- ``LastBlockHeight (int64)``: Latest block for which the app has | |||||
called Commit | |||||
- ``LastBlockAppHash ([]byte)``: Latest result of Commit | |||||
- **Usage**: | |||||
- Return information about the application state. | |||||
- Used to sync Tendermint with the application during a handshake that | |||||
happens on startup. | |||||
- Tendermint expects ``LastBlockAppHash`` and ``LastBlockHeight`` to be | |||||
updated during ``Commit``, ensuring that ``Commit`` is never called twice | |||||
for the same block height. | |||||
SetOption | |||||
^^^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Key (string)``: Key to set | |||||
- ``Value (string)``: Value to set for key | |||||
- **Returns**: | |||||
- ``Code (uint32)``: Response code | |||||
- ``Log (string)``: The output of the application's logger. May be non-deterministic. | |||||
- ``Info (string)``: Additional information. May be non-deterministic. | |||||
- **Usage**: | |||||
- Set non-consensus critical application specific options. | |||||
- e.g. Key="min-fee", Value="100fermion" could set the minimum fee required for CheckTx | |||||
(but not DeliverTx - that would be consensus critical). | |||||
InitChain | |||||
^^^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Validators ([]Validator)``: Initial genesis validators | |||||
- ``AppStateBytes ([]byte)``: Serialized initial application state | |||||
- **Usage**: | |||||
- Called once upon genesis. | |||||
Query | |||||
^^^^^ | |||||
- **Arguments**: | |||||
- ``Data ([]byte)``: Raw query bytes. Can be used with or in lieu of | |||||
Path. | |||||
- ``Path (string)``: Path of request, like an HTTP GET path. Can be | |||||
used with or in liue of Data. | |||||
- Apps MUST interpret '/store' as a query by key on the underlying | |||||
store. The key SHOULD be specified in the Data field. | |||||
- Apps SHOULD allow queries over specific types like '/accounts/...' | |||||
or '/votes/...' | |||||
- ``Height (int64)``: The block height for which you want the query | |||||
(default=0 returns data for the latest committed block). Note that | |||||
this is the height of the block containing the application's | |||||
Merkle root hash, which represents the state as it was after | |||||
committing the block at Height-1 | |||||
- ``Prove (bool)``: Return Merkle proof with response if possible | |||||
- **Returns**: | |||||
- ``Code (uint32)``: Response code. | |||||
- ``Log (string)``: The output of the application's logger. May be non-deterministic. | |||||
- ``Info (string)``: Additional information. May be non-deterministic. | |||||
- ``Index (int64)``: The index of the key in the tree. | |||||
- ``Key ([]byte)``: The key of the matching data. | |||||
- ``Value ([]byte)``: The value of the matching data. | |||||
- ``Proof ([]byte)``: Proof for the data, if requested. | |||||
- ``Height (int64)``: The block height from which data was derived. | |||||
Note that this is the height of the block containing the | |||||
application's Merkle root hash, which represents the state as it | |||||
was after committing the block at Height-1 | |||||
- **Usage**: | |||||
- Query for data from the application at current or past height. | |||||
- Optionally return Merkle proof. | |||||
BeginBlock | |||||
^^^^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Hash ([]byte)``: The block's hash. This can be derived from the | |||||
block header. | |||||
- ``Header (struct{})``: The block header | |||||
- ``AbsentValidators ([]int32)``: List of indices of validators not | |||||
included in the LastCommit | |||||
- ``ByzantineValidators ([]Evidence)``: List of evidence of | |||||
validators that acted maliciously | |||||
- **Usage**: | |||||
- Signals the beginning of a new block. Called prior to any DeliverTxs. | |||||
- The header is expected to at least contain the Height. | |||||
- The ``AbsentValidators`` and ``ByzantineValidators`` can be used to | |||||
determine rewards and punishments for the validators. | |||||
CheckTx | |||||
^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Tx ([]byte)``: The request transaction bytes | |||||
- **Returns**: | |||||
- ``Code (uint32)``: Response code | |||||
- ``Data ([]byte)``: Result bytes, if any. | |||||
- ``Log (string)``: The output of the application's logger. May be non-deterministic. | |||||
- ``Info (string)``: Additional information. May be non-deterministic. | |||||
- ``GasWanted (int64)``: Amount of gas request for transaction. | |||||
- ``GasUsed (int64)``: Amount of gas consumed by transaction. | |||||
- ``Tags ([]cmn.KVPair)``: Key-Value tags for filtering and indexing transactions (eg. by account). | |||||
- ``Fee (cmn.KI64Pair)``: Fee paid for the transaction. | |||||
- **Usage**: Validate a mempool transaction, prior to broadcasting or | |||||
proposing. CheckTx should perform stateful but light-weight checks | |||||
of the validity of the transaction (like checking signatures and account balances), | |||||
but need not execute in full (like running a smart contract). | |||||
Tendermint runs CheckTx and DeliverTx concurrently with eachother, | |||||
though on distinct ABCI connections - the mempool connection and the consensus | |||||
connection, respectively. | |||||
The application should maintain a separate state to support CheckTx. | |||||
This state can be reset to the latest committed state during ``Commit``, | |||||
where Tendermint ensures the mempool is locked and not sending new ``CheckTx``. | |||||
After ``Commit``, the mempool will rerun CheckTx on all remaining | |||||
transactions, throwing out any that are no longer valid. | |||||
Keys and values in Tags must be UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance": "100.0", "date": "2018-01-02") | |||||
DeliverTx | |||||
^^^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Tx ([]byte)``: The request transaction bytes. | |||||
- **Returns**: | |||||
- ``Code (uint32)``: Response code. | |||||
- ``Data ([]byte)``: Result bytes, if any. | |||||
- ``Log (string)``: The output of the application's logger. May be non-deterministic. | |||||
- ``Info (string)``: Additional information. May be non-deterministic. | |||||
- ``GasWanted (int64)``: Amount of gas requested for transaction. | |||||
- ``GasUsed (int64)``: Amount of gas consumed by transaction. | |||||
- ``Tags ([]cmn.KVPair)``: Key-Value tags for filtering and indexing transactions (eg. by account). | |||||
- ``Fee (cmn.KI64Pair)``: Fee paid for the transaction. | |||||
- **Usage**: | |||||
- Deliver a transaction to be executed in full by the application. If the transaction is valid, | |||||
returns CodeType.OK. | |||||
- Keys and values in Tags must be UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance": "100.0", "time": "2018-01-02T12:30:00Z") | |||||
EndBlock | |||||
^^^^^^^^ | |||||
- **Arguments**: | |||||
- ``Height (int64)``: Height of the block just executed. | |||||
- **Returns**: | |||||
- ``ValidatorUpdates ([]Validator)``: Changes to validator set (set | |||||
voting power to 0 to remove). | |||||
- ``ConsensusParamUpdates (ConsensusParams)``: Changes to | |||||
consensus-critical time, size, and other parameters. | |||||
- **Usage**: | |||||
- Signals the end of a block. | |||||
- Called prior to each Commit, after all transactions. | |||||
- Validator set and consensus params are updated with the result. | |||||
- Validator pubkeys are expected to be go-wire encoded. | |||||
Commit | |||||
^^^^^^ | |||||
- **Returns**: | |||||
- ``Data ([]byte)``: The Merkle root hash | |||||
- **Usage**: | |||||
- Persist the application state. | |||||
- Return a Merkle root hash of the application state. | |||||
- It's critical that all application instances return the same hash. If not, | |||||
they will not be able to agree on the next block, because the hash is | |||||
included in the next block! |
@ -0,0 +1 @@ | |||||
package benchmarks |
@ -0,0 +1,55 @@ | |||||
package main | |||||
import ( | |||||
"bufio" | |||||
"fmt" | |||||
"log" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func main() { | |||||
conn, err := cmn.Connect("unix://test.sock") | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
// Read a bunch of responses | |||||
go func() { | |||||
counter := 0 | |||||
for { | |||||
var res = &types.Response{} | |||||
err := types.ReadMessage(conn, res) | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
counter++ | |||||
if counter%1000 == 0 { | |||||
fmt.Println("Read", counter) | |||||
} | |||||
} | |||||
}() | |||||
// Write a bunch of requests | |||||
counter := 0 | |||||
for i := 0; ; i++ { | |||||
var bufWriter = bufio.NewWriter(conn) | |||||
var req = types.ToRequestEcho("foobar") | |||||
err := types.WriteMessage(req, bufWriter) | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
err = bufWriter.Flush() | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
counter++ | |||||
if counter%1000 == 0 { | |||||
fmt.Println("Write", counter) | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,69 @@ | |||||
package main | |||||
import ( | |||||
"bufio" | |||||
"fmt" | |||||
"log" | |||||
"net" | |||||
"reflect" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func main() { | |||||
conn, err := cmn.Connect("unix://test.sock") | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
// Make a bunch of requests | |||||
counter := 0 | |||||
for i := 0; ; i++ { | |||||
req := types.ToRequestEcho("foobar") | |||||
_, err := makeRequest(conn, req) | |||||
if err != nil { | |||||
log.Fatal(err.Error()) | |||||
} | |||||
counter++ | |||||
if counter%1000 == 0 { | |||||
fmt.Println(counter) | |||||
} | |||||
} | |||||
} | |||||
func makeRequest(conn net.Conn, req *types.Request) (*types.Response, error) { | |||||
var bufWriter = bufio.NewWriter(conn) | |||||
// Write desired request | |||||
err := types.WriteMessage(req, bufWriter) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
err = types.WriteMessage(types.ToRequestFlush(), bufWriter) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
err = bufWriter.Flush() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
// Read desired response | |||||
var res = &types.Response{} | |||||
err = types.ReadMessage(conn, res) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
var resFlush = &types.Response{} | |||||
err = types.ReadMessage(conn, resFlush) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if _, ok := resFlush.Value.(*types.Response_Flush); !ok { | |||||
return nil, fmt.Errorf("Expected flush response but got something else: %v", reflect.TypeOf(resFlush)) | |||||
} | |||||
return res, nil | |||||
} |
@ -0,0 +1,27 @@ | |||||
package tests | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
abciclient "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/example/kvstore" | |||||
abciserver "github.com/tendermint/abci/server" | |||||
) | |||||
func TestClientServerNoAddrPrefix(t *testing.T) { | |||||
addr := "localhost:26658" | |||||
transport := "socket" | |||||
app := kvstore.NewKVStoreApplication() | |||||
server, err := abciserver.NewServer(addr, transport, app) | |||||
assert.NoError(t, err, "expected no error on NewServer") | |||||
err = server.Start() | |||||
assert.NoError(t, err, "expected no error on server.Start") | |||||
client, err := abciclient.NewClient(addr, transport, true) | |||||
assert.NoError(t, err, "expected no error on NewClient") | |||||
err = client.Start() | |||||
assert.NoError(t, err, "expected no error on client.Start") | |||||
} |
@ -0,0 +1,96 @@ | |||||
package testsuite | |||||
import ( | |||||
"bytes" | |||||
"errors" | |||||
"fmt" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/types" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func InitChain(client abcicli.Client) error { | |||||
total := 10 | |||||
vals := make([]types.Validator, total) | |||||
for i := 0; i < total; i++ { | |||||
pubkey := cmn.RandBytes(33) | |||||
power := cmn.RandInt() | |||||
vals[i] = types.Ed25519Validator(pubkey, int64(power)) | |||||
} | |||||
_, err := client.InitChainSync(types.RequestInitChain{ | |||||
Validators: vals, | |||||
}) | |||||
if err != nil { | |||||
fmt.Printf("Failed test: InitChain - %v\n", err) | |||||
return err | |||||
} | |||||
fmt.Println("Passed test: InitChain") | |||||
return nil | |||||
} | |||||
func SetOption(client abcicli.Client, key, value string) error { | |||||
_, err := client.SetOptionSync(types.RequestSetOption{Key: key, Value: value}) | |||||
if err != nil { | |||||
fmt.Println("Failed test: SetOption") | |||||
fmt.Printf("error while setting %v=%v: \nerror: %v\n", key, value, err) | |||||
return err | |||||
} | |||||
fmt.Println("Passed test: SetOption") | |||||
return nil | |||||
} | |||||
func Commit(client abcicli.Client, hashExp []byte) error { | |||||
res, err := client.CommitSync() | |||||
data := res.Data | |||||
if err != nil { | |||||
fmt.Println("Failed test: Commit") | |||||
fmt.Printf("error while committing: %v\n", err) | |||||
return err | |||||
} | |||||
if !bytes.Equal(data, hashExp) { | |||||
fmt.Println("Failed test: Commit") | |||||
fmt.Printf("Commit hash was unexpected. Got %X expected %X\n", data, hashExp) | |||||
return errors.New("CommitTx failed") | |||||
} | |||||
fmt.Println("Passed test: Commit") | |||||
return nil | |||||
} | |||||
func DeliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { | |||||
res, _ := client.DeliverTxSync(txBytes) | |||||
code, data, log := res.Code, res.Data, res.Log | |||||
if code != codeExp { | |||||
fmt.Println("Failed test: DeliverTx") | |||||
fmt.Printf("DeliverTx response code was unexpected. Got %v expected %v. Log: %v\n", | |||||
code, codeExp, log) | |||||
return errors.New("DeliverTx error") | |||||
} | |||||
if !bytes.Equal(data, dataExp) { | |||||
fmt.Println("Failed test: DeliverTx") | |||||
fmt.Printf("DeliverTx response data was unexpected. Got %X expected %X\n", | |||||
data, dataExp) | |||||
return errors.New("DeliverTx error") | |||||
} | |||||
fmt.Println("Passed test: DeliverTx") | |||||
return nil | |||||
} | |||||
func CheckTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { | |||||
res, _ := client.CheckTxSync(txBytes) | |||||
code, data, log := res.Code, res.Data, res.Log | |||||
if code != codeExp { | |||||
fmt.Println("Failed test: CheckTx") | |||||
fmt.Printf("CheckTx response code was unexpected. Got %v expected %v. Log: %v\n", | |||||
code, codeExp, log) | |||||
return errors.New("CheckTx") | |||||
} | |||||
if !bytes.Equal(data, dataExp) { | |||||
fmt.Println("Failed test: CheckTx") | |||||
fmt.Printf("CheckTx response data was unexpected. Got %X expected %X\n", | |||||
data, dataExp) | |||||
return errors.New("CheckTx") | |||||
} | |||||
fmt.Println("Passed test: CheckTx") | |||||
return nil | |||||
} |
@ -0,0 +1,78 @@ | |||||
package main | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"os" | |||||
abcicli "github.com/tendermint/abci/client" | |||||
"github.com/tendermint/abci/types" | |||||
"github.com/tendermint/tmlibs/log" | |||||
) | |||||
func startClient(abciType string) abcicli.Client { | |||||
// Start client | |||||
client, err := abcicli.NewClient("tcp://127.0.0.1:26658", abciType, true) | |||||
if err != nil { | |||||
panic(err.Error()) | |||||
} | |||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||||
client.SetLogger(logger.With("module", "abcicli")) | |||||
if err := client.Start(); err != nil { | |||||
panicf("connecting to abci_app: %v", err.Error()) | |||||
} | |||||
return client | |||||
} | |||||
func setOption(client abcicli.Client, key, value string) { | |||||
_, err := client.SetOptionSync(types.RequestSetOption{key, value}) | |||||
if err != nil { | |||||
panicf("setting %v=%v: \nerr: %v", key, value, err) | |||||
} | |||||
} | |||||
func commit(client abcicli.Client, hashExp []byte) { | |||||
res, err := client.CommitSync() | |||||
if err != nil { | |||||
panicf("client error: %v", err) | |||||
} | |||||
if !bytes.Equal(res.Data, hashExp) { | |||||
panicf("Commit hash was unexpected. Got %X expected %X", res.Data, hashExp) | |||||
} | |||||
} | |||||
func deliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) { | |||||
res, err := client.DeliverTxSync(txBytes) | |||||
if err != nil { | |||||
panicf("client error: %v", err) | |||||
} | |||||
if res.Code != codeExp { | |||||
panicf("DeliverTx response code was unexpected. Got %v expected %v. Log: %v", res.Code, codeExp, res.Log) | |||||
} | |||||
if !bytes.Equal(res.Data, dataExp) { | |||||
panicf("DeliverTx response data was unexpected. Got %X expected %X", res.Data, dataExp) | |||||
} | |||||
} | |||||
/*func checkTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) { | |||||
res, err := client.CheckTxSync(txBytes) | |||||
if err != nil { | |||||
panicf("client error: %v", err) | |||||
} | |||||
if res.IsErr() { | |||||
panicf("checking tx %X: %v\nlog: %v", txBytes, res.Log) | |||||
} | |||||
if res.Code != codeExp { | |||||
panicf("CheckTx response code was unexpected. Got %v expected %v. Log: %v", | |||||
res.Code, codeExp, res.Log) | |||||
} | |||||
if !bytes.Equal(res.Data, dataExp) { | |||||
panicf("CheckTx response data was unexpected. Got %X expected %X", | |||||
res.Data, dataExp) | |||||
} | |||||
}*/ | |||||
func panicf(format string, a ...interface{}) { | |||||
panic(fmt.Sprintf(format, a...)) | |||||
} |
@ -0,0 +1,84 @@ | |||||
package main | |||||
import ( | |||||
"fmt" | |||||
"log" | |||||
"os" | |||||
"os/exec" | |||||
"time" | |||||
"github.com/tendermint/abci/example/code" | |||||
"github.com/tendermint/abci/types" | |||||
) | |||||
var abciType string | |||||
func init() { | |||||
abciType = os.Getenv("ABCI") | |||||
if abciType == "" { | |||||
abciType = "socket" | |||||
} | |||||
} | |||||
func main() { | |||||
testCounter() | |||||
} | |||||
const ( | |||||
maxABCIConnectTries = 10 | |||||
) | |||||
func ensureABCIIsUp(typ string, n int) error { | |||||
var err error | |||||
cmdString := "abci-cli echo hello" | |||||
if typ == "grpc" { | |||||
cmdString = "abci-cli --abci grpc echo hello" | |||||
} | |||||
for i := 0; i < n; i++ { | |||||
cmd := exec.Command("bash", "-c", cmdString) // nolint: gas | |||||
_, err = cmd.CombinedOutput() | |||||
if err == nil { | |||||
break | |||||
} | |||||
<-time.After(500 * time.Millisecond) | |||||
} | |||||
return err | |||||
} | |||||
func testCounter() { | |||||
abciApp := os.Getenv("ABCI_APP") | |||||
if abciApp == "" { | |||||
panic("No ABCI_APP specified") | |||||
} | |||||
fmt.Printf("Running %s test with abci=%s\n", abciApp, abciType) | |||||
cmd := exec.Command("bash", "-c", fmt.Sprintf("abci-cli %s", abciApp)) // nolint: gas | |||||
cmd.Stdout = os.Stdout | |||||
if err := cmd.Start(); err != nil { | |||||
log.Fatalf("starting %q err: %v", abciApp, err) | |||||
} | |||||
defer cmd.Wait() | |||||
defer cmd.Process.Kill() | |||||
if err := ensureABCIIsUp(abciType, maxABCIConnectTries); err != nil { | |||||
log.Fatalf("echo failed: %v", err) | |||||
} | |||||
client := startClient(abciType) | |||||
defer client.Stop() | |||||
setOption(client, "serial", "on") | |||||
commit(client, nil) | |||||
deliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil) | |||||
commit(client, nil) | |||||
deliverTx(client, []byte{0x00}, types.CodeTypeOK, nil) | |||||
commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) | |||||
deliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil) | |||||
deliverTx(client, []byte{0x01}, types.CodeTypeOK, nil) | |||||
deliverTx(client, []byte{0x00, 0x02}, types.CodeTypeOK, nil) | |||||
deliverTx(client, []byte{0x00, 0x03}, types.CodeTypeOK, nil) | |||||
deliverTx(client, []byte{0x00, 0x00, 0x04}, types.CodeTypeOK, nil) | |||||
deliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil) | |||||
commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) | |||||
} |
@ -0,0 +1,27 @@ | |||||
#! /bin/bash | |||||
set -e | |||||
# These tests spawn the counter app and server by execing the ABCI_APP command and run some simple client tests against it | |||||
# Get the directory of where this script is. | |||||
SOURCE="${BASH_SOURCE[0]}" | |||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done | |||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" | |||||
# Change into that dir because we expect that. | |||||
cd "$DIR" | |||||
echo "RUN COUNTER OVER SOCKET" | |||||
# test golang counter | |||||
ABCI_APP="counter" go run ./*.go | |||||
echo "----------------------" | |||||
echo "RUN COUNTER OVER GRPC" | |||||
# test golang counter via grpc | |||||
ABCI_APP="counter --abci=grpc" ABCI="grpc" go run ./*.go | |||||
echo "----------------------" | |||||
# test nodejs counter | |||||
# TODO: fix node app | |||||
#ABCI_APP="node $GOPATH/src/github.com/tendermint/js-abci/example/app.js" go test -test.run TestCounter |
@ -0,0 +1,10 @@ | |||||
echo hello | |||||
info | |||||
commit | |||||
deliver_tx "abc" | |||||
info | |||||
commit | |||||
query "abc" | |||||
deliver_tx "def=xyz" | |||||
commit | |||||
query "def" |
@ -0,0 +1,47 @@ | |||||
> echo hello | |||||
-> code: OK | |||||
-> data: hello | |||||
-> data.hex: 0x68656C6C6F | |||||
> info | |||||
-> code: OK | |||||
-> data: {"size":0} | |||||
-> data.hex: 0x7B2273697A65223A307D | |||||
> commit | |||||
-> code: OK | |||||
-> data.hex: 0x0000000000000000 | |||||
> deliver_tx "abc" | |||||
-> code: OK | |||||
> info | |||||
-> code: OK | |||||
-> data: {"size":1} | |||||
-> data.hex: 0x7B2273697A65223A317D | |||||
> commit | |||||
-> code: OK | |||||
-> data.hex: 0x0200000000000000 | |||||
> query "abc" | |||||
-> code: OK | |||||
-> log: exists | |||||
-> height: 0 | |||||
-> value: abc | |||||
-> value.hex: 616263 | |||||
> deliver_tx "def=xyz" | |||||
-> code: OK | |||||
> commit | |||||
-> code: OK | |||||
-> data.hex: 0x0400000000000000 | |||||
> query "def" | |||||
-> code: OK | |||||
-> log: exists | |||||
-> height: 0 | |||||
-> value: xyz | |||||
-> value.hex: 78797A | |||||
@ -0,0 +1,8 @@ | |||||
set_option serial on | |||||
check_tx 0x00 | |||||
check_tx 0xff | |||||
deliver_tx 0x00 | |||||
check_tx 0x00 | |||||
deliver_tx 0x01 | |||||
deliver_tx 0x04 | |||||
info |
@ -0,0 +1,29 @@ | |||||
> set_option serial on | |||||
-> code: OK | |||||
-> log: OK (SetOption doesn't return anything.) | |||||
> check_tx 0x00 | |||||
-> code: OK | |||||
> check_tx 0xff | |||||
-> code: OK | |||||
> deliver_tx 0x00 | |||||
-> code: OK | |||||
> check_tx 0x00 | |||||
-> code: 2 | |||||
-> log: Invalid nonce. Expected >= 1, got 0 | |||||
> deliver_tx 0x01 | |||||
-> code: OK | |||||
> deliver_tx 0x04 | |||||
-> code: 2 | |||||
-> log: Invalid nonce. Expected 2, got 4 | |||||
> info | |||||
-> code: OK | |||||
-> data: {"hashes":0,"txs":2} | |||||
-> data.hex: 0x7B22686173686573223A302C22747873223A327D | |||||
@ -0,0 +1,42 @@ | |||||
#! /bin/bash | |||||
set -e | |||||
# Get the root directory. | |||||
SOURCE="${BASH_SOURCE[0]}" | |||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done | |||||
DIR="$( cd -P "$( dirname "$SOURCE" )/../.." && pwd )" | |||||
# Change into that dir because we expect that. | |||||
cd "$DIR" || exit | |||||
function testExample() { | |||||
N=$1 | |||||
INPUT=$2 | |||||
APP="$3 $4" | |||||
echo "Example $N: $APP" | |||||
$APP &> /dev/null & | |||||
sleep 2 | |||||
abci-cli --log_level=error --verbose batch < "$INPUT" > "${INPUT}.out.new" | |||||
killall "$3" | |||||
pre=$(shasum < "${INPUT}.out") | |||||
post=$(shasum < "${INPUT}.out.new") | |||||
if [[ "$pre" != "$post" ]]; then | |||||
echo "You broke the tutorial" | |||||
echo "Got:" | |||||
cat "${INPUT}.out.new" | |||||
echo "Expected:" | |||||
cat "${INPUT}.out" | |||||
exit 1 | |||||
fi | |||||
rm "${INPUT}".out.new | |||||
} | |||||
testExample 1 tests/test_cli/ex1.abci abci-cli kvstore | |||||
testExample 2 tests/test_cli/ex2.abci abci-cli counter | |||||
echo "" | |||||
echo "PASS" |
@ -0,0 +1,13 @@ | |||||
#!/usr/bin/env bash | |||||
set -e | |||||
echo "" > coverage.txt | |||||
echo "==> Running unit tests" | |||||
for d in $(go list ./... | grep -v vendor); do | |||||
go test -race -coverprofile=profile.out -covermode=atomic "$d" | |||||
if [ -f profile.out ]; then | |||||
cat profile.out >> coverage.txt | |||||
rm profile.out | |||||
fi | |||||
done |
@ -0,0 +1 @@ | |||||
package tests |
@ -0,0 +1,138 @@ | |||||
package types // nolint: goimports | |||||
import ( | |||||
context "golang.org/x/net/context" | |||||
) | |||||
// Application is an interface that enables any finite, deterministic state machine | |||||
// to be driven by a blockchain-based replication engine via the ABCI. | |||||
// All methods take a RequestXxx argument and return a ResponseXxx argument, | |||||
// except CheckTx/DeliverTx, which take `tx []byte`, and `Commit`, which takes nothing. | |||||
type Application interface { | |||||
// Info/Query Connection | |||||
Info(RequestInfo) ResponseInfo // Return application info | |||||
SetOption(RequestSetOption) ResponseSetOption // Set application option | |||||
Query(RequestQuery) ResponseQuery // Query for state | |||||
// Mempool Connection | |||||
CheckTx(tx []byte) ResponseCheckTx // Validate a tx for the mempool | |||||
// Consensus Connection | |||||
InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain with validators and other info from TendermintCore | |||||
BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block | |||||
DeliverTx(tx []byte) ResponseDeliverTx // Deliver a tx for full processing | |||||
EndBlock(RequestEndBlock) ResponseEndBlock // Signals the end of a block, returns changes to the validator set | |||||
Commit() ResponseCommit // Commit the state and return the application Merkle root hash | |||||
} | |||||
//------------------------------------------------------- | |||||
// BaseApplication is a base form of Application | |||||
var _ Application = (*BaseApplication)(nil) | |||||
type BaseApplication struct { | |||||
} | |||||
func NewBaseApplication() *BaseApplication { | |||||
return &BaseApplication{} | |||||
} | |||||
func (BaseApplication) Info(req RequestInfo) ResponseInfo { | |||||
return ResponseInfo{} | |||||
} | |||||
func (BaseApplication) SetOption(req RequestSetOption) ResponseSetOption { | |||||
return ResponseSetOption{} | |||||
} | |||||
func (BaseApplication) DeliverTx(tx []byte) ResponseDeliverTx { | |||||
return ResponseDeliverTx{Code: CodeTypeOK} | |||||
} | |||||
func (BaseApplication) CheckTx(tx []byte) ResponseCheckTx { | |||||
return ResponseCheckTx{Code: CodeTypeOK} | |||||
} | |||||
func (BaseApplication) Commit() ResponseCommit { | |||||
return ResponseCommit{} | |||||
} | |||||
func (BaseApplication) Query(req RequestQuery) ResponseQuery { | |||||
return ResponseQuery{Code: CodeTypeOK} | |||||
} | |||||
func (BaseApplication) InitChain(req RequestInitChain) ResponseInitChain { | |||||
return ResponseInitChain{} | |||||
} | |||||
func (BaseApplication) BeginBlock(req RequestBeginBlock) ResponseBeginBlock { | |||||
return ResponseBeginBlock{} | |||||
} | |||||
func (BaseApplication) EndBlock(req RequestEndBlock) ResponseEndBlock { | |||||
return ResponseEndBlock{} | |||||
} | |||||
//------------------------------------------------------- | |||||
// GRPCApplication is a GRPC wrapper for Application | |||||
type GRPCApplication struct { | |||||
app Application | |||||
} | |||||
func NewGRPCApplication(app Application) *GRPCApplication { | |||||
return &GRPCApplication{app} | |||||
} | |||||
func (app *GRPCApplication) Echo(ctx context.Context, req *RequestEcho) (*ResponseEcho, error) { | |||||
return &ResponseEcho{req.Message}, nil | |||||
} | |||||
func (app *GRPCApplication) Flush(ctx context.Context, req *RequestFlush) (*ResponseFlush, error) { | |||||
return &ResponseFlush{}, nil | |||||
} | |||||
func (app *GRPCApplication) Info(ctx context.Context, req *RequestInfo) (*ResponseInfo, error) { | |||||
res := app.app.Info(*req) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) SetOption(ctx context.Context, req *RequestSetOption) (*ResponseSetOption, error) { | |||||
res := app.app.SetOption(*req) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) DeliverTx(ctx context.Context, req *RequestDeliverTx) (*ResponseDeliverTx, error) { | |||||
res := app.app.DeliverTx(req.Tx) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { | |||||
res := app.app.CheckTx(req.Tx) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) Query(ctx context.Context, req *RequestQuery) (*ResponseQuery, error) { | |||||
res := app.app.Query(*req) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) Commit(ctx context.Context, req *RequestCommit) (*ResponseCommit, error) { | |||||
res := app.app.Commit() | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) InitChain(ctx context.Context, req *RequestInitChain) (*ResponseInitChain, error) { | |||||
res := app.app.InitChain(*req) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) BeginBlock(ctx context.Context, req *RequestBeginBlock) (*ResponseBeginBlock, error) { | |||||
res := app.app.BeginBlock(*req) | |||||
return &res, nil | |||||
} | |||||
func (app *GRPCApplication) EndBlock(ctx context.Context, req *RequestEndBlock) (*ResponseEndBlock, error) { | |||||
res := app.app.EndBlock(*req) | |||||
return &res, nil | |||||
} |
@ -0,0 +1,210 @@ | |||||
package types | |||||
import ( | |||||
"bufio" | |||||
"encoding/binary" | |||||
"io" | |||||
"github.com/gogo/protobuf/proto" | |||||
) | |||||
const ( | |||||
maxMsgSize = 104857600 // 100MB | |||||
) | |||||
// WriteMessage writes a varint length-delimited protobuf message. | |||||
func WriteMessage(msg proto.Message, w io.Writer) error { | |||||
bz, err := proto.Marshal(msg) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return encodeByteSlice(w, bz) | |||||
} | |||||
// ReadMessage reads a varint length-delimited protobuf message. | |||||
func ReadMessage(r io.Reader, msg proto.Message) error { | |||||
return readProtoMsg(r, msg, maxMsgSize) | |||||
} | |||||
func readProtoMsg(r io.Reader, msg proto.Message, maxSize int) error { | |||||
// binary.ReadVarint takes an io.ByteReader, eg. a bufio.Reader | |||||
reader, ok := r.(*bufio.Reader) | |||||
if !ok { | |||||
reader = bufio.NewReader(r) | |||||
} | |||||
length64, err := binary.ReadVarint(reader) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
length := int(length64) | |||||
if length < 0 || length > maxSize { | |||||
return io.ErrShortBuffer | |||||
} | |||||
buf := make([]byte, length) | |||||
if _, err := io.ReadFull(reader, buf); err != nil { | |||||
return err | |||||
} | |||||
return proto.Unmarshal(buf, msg) | |||||
} | |||||
//----------------------------------------------------------------------- | |||||
// NOTE: we copied wire.EncodeByteSlice from go-wire rather than keep | |||||
// go-wire as a dep | |||||
func encodeByteSlice(w io.Writer, bz []byte) (err error) { | |||||
err = encodeVarint(w, int64(len(bz))) | |||||
if err != nil { | |||||
return | |||||
} | |||||
_, err = w.Write(bz) | |||||
return | |||||
} | |||||
func encodeVarint(w io.Writer, i int64) (err error) { | |||||
var buf [10]byte | |||||
n := binary.PutVarint(buf[:], i) | |||||
_, err = w.Write(buf[0:n]) | |||||
return | |||||
} | |||||
//---------------------------------------- | |||||
func ToRequestEcho(message string) *Request { | |||||
return &Request{ | |||||
Value: &Request_Echo{&RequestEcho{message}}, | |||||
} | |||||
} | |||||
func ToRequestFlush() *Request { | |||||
return &Request{ | |||||
Value: &Request_Flush{&RequestFlush{}}, | |||||
} | |||||
} | |||||
func ToRequestInfo(req RequestInfo) *Request { | |||||
return &Request{ | |||||
Value: &Request_Info{&req}, | |||||
} | |||||
} | |||||
func ToRequestSetOption(req RequestSetOption) *Request { | |||||
return &Request{ | |||||
Value: &Request_SetOption{&req}, | |||||
} | |||||
} | |||||
func ToRequestDeliverTx(tx []byte) *Request { | |||||
return &Request{ | |||||
Value: &Request_DeliverTx{&RequestDeliverTx{tx}}, | |||||
} | |||||
} | |||||
func ToRequestCheckTx(tx []byte) *Request { | |||||
return &Request{ | |||||
Value: &Request_CheckTx{&RequestCheckTx{tx}}, | |||||
} | |||||
} | |||||
func ToRequestCommit() *Request { | |||||
return &Request{ | |||||
Value: &Request_Commit{&RequestCommit{}}, | |||||
} | |||||
} | |||||
func ToRequestQuery(req RequestQuery) *Request { | |||||
return &Request{ | |||||
Value: &Request_Query{&req}, | |||||
} | |||||
} | |||||
func ToRequestInitChain(req RequestInitChain) *Request { | |||||
return &Request{ | |||||
Value: &Request_InitChain{&req}, | |||||
} | |||||
} | |||||
func ToRequestBeginBlock(req RequestBeginBlock) *Request { | |||||
return &Request{ | |||||
Value: &Request_BeginBlock{&req}, | |||||
} | |||||
} | |||||
func ToRequestEndBlock(req RequestEndBlock) *Request { | |||||
return &Request{ | |||||
Value: &Request_EndBlock{&req}, | |||||
} | |||||
} | |||||
//---------------------------------------- | |||||
func ToResponseException(errStr string) *Response { | |||||
return &Response{ | |||||
Value: &Response_Exception{&ResponseException{errStr}}, | |||||
} | |||||
} | |||||
func ToResponseEcho(message string) *Response { | |||||
return &Response{ | |||||
Value: &Response_Echo{&ResponseEcho{message}}, | |||||
} | |||||
} | |||||
func ToResponseFlush() *Response { | |||||
return &Response{ | |||||
Value: &Response_Flush{&ResponseFlush{}}, | |||||
} | |||||
} | |||||
func ToResponseInfo(res ResponseInfo) *Response { | |||||
return &Response{ | |||||
Value: &Response_Info{&res}, | |||||
} | |||||
} | |||||
func ToResponseSetOption(res ResponseSetOption) *Response { | |||||
return &Response{ | |||||
Value: &Response_SetOption{&res}, | |||||
} | |||||
} | |||||
func ToResponseDeliverTx(res ResponseDeliverTx) *Response { | |||||
return &Response{ | |||||
Value: &Response_DeliverTx{&res}, | |||||
} | |||||
} | |||||
func ToResponseCheckTx(res ResponseCheckTx) *Response { | |||||
return &Response{ | |||||
Value: &Response_CheckTx{&res}, | |||||
} | |||||
} | |||||
func ToResponseCommit(res ResponseCommit) *Response { | |||||
return &Response{ | |||||
Value: &Response_Commit{&res}, | |||||
} | |||||
} | |||||
func ToResponseQuery(res ResponseQuery) *Response { | |||||
return &Response{ | |||||
Value: &Response_Query{&res}, | |||||
} | |||||
} | |||||
func ToResponseInitChain(res ResponseInitChain) *Response { | |||||
return &Response{ | |||||
Value: &Response_InitChain{&res}, | |||||
} | |||||
} | |||||
func ToResponseBeginBlock(res ResponseBeginBlock) *Response { | |||||
return &Response{ | |||||
Value: &Response_BeginBlock{&res}, | |||||
} | |||||
} | |||||
func ToResponseEndBlock(res ResponseEndBlock) *Response { | |||||
return &Response{ | |||||
Value: &Response_EndBlock{&res}, | |||||
} | |||||
} |
@ -0,0 +1,104 @@ | |||||
package types | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"strings" | |||||
"testing" | |||||
"github.com/gogo/protobuf/proto" | |||||
"github.com/stretchr/testify/assert" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
func TestMarshalJSON(t *testing.T) { | |||||
b, err := json.Marshal(&ResponseDeliverTx{}) | |||||
assert.Nil(t, err) | |||||
// Do not include empty fields. | |||||
assert.False(t, strings.Contains(string(b), "code")) | |||||
r1 := ResponseCheckTx{ | |||||
Code: 1, | |||||
Data: []byte("hello"), | |||||
GasWanted: 43, | |||||
Tags: []cmn.KVPair{ | |||||
{[]byte("pho"), []byte("bo")}, | |||||
}, | |||||
} | |||||
b, err = json.Marshal(&r1) | |||||
assert.Nil(t, err) | |||||
var r2 ResponseCheckTx | |||||
err = json.Unmarshal(b, &r2) | |||||
assert.Nil(t, err) | |||||
assert.Equal(t, r1, r2) | |||||
} | |||||
func TestWriteReadMessageSimple(t *testing.T) { | |||||
cases := []proto.Message{ | |||||
&RequestEcho{ | |||||
Message: "Hello", | |||||
}, | |||||
} | |||||
for _, c := range cases { | |||||
buf := new(bytes.Buffer) | |||||
err := WriteMessage(c, buf) | |||||
assert.Nil(t, err) | |||||
msg := new(RequestEcho) | |||||
err = ReadMessage(buf, msg) | |||||
assert.Nil(t, err) | |||||
assert.Equal(t, c, msg) | |||||
} | |||||
} | |||||
func TestWriteReadMessage(t *testing.T) { | |||||
cases := []proto.Message{ | |||||
&Header{ | |||||
NumTxs: 4, | |||||
}, | |||||
// TODO: add the rest | |||||
} | |||||
for _, c := range cases { | |||||
buf := new(bytes.Buffer) | |||||
err := WriteMessage(c, buf) | |||||
assert.Nil(t, err) | |||||
msg := new(Header) | |||||
err = ReadMessage(buf, msg) | |||||
assert.Nil(t, err) | |||||
assert.Equal(t, c, msg) | |||||
} | |||||
} | |||||
func TestWriteReadMessage2(t *testing.T) { | |||||
phrase := "hello-world" | |||||
cases := []proto.Message{ | |||||
&ResponseCheckTx{ | |||||
Data: []byte(phrase), | |||||
Log: phrase, | |||||
GasWanted: 10, | |||||
Tags: []cmn.KVPair{ | |||||
cmn.KVPair{[]byte("abc"), []byte("def")}, | |||||
}, | |||||
// Fee: cmn.KI64Pair{ | |||||
}, | |||||
// TODO: add the rest | |||||
} | |||||
for _, c := range cases { | |||||
buf := new(bytes.Buffer) | |||||
err := WriteMessage(c, buf) | |||||
assert.Nil(t, err) | |||||
msg := new(ResponseCheckTx) | |||||
err = ReadMessage(buf, msg) | |||||
assert.Nil(t, err) | |||||
assert.Equal(t, c, msg) | |||||
} | |||||
} |
@ -0,0 +1,55 @@ | |||||
// +build ignore | |||||
package main | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"io/ioutil" | |||||
"os" | |||||
"os/exec" | |||||
"regexp" | |||||
"strings" | |||||
) | |||||
// This script replaces most `[]byte` with `data.Bytes` in a `.pb.go` file. | |||||
// It was written before we realized we could use `gogo/protobuf` to achieve | |||||
// this more natively. So it's here for safe keeping in case we ever need to | |||||
// abandon `gogo/protobuf`. | |||||
func main() { | |||||
bytePattern := regexp.MustCompile("[[][]]byte") | |||||
const oldPath = "types/types.pb.go" | |||||
const tmpPath = "types/types.pb.new" | |||||
content, err := ioutil.ReadFile(oldPath) | |||||
if err != nil { | |||||
panic("cannot read " + oldPath) | |||||
os.Exit(1) | |||||
} | |||||
lines := bytes.Split(content, []byte("\n")) | |||||
outFile, _ := os.Create(tmpPath) | |||||
wroteImport := false | |||||
for _, line_bytes := range lines { | |||||
line := string(line_bytes) | |||||
gotPackageLine := strings.HasPrefix(line, "package ") | |||||
writeImportTime := strings.HasPrefix(line, "import ") | |||||
containsDescriptor := strings.Contains(line, "Descriptor") | |||||
containsByteArray := strings.Contains(line, "[]byte") | |||||
if containsByteArray && !containsDescriptor { | |||||
line = string(bytePattern.ReplaceAll([]byte(line), []byte("data.Bytes"))) | |||||
} | |||||
if writeImportTime && !wroteImport { | |||||
wroteImport = true | |||||
fmt.Fprintf(outFile, "import \"github.com/tendermint/go-wire/data\"\n") | |||||
} | |||||
if gotPackageLine { | |||||
fmt.Fprintf(outFile, "%s\n", "//nolint: gas") | |||||
} | |||||
fmt.Fprintf(outFile, "%s\n", line) | |||||
} | |||||
outFile.Close() | |||||
os.Remove(oldPath) | |||||
os.Rename(tmpPath, oldPath) | |||||
exec.Command("goimports", "-w", oldPath) | |||||
} |
@ -0,0 +1,16 @@ | |||||
package types | |||||
const ( | |||||
PubKeyEd25519 = "ed25519" | |||||
) | |||||
func Ed25519Validator(pubkey []byte, power int64) Validator { | |||||
return Validator{ | |||||
// Address: | |||||
PubKey: PubKey{ | |||||
Type: PubKeyEd25519, | |||||
Data: pubkey, | |||||
}, | |||||
Power: power, | |||||
} | |||||
} |
@ -0,0 +1,121 @@ | |||||
package types | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"github.com/gogo/protobuf/jsonpb" | |||||
) | |||||
const ( | |||||
CodeTypeOK uint32 = 0 | |||||
) | |||||
// IsOK returns true if Code is OK. | |||||
func (r ResponseCheckTx) IsOK() bool { | |||||
return r.Code == CodeTypeOK | |||||
} | |||||
// IsErr returns true if Code is something other than OK. | |||||
func (r ResponseCheckTx) IsErr() bool { | |||||
return r.Code != CodeTypeOK | |||||
} | |||||
// IsOK returns true if Code is OK. | |||||
func (r ResponseDeliverTx) IsOK() bool { | |||||
return r.Code == CodeTypeOK | |||||
} | |||||
// IsErr returns true if Code is something other than OK. | |||||
func (r ResponseDeliverTx) IsErr() bool { | |||||
return r.Code != CodeTypeOK | |||||
} | |||||
// IsOK returns true if Code is OK. | |||||
func (r ResponseQuery) IsOK() bool { | |||||
return r.Code == CodeTypeOK | |||||
} | |||||
// IsErr returns true if Code is something other than OK. | |||||
func (r ResponseQuery) IsErr() bool { | |||||
return r.Code != CodeTypeOK | |||||
} | |||||
//--------------------------------------------------------------------------- | |||||
// override JSON marshalling so we dont emit defaults (ie. disable omitempty) | |||||
// note we need Unmarshal functions too because protobuf had the bright idea | |||||
// to marshal int64->string. cool. cool, cool, cool: https://developers.google.com/protocol-buffers/docs/proto3#json | |||||
var ( | |||||
jsonpbMarshaller = jsonpb.Marshaler{ | |||||
EnumsAsInts: true, | |||||
EmitDefaults: false, | |||||
} | |||||
jsonpbUnmarshaller = jsonpb.Unmarshaler{} | |||||
) | |||||
func (r *ResponseSetOption) MarshalJSON() ([]byte, error) { | |||||
s, err := jsonpbMarshaller.MarshalToString(r) | |||||
return []byte(s), err | |||||
} | |||||
func (r *ResponseSetOption) UnmarshalJSON(b []byte) error { | |||||
reader := bytes.NewBuffer(b) | |||||
return jsonpbUnmarshaller.Unmarshal(reader, r) | |||||
} | |||||
func (r *ResponseCheckTx) MarshalJSON() ([]byte, error) { | |||||
s, err := jsonpbMarshaller.MarshalToString(r) | |||||
return []byte(s), err | |||||
} | |||||
func (r *ResponseCheckTx) UnmarshalJSON(b []byte) error { | |||||
reader := bytes.NewBuffer(b) | |||||
return jsonpbUnmarshaller.Unmarshal(reader, r) | |||||
} | |||||
func (r *ResponseDeliverTx) MarshalJSON() ([]byte, error) { | |||||
s, err := jsonpbMarshaller.MarshalToString(r) | |||||
return []byte(s), err | |||||
} | |||||
func (r *ResponseDeliverTx) UnmarshalJSON(b []byte) error { | |||||
reader := bytes.NewBuffer(b) | |||||
return jsonpbUnmarshaller.Unmarshal(reader, r) | |||||
} | |||||
func (r *ResponseQuery) MarshalJSON() ([]byte, error) { | |||||
s, err := jsonpbMarshaller.MarshalToString(r) | |||||
return []byte(s), err | |||||
} | |||||
func (r *ResponseQuery) UnmarshalJSON(b []byte) error { | |||||
reader := bytes.NewBuffer(b) | |||||
return jsonpbUnmarshaller.Unmarshal(reader, r) | |||||
} | |||||
func (r *ResponseCommit) MarshalJSON() ([]byte, error) { | |||||
s, err := jsonpbMarshaller.MarshalToString(r) | |||||
return []byte(s), err | |||||
} | |||||
func (r *ResponseCommit) UnmarshalJSON(b []byte) error { | |||||
reader := bytes.NewBuffer(b) | |||||
return jsonpbUnmarshaller.Unmarshal(reader, r) | |||||
} | |||||
// Some compile time assertions to ensure we don't | |||||
// have accidental runtime surprises later on. | |||||
// jsonEncodingRoundTripper ensures that asserted | |||||
// interfaces implement both MarshalJSON and UnmarshalJSON | |||||
type jsonRoundTripper interface { | |||||
json.Marshaler | |||||
json.Unmarshaler | |||||
} | |||||
var _ jsonRoundTripper = (*ResponseCommit)(nil) | |||||
var _ jsonRoundTripper = (*ResponseQuery)(nil) | |||||
var _ jsonRoundTripper = (*ResponseDeliverTx)(nil) | |||||
var _ jsonRoundTripper = (*ResponseCheckTx)(nil) | |||||
var _ jsonRoundTripper = (*ResponseSetOption)(nil) |
@ -0,0 +1,282 @@ | |||||
syntax = "proto3"; | |||||
package types; | |||||
// For more information on gogo.proto, see: | |||||
// https://github.com/gogo/protobuf/blob/master/extensions.md | |||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto"; | |||||
import "github.com/tendermint/tmlibs/common/types.proto"; | |||||
// This file is copied from http://github.com/tendermint/abci | |||||
// NOTE: When using custom types, mind the warnings. | |||||
// https://github.com/gogo/protobuf/blob/master/custom_types.md#warnings-and-issues | |||||
//---------------------------------------- | |||||
// Request types | |||||
message Request { | |||||
oneof value { | |||||
RequestEcho echo = 2; | |||||
RequestFlush flush = 3; | |||||
RequestInfo info = 4; | |||||
RequestSetOption set_option = 5; | |||||
RequestInitChain init_chain = 6; | |||||
RequestQuery query = 7; | |||||
RequestBeginBlock begin_block = 8; | |||||
RequestCheckTx check_tx = 9; | |||||
RequestDeliverTx deliver_tx = 19; | |||||
RequestEndBlock end_block = 11; | |||||
RequestCommit commit = 12; | |||||
} | |||||
} | |||||
message RequestEcho { | |||||
string message = 1; | |||||
} | |||||
message RequestFlush { | |||||
} | |||||
message RequestInfo { | |||||
string version = 1; | |||||
} | |||||
// nondeterministic | |||||
message RequestSetOption { | |||||
string key = 1; | |||||
string value = 2; | |||||
} | |||||
message RequestInitChain { | |||||
int64 time = 1; | |||||
string chain_id = 2; | |||||
ConsensusParams consensus_params = 3; | |||||
repeated Validator validators = 4 [(gogoproto.nullable)=false]; | |||||
bytes app_state_bytes = 5; | |||||
} | |||||
message RequestQuery { | |||||
bytes data = 1; | |||||
string path = 2; | |||||
int64 height = 3; | |||||
bool prove = 4; | |||||
} | |||||
message RequestBeginBlock { | |||||
bytes hash = 1; | |||||
Header header = 2 [(gogoproto.nullable)=false]; | |||||
repeated SigningValidator validators = 3 [(gogoproto.nullable)=false]; | |||||
repeated Evidence byzantine_validators = 4 [(gogoproto.nullable)=false]; | |||||
} | |||||
message RequestCheckTx { | |||||
bytes tx = 1; | |||||
} | |||||
message RequestDeliverTx { | |||||
bytes tx = 1; | |||||
} | |||||
message RequestEndBlock { | |||||
int64 height = 1; | |||||
} | |||||
message RequestCommit { | |||||
} | |||||
//---------------------------------------- | |||||
// Response types | |||||
message Response { | |||||
oneof value { | |||||
ResponseException exception = 1; | |||||
ResponseEcho echo = 2; | |||||
ResponseFlush flush = 3; | |||||
ResponseInfo info = 4; | |||||
ResponseSetOption set_option = 5; | |||||
ResponseInitChain init_chain = 6; | |||||
ResponseQuery query = 7; | |||||
ResponseBeginBlock begin_block = 8; | |||||
ResponseCheckTx check_tx = 9; | |||||
ResponseDeliverTx deliver_tx = 10; | |||||
ResponseEndBlock end_block = 11; | |||||
ResponseCommit commit = 12; | |||||
} | |||||
} | |||||
// nondeterministic | |||||
message ResponseException { | |||||
string error = 1; | |||||
} | |||||
message ResponseEcho { | |||||
string message = 1; | |||||
} | |||||
message ResponseFlush { | |||||
} | |||||
message ResponseInfo { | |||||
string data = 1; | |||||
string version = 2; | |||||
int64 last_block_height = 3; | |||||
bytes last_block_app_hash = 4; | |||||
} | |||||
// nondeterministic | |||||
message ResponseSetOption { | |||||
uint32 code = 1; | |||||
// bytes data = 2; | |||||
string log = 3; | |||||
string info = 4; | |||||
} | |||||
message ResponseInitChain { | |||||
ConsensusParams consensus_params = 1; | |||||
repeated Validator validators = 2 [(gogoproto.nullable)=false]; | |||||
} | |||||
message ResponseQuery { | |||||
uint32 code = 1; | |||||
// bytes data = 2; // use "value" instead. | |||||
string log = 3; // nondeterministic | |||||
string info = 4; // nondeterministic | |||||
int64 index = 5; | |||||
bytes key = 6; | |||||
bytes value = 7; | |||||
bytes proof = 8; | |||||
int64 height = 9; | |||||
} | |||||
message ResponseBeginBlock { | |||||
repeated common.KVPair tags = 1 [(gogoproto.nullable)=false, (gogoproto.jsontag)="tags,omitempty"]; | |||||
} | |||||
message ResponseCheckTx { | |||||
uint32 code = 1; | |||||
bytes data = 2; | |||||
string log = 3; // nondeterministic | |||||
string info = 4; // nondeterministic | |||||
int64 gas_wanted = 5; | |||||
int64 gas_used = 6; | |||||
repeated common.KVPair tags = 7 [(gogoproto.nullable)=false, (gogoproto.jsontag)="tags,omitempty"]; | |||||
common.KI64Pair fee = 8 [(gogoproto.nullable)=false]; | |||||
} | |||||
message ResponseDeliverTx { | |||||
uint32 code = 1; | |||||
bytes data = 2; | |||||
string log = 3; // nondeterministic | |||||
string info = 4; // nondeterministic | |||||
int64 gas_wanted = 5; | |||||
int64 gas_used = 6; | |||||
repeated common.KVPair tags = 7 [(gogoproto.nullable)=false, (gogoproto.jsontag)="tags,omitempty"]; | |||||
common.KI64Pair fee = 8 [(gogoproto.nullable)=false]; | |||||
} | |||||
message ResponseEndBlock { | |||||
repeated Validator validator_updates = 1 [(gogoproto.nullable)=false]; | |||||
ConsensusParams consensus_param_updates = 2; | |||||
repeated common.KVPair tags = 3 [(gogoproto.nullable)=false, (gogoproto.jsontag)="tags,omitempty"]; | |||||
} | |||||
message ResponseCommit { | |||||
// reserve 1 | |||||
bytes data = 2; | |||||
} | |||||
//---------------------------------------- | |||||
// Misc. | |||||
// ConsensusParams contains all consensus-relevant parameters | |||||
// that can be adjusted by the abci app | |||||
message ConsensusParams { | |||||
BlockSize block_size = 1; | |||||
TxSize tx_size = 2; | |||||
BlockGossip block_gossip = 3; | |||||
} | |||||
// BlockSize contain limits on the block size. | |||||
message BlockSize { | |||||
int32 max_bytes = 1; | |||||
int32 max_txs = 2; | |||||
int64 max_gas = 3; | |||||
} | |||||
// TxSize contain limits on the tx size. | |||||
message TxSize { | |||||
int32 max_bytes = 1; | |||||
int64 max_gas = 2; | |||||
} | |||||
// BlockGossip determine consensus critical | |||||
// elements of how blocks are gossiped | |||||
message BlockGossip { | |||||
// Note: must not be 0 | |||||
int32 block_part_size_bytes = 1; | |||||
} | |||||
//---------------------------------------- | |||||
// Blockchain Types | |||||
// just the minimum the app might need | |||||
message Header { | |||||
// basics | |||||
string chain_id = 1 [(gogoproto.customname)="ChainID"]; | |||||
int64 height = 2; | |||||
int64 time = 3; | |||||
// txs | |||||
int32 num_txs = 4; | |||||
int64 total_txs = 5; | |||||
// hashes | |||||
bytes last_block_hash = 6; | |||||
bytes validators_hash = 7; | |||||
bytes app_hash = 8; | |||||
// consensus | |||||
Validator proposer = 9 [(gogoproto.nullable)=false]; | |||||
} | |||||
// Validator | |||||
message Validator { | |||||
bytes address = 1; | |||||
PubKey pub_key = 2 [(gogoproto.nullable)=false]; | |||||
int64 power = 3; | |||||
} | |||||
// Validator with an extra bool | |||||
message SigningValidator { | |||||
Validator validator = 1 [(gogoproto.nullable)=false]; | |||||
bool signed_last_block = 2; | |||||
} | |||||
message PubKey { | |||||
string type = 1; | |||||
bytes data = 2; | |||||
} | |||||
message Evidence { | |||||
string type = 1; | |||||
Validator validator = 2 [(gogoproto.nullable)=false]; | |||||
int64 height = 3; | |||||
int64 time = 4; | |||||
int64 total_voting_power = 5; | |||||
} | |||||
//---------------------------------------- | |||||
// Service Definition | |||||
service ABCIApplication { | |||||
rpc Echo(RequestEcho) returns (ResponseEcho) ; | |||||
rpc Flush(RequestFlush) returns (ResponseFlush); | |||||
rpc Info(RequestInfo) returns (ResponseInfo); | |||||
rpc SetOption(RequestSetOption) returns (ResponseSetOption); | |||||
rpc DeliverTx(RequestDeliverTx) returns (ResponseDeliverTx); | |||||
rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); | |||||
rpc Query(RequestQuery) returns (ResponseQuery); | |||||
rpc Commit(RequestCommit) returns (ResponseCommit); | |||||
rpc InitChain(RequestInitChain) returns (ResponseInitChain); | |||||
rpc BeginBlock(RequestBeginBlock) returns (ResponseBeginBlock); | |||||
rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock); | |||||
} |
@ -0,0 +1,31 @@ | |||||
package types | |||||
import ( | |||||
"testing" | |||||
asrt "github.com/stretchr/testify/assert" | |||||
) | |||||
func TestConsensusParams(t *testing.T) { | |||||
assert := asrt.New(t) | |||||
params := &ConsensusParams{ | |||||
BlockSize: &BlockSize{MaxGas: 12345}, | |||||
BlockGossip: &BlockGossip{BlockPartSizeBytes: 54321}, | |||||
} | |||||
var noParams *ConsensusParams // nil | |||||
// no error with nil fields | |||||
assert.Nil(noParams.GetBlockSize()) | |||||
assert.EqualValues(noParams.GetBlockSize().GetMaxGas(), 0) | |||||
// get values with real fields | |||||
assert.NotNil(params.GetBlockSize()) | |||||
assert.EqualValues(params.GetBlockSize().GetMaxTxs(), 0) | |||||
assert.EqualValues(params.GetBlockSize().GetMaxGas(), 12345) | |||||
assert.NotNil(params.GetBlockGossip()) | |||||
assert.EqualValues(params.GetBlockGossip().GetBlockPartSizeBytes(), 54321) | |||||
assert.Nil(params.GetTxSize()) | |||||
assert.EqualValues(params.GetTxSize().GetMaxBytes(), 0) | |||||
} |
@ -0,0 +1,59 @@ | |||||
package types | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"sort" | |||||
cmn "github.com/tendermint/tmlibs/common" | |||||
) | |||||
//------------------------------------------------------------------------------ | |||||
// Validators is a list of validators that implements the Sort interface | |||||
type Validators []Validator | |||||
var _ sort.Interface = (Validators)(nil) | |||||
// All these methods for Validators: | |||||
// Len, Less and Swap | |||||
// are for Validators to implement sort.Interface | |||||
// which will be used by the sort package. | |||||
// See Issue https://github.com/tendermint/abci/issues/212 | |||||
func (v Validators) Len() int { | |||||
return len(v) | |||||
} | |||||
// XXX: doesn't distinguish same validator with different power | |||||
func (v Validators) Less(i, j int) bool { | |||||
return bytes.Compare(v[i].PubKey.Data, v[j].PubKey.Data) <= 0 | |||||
} | |||||
func (v Validators) Swap(i, j int) { | |||||
v1 := v[i] | |||||
v[i] = v[j] | |||||
v[j] = v1 | |||||
} | |||||
func ValidatorsString(vs Validators) string { | |||||
s := make([]validatorPretty, len(vs)) | |||||
for i, v := range vs { | |||||
s[i] = validatorPretty{ | |||||
Address: v.Address, | |||||
PubKey: v.PubKey.Data, | |||||
Power: v.Power, | |||||
} | |||||
} | |||||
b, err := json.Marshal(s) | |||||
if err != nil { | |||||
panic(err.Error()) | |||||
} | |||||
return string(b) | |||||
} | |||||
type validatorPretty struct { | |||||
Address cmn.HexBytes `json:"address"` | |||||
PubKey []byte `json:"pub_key"` | |||||
Power int64 `json:"power"` | |||||
} |
@ -0,0 +1,9 @@ | |||||
package version | |||||
// NOTE: we should probably be versioning the ABCI and the abci-cli separately | |||||
const Maj = "0" | |||||
const Min = "12" | |||||
const Fix = "0" | |||||
const Version = "0.12.0" |