Add abci repo
This commit is contained in:
commit
4bee228ba7
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
Loading…
Reference in New Issue