Browse Source

Add abci repo

pull/1796/head
Alexander Simmerl 7 years ago
parent
commit
4bee228ba7
No known key found for this signature in database GPG Key ID: 4694E95C9CC61BDA
75 changed files with 10318 additions and 0 deletions
  1. +139
    -0
      abci/.circleci/config.yml
  2. +19
    -0
      abci/.editorconfig
  3. +7
    -0
      abci/.gitignore
  4. +352
    -0
      abci/CHANGELOG.md
  5. +23
    -0
      abci/Dockerfile.develop
  6. +213
    -0
      abci/Gopkg.lock
  7. +56
    -0
      abci/Gopkg.toml
  8. +193
    -0
      abci/LICENSE
  9. +174
    -0
      abci/Makefile
  10. +168
    -0
      abci/README.md
  11. +129
    -0
      abci/client/client.go
  12. +301
    -0
      abci/client/grpc_client.go
  13. +230
    -0
      abci/client/local_client.go
  14. +399
    -0
      abci/client/socket_client.go
  15. +28
    -0
      abci/client/socket_client_test.go
  16. +765
    -0
      abci/cmd/abci-cli/abci-cli.go
  17. +14
    -0
      abci/cmd/abci-cli/main.go
  18. +9
    -0
      abci/example/code/code.go
  19. +104
    -0
      abci/example/counter/counter.go
  20. +3
    -0
      abci/example/example.go
  21. +154
    -0
      abci/example/example_test.go
  22. +1
    -0
      abci/example/js/.gitignore
  23. +1
    -0
      abci/example/js/README.md
  24. +31
    -0
      abci/example/kvstore/README.md
  25. +38
    -0
      abci/example/kvstore/helpers.go
  26. +126
    -0
      abci/example/kvstore/kvstore.go
  27. +310
    -0
      abci/example/kvstore/kvstore_test.go
  28. +200
    -0
      abci/example/kvstore/persistent_kvstore.go
  29. +0
    -0
      abci/example/python/abci/__init__.py
  30. +50
    -0
      abci/example/python/abci/msg.py
  31. +56
    -0
      abci/example/python/abci/reader.py
  32. +202
    -0
      abci/example/python/abci/server.py
  33. +115
    -0
      abci/example/python/abci/wire.py
  34. +82
    -0
      abci/example/python/app.py
  35. +0
    -0
      abci/example/python3/abci/__init__.py
  36. +50
    -0
      abci/example/python3/abci/msg.py
  37. +56
    -0
      abci/example/python3/abci/reader.py
  38. +196
    -0
      abci/example/python3/abci/server.py
  39. +119
    -0
      abci/example/python3/abci/wire.py
  40. +82
    -0
      abci/example/python3/app.py
  41. +12
    -0
      abci/scripts/abci-builder/Dockerfile
  42. +52
    -0
      abci/scripts/dist.sh
  43. +53
    -0
      abci/scripts/dist_build.sh
  44. +7
    -0
      abci/scripts/publish.sh
  45. +57
    -0
      abci/server/grpc_server.go
  46. +31
    -0
      abci/server/server.go
  47. +226
    -0
      abci/server/socket_server.go
  48. +324
    -0
      abci/specification.md
  49. +294
    -0
      abci/specification.rst
  50. +1
    -0
      abci/tests/benchmarks/blank.go
  51. +55
    -0
      abci/tests/benchmarks/parallel/parallel.go
  52. +69
    -0
      abci/tests/benchmarks/simple/simple.go
  53. +27
    -0
      abci/tests/client_server_test.go
  54. +96
    -0
      abci/tests/server/client.go
  55. +78
    -0
      abci/tests/test_app/app.go
  56. +84
    -0
      abci/tests/test_app/main.go
  57. +27
    -0
      abci/tests/test_app/test.sh
  58. +10
    -0
      abci/tests/test_cli/ex1.abci
  59. +47
    -0
      abci/tests/test_cli/ex1.abci.out
  60. +8
    -0
      abci/tests/test_cli/ex2.abci
  61. +29
    -0
      abci/tests/test_cli/ex2.abci.out
  62. +42
    -0
      abci/tests/test_cli/test.sh
  63. +13
    -0
      abci/tests/test_cover.sh
  64. +1
    -0
      abci/tests/tests.go
  65. +138
    -0
      abci/types/application.go
  66. +210
    -0
      abci/types/messages.go
  67. +104
    -0
      abci/types/messages_test.go
  68. +55
    -0
      abci/types/protoreplace/protoreplace.go
  69. +16
    -0
      abci/types/pubkey.go
  70. +121
    -0
      abci/types/result.go
  71. +2455
    -0
      abci/types/types.pb.go
  72. +282
    -0
      abci/types/types.proto
  73. +31
    -0
      abci/types/types_test.go
  74. +59
    -0
      abci/types/util.go
  75. +9
    -0
      abci/version/version.go

+ 139
- 0
abci/.circleci/config.yml View File

@ -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

+ 19
- 0
abci/.editorconfig View File

@ -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

+ 7
- 0
abci/.gitignore View File

@ -0,0 +1,7 @@
vendor
.glide
types/types.pb.go
*.sw[op]
abci-cli
coverage.txt
profile.out

+ 352
- 0
abci/CHANGELOG.md View File

@ -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.

+ 23
- 0
abci/Dockerfile.develop View File

@ -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

+ 213
- 0
abci/Gopkg.lock View File

@ -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

+ 56
- 0
abci/Gopkg.toml View File

@ -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

+ 193
- 0
abci/LICENSE View File

@ -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.

+ 174
- 0
abci/Makefile View File

@ -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

+ 168
- 0
abci/README.md View File

@ -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
}
```

+ 129
- 0
abci/client/client.go View File

@ -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
}

+ 301
- 0
abci/client/grpc_client.go View File

@ -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()
}

+ 230
- 0
abci/client/local_client.go View File

@ -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
}

+ 399
- 0
abci/client/socket_client.go View File

@ -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
}

+ 28
- 0
abci/client/socket_client_test.go View File

@ -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")
}
}

+ 765
- 0
abci/cmd/abci-cli/abci-cli.go View File

@ -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
}

+ 14
- 0
abci/cmd/abci-cli/main.go View File

@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
err := Execute()
if err != nil {
fmt.Print(err)
os.Exit(1)
}
}

+ 9
- 0
abci/example/code/code.go View File

@ -0,0 +1,9 @@
package code
// Return codes for the examples
const (
CodeTypeOK uint32 = 0
CodeTypeEncodingError uint32 = 1
CodeTypeBadNonce uint32 = 2
CodeTypeUnauthorized uint32 = 3
)

+ 104
- 0
abci/example/counter/counter.go View File

@ -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)}
}
}

+ 3
- 0
abci/example/example.go View File

@ -0,0 +1,3 @@
package example
// so the go tool doesn't return errors about no buildable go files ...

+ 154
- 0
abci/example/example_test.go View File

@ -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
}()
}
}
}

+ 1
- 0
abci/example/js/.gitignore View File

@ -0,0 +1 @@
node_modules

+ 1
- 0
abci/example/js/README.md View File

@ -0,0 +1 @@
This example has been moved here: https://github.com/tendermint/js-abci/tree/master/example

+ 31
- 0
abci/example/kvstore/README.md View File

@ -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`.

+ 38
- 0
abci/example/kvstore/helpers.go View File

@ -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),
})
}

+ 126
- 0
abci/example/kvstore/kvstore.go View File

@ -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
}
}

+ 310
- 0
abci/example/kvstore/kvstore_test.go View File

@ -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))
}

+ 200
- 0
abci/example/kvstore/persistent_kvstore.go View File

@ -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
abci/example/python/abci/__init__.py View File


+ 50
- 0
abci/example/python/abci/msg.py View File

@ -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

+ 56
- 0
abci/example/python/abci/reader.py View File

@ -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

+ 202
- 0
abci/example/python/abci/server.py View File

@ -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

+ 115
- 0
abci/example/python/abci/wire.py View File

@ -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]

+ 82
- 0
abci/example/python/app.py View File

@ -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
abci/example/python3/abci/__init__.py View File


+ 50
- 0
abci/example/python3/abci/msg.py View File

@ -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

+ 56
- 0
abci/example/python3/abci/reader.py View File

@ -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

+ 196
- 0
abci/example/python3/abci/server.py View File

@ -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

+ 119
- 0
abci/example/python3/abci/wire.py View File

@ -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])

+ 82
- 0
abci/example/python3/app.py View File

@ -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()

+ 12
- 0
abci/scripts/abci-builder/Dockerfile View File

@ -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

+ 52
- 0
abci/scripts/dist.sh View File

@ -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

+ 53
- 0
abci/scripts/dist_build.sh View File

@ -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

+ 7
- 0
abci/scripts/publish.sh View File

@ -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

+ 57
- 0
abci/server/grpc_server.go View File

@ -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()
}

+ 31
- 0
abci/server/server.go View File

@ -0,0 +1,31 @@
/*
Package server is used to start a new ABCI server.
It contains two server implementation:
* gRPC server
* socket server
*/
package server
import (
"fmt"
"github.com/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
}

+ 226
- 0
abci/server/socket_server.go View File

@ -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++
}
}

+ 324
- 0
abci/specification.md View File

@ -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`

+ 294
- 0
abci/specification.rst View File

@ -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!

+ 1
- 0
abci/tests/benchmarks/blank.go View File

@ -0,0 +1 @@
package benchmarks

+ 55
- 0
abci/tests/benchmarks/parallel/parallel.go View File

@ -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)
}
}
}

+ 69
- 0
abci/tests/benchmarks/simple/simple.go View File

@ -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
}

+ 27
- 0
abci/tests/client_server_test.go View File

@ -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")
}

+ 96
- 0
abci/tests/server/client.go View File

@ -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
}

+ 78
- 0
abci/tests/test_app/app.go View File

@ -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...))
}

+ 84
- 0
abci/tests/test_app/main.go View File

@ -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})
}

+ 27
- 0
abci/tests/test_app/test.sh View File

@ -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

+ 10
- 0
abci/tests/test_cli/ex1.abci View File

@ -0,0 +1,10 @@
echo hello
info
commit
deliver_tx "abc"
info
commit
query "abc"
deliver_tx "def=xyz"
commit
query "def"

+ 47
- 0
abci/tests/test_cli/ex1.abci.out View File

@ -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

+ 8
- 0
abci/tests/test_cli/ex2.abci View File

@ -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

+ 29
- 0
abci/tests/test_cli/ex2.abci.out View File

@ -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

+ 42
- 0
abci/tests/test_cli/test.sh View File

@ -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"

+ 13
- 0
abci/tests/test_cover.sh View File

@ -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

+ 1
- 0
abci/tests/tests.go View File

@ -0,0 +1 @@
package tests

+ 138
- 0
abci/types/application.go View File

@ -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
}

+ 210
- 0
abci/types/messages.go View File

@ -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},
}
}

+ 104
- 0
abci/types/messages_test.go View File

@ -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)
}
}

+ 55
- 0
abci/types/protoreplace/protoreplace.go View File

@ -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)
}

+ 16
- 0
abci/types/pubkey.go View File

@ -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,
}
}

+ 121
- 0
abci/types/result.go View File

@ -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)

+ 2455
- 0
abci/types/types.pb.go
File diff suppressed because it is too large
View File


+ 282
- 0
abci/types/types.proto View File

@ -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);
}

+ 31
- 0
abci/types/types_test.go View File

@ -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)
}

+ 59
- 0
abci/types/util.go View File

@ -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"`
}

+ 9
- 0
abci/version/version.go View File

@ -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…
Cancel
Save