Merge pull request #154 from tendermint/develop

Release 0.8.0
This commit is contained in:
Ethan Buchman 2017-12-06 02:39:56 -05:00 committed by GitHub
commit 12dca48768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1800 additions and 1457 deletions

View File

@ -13,3 +13,7 @@ indent_style = tab
[*.sh] [*.sh]
indent_style = tab indent_style = tab
[*.proto]
indent_style = space
indent_size = 2

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
vendor vendor
.glide .glide
types/types.pb.go

View File

@ -1,5 +1,31 @@
# Changelog # Changelog
## 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) ## 0.7.1 (November 14, 2017)
IMPROVEMENTS: IMPROVEMENTS:

21
Dockerfile.develop Normal file
View File

@ -0,0 +1,21 @@
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 install_protoc && ldconfig
# killall is used in tests
RUN apt-get update && apt-get install -y \
psmisc \
&& rm -rf /var/lib/apt/lists/*
COPY glide.yaml /go/src/github.com/tendermint/abci/
COPY glide.lock /go/src/github.com/tendermint/abci/
RUN make get_vendor_deps
COPY . /go/src/github.com/tendermint/abci

View File

@ -1,18 +1,33 @@
GOTOOLS = \ GOTOOLS = \
github.com/mitchellh/gox \ github.com/mitchellh/gox \
github.com/Masterminds/glide \ github.com/Masterminds/glide \
github.com/alecthomas/gometalinter github.com/alecthomas/gometalinter \
github.com/ckaznocha/protoc-gen-lint \
github.com/gogo/protobuf/protoc-gen-gogo \
github.com/gogo/protobuf/gogoproto
all: protoc install test INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf
all: install test
PACKAGES=$(shell go list ./... | grep -v '/vendor/') PACKAGES=$(shell go list ./... | grep -v '/vendor/')
install-protoc: install_protoc:
# Download: https://github.com/google/protobuf/releases # https://github.com/google/protobuf/releases
go get github.com/golang/protobuf/protoc-gen-go 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
protoc: protoc:
@ protoc --go_out=plugins=grpc:. types/*.proto ## Note to self:
## On "error while loading shared libraries: libprotobuf.so.14: cannot open shared object file: No such file or directory"
## ldconfig (may require sudo)
## https://stackoverflow.com/a/25518702
protoc $(INCLUDE) --gogo_out=plugins=grpc:. --lint_out=. types/*.proto
install: install:
@ go install ./cmd/... @ go install ./cmd/...
@ -24,37 +39,45 @@ dist:
@ bash scripts/dist.sh @ bash scripts/dist.sh
@ bash scripts/publish.sh @ bash scripts/publish.sh
# test.sh requires that we run the installed cmds, must not be out of date test:
test: install @ find . -path ./vendor -prune -o -name "*.sock" -exec rm {} \;
find . -path ./vendor -prune -o -name *.sock -exec rm {} \; @ echo "==> Running linter"
@ make metalinter_test
@ echo "==> Running go test"
@ go test $(PACKAGES) @ go test $(PACKAGES)
@ bash tests/test.sh
test_race:
@ find . -path ./vendor -prune -o -name "*.sock" -exec rm {} \;
@ echo "==> Running go test --race"
@go test -v -race $(PACKAGES)
test_integrations:
@ bash test.sh
fmt: fmt:
@ go fmt ./... @ go fmt ./...
test_integrations: get_vendor_deps install test
get_deps: get_deps:
@ go get -d $(PACKAGES) @ go get -d $(PACKAGES)
tools: ensure_tools:
go get -u -v $(GOTOOLS) go get -u -v $(GOTOOLS)
@gometalinter --install
get_vendor_deps: get_vendor_deps: ensure_tools
@ go get github.com/Masterminds/glide @rm -rf vendor/
@echo "--> Running glide install"
@ glide install @ glide install
metalinter: tools metalinter:
@gometalinter --install protoc $(INCLUDE) --lint_out=. types/*.proto
gometalinter --vendor --deadline=600s --enable-all --disable=lll ./... gometalinter --vendor --deadline=600s --enable-all --disable=lll ./...
metalinter_test: tools metalinter_test:
@gometalinter --install protoc $(INCLUDE) --lint_out=. types/*.proto
gometalinter --vendor --deadline=600s --disable-all \ gometalinter --vendor --deadline=600s --disable-all \
--enable=maligned \ --enable=maligned \
--enable=deadcode \ --enable=deadcode \
--enable=gas \
--enable=goconst \ --enable=goconst \
--enable=goimports \ --enable=goimports \
--enable=gosimple \ --enable=gosimple \
@ -70,6 +93,7 @@ metalinter_test: tools
--enable=vetshadow \ --enable=vetshadow \
./... ./...
#--enable=gas \
#--enable=dupl \ #--enable=dupl \
#--enable=errcheck \ #--enable=errcheck \
#--enable=gocyclo \ #--enable=gocyclo \
@ -79,4 +103,10 @@ metalinter_test: tools
#--enable=unparam \ #--enable=unparam \
#--enable=vet \ #--enable=vet \
.PHONY: all build test fmt get_deps tools build-docker:
docker build -t "tendermint/abci-dev" -f Dockerfile.develop .
run-docker:
docker run -it --rm -v "$PWD:/go/src/github.com/tendermint/abci" -w "/go/src/github.com/tendermint/abci" "tendermint/abci-dev" /bin/bash
.PHONY: all build test fmt get_deps ensure_tools protoc install_protoc build-docker run-docker

View File

@ -2,21 +2,12 @@
[![CircleCI](https://circleci.com/gh/tendermint/abci.svg?style=svg)](https://circleci.com/gh/tendermint/abci) [![CircleCI](https://circleci.com/gh/tendermint/abci.svg?style=svg)](https://circleci.com/gh/tendermint/abci)
Blockchains are a system for multi-master state machine replication. Blockchains are systems for multi-master state machine replication.
**ABCI** is an interface that defines the boundary between the replication engine (the blockchain), **ABCI** is an interface that defines the boundary between the replication engine (the blockchain),
and the state machine (the application). and the state machine (the application).
By using a socket protocol, we enable a consensus engine running in one process By using a socket protocol, we enable a consensus engine running in one process
to manage an application state running in another. to manage an application state running in another.
## Install
```
go get github.com/tendermint/abci
cd $GOPATH/src/github.com/tendermint/abci
make get_vendor_deps
make install
```
For background information on ABCI, motivations, and tendermint, please visit [the documentation](http://tendermint.readthedocs.io/en/master/). For 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`. The two guides to focus on are the `Application Development Guide` and `Using ABCI-CLI`.
@ -28,11 +19,19 @@ The community has provided a number of addtional implementations, see the `Tende
We provide three implementations of the ABCI in Go: We provide three implementations of the ABCI in Go:
- Golang in-process
- ABCI-socket - ABCI-socket
- GRPC - GRPC
- Golang in-process
### Socket 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.
### Socket (TSP)
ABCI is best implemented as a streaming protocol. ABCI is best implemented as a streaming protocol.
The socket implementation provides for asynchronous, ordered message passing over unix or tcp. The socket implementation provides for asynchronous, ordered message passing over unix or tcp.
@ -45,25 +44,33 @@ For example, if the Protobuf3 encoded ABCI message is `0xDEADBEEF` (4 bytes), th
GRPC is an rpc framework native to Protocol Buffers with support in many languages. 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 Implementing the ABCI using GRPC can allow for faster prototyping, but is expected to be much slower than
the ordered, asynchronous socket protocol. 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. Note the length-prefixing used in the socket implementation does not apply for GRPC.
### In Process ## Tools
The simplest implementation just uses function calls within Go. The `abci-cli` tool wraps an ABCI client and can be used for probing/testing an ABCI server.
This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary. 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.
## Example Apps
The `abci-cli` tool wraps any ABCI client and can be used for probing/testing an ABCI application.
See [the documentation](http://tendermint.readthedocs.io/en/master/) for more details. See [the documentation](http://tendermint.readthedocs.io/en/master/) for more details.
### Example Apps
Multiple example apps are included: Multiple example apps are included:
- the `abci-cli counter` application, which illustrates nonce checking in txs - the `abci-cli counter` application, which illustrates nonce checking in txs
- the `abci-cli dummy` application, which illustrates a simple key-value merkle tree - the `abci-cli dummy` application, which illustrates a simple key-value merkle tree
- the `abci-cli dummy --persistent` application, which augments the dummy with persistence and validator set changes - the `abci-cli dummy --persistent` application, which augments the dummy with persistence and validator set changes
### Install
```
go get github.com/tendermint/abci
cd $GOPATH/src/github.com/tendermint/abci
make get_vendor_deps
make install
```
## Specification ## Specification
The [primary specification](https://github.com/tendermint/abci/blob/master/types/types.proto) is made using Protocol Buffers. The [primary specification](https://github.com/tendermint/abci/blob/master/types/types.proto) is made using Protocol Buffers.
@ -91,6 +98,7 @@ Here, we describe the requests and responses as function arguments and return va
* `Code (uint32)`: Response code * `Code (uint32)`: Response code
* `Data ([]byte)`: Result bytes, if any * `Data ([]byte)`: Result bytes, if any
* `Log (string)`: Debug or error message * `Log (string)`: Debug or error message
* `Tags ([]*KVPair)`: Optional tags for indexing
* __Usage__:<br/> * __Usage__:<br/>
Append and run a transaction. If the transaction is valid, returns CodeType.OK Append and run a transaction. If the transaction is valid, returns CodeType.OK
@ -101,6 +109,8 @@ Here, we describe the requests and responses as function arguments and return va
* `Code (uint32)`: Response code * `Code (uint32)`: Response code
* `Data ([]byte)`: Result bytes, if any * `Data ([]byte)`: Result bytes, if any
* `Log (string)`: Debug or error message * `Log (string)`: Debug or error message
* `Gas (int64)`: Amount of gas consumed by transaction
* `Fee (int64)`: Fee paid by transaction
* __Usage__:<br/> * __Usage__:<br/>
Validate a mempool transaction, prior to broadcasting or proposing. This message should not mutate the main state, but application Validate a mempool transaction, prior to broadcasting or proposing. This message should not mutate the main state, but application
developers may want to keep a separate CheckTx state that gets reset upon Commit. developers may want to keep a separate CheckTx state that gets reset upon Commit.
@ -124,14 +134,14 @@ Here, we describe the requests and responses as function arguments and return va
* `Path (string)`: Path of request, like an HTTP GET path. Can be used with or in liue of Data. * `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 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/...' * Apps SHOULD allow queries over specific types like '/accounts/...' or '/votes/...'
* `Height (uint64)`: 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 * `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 * `Prove (bool)`: Return Merkle proof with response if possible
* __Returns__: * __Returns__:
* `Code (uint32)`: Response code * `Code (uint32)`: Response code
* `Key ([]byte)`: The key of the matching data * `Key ([]byte)`: The key of the matching data
* `Value ([]byte)`: The value of the matching data * `Value ([]byte)`: The value of the matching data
* `Proof ([]byte)`: Proof for the data, if requested * `Proof ([]byte)`: Proof for the data, if requested
* `Height (uint64)`: 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 * `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
* `Log (string)`: Debug or error message * `Log (string)`: Debug or error message
*Please note* The current implementation of go-merkle doesn't support querying proofs from past blocks, so for the present moment, any height other than 0 will return an error (recall height=0 defaults to latest block). Hopefully this will be improved soon(ish) *Please note* The current implementation of go-merkle doesn't support querying proofs from past blocks, so for the present moment, any height other than 0 will return an error (recall height=0 defaults to latest block). Hopefully this will be improved soon(ish)
@ -139,7 +149,7 @@ Here, we describe the requests and responses as function arguments and return va
* __Returns__: * __Returns__:
* `Data (string)`: Some arbitrary information * `Data (string)`: Some arbitrary information
* `Version (Version)`: Version information * `Version (Version)`: Version information
* `LastBlockHeight (uint64)`: Latest block for which the app has called Commit * `LastBlockHeight (int64)`: Latest block for which the app has called Commit
* `LastBlockAppHash ([]byte)`: Latest result of Commit * `LastBlockAppHash ([]byte)`: Latest result of Commit
* __Usage__:<br/> * __Usage__:<br/>
@ -150,6 +160,7 @@ Here, we describe the requests and responses as function arguments and return va
* `Key (string)`: Key to set * `Key (string)`: Key to set
* `Value (string)`: Value to set for key * `Value (string)`: Value to set for key
* __Returns__: * __Returns__:
* `Code (uint32)`: Response code
* `Log (string)`: Debug or error message * `Log (string)`: Debug or error message
* __Usage__:<br/> * __Usage__:<br/>
Set application options. E.g. Key="mode", Value="mempool" for a mempool connection, or Key="mode", Value="consensus" for a consensus connection. Set application options. E.g. Key="mode", Value="mempool" for a mempool connection, or Key="mode", Value="consensus" for a consensus connection.
@ -165,12 +176,14 @@ Here, we describe the requests and responses as function arguments and return va
* __Arguments__: * __Arguments__:
* `Hash ([]byte)`: The block's hash. This can be derived from the block header. * `Hash ([]byte)`: The block's hash. This can be derived from the block header.
* `Header (struct{})`: 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__:<br/> * __Usage__:<br/>
Signals the beginning of a new block. Called prior to any DeliverTxs. The header is expected to at least contain the Height. 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.
#### EndBlock #### EndBlock
* __Arguments__: * __Arguments__:
* `Height (uint64)`: The block height that ended * `Height (int64)`: The block height that ended
* __Returns__: * __Returns__:
* `Diffs ([]Validator)`: Changed validators with new voting powers (0 to remove) * `Diffs ([]Validator)`: Changed validators with new voting powers (0 to remove)
* __Usage__:<br/> * __Usage__:<br/>
@ -187,3 +200,4 @@ Here, we describe the requests and responses as function arguments and return va
#### Flush #### Flush
* __Usage__:<br/> * __Usage__:<br/>
* 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. * 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.

View File

@ -12,11 +12,11 @@ checkout:
- rm -rf $REPO - rm -rf $REPO
- mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME - mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME
- mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO - mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO
# - git submodule sync - go version
# - git submodule update --init # use submodules
test: test:
override: override:
- "go version" - cd $REPO && make get_vendor_deps && make test_integrations
- "cd $REPO && make get_vendor_deps && make metalinter_test" post:
- "cd $REPO && make test_integrations" - cd "$REPO" && bash <(curl -s https://codecov.io/bash) -f coverage.txt
- cd "$REPO" && mv coverage.txt "${CIRCLE_ARTIFACTS}"

View File

@ -8,6 +8,16 @@ import (
cmn "github.com/tendermint/tmlibs/common" 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 { type Client interface {
cmn.Service cmn.Service
@ -17,28 +27,26 @@ type Client interface {
FlushAsync() *ReqRes FlushAsync() *ReqRes
EchoAsync(msg string) *ReqRes EchoAsync(msg string) *ReqRes
InfoAsync(types.RequestInfo) *ReqRes InfoAsync(types.RequestInfo) *ReqRes
SetOptionAsync(key string, value string) *ReqRes SetOptionAsync(types.RequestSetOption) *ReqRes
DeliverTxAsync(tx []byte) *ReqRes DeliverTxAsync(tx []byte) *ReqRes
CheckTxAsync(tx []byte) *ReqRes CheckTxAsync(tx []byte) *ReqRes
QueryAsync(types.RequestQuery) *ReqRes QueryAsync(types.RequestQuery) *ReqRes
CommitAsync() *ReqRes CommitAsync() *ReqRes
FlushSync() error
EchoSync(msg string) (res types.Result)
InfoSync(types.RequestInfo) (resInfo types.ResponseInfo, err error)
SetOptionSync(key string, value string) (res types.Result)
DeliverTxSync(tx []byte) (res types.Result)
CheckTxSync(tx []byte) (res types.Result)
QuerySync(types.RequestQuery) (resQuery types.ResponseQuery, err error)
CommitSync() (res types.Result)
InitChainAsync(types.RequestInitChain) *ReqRes InitChainAsync(types.RequestInitChain) *ReqRes
BeginBlockAsync(types.RequestBeginBlock) *ReqRes BeginBlockAsync(types.RequestBeginBlock) *ReqRes
EndBlockAsync(height uint64) *ReqRes EndBlockAsync(types.RequestEndBlock) *ReqRes
InitChainSync(types.RequestInitChain) (err error) FlushSync() error
BeginBlockSync(types.RequestBeginBlock) (err error) EchoSync(msg string) (*types.ResponseEcho, error)
EndBlockSync(height uint64) (resEndBlock types.ResponseEndBlock, err 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)
} }
//---------------------------------------- //----------------------------------------

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
context "golang.org/x/net/context" context "golang.org/x/net/context"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
@ -13,6 +14,8 @@ import (
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
var _ Client = (*grpcClient)(nil)
// A stripped copy of the remoteClient that makes // A stripped copy of the remoteClient that makes
// synchronous calls using grpc // synchronous calls using grpc
type grpcClient struct { type grpcClient struct {
@ -45,7 +48,6 @@ func (cli *grpcClient) OnStart() error {
return err return err
} }
RETRY_LOOP: RETRY_LOOP:
for { for {
conn, err := grpc.Dial(cli.addr, grpc.WithInsecure(), grpc.WithDialer(dialerFunc)) conn, err := grpc.Dial(cli.addr, grpc.WithInsecure(), grpc.WithDialer(dialerFunc))
if err != nil { if err != nil {
@ -53,10 +55,11 @@ RETRY_LOOP:
return err return err
} }
cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr)) cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr))
time.Sleep(time.Second * 3) time.Sleep(time.Second * dialRetryIntervalSeconds)
continue RETRY_LOOP continue RETRY_LOOP
} }
cli.Logger.Info("Dialed server. Waiting for echo.", "addr", cli.addr)
client := types.NewABCIApplicationClient(conn) client := types.NewABCIApplicationClient(conn)
ENSURE_CONNECTED: ENSURE_CONNECTED:
@ -65,7 +68,8 @@ RETRY_LOOP:
if err == nil { if err == nil {
break ENSURE_CONNECTED break ENSURE_CONNECTED
} }
time.Sleep(time.Second) cli.Logger.Error("Echo failed", "err", err)
time.Sleep(time.Second * echoRetryIntervalSeconds)
} }
cli.client = client cli.client = client
@ -78,8 +82,8 @@ func (cli *grpcClient) OnStop() {
cli.mtx.Lock() cli.mtx.Lock()
defer cli.mtx.Unlock() defer cli.mtx.Unlock()
// TODO: how to close conn? its not a net.Conn and grpc doesn't expose a Close() // TODO: how to close conn? its not a net.Conn and grpc doesn't expose a Close()
/*if cli.conn != nil { /*if cli.client.conn != nil {
cli.conn.Close() cli.client.conn.Close()
}*/ }*/
} }
@ -101,7 +105,7 @@ func (cli *grpcClient) StopForError(err error) {
func (cli *grpcClient) Error() error { func (cli *grpcClient) Error() error {
cli.mtx.Lock() cli.mtx.Lock()
defer cli.mtx.Unlock() defer cli.mtx.Unlock()
return cli.err return errors.Wrap(cli.err, "grpc client error")
} }
// Set listener for all responses // Set listener for all responses
@ -147,8 +151,8 @@ func (cli *grpcClient) InfoAsync(params types.RequestInfo) *ReqRes {
return cli.finishAsyncCall(req, &types.Response{&types.Response_Info{res}}) return cli.finishAsyncCall(req, &types.Response{&types.Response_Info{res}})
} }
func (cli *grpcClient) SetOptionAsync(key string, value string) *ReqRes { func (cli *grpcClient) SetOptionAsync(params types.RequestSetOption) *ReqRes {
req := types.ToRequestSetOption(key, value) req := types.ToRequestSetOption(params)
res, err := cli.client.SetOption(context.Background(), req.GetSetOption(), grpc.FailFast(true)) res, err := cli.client.SetOption(context.Background(), req.GetSetOption(), grpc.FailFast(true))
if err != nil { if err != nil {
cli.StopForError(err) cli.StopForError(err)
@ -210,8 +214,8 @@ func (cli *grpcClient) BeginBlockAsync(params types.RequestBeginBlock) *ReqRes {
return cli.finishAsyncCall(req, &types.Response{&types.Response_BeginBlock{res}}) return cli.finishAsyncCall(req, &types.Response{&types.Response_BeginBlock{res}})
} }
func (cli *grpcClient) EndBlockAsync(height uint64) *ReqRes { func (cli *grpcClient) EndBlockAsync(params types.RequestEndBlock) *ReqRes {
req := types.ToRequestEndBlock(height) req := types.ToRequestEndBlock(params)
res, err := cli.client.EndBlock(context.Background(), req.GetEndBlock(), grpc.FailFast(true)) res, err := cli.client.EndBlock(context.Background(), req.GetEndBlock(), grpc.FailFast(true))
if err != nil { if err != nil {
cli.StopForError(err) cli.StopForError(err)
@ -240,104 +244,59 @@ func (cli *grpcClient) finishAsyncCall(req *types.Request, res *types.Response)
return reqres return reqres
} }
func (cli *grpcClient) checkErrGetResult() types.Result {
if err := cli.Error(); err != nil {
// StopForError should already have been called if error is set
return types.ErrInternalError.SetLog(err.Error())
}
return types.Result{}
}
//---------------------------------------- //----------------------------------------
func (cli *grpcClient) EchoSync(msg string) (res types.Result) {
reqres := cli.EchoAsync(msg)
if res := cli.checkErrGetResult(); res.IsErr() {
return res
}
resp := reqres.Response.GetEcho()
return types.NewResultOK([]byte(resp.Message), "")
}
func (cli *grpcClient) FlushSync() error { func (cli *grpcClient) FlushSync() error {
return nil return nil
} }
func (cli *grpcClient) InfoSync(req types.RequestInfo) (resInfo types.ResponseInfo, err error) { 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) reqres := cli.InfoAsync(req)
if err = cli.Error(); err != nil { return reqres.Response.GetInfo(), cli.Error()
return resInfo, err
}
if info := reqres.Response.GetInfo(); info != nil {
return *info, nil
}
return resInfo, nil
} }
func (cli *grpcClient) SetOptionSync(key string, value string) (res types.Result) { func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
reqres := cli.SetOptionAsync(key, value) reqres := cli.SetOptionAsync(req)
if res := cli.checkErrGetResult(); res.IsErr() { return reqres.Response.GetSetOption(), cli.Error()
return res
}
resp := reqres.Response.GetSetOption()
return types.Result{Code: OK, Data: nil, Log: resp.Log}
} }
func (cli *grpcClient) DeliverTxSync(tx []byte) (res types.Result) { func (cli *grpcClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) {
reqres := cli.DeliverTxAsync(tx) reqres := cli.DeliverTxAsync(tx)
if res := cli.checkErrGetResult(); res.IsErr() { return reqres.Response.GetDeliverTx(), cli.Error()
return res
}
resp := reqres.Response.GetDeliverTx()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *grpcClient) CheckTxSync(tx []byte) (res types.Result) { func (cli *grpcClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) {
reqres := cli.CheckTxAsync(tx) reqres := cli.CheckTxAsync(tx)
if res := cli.checkErrGetResult(); res.IsErr() { return reqres.Response.GetCheckTx(), cli.Error()
return res
}
resp := reqres.Response.GetCheckTx()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *grpcClient) QuerySync(reqQuery types.RequestQuery) (resQuery types.ResponseQuery, err error) { func (cli *grpcClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
reqres := cli.QueryAsync(reqQuery) reqres := cli.QueryAsync(req)
if err = cli.Error(); err != nil { return reqres.Response.GetQuery(), cli.Error()
return resQuery, err
}
if resQuery_ := reqres.Response.GetQuery(); resQuery_ != nil {
return *resQuery_, nil
}
return resQuery, nil
} }
func (cli *grpcClient) CommitSync() (res types.Result) { func (cli *grpcClient) CommitSync() (*types.ResponseCommit, error) {
reqres := cli.CommitAsync() reqres := cli.CommitAsync()
if res := cli.checkErrGetResult(); res.IsErr() { return reqres.Response.GetCommit(), cli.Error()
return res
}
resp := reqres.Response.GetCommit()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *grpcClient) InitChainSync(params types.RequestInitChain) (err error) { func (cli *grpcClient) InitChainSync(params types.RequestInitChain) (*types.ResponseInitChain, error) {
cli.InitChainAsync(params) reqres := cli.InitChainAsync(params)
return cli.Error() return reqres.Response.GetInitChain(), cli.Error()
} }
func (cli *grpcClient) BeginBlockSync(params types.RequestBeginBlock) (err error) { func (cli *grpcClient) BeginBlockSync(params types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
cli.BeginBlockAsync(params) reqres := cli.BeginBlockAsync(params)
return cli.Error() return reqres.Response.GetBeginBlock(), cli.Error()
} }
func (cli *grpcClient) EndBlockSync(height uint64) (resEndBlock types.ResponseEndBlock, err error) { func (cli *grpcClient) EndBlockSync(params types.RequestEndBlock) (*types.ResponseEndBlock, error) {
reqres := cli.EndBlockAsync(height) reqres := cli.EndBlockAsync(params)
if err := cli.Error(); err != nil { return reqres.Response.GetEndBlock(), cli.Error()
return resEndBlock, err
}
if blk := reqres.Response.GetEndBlock(); blk != nil {
return *blk, nil
}
return resEndBlock, nil
} }

View File

@ -7,6 +7,8 @@ import (
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
var _ Client = (*localClient)(nil)
type localClient struct { type localClient struct {
cmn.BaseService cmn.BaseService
mtx *sync.Mutex mtx *sync.Mutex
@ -51,21 +53,21 @@ func (app *localClient) EchoAsync(msg string) *ReqRes {
func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes { func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
resInfo := app.Application.Info(req) res := app.Application.Info(req)
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestInfo(req), types.ToRequestInfo(req),
types.ToResponseInfo(resInfo), types.ToResponseInfo(res),
) )
} }
func (app *localClient) SetOptionAsync(key string, value string) *ReqRes { func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
log := app.Application.SetOption(key, value) res := app.Application.SetOption(req)
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestSetOption(key, value), types.ToRequestSetOption(req),
types.ToResponseSetOption(log), types.ToResponseSetOption(res),
) )
} }
@ -75,7 +77,7 @@ func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes {
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestDeliverTx(tx), types.ToRequestDeliverTx(tx),
types.ToResponseDeliverTx(res.Code, res.Data, res.Log), types.ToResponseDeliverTx(res),
) )
} }
@ -85,17 +87,17 @@ func (app *localClient) CheckTxAsync(tx []byte) *ReqRes {
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestCheckTx(tx), types.ToRequestCheckTx(tx),
types.ToResponseCheckTx(res.Code, res.Data, res.Log), types.ToResponseCheckTx(res),
) )
} }
func (app *localClient) QueryAsync(reqQuery types.RequestQuery) *ReqRes { func (app *localClient) QueryAsync(req types.RequestQuery) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
resQuery := app.Application.Query(reqQuery) res := app.Application.Query(req)
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestQuery(reqQuery), types.ToRequestQuery(req),
types.ToResponseQuery(resQuery), types.ToResponseQuery(res),
) )
} }
@ -105,38 +107,38 @@ func (app *localClient) CommitAsync() *ReqRes {
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestCommit(), types.ToRequestCommit(),
types.ToResponseCommit(res.Code, res.Data, res.Log), types.ToResponseCommit(res),
) )
} }
func (app *localClient) InitChainAsync(params types.RequestInitChain) *ReqRes { func (app *localClient) InitChainAsync(req types.RequestInitChain) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
app.Application.InitChain(params) res := app.Application.InitChain(req)
reqRes := app.callback( reqRes := app.callback(
types.ToRequestInitChain(params), types.ToRequestInitChain(req),
types.ToResponseInitChain(), types.ToResponseInitChain(res),
) )
app.mtx.Unlock() app.mtx.Unlock()
return reqRes return reqRes
} }
func (app *localClient) BeginBlockAsync(params types.RequestBeginBlock) *ReqRes { func (app *localClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
app.Application.BeginBlock(params) res := app.Application.BeginBlock(req)
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestBeginBlock(params), types.ToRequestBeginBlock(req),
types.ToResponseBeginBlock(), types.ToResponseBeginBlock(res),
) )
} }
func (app *localClient) EndBlockAsync(height uint64) *ReqRes { func (app *localClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes {
app.mtx.Lock() app.mtx.Lock()
resEndBlock := app.Application.EndBlock(height) res := app.Application.EndBlock(req)
app.mtx.Unlock() app.mtx.Unlock()
return app.callback( return app.callback(
types.ToRequestEndBlock(height), types.ToRequestEndBlock(req),
types.ToResponseEndBlock(resEndBlock), types.ToResponseEndBlock(res),
) )
} }
@ -146,71 +148,71 @@ func (app *localClient) FlushSync() error {
return nil return nil
} }
func (app *localClient) EchoSync(msg string) (res types.Result) { func (app *localClient) EchoSync(msg string) (*types.ResponseEcho, error) {
return types.OK.SetData([]byte(msg)) return &types.ResponseEcho{msg}, nil
} }
func (app *localClient) InfoSync(req types.RequestInfo) (resInfo types.ResponseInfo, err error) { func (app *localClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) {
app.mtx.Lock() app.mtx.Lock()
defer app.mtx.Unlock() res := app.Application.Info(req)
resInfo = app.Application.Info(req)
return resInfo, nil
}
func (app *localClient) SetOptionSync(key string, value string) (res types.Result) {
app.mtx.Lock()
log := app.Application.SetOption(key, value)
app.mtx.Unlock() app.mtx.Unlock()
return types.OK.SetLog(log) return &res, nil
} }
func (app *localClient) DeliverTxSync(tx []byte) (res types.Result) { func (app *localClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
app.mtx.Lock() app.mtx.Lock()
res = app.Application.DeliverTx(tx) res := app.Application.SetOption(req)
app.mtx.Unlock() app.mtx.Unlock()
return res return &res, nil
} }
func (app *localClient) CheckTxSync(tx []byte) (res types.Result) { func (app *localClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) {
app.mtx.Lock() app.mtx.Lock()
res = app.Application.CheckTx(tx) res := app.Application.DeliverTx(tx)
app.mtx.Unlock() app.mtx.Unlock()
return res return &res, nil
} }
func (app *localClient) QuerySync(reqQuery types.RequestQuery) (resQuery types.ResponseQuery, err error) { func (app *localClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) {
app.mtx.Lock() app.mtx.Lock()
resQuery = app.Application.Query(reqQuery) res := app.Application.CheckTx(tx)
app.mtx.Unlock() app.mtx.Unlock()
return resQuery, nil return &res, nil
} }
func (app *localClient) CommitSync() (res types.Result) { func (app *localClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
app.mtx.Lock() app.mtx.Lock()
res = app.Application.Commit() res := app.Application.Query(req)
app.mtx.Unlock() app.mtx.Unlock()
return res return &res, nil
} }
func (app *localClient) InitChainSync(params types.RequestInitChain) (err error) { func (app *localClient) CommitSync() (*types.ResponseCommit, error) {
app.mtx.Lock() app.mtx.Lock()
app.Application.InitChain(params) res := app.Application.Commit()
app.mtx.Unlock() app.mtx.Unlock()
return nil return &res, nil
} }
func (app *localClient) BeginBlockSync(params types.RequestBeginBlock) (err error) { func (app *localClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) {
app.mtx.Lock() app.mtx.Lock()
app.Application.BeginBlock(params) res := app.Application.InitChain(req)
app.mtx.Unlock() app.mtx.Unlock()
return nil return &res, nil
} }
func (app *localClient) EndBlockSync(height uint64) (resEndBlock types.ResponseEndBlock, err error) { func (app *localClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
app.mtx.Lock() app.mtx.Lock()
resEndBlock = app.Application.EndBlock(height) res := app.Application.BeginBlock(req)
app.mtx.Unlock() app.mtx.Unlock()
return resEndBlock, nil 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
} }
//------------------------------------------------------- //-------------------------------------------------------

View File

@ -3,26 +3,23 @@ package abcicli
import ( import (
"bufio" "bufio"
"container/list" "container/list"
"errors"
"fmt" "fmt"
"net" "net"
"reflect" "reflect"
"sync" "sync"
"time" "time"
"github.com/pkg/errors"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
const (
OK = types.CodeType_OK
LOG = ""
)
const reqQueueSize = 256 // TODO make configurable const reqQueueSize = 256 // TODO make configurable
// const maxResponseSize = 1048576 // 1MB TODO make configurable // const maxResponseSize = 1048576 // 1MB TODO make configurable
const flushThrottleMS = 20 // Don't wait longer than... const flushThrottleMS = 20 // Don't wait longer than...
var _ Client = (*socketClient)(nil)
// This is goroutine-safe, but users should beware that // This is goroutine-safe, but users should beware that
// the application in general is not meant to be interfaced // the application in general is not meant to be interfaced
// with concurrent callers. // with concurrent callers.
@ -71,7 +68,7 @@ RETRY_LOOP:
return err return err
} }
cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr)) cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr))
time.Sleep(time.Second * 3) time.Sleep(time.Second * dialRetryIntervalSeconds)
continue RETRY_LOOP continue RETRY_LOOP
} }
cli.conn = conn cli.conn = conn
@ -114,7 +111,7 @@ func (cli *socketClient) StopForError(err error) {
func (cli *socketClient) Error() error { func (cli *socketClient) Error() error {
cli.mtx.Lock() cli.mtx.Lock()
defer cli.mtx.Unlock() defer cli.mtx.Unlock()
return cli.err return errors.Wrap(cli.err, "socket client error")
} }
// Set listener for all responses // Set listener for all responses
@ -237,8 +234,8 @@ func (cli *socketClient) InfoAsync(req types.RequestInfo) *ReqRes {
return cli.queueRequest(types.ToRequestInfo(req)) return cli.queueRequest(types.ToRequestInfo(req))
} }
func (cli *socketClient) SetOptionAsync(key string, value string) *ReqRes { func (cli *socketClient) SetOptionAsync(req types.RequestSetOption) *ReqRes {
return cli.queueRequest(types.ToRequestSetOption(key, value)) return cli.queueRequest(types.ToRequestSetOption(req))
} }
func (cli *socketClient) DeliverTxAsync(tx []byte) *ReqRes { func (cli *socketClient) DeliverTxAsync(tx []byte) *ReqRes {
@ -249,133 +246,95 @@ func (cli *socketClient) CheckTxAsync(tx []byte) *ReqRes {
return cli.queueRequest(types.ToRequestCheckTx(tx)) return cli.queueRequest(types.ToRequestCheckTx(tx))
} }
func (cli *socketClient) QueryAsync(reqQuery types.RequestQuery) *ReqRes { func (cli *socketClient) QueryAsync(req types.RequestQuery) *ReqRes {
return cli.queueRequest(types.ToRequestQuery(reqQuery)) return cli.queueRequest(types.ToRequestQuery(req))
} }
func (cli *socketClient) CommitAsync() *ReqRes { func (cli *socketClient) CommitAsync() *ReqRes {
return cli.queueRequest(types.ToRequestCommit()) return cli.queueRequest(types.ToRequestCommit())
} }
func (cli *socketClient) InitChainAsync(params types.RequestInitChain) *ReqRes { func (cli *socketClient) InitChainAsync(req types.RequestInitChain) *ReqRes {
return cli.queueRequest(types.ToRequestInitChain(params)) return cli.queueRequest(types.ToRequestInitChain(req))
} }
func (cli *socketClient) BeginBlockAsync(params types.RequestBeginBlock) *ReqRes { func (cli *socketClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes {
return cli.queueRequest(types.ToRequestBeginBlock(params)) return cli.queueRequest(types.ToRequestBeginBlock(req))
} }
func (cli *socketClient) EndBlockAsync(height uint64) *ReqRes { func (cli *socketClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes {
return cli.queueRequest(types.ToRequestEndBlock(height)) return cli.queueRequest(types.ToRequestEndBlock(req))
} }
//---------------------------------------- //----------------------------------------
func (cli *socketClient) EchoSync(msg string) (res types.Result) {
reqres := cli.queueRequest(types.ToRequestEcho(msg))
cli.FlushSync()
if err := cli.Error(); err != nil {
return types.ErrInternalError.SetLog(err.Error())
}
resp := reqres.Response.GetEcho()
return types.Result{Code: OK, Data: []byte(resp.Message)}
}
func (cli *socketClient) FlushSync() error { func (cli *socketClient) FlushSync() error {
reqRes := cli.queueRequest(types.ToRequestFlush()) reqRes := cli.queueRequest(types.ToRequestFlush())
if err := cli.Error(); err != nil { if err := cli.Error(); err != nil {
return types.ErrInternalError.SetLog(err.Error()) return err
} }
reqRes.Wait() // NOTE: if we don't flush the queue, its possible to get stuck here reqRes.Wait() // NOTE: if we don't flush the queue, its possible to get stuck here
return cli.Error() return cli.Error()
} }
func (cli *socketClient) InfoSync(req types.RequestInfo) (resInfo types.ResponseInfo, err 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)) reqres := cli.queueRequest(types.ToRequestInfo(req))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetInfo(), cli.Error()
return resInfo, err
}
if resInfo_ := reqres.Response.GetInfo(); resInfo_ != nil {
return *resInfo_, nil
}
return resInfo, nil
} }
func (cli *socketClient) SetOptionSync(key string, value string) (res types.Result) { func (cli *socketClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) {
reqres := cli.queueRequest(types.ToRequestSetOption(key, value)) reqres := cli.queueRequest(types.ToRequestSetOption(req))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetSetOption(), cli.Error()
return types.ErrInternalError.SetLog(err.Error())
}
resp := reqres.Response.GetSetOption()
return types.Result{Code: OK, Data: nil, Log: resp.Log}
} }
func (cli *socketClient) DeliverTxSync(tx []byte) (res types.Result) { func (cli *socketClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) {
reqres := cli.queueRequest(types.ToRequestDeliverTx(tx)) reqres := cli.queueRequest(types.ToRequestDeliverTx(tx))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetDeliverTx(), cli.Error()
return types.ErrInternalError.SetLog(err.Error())
}
resp := reqres.Response.GetDeliverTx()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *socketClient) CheckTxSync(tx []byte) (res types.Result) { func (cli *socketClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) {
reqres := cli.queueRequest(types.ToRequestCheckTx(tx)) reqres := cli.queueRequest(types.ToRequestCheckTx(tx))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetCheckTx(), cli.Error()
return types.ErrInternalError.SetLog(err.Error())
}
resp := reqres.Response.GetCheckTx()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *socketClient) QuerySync(reqQuery types.RequestQuery) (resQuery types.ResponseQuery, err error) { func (cli *socketClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) {
reqres := cli.queueRequest(types.ToRequestQuery(reqQuery)) reqres := cli.queueRequest(types.ToRequestQuery(req))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetQuery(), cli.Error()
return resQuery, err
}
if resQuery_ := reqres.Response.GetQuery(); resQuery_ != nil {
return *resQuery_, nil
}
return resQuery, nil
} }
func (cli *socketClient) CommitSync() (res types.Result) { func (cli *socketClient) CommitSync() (*types.ResponseCommit, error) {
reqres := cli.queueRequest(types.ToRequestCommit()) reqres := cli.queueRequest(types.ToRequestCommit())
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetCommit(), cli.Error()
return types.ErrInternalError.SetLog(err.Error())
}
resp := reqres.Response.GetCommit()
return types.Result{Code: resp.Code, Data: resp.Data, Log: resp.Log}
} }
func (cli *socketClient) InitChainSync(params types.RequestInitChain) (err error) { func (cli *socketClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) {
cli.queueRequest(types.ToRequestInitChain(params)) reqres := cli.queueRequest(types.ToRequestInitChain(req))
cli.FlushSync() cli.FlushSync()
return cli.Error() return reqres.Response.GetInitChain(), cli.Error()
} }
func (cli *socketClient) BeginBlockSync(params types.RequestBeginBlock) (err error) { func (cli *socketClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) {
cli.queueRequest(types.ToRequestBeginBlock(params)) reqres := cli.queueRequest(types.ToRequestBeginBlock(req))
cli.FlushSync() cli.FlushSync()
return cli.Error() return reqres.Response.GetBeginBlock(), cli.Error()
} }
func (cli *socketClient) EndBlockSync(height uint64) (resEndBlock types.ResponseEndBlock, err error) { func (cli *socketClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) {
reqres := cli.queueRequest(types.ToRequestEndBlock(height)) reqres := cli.queueRequest(types.ToRequestEndBlock(req))
cli.FlushSync() cli.FlushSync()
if err := cli.Error(); err != nil { return reqres.Response.GetEndBlock(), cli.Error()
return resEndBlock, err
}
if blk := reqres.Response.GetEndBlock(); blk != nil {
return *blk, nil
}
return resEndBlock, nil
} }
//---------------------------------------- //----------------------------------------

View File

@ -10,59 +10,47 @@ import (
"os/exec" "os/exec"
"strings" "strings"
abcicli "github.com/tendermint/abci/client" "github.com/spf13/cobra"
"github.com/tendermint/abci/example/counter"
"github.com/tendermint/abci/example/dummy"
"github.com/tendermint/abci/server"
"github.com/tendermint/abci/types"
"github.com/tendermint/abci/version"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/spf13/cobra" abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/example/counter"
"github.com/tendermint/abci/example/dummy"
"github.com/tendermint/abci/server"
servertest "github.com/tendermint/abci/tests/server"
"github.com/tendermint/abci/types"
"github.com/tendermint/abci/version"
) )
// Structure for data passed to print response.
type response struct {
// generic abci response
Data []byte
Code types.CodeType
Log string
Query *queryResponse
}
type queryResponse struct {
Key []byte
Value []byte
Height uint64
Proof []byte
}
// client is a global variable so it can be reused by the console // client is a global variable so it can be reused by the console
var client abcicli.Client var (
client abcicli.Client
var logger log.Logger logger log.Logger
)
// flags // flags
var ( var (
// global // global
address string flagAddress string
abci string flagAbci string
verbose bool flagVerbose bool // for the println output
flagLogLevel string // for the logger
// query // query
path string flagPath string
height int flagHeight int
prove bool flagProve bool
// counter // counter
addrC string flagAddrC string
serial bool flagSerial bool
// dummy // dummy
addrD string flagAddrD string
persist string flagPersist string
) )
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
@ -79,16 +67,20 @@ var RootCmd = &cobra.Command{
} }
if logger == nil { if logger == nil {
logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError()) allowLevel, err := log.AllowLevel(flagLogLevel)
if err != nil {
return err
}
logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), allowLevel)
} }
if client == nil { if client == nil {
var err error var err error
client, err = abcicli.NewClient(address, abci, false) client, err = abcicli.NewClient(flagAddress, flagAbci, false)
if err != nil { if err != nil {
return err return err
} }
client.SetLogger(logger.With("module", "abci-client")) client.SetLogger(logger.With("module", "abci-client"))
if _, err := client.Start(); err != nil { if err := client.Start(); err != nil {
return err return err
} }
} }
@ -96,32 +88,50 @@ var RootCmd = &cobra.Command{
}, },
} }
func Execute() { // Structure for data passed to print response.
type response struct {
// generic abci response
Data []byte
Code uint32
Log string
Query *queryResponse
}
type queryResponse struct {
Key []byte
Value []byte
Height int64
Proof []byte
}
func Execute() error {
addGlobalFlags() addGlobalFlags()
addCommands() addCommands()
RootCmd.Execute() return RootCmd.Execute()
} }
func addGlobalFlags() { func addGlobalFlags() {
RootCmd.PersistentFlags().StringVarP(&address, "address", "", "tcp://127.0.0.1:46658", "Address of application socket") RootCmd.PersistentFlags().StringVarP(&flagAddress, "address", "", "tcp://0.0.0.0:46658", "Address of application socket")
RootCmd.PersistentFlags().StringVarP(&abci, "abci", "", "socket", "Either socket or grpc") RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "Either socket or grpc")
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the command and results as if it were a console session") 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() { func addQueryFlags() {
queryCmd.PersistentFlags().StringVarP(&path, "path", "", "/store", "Path to prefix query with") queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "Path to prefix query with")
queryCmd.PersistentFlags().IntVarP(&height, "height", "", 0, "Height to query the blockchain at") queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "Height to query the blockchain at")
queryCmd.PersistentFlags().BoolVarP(&prove, "prove", "", false, "Whether or not to return a merkle proof of the query result") queryCmd.PersistentFlags().BoolVarP(&flagProve, "prove", "", false, "Whether or not to return a merkle proof of the query result")
} }
func addCounterFlags() { func addCounterFlags() {
counterCmd.PersistentFlags().StringVarP(&addrC, "addr", "", "tcp://0.0.0.0:46658", "Listen address") counterCmd.PersistentFlags().StringVarP(&flagAddrC, "addr", "", "tcp://0.0.0.0:46658", "Listen address")
counterCmd.PersistentFlags().BoolVarP(&serial, "serial", "", false, "Enforce incrementing (serial) transactions") counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "Enforce incrementing (serial) transactions")
} }
func addDummyFlags() { func addDummyFlags() {
dummyCmd.PersistentFlags().StringVarP(&addrD, "addr", "", "tcp://0.0.0.0:46658", "Listen address") dummyCmd.PersistentFlags().StringVarP(&flagAddrD, "addr", "", "tcp://0.0.0.0:46658", "Listen address")
dummyCmd.PersistentFlags().StringVarP(&persist, "persist", "", "", "Directory to use for a database") dummyCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "Directory to use for a database")
} }
func addCommands() { func addCommands() {
RootCmd.AddCommand(batchCmd) RootCmd.AddCommand(batchCmd)
@ -133,6 +143,7 @@ func addCommands() {
RootCmd.AddCommand(checkTxCmd) RootCmd.AddCommand(checkTxCmd)
RootCmd.AddCommand(commitCmd) RootCmd.AddCommand(commitCmd)
RootCmd.AddCommand(versionCmd) RootCmd.AddCommand(versionCmd)
RootCmd.AddCommand(testCmd)
addQueryFlags() addQueryFlags()
RootCmd.AddCommand(queryCmd) RootCmd.AddCommand(queryCmd)
@ -263,6 +274,16 @@ var dummyCmd = &cobra.Command{
}, },
} }
var testCmd = &cobra.Command{
Use: "test",
Short: "Run integration tests",
Long: "",
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 // Generates new Args array based off of previous call args to maintain flag persistence
func persistentArgs(line []byte) []string { func persistentArgs(line []byte) []string {
@ -279,6 +300,53 @@ func persistentArgs(line []byte) []string {
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
func or(err1 error, err2 error) error {
if err1 == nil {
return err2
} else {
return err1
}
}
func cmdTest(cmd *cobra.Command, args []string) error {
fmt.Println("Running tests")
var err error
err = servertest.InitChain(client)
fmt.Println("")
err = or(err, servertest.SetOption(client, "serial", "on"))
fmt.Println("")
err = or(err, servertest.Commit(client, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil))
fmt.Println("")
err = or(err, servertest.Commit(client, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeOK, nil))
fmt.Println("")
err = or(err, servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x01}, code.CodeTypeOK, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00, 0x02}, code.CodeTypeOK, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00, 0x03}, code.CodeTypeOK, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00, 0x00, 0x04}, code.CodeTypeOK, nil))
fmt.Println("")
err = or(err, servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil))
fmt.Println("")
err = or(err, servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}))
if err != nil {
return errors.New("Some checks didn't pass, please inspect stdout to see the exact failures.")
}
return nil
}
func cmdBatch(cmd *cobra.Command, args []string) error { func cmdBatch(cmd *cobra.Command, args []string) error {
bufReader := bufio.NewReader(os.Stdin) bufReader := bufio.NewReader(os.Stdin)
for { for {
@ -294,7 +362,7 @@ func cmdBatch(cmd *cobra.Command, args []string) error {
} }
pArgs := persistentArgs(line) pArgs := persistentArgs(line)
out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() // nolint: gas
if err != nil { if err != nil {
return err return err
} }
@ -316,7 +384,7 @@ func cmdConsole(cmd *cobra.Command, args []string) error {
} }
pArgs := persistentArgs(line) pArgs := persistentArgs(line)
out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() // nolint: gas
if err != nil { if err != nil {
return err return err
} }
@ -327,9 +395,12 @@ func cmdConsole(cmd *cobra.Command, args []string) error {
// Have the application echo a message // Have the application echo a message
func cmdEcho(cmd *cobra.Command, args []string) error { func cmdEcho(cmd *cobra.Command, args []string) error {
resEcho := client.EchoSync(args[0]) res, err := client.EchoSync(args[0])
if err != nil {
return err
}
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Data: resEcho.Data, Data: []byte(res.Message),
}) })
return nil return nil
} }
@ -340,21 +411,26 @@ func cmdInfo(cmd *cobra.Command, args []string) error {
if len(args) == 1 { if len(args) == 1 {
version = args[0] version = args[0]
} }
resInfo, err := client.InfoSync(types.RequestInfo{version}) res, err := client.InfoSync(types.RequestInfo{version})
if err != nil { if err != nil {
return err return err
} }
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Data: []byte(resInfo.Data), Data: []byte(res.Data),
}) })
return nil return nil
} }
// Set an option on the application // Set an option on the application
func cmdSetOption(cmd *cobra.Command, args []string) error { func cmdSetOption(cmd *cobra.Command, args []string) error {
resSetOption := client.SetOptionSync(args[0], args[1]) key, val := args[0], args[1]
res, err := client.SetOptionSync(types.RequestSetOption{key, val})
if err != nil {
return err
}
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Log: resSetOption.Log, Code: res.Code,
Log: res.Log,
}) })
return nil return nil
} }
@ -365,7 +441,10 @@ func cmdDeliverTx(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
res := client.DeliverTxSync(txBytes) res, err := client.DeliverTxSync(txBytes)
if err != nil {
return err
}
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
@ -380,7 +459,10 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
res := client.CheckTxSync(txBytes) res, err := client.CheckTxSync(txBytes)
if err != nil {
return err
}
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
@ -391,7 +473,10 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error {
// Get application Merkle root hash // Get application Merkle root hash
func cmdCommit(cmd *cobra.Command, args []string) error { func cmdCommit(cmd *cobra.Command, args []string) error {
res := client.CommitSync() res, err := client.CommitSync()
if err != nil {
return err
}
printResponse(cmd, args, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
@ -409,9 +494,9 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
resQuery, err := client.QuerySync(types.RequestQuery{ resQuery, err := client.QuerySync(types.RequestQuery{
Data: queryBytes, Data: queryBytes,
Path: path, Path: flagPath,
Height: uint64(height), Height: int64(flagHeight),
Prove: prove, Prove: flagProve,
}) })
if err != nil { if err != nil {
return err return err
@ -431,17 +516,17 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
func cmdCounter(cmd *cobra.Command, args []string) error { func cmdCounter(cmd *cobra.Command, args []string) error {
app := counter.NewCounterApplication(serial) app := counter.NewCounterApplication(flagSerial)
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Start the listener // Start the listener
srv, err := server.NewServer(addrC, abci, app) srv, err := server.NewServer(flagAddrC, flagAbci, app)
if err != nil { if err != nil {
return err return err
} }
srv.SetLogger(logger.With("module", "abci-server")) srv.SetLogger(logger.With("module", "abci-server"))
if _, err := srv.Start(); err != nil { if err := srv.Start(); err != nil {
return err return err
} }
@ -458,20 +543,20 @@ func cmdDummy(cmd *cobra.Command, args []string) error {
// Create the application - in memory or persisted to disk // Create the application - in memory or persisted to disk
var app types.Application var app types.Application
if persist == "" { if flagPersist == "" {
app = dummy.NewDummyApplication() app = dummy.NewDummyApplication()
} else { } else {
app = dummy.NewPersistentDummyApplication(persist) app = dummy.NewPersistentDummyApplication(flagPersist)
app.(*dummy.PersistentDummyApplication).SetLogger(logger.With("module", "dummy")) app.(*dummy.PersistentDummyApplication).SetLogger(logger.With("module", "dummy"))
} }
// Start the listener // Start the listener
srv, err := server.NewServer(addrD, abci, app) srv, err := server.NewServer(flagAddrD, flagAbci, app)
if err != nil { if err != nil {
return err return err
} }
srv.SetLogger(logger.With("module", "abci-server")) srv.SetLogger(logger.With("module", "abci-server"))
if _, err := srv.Start(); err != nil { if err := srv.Start(); err != nil {
return err return err
} }
@ -487,12 +572,17 @@ func cmdDummy(cmd *cobra.Command, args []string) error {
func printResponse(cmd *cobra.Command, args []string, rsp response) { func printResponse(cmd *cobra.Command, args []string, rsp response) {
if verbose { if flagVerbose {
fmt.Println(">", cmd.Use, strings.Join(args, " ")) fmt.Println(">", cmd.Use, strings.Join(args, " "))
} }
// Always print the status code. // Always print the status code.
fmt.Printf("-> code: %s\n", rsp.Code.String()) if rsp.Code == types.CodeTypeOK {
fmt.Printf("-> code: OK\n")
} else {
fmt.Printf("-> code: %d\n", rsp.Code)
}
if len(rsp.Data) != 0 { if len(rsp.Data) != 0 {
// Do no print this line when using the commit command // Do no print this line when using the commit command

View File

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

View File

@ -1,65 +0,0 @@
package main
import (
"flag"
"os"
"github.com/tendermint/abci/server"
"github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
func main() {
addrPtr := flag.String("addr", "tcp://0.0.0.0:46658", "Listen address")
abciPtr := flag.String("abci", "socket", "socket | grpc")
flag.Parse()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Start the listener
srv, err := server.NewServer(*addrPtr, *abciPtr, NewChainAwareApplication())
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
srv.SetLogger(logger.With("module", "abci-server"))
if _, err := srv.Start(); err != nil {
logger.Error(err.Error())
os.Exit(1)
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
srv.Stop()
})
}
type ChainAwareApplication struct {
types.BaseApplication
beginCount int
endCount int
}
func NewChainAwareApplication() *ChainAwareApplication {
return &ChainAwareApplication{}
}
func (app *ChainAwareApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
return types.ResponseQuery{
Value: []byte(cmn.Fmt("%d,%d", app.beginCount, app.endCount)),
}
}
func (app *ChainAwareApplication) BeginBlock(reqBeginBlock types.RequestBeginBlock) {
app.beginCount++
}
func (app *ChainAwareApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) {
app.endCount++
return
}

View File

@ -1,57 +0,0 @@
package main
import (
"strconv"
"strings"
"testing"
abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/server"
"github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
)
func TestChainAware(t *testing.T) {
app := NewChainAwareApplication()
// Start the listener
srv, err := server.NewServer("unix://test.sock", "socket", app)
if err != nil {
t.Fatal(err)
}
srv.SetLogger(log.TestingLogger().With("module", "abci-server"))
if _, err := srv.Start(); err != nil {
t.Fatal(err.Error())
}
defer srv.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()
n := uint64(5)
hash := []byte("fake block hash")
header := &types.Header{}
for i := uint64(0); i < n; i++ {
client.BeginBlockSync(types.RequestBeginBlock{hash, header})
client.EndBlockSync(i)
client.CommitSync()
}
r := app.Query(types.RequestQuery{})
spl := strings.Split(string(r.Value), ",")
if len(spl) != 2 {
t.Fatal("expected %d,%d ; got %s", n, n, string(r.Value))
}
beginCount, _ := strconv.Atoi(spl[0])
endCount, _ := strconv.Atoi(spl[1])
if uint64(beginCount) != n {
t.Fatalf("expected beginCount of %d, got %d", n, beginCount)
} else if uint64(endCount) != n {
t.Fatalf("expected endCount of %d, got %d", n, endCount)
}
}

11
example/code/code.go Normal file
View File

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

View File

@ -2,7 +2,9 @@ package counter
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
@ -23,52 +25,69 @@ func (app *CounterApplication) Info(req types.RequestInfo) types.ResponseInfo {
return types.ResponseInfo{Data: cmn.Fmt("{\"hashes\":%v,\"txs\":%v}", app.hashCount, app.txCount)} return types.ResponseInfo{Data: cmn.Fmt("{\"hashes\":%v,\"txs\":%v}", app.hashCount, app.txCount)}
} }
func (app *CounterApplication) SetOption(key string, value string) (log string) { func (app *CounterApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption {
key, value := req.Key, req.Value
if key == "serial" && value == "on" { if key == "serial" && value == "on" {
app.serial = true app.serial = true
} else {
return types.ResponseSetOption{
Code: code.CodeTypeBadOption,
Log: cmn.Fmt("Unknown key (%s) or value (%s)", key, value),
}
}
return types.ResponseSetOption{
Code: code.CodeTypeOK,
} }
return ""
} }
func (app *CounterApplication) DeliverTx(tx []byte) types.Result { func (app *CounterApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
if app.serial { if app.serial {
if len(tx) > 8 { if len(tx) > 8 {
return types.ErrEncodingError.SetLog(cmn.Fmt("Max tx size is 8 bytes, got %d", len(tx))) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))}
} }
tx8 := make([]byte, 8) tx8 := make([]byte, 8)
copy(tx8[len(tx8)-len(tx):], tx) copy(tx8[len(tx8)-len(tx):], tx)
txValue := binary.BigEndian.Uint64(tx8) txValue := binary.BigEndian.Uint64(tx8)
if txValue != uint64(app.txCount) { if txValue != uint64(app.txCount) {
return types.ErrBadNonce.SetLog(cmn.Fmt("Invalid nonce. Expected %v, got %v", app.txCount, txValue)) return types.ResponseDeliverTx{
Code: code.CodeTypeBadNonce,
Log: fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
} }
} }
app.txCount++ app.txCount++
return types.OK return types.ResponseDeliverTx{Code: code.CodeTypeOK}
} }
func (app *CounterApplication) CheckTx(tx []byte) types.Result { func (app *CounterApplication) CheckTx(tx []byte) types.ResponseCheckTx {
if app.serial { if app.serial {
if len(tx) > 8 { if len(tx) > 8 {
return types.ErrEncodingError.SetLog(cmn.Fmt("Max tx size is 8 bytes, got %d", len(tx))) return types.ResponseCheckTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))}
} }
tx8 := make([]byte, 8) tx8 := make([]byte, 8)
copy(tx8[len(tx8)-len(tx):], tx) copy(tx8[len(tx8)-len(tx):], tx)
txValue := binary.BigEndian.Uint64(tx8) txValue := binary.BigEndian.Uint64(tx8)
if txValue < uint64(app.txCount) { if txValue < uint64(app.txCount) {
return types.ErrBadNonce.SetLog(cmn.Fmt("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)) return types.ResponseCheckTx{
Code: code.CodeTypeBadNonce,
Log: fmt.Sprintf("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)}
} }
} }
return types.OK return types.ResponseCheckTx{Code: code.CodeTypeOK}
} }
func (app *CounterApplication) Commit() types.Result { func (app *CounterApplication) Commit() (resp types.ResponseCommit) {
app.hashCount++ app.hashCount++
if app.txCount == 0 { if app.txCount == 0 {
return types.OK return types.ResponseCommit{Code: code.CodeTypeOK}
} }
hash := make([]byte, 8) hash := make([]byte, 8)
binary.BigEndian.PutUint64(hash, uint64(app.txCount)) binary.BigEndian.PutUint64(hash, uint64(app.txCount))
return types.NewResultOK(hash, "") return types.ResponseCommit{Code: code.CodeTypeOK, Data: hash}
} }
func (app *CounterApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery { func (app *CounterApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery {

View File

@ -1,15 +1,18 @@
package dummy package dummy
import ( import (
"strings" "bytes"
"fmt"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/iavl" "github.com/tendermint/iavl"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db" dbm "github.com/tendermint/tmlibs/db"
) )
var _ types.Application = (*DummyApplication)(nil)
type DummyApplication struct { type DummyApplication struct {
types.BaseApplication types.BaseApplication
@ -22,25 +25,32 @@ func NewDummyApplication() *DummyApplication {
} }
func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())} return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size())}
} }
// tx is either "key=value" or just arbitrary bytes // tx is either "key=value" or just arbitrary bytes
func (app *DummyApplication) DeliverTx(tx []byte) types.Result { func (app *DummyApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
parts := strings.Split(string(tx), "=") var key, value []byte
parts := bytes.Split(tx, []byte("="))
if len(parts) == 2 { if len(parts) == 2 {
app.state.Set([]byte(parts[0]), []byte(parts[1])) key, value = parts[0], parts[1]
} else { } else {
app.state.Set(tx, tx) key, value = tx, tx
} }
return types.OK app.state.Set(key, value)
tags := []*types.KVPair{
{Key: "app.creator", ValueType: types.KVPair_STRING, ValueString: "jae"},
{Key: "app.key", ValueType: types.KVPair_STRING, ValueString: string(key)},
}
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags}
} }
func (app *DummyApplication) CheckTx(tx []byte) types.Result { func (app *DummyApplication) CheckTx(tx []byte) types.ResponseCheckTx {
return types.OK return types.ResponseCheckTx{Code: code.CodeTypeOK}
} }
func (app *DummyApplication) Commit() types.Result { func (app *DummyApplication) Commit() types.ResponseCommit {
// Save a new version // Save a new version
var hash []byte var hash []byte
var err error var err error
@ -55,7 +65,7 @@ func (app *DummyApplication) Commit() types.Result {
} }
} }
return types.NewResultOK(hash, "") return types.ResponseCommit{Code: code.CodeTypeOK, Data: hash}
} }
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {

View File

@ -7,12 +7,15 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abcicli "github.com/tendermint/abci/client"
abciserver "github.com/tendermint/abci/server"
"github.com/tendermint/abci/types"
"github.com/tendermint/iavl" "github.com/tendermint/iavl"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log" "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 testDummy(t *testing.T, app types.Application, tx []byte, key, value string) { func testDummy(t *testing.T, app types.Application, tx []byte, key, value string) {
@ -27,7 +30,7 @@ func testDummy(t *testing.T, app types.Application, tx []byte, key, value string
Path: "/store", Path: "/store",
Data: []byte(key), Data: []byte(key),
}) })
require.Equal(t, types.CodeType_OK, resQuery.Code) require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, value, string(resQuery.Value)) require.Equal(t, value, string(resQuery.Value))
// make sure proof is fine // make sure proof is fine
@ -36,7 +39,7 @@ func testDummy(t *testing.T, app types.Application, tx []byte, key, value string
Data: []byte(key), Data: []byte(key),
Prove: true, Prove: true,
}) })
require.Equal(t, types.CodeType_OK, resQuery.Code) require.EqualValues(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, value, string(resQuery.Value)) require.Equal(t, value, string(resQuery.Value))
proof, err := iavl.ReadKeyExistsProof(resQuery.Proof) proof, err := iavl.ReadKeyExistsProof(resQuery.Proof)
require.Nil(t, err) require.Nil(t, err)
@ -79,7 +82,7 @@ func TestPersistentDummyInfo(t *testing.T) {
} }
dummy := NewPersistentDummyApplication(dir) dummy := NewPersistentDummyApplication(dir)
InitDummy(dummy) InitDummy(dummy)
height := uint64(0) height := int64(0)
resInfo := dummy.Info(types.RequestInfo{}) resInfo := dummy.Info(types.RequestInfo{})
if resInfo.LastBlockHeight != height { if resInfo.LastBlockHeight != height {
@ -87,13 +90,13 @@ func TestPersistentDummyInfo(t *testing.T) {
} }
// make and apply block // make and apply block
height = uint64(1) height = int64(1)
hash := []byte("foo") hash := []byte("foo")
header := &types.Header{ header := &types.Header{
Height: uint64(height), Height: int64(height),
} }
dummy.BeginBlock(types.RequestBeginBlock{hash, header}) dummy.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil})
dummy.EndBlock(height) dummy.EndBlock(types.RequestEndBlock{header.Height})
dummy.Commit() dummy.Commit()
resInfo = dummy.Info(types.RequestInfo{}) resInfo = dummy.Info(types.RequestInfo{})
@ -170,19 +173,19 @@ func TestValSetChanges(t *testing.T) {
func makeApplyBlock(t *testing.T, dummy types.Application, heightInt int, diff []*types.Validator, txs ...[]byte) { func makeApplyBlock(t *testing.T, dummy types.Application, heightInt int, diff []*types.Validator, txs ...[]byte) {
// make and apply block // make and apply block
height := uint64(heightInt) height := int64(heightInt)
hash := []byte("foo") hash := []byte("foo")
header := &types.Header{ header := &types.Header{
Height: height, Height: height,
} }
dummy.BeginBlock(types.RequestBeginBlock{hash, header}) dummy.BeginBlock(types.RequestBeginBlock{hash, header, nil, nil})
for _, tx := range txs { for _, tx := range txs {
if r := dummy.DeliverTx(tx); r.IsErr() { if r := dummy.DeliverTx(tx); r.IsErr() {
t.Fatal(r) t.Fatal(r)
} }
} }
resEndBlock := dummy.EndBlock(height) resEndBlock := dummy.EndBlock(types.RequestEndBlock{header.Height})
dummy.Commit() dummy.Commit()
valsEqual(t, diff, resEndBlock.Diffs) valsEqual(t, diff, resEndBlock.Diffs)
@ -212,14 +215,14 @@ func makeSocketClientServer(app types.Application, name string) (abcicli.Client,
server := abciserver.NewSocketServer(socket, app) server := abciserver.NewSocketServer(socket, app)
server.SetLogger(logger.With("module", "abci-server")) server.SetLogger(logger.With("module", "abci-server"))
if _, err := server.Start(); err != nil { if err := server.Start(); err != nil {
return nil, nil, err return nil, nil, err
} }
// Connect to the socket // Connect to the socket
client := abcicli.NewSocketClient(socket, false) client := abcicli.NewSocketClient(socket, false)
client.SetLogger(logger.With("module", "abci-client")) client.SetLogger(logger.With("module", "abci-client"))
if _, err := client.Start(); err != nil { if err := client.Start(); err != nil {
server.Stop() server.Stop()
return nil, nil, err return nil, nil, err
} }
@ -235,13 +238,13 @@ func makeGRPCClientServer(app types.Application, name string) (abcicli.Client, c
gapp := types.NewGRPCApplication(app) gapp := types.NewGRPCApplication(app)
server := abciserver.NewGRPCServer(socket, gapp) server := abciserver.NewGRPCServer(socket, gapp)
server.SetLogger(logger.With("module", "abci-server")) server.SetLogger(logger.With("module", "abci-server"))
if _, err := server.Start(); err != nil { if err := server.Start(); err != nil {
return nil, nil, err return nil, nil, err
} }
client := abcicli.NewGRPCClient(socket, true) client := abcicli.NewGRPCClient(socket, true)
client.SetLogger(logger.With("module", "abci-client")) client.SetLogger(logger.With("module", "abci-client"))
if _, err := client.Start(); err != nil { if err := client.Start(); err != nil {
server.Stop() server.Stop()
return nil, nil, err return nil, nil, err
} }
@ -281,10 +284,12 @@ func runClientTests(t *testing.T, client abcicli.Client) {
} }
func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) { func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) {
ar := app.DeliverTxSync(tx) ar, err := app.DeliverTxSync(tx)
require.NoError(t, err)
require.False(t, ar.IsErr(), ar) require.False(t, ar.IsErr(), ar)
// repeating tx doesn't raise error // repeating tx doesn't raise error
ar = app.DeliverTxSync(tx) ar, err = app.DeliverTxSync(tx)
require.NoError(t, err)
require.False(t, ar.IsErr(), ar) require.False(t, ar.IsErr(), ar)
// make sure query is fine // make sure query is fine
@ -293,7 +298,7 @@ func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string)
Data: []byte(key), Data: []byte(key),
}) })
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, types.CodeType_OK, resQuery.Code) require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, value, string(resQuery.Value)) require.Equal(t, value, string(resQuery.Value))
// make sure proof is fine // make sure proof is fine
@ -303,7 +308,7 @@ func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string)
Prove: true, Prove: true,
}) })
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, types.CodeType_OK, resQuery.Code) require.Equal(t, code.CodeTypeOK, resQuery.Code)
require.Equal(t, value, string(resQuery.Value)) require.Equal(t, value, string(resQuery.Value))
proof, err := iavl.ReadKeyExistsProof(resQuery.Proof) proof, err := iavl.ReadKeyExistsProof(resQuery.Proof)
require.Nil(t, err) require.Nil(t, err)

View File

@ -11,7 +11,7 @@ import (
func RandVal(i int) *types.Validator { func RandVal(i int) *types.Validator {
pubkey := crypto.GenPrivKeyEd25519FromSecret([]byte(cmn.Fmt("test%d", i))).PubKey().Bytes() pubkey := crypto.GenPrivKeyEd25519FromSecret([]byte(cmn.Fmt("test%d", i))).PubKey().Bytes()
power := cmn.RandUint16() + 1 power := cmn.RandUint16() + 1
return &types.Validator{pubkey, uint64(power)} return &types.Validator{pubkey, int64(power)}
} }
// RandVals returns a list of cnt validators for initializing // RandVals returns a list of cnt validators for initializing

View File

@ -3,9 +3,11 @@ package dummy
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto" crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/iavl" "github.com/tendermint/iavl"
@ -20,6 +22,8 @@ const (
//----------------------------------------- //-----------------------------------------
var _ types.Application = (*PersistentDummyApplication)(nil)
type PersistentDummyApplication struct { type PersistentDummyApplication struct {
app *DummyApplication app *DummyApplication
@ -49,19 +53,20 @@ func (app *PersistentDummyApplication) SetLogger(l log.Logger) {
app.logger = l app.logger = l
} }
func (app *PersistentDummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { func (app *PersistentDummyApplication) Info(req types.RequestInfo) types.ResponseInfo {
resInfo = app.app.Info(req) res := app.app.Info(req)
resInfo.LastBlockHeight = app.app.state.LatestVersion() var latestVersion uint64 = app.app.state.LatestVersion() // TODO: change to int64
resInfo.LastBlockAppHash = app.app.state.Hash() res.LastBlockHeight = int64(latestVersion)
return resInfo res.LastBlockAppHash = app.app.state.Hash()
return res
} }
func (app *PersistentDummyApplication) SetOption(key string, value string) (log string) { func (app *PersistentDummyApplication) SetOption(req types.RequestSetOption) types.ResponseSetOption {
return app.app.SetOption(key, value) return app.app.SetOption(req)
} }
// tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes // tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes
func (app *PersistentDummyApplication) DeliverTx(tx []byte) types.Result { func (app *PersistentDummyApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
// if it starts with "val:", update the validator set // if it starts with "val:", update the validator set
// format is "val:pubkey/power" // format is "val:pubkey/power"
if isValidatorTx(tx) { if isValidatorTx(tx) {
@ -74,12 +79,12 @@ func (app *PersistentDummyApplication) DeliverTx(tx []byte) types.Result {
return app.app.DeliverTx(tx) return app.app.DeliverTx(tx)
} }
func (app *PersistentDummyApplication) CheckTx(tx []byte) types.Result { func (app *PersistentDummyApplication) CheckTx(tx []byte) types.ResponseCheckTx {
return app.app.CheckTx(tx) return app.app.CheckTx(tx)
} }
// Commit will panic if InitChain was not called // Commit will panic if InitChain was not called
func (app *PersistentDummyApplication) Commit() types.Result { func (app *PersistentDummyApplication) Commit() types.ResponseCommit {
// Save a new version for next height // Save a new version for next height
height := app.app.state.LatestVersion() + 1 height := app.app.state.LatestVersion() + 1
@ -93,7 +98,7 @@ func (app *PersistentDummyApplication) Commit() types.Result {
} }
app.logger.Info("Commit block", "height", height, "root", appHash) app.logger.Info("Commit block", "height", height, "root", appHash)
return types.NewResultOK(appHash, "") return types.ResponseCommit{Code: code.CodeTypeOK, Data: appHash}
} }
func (app *PersistentDummyApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery { func (app *PersistentDummyApplication) Query(reqQuery types.RequestQuery) types.ResponseQuery {
@ -101,23 +106,25 @@ func (app *PersistentDummyApplication) Query(reqQuery types.RequestQuery) types.
} }
// Save the validators in the merkle tree // Save the validators in the merkle tree
func (app *PersistentDummyApplication) InitChain(params types.RequestInitChain) { func (app *PersistentDummyApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain {
for _, v := range params.Validators { for _, v := range req.Validators {
r := app.updateValidator(v) r := app.updateValidator(v)
if r.IsErr() { if r.IsErr() {
app.logger.Error("Error updating validators", "r", r) app.logger.Error("Error updating validators", "r", r)
} }
} }
return types.ResponseInitChain{}
} }
// Track the block hash and header information // Track the block hash and header information
func (app *PersistentDummyApplication) BeginBlock(params types.RequestBeginBlock) { func (app *PersistentDummyApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock {
// reset valset changes // reset valset changes
app.changes = make([]*types.Validator, 0) app.changes = make([]*types.Validator, 0)
return types.ResponseBeginBlock{}
} }
// Update the validator set // Update the validator set
func (app *PersistentDummyApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) { func (app *PersistentDummyApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
return types.ResponseEndBlock{Diffs: app.changes} return types.ResponseEndBlock{Diffs: app.changes}
} }
@ -139,7 +146,7 @@ func (app *PersistentDummyApplication) Validators() (validators []*types.Validat
return return
} }
func MakeValSetChangeTx(pubkey []byte, power uint64) []byte { func MakeValSetChangeTx(pubkey []byte, power int64) []byte {
return []byte(cmn.Fmt("val:%X/%d", pubkey, power)) return []byte(cmn.Fmt("val:%X/%d", pubkey, power))
} }
@ -148,50 +155,62 @@ func isValidatorTx(tx []byte) bool {
} }
// format is "val:pubkey1/power1,addr2/power2,addr3/power3"tx // format is "val:pubkey1/power1,addr2/power2,addr3/power3"tx
func (app *PersistentDummyApplication) execValidatorTx(tx []byte) types.Result { func (app *PersistentDummyApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {
tx = tx[len(ValidatorSetChangePrefix):] tx = tx[len(ValidatorSetChangePrefix):]
//get the pubkey and power //get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "/") pubKeyAndPower := strings.Split(string(tx), "/")
if len(pubKeyAndPower) != 2 { if len(pubKeyAndPower) != 2 {
return types.ErrEncodingError.SetLog(cmn.Fmt("Expected 'pubkey/power'. Got %v", pubKeyAndPower)) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey/power'. Got %v", pubKeyAndPower)}
} }
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1] pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey, ensuring its go-crypto encoded // decode the pubkey, ensuring its go-crypto encoded
pubkey, err := hex.DecodeString(pubkeyS) pubkey, err := hex.DecodeString(pubkeyS)
if err != nil { if err != nil {
return types.ErrEncodingError.SetLog(cmn.Fmt("Pubkey (%s) is invalid hex", pubkeyS)) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid hex", pubkeyS)}
} }
_, err = crypto.PubKeyFromBytes(pubkey) _, err = crypto.PubKeyFromBytes(pubkey)
if err != nil { if err != nil {
return types.ErrEncodingError.SetLog(cmn.Fmt("Pubkey (%X) is invalid go-crypto encoded", pubkey)) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%X) is invalid go-crypto encoded", pubkey)}
} }
// decode the power // decode the power
power, err := strconv.Atoi(powerS) power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil { if err != nil {
return types.ErrEncodingError.SetLog(cmn.Fmt("Power (%s) is not an int", powerS)) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
} }
// update // update
return app.updateValidator(&types.Validator{pubkey, uint64(power)}) return app.updateValidator(&types.Validator{pubkey, power})
} }
// add, update, or remove a validator // add, update, or remove a validator
func (app *PersistentDummyApplication) updateValidator(v *types.Validator) types.Result { func (app *PersistentDummyApplication) updateValidator(v *types.Validator) types.ResponseDeliverTx {
key := []byte("val:" + string(v.PubKey)) key := []byte("val:" + string(v.PubKey))
if v.Power == 0 { if v.Power == 0 {
// remove validator // remove validator
if !app.app.state.Has(key) { if !app.app.state.Has(key) {
return types.ErrUnauthorized.SetLog(cmn.Fmt("Cannot remove non-existent validator %X", key)) return types.ResponseDeliverTx{
Code: code.CodeTypeUnauthorized,
Log: fmt.Sprintf("Cannot remove non-existent validator %X", key)}
} }
app.app.state.Remove(key) app.app.state.Remove(key)
} else { } else {
// add or update validator // add or update validator
value := bytes.NewBuffer(make([]byte, 0)) value := bytes.NewBuffer(make([]byte, 0))
if err := types.WriteMessage(v, value); err != nil { if err := types.WriteMessage(v, value); err != nil {
return types.ErrInternalError.SetLog(cmn.Fmt("Error encoding validator: %v", err)) return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Error encoding validator: %v", err)}
} }
app.app.state.Set(key, value.Bytes()) app.app.state.Set(key, value.Bytes())
} }
@ -199,5 +218,5 @@ func (app *PersistentDummyApplication) updateValidator(v *types.Validator) types
// we only update the changes array if we successfully updated the tree // we only update the changes array if we successfully updated the tree
app.changes = append(app.changes, v) app.changes = append(app.changes, v)
return types.OK return types.ResponseDeliverTx{Code: code.CodeTypeOK}
} }

View File

@ -11,12 +11,14 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
abcicli "github.com/tendermint/abci/client" abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/example/dummy" "github.com/tendermint/abci/example/dummy"
abciserver "github.com/tendermint/abci/server" abciserver "github.com/tendermint/abci/server"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
) )
func TestDummy(t *testing.T) { func TestDummy(t *testing.T) {
@ -40,7 +42,7 @@ func testStream(t *testing.T, app types.Application) {
// Start the listener // Start the listener
server := abciserver.NewSocketServer("unix://test.sock", app) server := abciserver.NewSocketServer("unix://test.sock", app)
server.SetLogger(log.TestingLogger().With("module", "abci-server")) server.SetLogger(log.TestingLogger().With("module", "abci-server"))
if _, err := server.Start(); err != nil { if err := server.Start(); err != nil {
t.Fatalf("Error starting socket server: %v", err.Error()) t.Fatalf("Error starting socket server: %v", err.Error())
} }
defer server.Stop() defer server.Stop()
@ -48,7 +50,7 @@ func testStream(t *testing.T, app types.Application) {
// Connect to the socket // Connect to the socket
client := abcicli.NewSocketClient("unix://test.sock", false) client := abcicli.NewSocketClient("unix://test.sock", false)
client.SetLogger(log.TestingLogger().With("module", "abci-client")) client.SetLogger(log.TestingLogger().With("module", "abci-client"))
if _, err := client.Start(); err != nil { if err := client.Start(); err != nil {
t.Fatalf("Error starting socket client: %v", err.Error()) t.Fatalf("Error starting socket client: %v", err.Error())
} }
defer client.Stop() defer client.Stop()
@ -60,7 +62,7 @@ func testStream(t *testing.T, app types.Application) {
switch r := res.Value.(type) { switch r := res.Value.(type) {
case *types.Response_DeliverTx: case *types.Response_DeliverTx:
counter++ counter++
if r.DeliverTx.Code != types.CodeType_OK { if r.DeliverTx.Code != code.CodeTypeOK {
t.Error("DeliverTx failed with ret_code", r.DeliverTx.Code) t.Error("DeliverTx failed with ret_code", r.DeliverTx.Code)
} }
if counter > numDeliverTxs { if counter > numDeliverTxs {
@ -113,7 +115,7 @@ func testGRPCSync(t *testing.T, app *types.GRPCApplication) {
// Start the listener // Start the listener
server := abciserver.NewGRPCServer("unix://test.sock", app) server := abciserver.NewGRPCServer("unix://test.sock", app)
server.SetLogger(log.TestingLogger().With("module", "abci-server")) server.SetLogger(log.TestingLogger().With("module", "abci-server"))
if _, err := server.Start(); err != nil { if err := server.Start(); err != nil {
t.Fatalf("Error starting GRPC server: %v", err.Error()) t.Fatalf("Error starting GRPC server: %v", err.Error())
} }
defer server.Stop() defer server.Stop()
@ -135,7 +137,7 @@ func testGRPCSync(t *testing.T, app *types.GRPCApplication) {
t.Fatalf("Error in GRPC DeliverTx: %v", err.Error()) t.Fatalf("Error in GRPC DeliverTx: %v", err.Error())
} }
counter++ counter++
if response.Code != types.CodeType_OK { if response.Code != code.CodeTypeOK {
t.Error("DeliverTx failed with ret_code", response.Code) t.Error("DeliverTx failed with ret_code", response.Code)
} }
if counter > numDeliverTxs { if counter > numDeliverTxs {

50
glide.lock generated
View File

@ -1,14 +1,12 @@
hash: 5501ab3d7136aa8fb425c995d45221849b33aefab76c5d2c192e721dad28da38 hash: 6cb2c869c8ce7d9e43b1e8930b9b1bc974ebb3d36d4b704fc78b77efba956a13
updated: 2017-11-14T18:23:41.2024073Z updated: 2017-11-30T17:08:29.176515576-05:00
imports: imports:
- name: github.com/btcsuite/btcd - name: github.com/btcsuite/btcd
version: b8df516b4b267acf2de46be593a9d948d1d2c420 version: 2e60448ffcc6bf78332d1fe590260095f554dd78
subpackages: subpackages:
- btcec - btcec
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/go-kit/kit - name: github.com/go-kit/kit
version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 version: e3b2152e0063c5f05efea89ecbe297852af2a92d
subpackages: subpackages:
- log - log
- log/level - log/level
@ -16,15 +14,21 @@ imports:
- name: github.com/go-logfmt/logfmt - name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales - name: github.com/go-playground/locales
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534 version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6
subpackages: subpackages:
- currency - currency
- name: github.com/go-playground/universal-translator - name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538 version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack - name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc
- name: github.com/gogo/protobuf
version: 342cbe0a04158f6dcb03ca0079991a51a4248c02
subpackages:
- gogoproto
- proto
- protoc-gen-gogo/descriptor
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 version: 1e59b77b52bf8e4b449a57e6f79f21226d571845
subpackages: subpackages:
- proto - proto
- ptypes - ptypes
@ -40,13 +44,13 @@ imports:
- name: github.com/kr/logfmt - name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d version: f15c970de5b76fac0b59abb32d62c17cc7bed265
- name: github.com/spf13/cobra - name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 80fe0fb4eba54167e2ccae1c6c950e72abf61b73 version: 4c012f6dcd9546820e378d0bdda4d8fc772cdfea
- name: github.com/syndtr/goleveldb - name: github.com/syndtr/goleveldb
version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 version: adf24ef3f94bd13ec4163060b21a5678f22b429b
subpackages: subpackages:
- leveldb - leveldb
- leveldb/cache - leveldb/cache
@ -61,27 +65,27 @@ imports:
- leveldb/table - leveldb/table
- leveldb/util - leveldb/util
- name: github.com/tendermint/ed25519 - name: github.com/tendermint/ed25519
version: 1f52c6f8b8a5c7908aff4497c186af344b428925 version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
subpackages: subpackages:
- edwards25519 - edwards25519
- extra25519 - extra25519
- name: github.com/tendermint/go-crypto - name: github.com/tendermint/go-crypto
version: b4f04f196cd719660e43b91202cd60d9a95b1837 version: b4f04f196cd719660e43b91202cd60d9a95b1837
- name: github.com/tendermint/go-wire - name: github.com/tendermint/go-wire
version: 1c96861c03231361546944d883d99593b2e6b408 version: 5ab49b4c6ad674da6b81442911cf713ef0afb544
subpackages: subpackages:
- data - data
- name: github.com/tendermint/iavl - name: github.com/tendermint/iavl
version: 595f3dcd5b6cd4a292e90757ae6d367fd7a6e653 version: 595f3dcd5b6cd4a292e90757ae6d367fd7a6e653
- name: github.com/tendermint/tmlibs - name: github.com/tendermint/tmlibs
version: 2442a0a698d271d5cf5d6a8e7c1db20335e959c1 version: 21fb7819891997c96838308b4eba5a50b07ff03f
subpackages: subpackages:
- common - common
- db - db
- log - log
- process - process
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e version: 94eea52f7b742c7cbe0b03b22f0c4c8631ece122
subpackages: subpackages:
- nacl/secretbox - nacl/secretbox
- openpgp/armor - openpgp/armor
@ -90,7 +94,7 @@ imports:
- ripemd160 - ripemd160
- salsa20/salsa - salsa20/salsa
- name: golang.org/x/net - name: golang.org/x/net
version: cd69bc3fc700721b709c3a59e16e24c67b58f6ff version: a8b9294777976932365dabb6640cf1468d95c70f
subpackages: subpackages:
- context - context
- http2 - http2
@ -100,18 +104,18 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/text - name: golang.org/x/text
version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4 version: 75cc3cad82b5f47d3fb229ddda8c5167da14f294
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
- unicode/bidi - unicode/bidi
- unicode/norm - unicode/norm
- name: google.golang.org/genproto - name: google.golang.org/genproto
version: f676e0f3ac6395ff1a529ae59a6670878a8371a6 version: 7f0da29060c682909f650ad8ed4e515bd74fa12a
subpackages: subpackages:
- googleapis/rpc/status - googleapis/rpc/status
- name: google.golang.org/grpc - name: google.golang.org/grpc
version: f7bf885db0b7479a537ec317c6e48ce53145f3db version: 401e0e00e4bb830a10496d64cd95e068c5bf50de
subpackages: subpackages:
- balancer - balancer
- codes - codes
@ -130,10 +134,10 @@ imports:
- tap - tap
- transport - transport
- name: gopkg.in/go-playground/validator.v9 - name: gopkg.in/go-playground/validator.v9
version: 6d8c18553ea1ac493d049edd6f102f52e618f085 version: 61caf9d3038e1af346dbf5c2e16f6678e1548364
testImports: testImports:
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
subpackages: subpackages:
- spew - spew
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
@ -141,7 +145,7 @@ testImports:
subpackages: subpackages:
- difflib - difflib
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 version: 2aa2c176b9dab406a6970f6a55f513e8a8c8b18f
subpackages: subpackages:
- assert - assert
- require - require

View File

@ -1,9 +1,11 @@
package: github.com/tendermint/abci package: github.com/tendermint/abci
import: import:
- package: github.com/golang/protobuf - package: github.com/gogo/protobuf
version: v0.5
subpackages: subpackages:
- proto - proto
- package: github.com/spf13/cobra - package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/tendermint/go-crypto - package: github.com/tendermint/go-crypto
version: develop version: develop
- package: github.com/tendermint/go-wire - package: github.com/tendermint/go-wire
@ -23,6 +25,7 @@ import:
subpackages: subpackages:
- context - context
- package: google.golang.org/grpc - package: google.golang.org/grpc
version: v1.7.3
testImport: testImport:
- package: github.com/stretchr/testify - package: github.com/stretchr/testify
subpackages: subpackages:

View File

@ -42,6 +42,7 @@ func (s *GRPCServer) OnStart() error {
if err != nil { if err != nil {
return err return err
} }
s.Logger.Info("Listening", "proto", s.proto, "addr", s.addr)
s.listener = ln s.listener = ln
s.server = grpc.NewServer() s.server = grpc.NewServer()
types.RegisterABCIApplicationServer(s.server, s.app) types.RegisterABCIApplicationServer(s.server, s.app)

View File

@ -56,13 +56,17 @@ func (s *SocketServer) OnStart() error {
func (s *SocketServer) OnStop() { func (s *SocketServer) OnStop() {
s.BaseService.OnStop() s.BaseService.OnStop()
s.listener.Close() if err := s.listener.Close(); err != nil {
s.Logger.Error("Error closing listener", "err", err)
}
s.connsMtx.Lock() s.connsMtx.Lock()
defer s.connsMtx.Unlock() defer s.connsMtx.Unlock()
for id, conn := range s.conns { for id, conn := range s.conns {
delete(s.conns, id) delete(s.conns, id)
conn.Close() if err := conn.Close(); err != nil {
s.Logger.Error("Error closing connection", "id", id, "conn", conn, "err", err)
}
} }
} }
@ -168,33 +172,32 @@ func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types
case *types.Request_Flush: case *types.Request_Flush:
responses <- types.ToResponseFlush() responses <- types.ToResponseFlush()
case *types.Request_Info: case *types.Request_Info:
resInfo := s.app.Info(*r.Info) res := s.app.Info(*r.Info)
responses <- types.ToResponseInfo(resInfo) responses <- types.ToResponseInfo(res)
case *types.Request_SetOption: case *types.Request_SetOption:
so := r.SetOption res := s.app.SetOption(*r.SetOption)
logStr := s.app.SetOption(so.Key, so.Value) responses <- types.ToResponseSetOption(res)
responses <- types.ToResponseSetOption(logStr)
case *types.Request_DeliverTx: case *types.Request_DeliverTx:
res := s.app.DeliverTx(r.DeliverTx.Tx) res := s.app.DeliverTx(r.DeliverTx.Tx)
responses <- types.ToResponseDeliverTx(res.Code, res.Data, res.Log) responses <- types.ToResponseDeliverTx(res)
case *types.Request_CheckTx: case *types.Request_CheckTx:
res := s.app.CheckTx(r.CheckTx.Tx) res := s.app.CheckTx(r.CheckTx.Tx)
responses <- types.ToResponseCheckTx(res.Code, res.Data, res.Log) responses <- types.ToResponseCheckTx(res)
case *types.Request_Commit: case *types.Request_Commit:
res := s.app.Commit() res := s.app.Commit()
responses <- types.ToResponseCommit(res.Code, res.Data, res.Log) responses <- types.ToResponseCommit(res)
case *types.Request_Query: case *types.Request_Query:
resQuery := s.app.Query(*r.Query) res := s.app.Query(*r.Query)
responses <- types.ToResponseQuery(resQuery) responses <- types.ToResponseQuery(res)
case *types.Request_InitChain: case *types.Request_InitChain:
s.app.InitChain(*r.InitChain) res := s.app.InitChain(*r.InitChain)
responses <- types.ToResponseInitChain() responses <- types.ToResponseInitChain(res)
case *types.Request_BeginBlock: case *types.Request_BeginBlock:
s.app.BeginBlock(*r.BeginBlock) res := s.app.BeginBlock(*r.BeginBlock)
responses <- types.ToResponseBeginBlock() responses <- types.ToResponseBeginBlock(res)
case *types.Request_EndBlock: case *types.Request_EndBlock:
resEndBlock := s.app.EndBlock(r.EndBlock.Height) res := s.app.EndBlock(*r.EndBlock)
responses <- types.ToResponseEndBlock(resEndBlock) responses <- types.ToResponseEndBlock(res)
default: default:
responses <- types.ToResponseException("Unknown request") responses <- types.ToResponseException("Unknown request")
} }

19
test.sh Executable file
View File

@ -0,0 +1,19 @@
#!/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
echo "==> Running integration tests (./tests)"
find . -path ./vendor -prune -o -name "*.sock" -exec rm {} \;
# tests/test.sh requires that we run the installed cmds, must not be out of date
make install
bash tests/test.sh

View File

@ -1,4 +1,4 @@
package main package tests
import ( import (
"testing" "testing"
@ -17,11 +17,11 @@ func TestClientServerNoAddrPrefix(t *testing.T) {
server, err := abciserver.NewServer(addr, transport, app) server, err := abciserver.NewServer(addr, transport, app)
assert.NoError(t, err, "expected no error on NewServer") assert.NoError(t, err, "expected no error on NewServer")
_, err = server.Start() err = server.Start()
assert.NoError(t, err, "expected no error on server.Start") assert.NoError(t, err, "expected no error on server.Start")
client, err := abciclient.NewClient(addr, transport, true) client, err := abciclient.NewClient(addr, transport, true)
assert.NoError(t, err, "expected no error on NewClient") assert.NoError(t, err, "expected no error on NewClient")
_, err = client.Start() err = client.Start()
assert.NoError(t, err, "expected no error on client.Start") assert.NoError(t, err, "expected no error on client.Start")
} }

98
tests/server/client.go Normal file
View File

@ -0,0 +1,98 @@
package testsuite
import (
"bytes"
"errors"
"fmt"
abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
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 := crypto.GenPrivKeyEd25519FromSecret([]byte(cmn.Fmt("test%d", i))).PubKey().Bytes()
power := cmn.RandInt()
vals[i] = &types.Validator{pubkey, int64(power)}
}
_, err := client.InitChainSync(types.RequestInitChain{Validators: vals})
if err != nil {
fmt.Println("Failed test: InitChain - %v", err)
return err
}
fmt.Println("Passed test: InitChain")
return nil
}
func SetOption(client abcicli.Client, key, value string) error {
res, err := client.SetOptionSync(types.RequestSetOption{Key: key, Value: value})
log := res.GetLog()
if err != nil {
fmt.Println("Failed test: SetOption")
fmt.Printf("setting %v=%v: \nlog: %v", key, value, log)
fmt.Println("Failed test: SetOption")
return err
}
fmt.Println("Passed test: SetOption")
return nil
}
func Commit(client abcicli.Client, hashExp []byte) error {
res, err := client.CommitSync()
_, data := res.Code, res.Data
if err != nil {
fmt.Println("Failed test: Commit")
fmt.Printf("committing %v\nlog: %v", res.GetLog())
return err
}
if !bytes.Equal(data, hashExp) {
fmt.Println("Failed test: Commit")
fmt.Printf("Commit hash was unexpected. Got %X expected %X",
data.Bytes(), 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",
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",
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",
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",
data, dataExp)
return errors.New("CheckTx")
}
fmt.Println("Passed test: CheckTx")
return nil
}

View File

@ -1,4 +1,5 @@
#! /bin/bash #! /bin/bash
set -e
# test the counter using a go test script # test the counter using a go test script
bash tests/test_app/test.sh bash tests/test_app/test.sh

View File

@ -4,34 +4,12 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"time"
abcicli "github.com/tendermint/abci/client" abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/tendermint/tmlibs/process"
) )
func startApp(abciApp string) *process.Process {
// Start the app
//outBuf := NewBufferCloser(nil)
proc, err := process.StartProcess("abci_app",
"",
"bash",
[]string{"-c", fmt.Sprintf("abci-cli %s", abciApp)},
nil,
os.Stdout,
)
if err != nil {
panic("running abci_app: " + err.Error())
}
// TODO a better way to handle this?
time.Sleep(time.Second)
return proc
}
func startClient(abciType string) abcicli.Client { func startClient(abciType string) abcicli.Client {
// Start client // Start client
client, err := abcicli.NewClient("tcp://127.0.0.1:46658", abciType, true) client, err := abcicli.NewClient("tcp://127.0.0.1:46658", abciType, true)
@ -40,58 +18,64 @@ func startClient(abciType string) abcicli.Client {
} }
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
client.SetLogger(logger.With("module", "abcicli")) client.SetLogger(logger.With("module", "abcicli"))
if _, err := client.Start(); err != nil { if err := client.Start(); err != nil {
panic("connecting to abci_app: " + err.Error()) panicf("connecting to abci_app: %v", err.Error())
} }
return client return client
} }
func setOption(client abcicli.Client, key, value string) { func setOption(client abcicli.Client, key, value string) {
res := client.SetOptionSync(key, value) _, err := client.SetOptionSync(types.RequestSetOption{key, value})
_, _, log := res.Code, res.Data, res.Log if err != nil {
if res.IsErr() { panicf("setting %v=%v: \nerr: %v", key, value, err)
panic(fmt.Sprintf("setting %v=%v: \nlog: %v", key, value, log))
} }
} }
func commit(client abcicli.Client, hashExp []byte) { func commit(client abcicli.Client, hashExp []byte) {
res := client.CommitSync() res, err := client.CommitSync()
_, data, _ := res.Code, res.Data, res.Log if err != nil {
panicf("client error: %v", err)
}
if res.IsErr() { if res.IsErr() {
panic(fmt.Sprintf("committing err %v\n", res)) panicf("committing err %v\n", res)
} }
if !bytes.Equal(res.Data, hashExp) { if !bytes.Equal(res.Data, hashExp) {
panic(fmt.Sprintf("Commit hash was unexpected. Got %X expected %X", panicf("Commit hash was unexpected. Got %X expected %X", res.Data, hashExp)
data, hashExp))
} }
} }
func deliverTx(client abcicli.Client, txBytes []byte, codeExp types.CodeType, dataExp []byte) { func deliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) {
res := client.DeliverTxSync(txBytes) res, err := client.DeliverTxSync(txBytes)
code, data, log := res.Code, res.Data, res.Log if err != nil {
if code != codeExp { panicf("client error: %v", err)
panic(fmt.Sprintf("DeliverTx response code was unexpected. Got %v expected %v. Log: %v",
code, codeExp, log))
} }
if !bytes.Equal(data, dataExp) { if res.Code != codeExp {
panic(fmt.Sprintf("DeliverTx response data was unexpected. Got %X expected %X", panicf("DeliverTx response code was unexpected. Got %v expected %v. Log: %v", res.Code, codeExp, res.Log)
data, dataExp)) }
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 types.CodeType, dataExp []byte) { /*func checkTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) {
res := client.CheckTxSync(txBytes) res, err := client.CheckTxSync(txBytes)
code, data, log := res.Code, res.Data, res.Log if err != nil {
panicf("client error: %v", err)
}
if res.IsErr() { if res.IsErr() {
panic(fmt.Sprintf("checking tx %X: %v\nlog: %v", txBytes, log)) panicf("checking tx %X: %v\nlog: %v", txBytes, res.Log)
} }
if code != codeExp { if res.Code != codeExp {
panic(fmt.Sprintf("CheckTx response code was unexpected. Got %v expected %v. Log: %v", panicf("CheckTx response code was unexpected. Got %v expected %v. Log: %v",
code, codeExp, log)) res.Code, codeExp, res.Log)
} }
if !bytes.Equal(data, dataExp) { if !bytes.Equal(res.Data, dataExp) {
panic(fmt.Sprintf("CheckTx response data was unexpected. Got %X expected %X", panicf("CheckTx response data was unexpected. Got %X expected %X",
data, dataExp)) res.Data, dataExp)
} }
}*/ }*/
func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}

View File

@ -2,8 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec"
"time"
"github.com/tendermint/abci/example/code"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
) )
@ -20,6 +24,28 @@ func main() {
testCounter() 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() { func testCounter() {
abciApp := os.Getenv("ABCI_APP") abciApp := os.Getenv("ABCI_APP")
if abciApp == "" { if abciApp == "" {
@ -27,22 +53,32 @@ func testCounter() {
} }
fmt.Printf("Running %s test with abci=%s\n", abciApp, abciType) fmt.Printf("Running %s test with abci=%s\n", abciApp, abciType)
appProc := startApp(abciApp) cmd := exec.Command("bash", "-c", fmt.Sprintf("abci-cli %s", abciApp)) // nolint: gas
defer appProc.StopProcess(true) 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) client := startClient(abciType)
defer client.Stop() defer client.Stop()
setOption(client, "serial", "on") setOption(client, "serial", "on")
commit(client, nil) commit(client, nil)
deliverTx(client, []byte("abc"), types.CodeType_BadNonce, nil) deliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil)
commit(client, nil) commit(client, nil)
deliverTx(client, []byte{0x00}, types.CodeType_OK, nil) deliverTx(client, []byte{0x00}, types.CodeTypeOK, nil)
commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1})
deliverTx(client, []byte{0x00}, types.CodeType_BadNonce, nil) deliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil)
deliverTx(client, []byte{0x01}, types.CodeType_OK, nil) deliverTx(client, []byte{0x01}, types.CodeTypeOK, nil)
deliverTx(client, []byte{0x00, 0x02}, types.CodeType_OK, nil) deliverTx(client, []byte{0x00, 0x02}, types.CodeTypeOK, nil)
deliverTx(client, []byte{0x00, 0x03}, types.CodeType_OK, nil) deliverTx(client, []byte{0x00, 0x03}, types.CodeTypeOK, nil)
deliverTx(client, []byte{0x00, 0x00, 0x04}, types.CodeType_OK, nil) deliverTx(client, []byte{0x00, 0x00, 0x04}, types.CodeTypeOK, nil)
deliverTx(client, []byte{0x00, 0x00, 0x06}, types.CodeType_BadNonce, nil) deliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil)
commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5})
} }

View File

@ -11,11 +11,16 @@ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# Change into that dir because we expect that. # Change into that dir because we expect that.
cd "$DIR" cd "$DIR"
echo "RUN COUNTER OVER SOCKET"
# test golang counter # test golang counter
ABCI_APP="counter" go run ./*.go ABCI_APP="counter" go run ./*.go
echo "----------------------"
echo "RUN COUNTER OVER GRPC"
# test golang counter via grpc # test golang counter via grpc
ABCI_APP="counter --abci=grpc" ABCI="grpc" go run ./*.go ABCI_APP="counter --abci=grpc" ABCI="grpc" go run ./*.go
echo "----------------------"
# test nodejs counter # test nodejs counter
# TODO: fix node app # TODO: fix node app

View File

@ -11,14 +11,14 @@
-> code: OK -> code: OK
> check_tx 0x00 > check_tx 0x00
-> code: BadNonce -> code: 2
-> log: Invalid nonce. Expected >= 1, got 0 -> log: Invalid nonce. Expected >= 1, got 0
> deliver_tx 0x01 > deliver_tx 0x01
-> code: OK -> code: OK
> deliver_tx 0x04 > deliver_tx 0x04
-> code: BadNonce -> code: 2
-> log: Invalid nonce. Expected 2, got 4 -> log: Invalid nonce. Expected 2, got 4
> info > info

39
tests/test_cli/test.sh Normal file → Executable file
View File

@ -1,4 +1,5 @@
#! /bin/bash #! /bin/bash
set -e
# Get the root directory. # Get the root directory.
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
@ -9,29 +10,29 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/../.." && pwd )"
cd "$DIR" || exit cd "$DIR" || exit
function testExample() { function testExample() {
N=$1 N=$1
INPUT=$2 INPUT=$2
APP="$3 $4" APP="$3 $4"
echo "Example $N: $APP" echo "Example $N: $APP"
$APP &> /dev/null & $APP &> /dev/null &
sleep 2 sleep 2
abci-cli --verbose batch < "$INPUT" > "${INPUT}.out.new" abci-cli --log_level=error --verbose batch < "$INPUT" > "${INPUT}.out.new"
killall "$3" killall "$3"
pre=$(shasum < "${INPUT}.out") pre=$(shasum < "${INPUT}.out")
post=$(shasum < "${INPUT}.out.new") post=$(shasum < "${INPUT}.out.new")
if [[ "$pre" != "$post" ]]; then if [[ "$pre" != "$post" ]]; then
echo "You broke the tutorial" echo "You broke the tutorial"
echo "Got:" echo "Got:"
cat "${INPUT}.out.new" cat "${INPUT}.out.new"
echo "Expected:" echo "Expected:"
cat "${INPUT}.out" cat "${INPUT}.out"
exit 1 exit 1
fi fi
rm "${INPUT}".out.new rm "${INPUT}".out.new
} }
testExample 1 tests/test_cli/ex1.abci abci-cli dummy testExample 1 tests/test_cli/ex1.abci abci-cli dummy

1
tests/tests.go Normal file
View File

@ -0,0 +1 @@
package tests

View File

@ -5,25 +5,75 @@ import (
) )
// Application is an interface that enables any finite, deterministic state machine // Application is an interface that enables any finite, deterministic state machine
// to be driven by a blockchain-based replication engine via the ABCI // 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 { type Application interface {
// Info/Query Connection // Info/Query Connection
Info(RequestInfo) ResponseInfo // Return application info Info(RequestInfo) ResponseInfo // Return application info
SetOption(key string, value string) (log string) // Set application option SetOption(RequestSetOption) ResponseSetOption // Set application option
Query(RequestQuery) ResponseQuery // Query for state Query(RequestQuery) ResponseQuery // Query for state
// Mempool Connection // Mempool Connection
CheckTx(tx []byte) Result // Validate a tx for the mempool CheckTx(tx []byte) ResponseCheckTx // Validate a tx for the mempool
// Consensus Connection // Consensus Connection
InitChain(RequestInitChain) // Initialize blockchain with validators and other info from TendermintCore InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain with validators and other info from TendermintCore
BeginBlock(RequestBeginBlock) // Signals the beginning of a block BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block
DeliverTx(tx []byte) Result // Deliver a tx for full processing DeliverTx(tx []byte) ResponseDeliverTx // Deliver a tx for full processing
EndBlock(height uint64) ResponseEndBlock // Signals the end of a block, returns changes to the validator set EndBlock(RequestEndBlock) ResponseEndBlock // Signals the end of a block, returns changes to the validator set
Commit() Result // Commit the state and return the application Merkle root hash 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{Code: CodeTypeOK}
}
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{Code: CodeTypeOK}
}
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 // GRPCApplication is a GRPC wrapper for Application
type GRPCApplication struct { type GRPCApplication struct {
@ -43,45 +93,46 @@ func (app *GRPCApplication) Flush(ctx context.Context, req *RequestFlush) (*Resp
} }
func (app *GRPCApplication) Info(ctx context.Context, req *RequestInfo) (*ResponseInfo, error) { func (app *GRPCApplication) Info(ctx context.Context, req *RequestInfo) (*ResponseInfo, error) {
resInfo := app.app.Info(*req) res := app.app.Info(*req)
return &resInfo, nil return &res, nil
} }
func (app *GRPCApplication) SetOption(ctx context.Context, req *RequestSetOption) (*ResponseSetOption, error) { func (app *GRPCApplication) SetOption(ctx context.Context, req *RequestSetOption) (*ResponseSetOption, error) {
return &ResponseSetOption{app.app.SetOption(req.Key, req.Value)}, nil res := app.app.SetOption(*req)
return &res, nil
} }
func (app *GRPCApplication) DeliverTx(ctx context.Context, req *RequestDeliverTx) (*ResponseDeliverTx, error) { func (app *GRPCApplication) DeliverTx(ctx context.Context, req *RequestDeliverTx) (*ResponseDeliverTx, error) {
r := app.app.DeliverTx(req.Tx) res := app.app.DeliverTx(req.Tx)
return &ResponseDeliverTx{r.Code, r.Data, r.Log}, nil return &res, nil
} }
func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) {
r := app.app.CheckTx(req.Tx) res := app.app.CheckTx(req.Tx)
return &ResponseCheckTx{r.Code, r.Data, r.Log}, nil return &res, nil
} }
func (app *GRPCApplication) Query(ctx context.Context, req *RequestQuery) (*ResponseQuery, error) { func (app *GRPCApplication) Query(ctx context.Context, req *RequestQuery) (*ResponseQuery, error) {
resQuery := app.app.Query(*req) res := app.app.Query(*req)
return &resQuery, nil return &res, nil
} }
func (app *GRPCApplication) Commit(ctx context.Context, req *RequestCommit) (*ResponseCommit, error) { func (app *GRPCApplication) Commit(ctx context.Context, req *RequestCommit) (*ResponseCommit, error) {
r := app.app.Commit() res := app.app.Commit()
return &ResponseCommit{r.Code, r.Data, r.Log}, nil return &res, nil
} }
func (app *GRPCApplication) InitChain(ctx context.Context, req *RequestInitChain) (*ResponseInitChain, error) { func (app *GRPCApplication) InitChain(ctx context.Context, req *RequestInitChain) (*ResponseInitChain, error) {
app.app.InitChain(*req) res := app.app.InitChain(*req)
return &ResponseInitChain{}, nil // NOTE: empty return return &res, nil
} }
func (app *GRPCApplication) BeginBlock(ctx context.Context, req *RequestBeginBlock) (*ResponseBeginBlock, error) { func (app *GRPCApplication) BeginBlock(ctx context.Context, req *RequestBeginBlock) (*ResponseBeginBlock, error) {
app.app.BeginBlock(*req) res := app.app.BeginBlock(*req)
return &ResponseBeginBlock{}, nil // NOTE: empty return return &res, nil
} }
func (app *GRPCApplication) EndBlock(ctx context.Context, req *RequestEndBlock) (*ResponseEndBlock, error) { func (app *GRPCApplication) EndBlock(ctx context.Context, req *RequestEndBlock) (*ResponseEndBlock, error) {
resEndBlock := app.app.EndBlock(req.Height) res := app.app.EndBlock(*req)
return &resEndBlock, nil return &res, nil
} }

View File

@ -1,42 +0,0 @@
package types
type BaseApplication struct {
}
func NewBaseApplication() *BaseApplication {
return &BaseApplication{}
}
func (app *BaseApplication) Info(req RequestInfo) (resInfo ResponseInfo) {
return
}
func (app *BaseApplication) SetOption(key string, value string) (log string) {
return ""
}
func (app *BaseApplication) DeliverTx(tx []byte) Result {
return NewResultOK(nil, "")
}
func (app *BaseApplication) CheckTx(tx []byte) Result {
return NewResultOK(nil, "")
}
func (app *BaseApplication) Commit() Result {
return NewResultOK([]byte("nil"), "")
}
func (app *BaseApplication) Query(req RequestQuery) (resQuery ResponseQuery) {
return
}
func (app *BaseApplication) InitChain(req RequestInitChain) {
}
func (app *BaseApplication) BeginBlock(req RequestBeginBlock) {
}
func (app *BaseApplication) EndBlock(height uint64) (resEndBlock ResponseEndBlock) {
return
}

View File

@ -1,3 +0,0 @@
package types
func (c CodeType) IsOK() bool { return c == CodeType_OK }

View File

@ -1,26 +0,0 @@
package types
var (
OK = NewResultOK(nil, "")
ErrInternalError = NewError(CodeType_InternalError, "Internal error")
ErrEncodingError = NewError(CodeType_EncodingError, "Encoding error")
ErrBadNonce = NewError(CodeType_BadNonce, "Error bad nonce")
ErrUnauthorized = NewError(CodeType_Unauthorized, "Unauthorized")
ErrInsufficientFunds = NewError(CodeType_InsufficientFunds, "Insufficient funds")
ErrUnknownRequest = NewError(CodeType_UnknownRequest, "Unknown request")
ErrBaseDuplicateAddress = NewError(CodeType_BaseDuplicateAddress, "Error (base) duplicate address")
ErrBaseEncodingError = NewError(CodeType_BaseEncodingError, "Error (base) encoding error")
ErrBaseInsufficientFees = NewError(CodeType_BaseInsufficientFees, "Error (base) insufficient fees")
ErrBaseInsufficientFunds = NewError(CodeType_BaseInsufficientFunds, "Error (base) insufficient funds")
ErrBaseInsufficientGasPrice = NewError(CodeType_BaseInsufficientGasPrice, "Error (base) insufficient gas price")
ErrBaseInvalidInput = NewError(CodeType_BaseInvalidInput, "Error (base) invalid input")
ErrBaseInvalidOutput = NewError(CodeType_BaseInvalidOutput, "Error (base) invalid output")
ErrBaseInvalidPubKey = NewError(CodeType_BaseInvalidPubKey, "Error (base) invalid pubkey")
ErrBaseInvalidSequence = NewError(CodeType_BaseInvalidSequence, "Error (base) invalid sequence")
ErrBaseInvalidSignature = NewError(CodeType_BaseInvalidSignature, "Error (base) invalid signature")
ErrBaseUnknownAddress = NewError(CodeType_BaseUnknownAddress, "Error (base) unknown address")
ErrBaseUnknownPlugin = NewError(CodeType_BaseUnknownPlugin, "Error (base) unknown plugin")
ErrBaseUnknownPubKey = NewError(CodeType_BaseUnknownPubKey, "Error (base) unknown pubkey")
)

View File

@ -3,10 +3,35 @@ package types
import ( import (
"io" "io"
"github.com/golang/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
) )
// WriteMessage writes a length-delimited protobuf message.
func WriteMessage(msg proto.Message, w io.Writer) error {
bz, err := proto.Marshal(msg)
if err != nil {
return err
}
var n int
wire.WriteByteSlice(bz, w, &n, &err)
return err
}
// ReadMessage reads a length delimited protobuf message.
func ReadMessage(r io.Reader, msg proto.Message) error {
var n int
var err error
bz := wire.ReadByteSlice(r, 0, &n, &err) //XXX: no max
if err != nil {
return err
}
err = proto.Unmarshal(bz, msg)
return err
}
//----------------------------------------
func ToRequestEcho(message string) *Request { func ToRequestEcho(message string) *Request {
return &Request{ return &Request{
Value: &Request_Echo{&RequestEcho{message}}, Value: &Request_Echo{&RequestEcho{message}},
@ -25,21 +50,21 @@ func ToRequestInfo(req RequestInfo) *Request {
} }
} }
func ToRequestSetOption(key string, value string) *Request { func ToRequestSetOption(req RequestSetOption) *Request {
return &Request{ return &Request{
Value: &Request_SetOption{&RequestSetOption{key, value}}, Value: &Request_SetOption{&req},
} }
} }
func ToRequestDeliverTx(txBytes []byte) *Request { func ToRequestDeliverTx(tx []byte) *Request {
return &Request{ return &Request{
Value: &Request_DeliverTx{&RequestDeliverTx{txBytes}}, Value: &Request_DeliverTx{&RequestDeliverTx{tx}},
} }
} }
func ToRequestCheckTx(txBytes []byte) *Request { func ToRequestCheckTx(tx []byte) *Request {
return &Request{ return &Request{
Value: &Request_CheckTx{&RequestCheckTx{txBytes}}, Value: &Request_CheckTx{&RequestCheckTx{tx}},
} }
} }
@ -67,9 +92,9 @@ func ToRequestBeginBlock(req RequestBeginBlock) *Request {
} }
} }
func ToRequestEndBlock(height uint64) *Request { func ToRequestEndBlock(req RequestEndBlock) *Request {
return &Request{ return &Request{
Value: &Request_EndBlock{&RequestEndBlock{height}}, Value: &Request_EndBlock{&req},
} }
} }
@ -93,81 +118,56 @@ func ToResponseFlush() *Response {
} }
} }
func ToResponseInfo(resInfo ResponseInfo) *Response { func ToResponseInfo(res ResponseInfo) *Response {
return &Response{ return &Response{
Value: &Response_Info{&resInfo}, Value: &Response_Info{&res},
} }
} }
func ToResponseSetOption(log string) *Response { func ToResponseSetOption(res ResponseSetOption) *Response {
return &Response{ return &Response{
Value: &Response_SetOption{&ResponseSetOption{log}}, Value: &Response_SetOption{&res},
} }
} }
func ToResponseDeliverTx(code CodeType, data []byte, log string) *Response { func ToResponseDeliverTx(res ResponseDeliverTx) *Response {
return &Response{ return &Response{
Value: &Response_DeliverTx{&ResponseDeliverTx{code, data, log}}, Value: &Response_DeliverTx{&res},
} }
} }
func ToResponseCheckTx(code CodeType, data []byte, log string) *Response { func ToResponseCheckTx(res ResponseCheckTx) *Response {
return &Response{ return &Response{
Value: &Response_CheckTx{&ResponseCheckTx{code, data, log}}, Value: &Response_CheckTx{&res},
} }
} }
func ToResponseCommit(code CodeType, data []byte, log string) *Response { func ToResponseCommit(res ResponseCommit) *Response {
return &Response{ return &Response{
Value: &Response_Commit{&ResponseCommit{code, data, log}}, Value: &Response_Commit{&res},
} }
} }
func ToResponseQuery(resQuery ResponseQuery) *Response { func ToResponseQuery(res ResponseQuery) *Response {
return &Response{ return &Response{
Value: &Response_Query{&resQuery}, Value: &Response_Query{&res},
} }
} }
func ToResponseInitChain() *Response { func ToResponseInitChain(res ResponseInitChain) *Response {
return &Response{ return &Response{
Value: &Response_InitChain{&ResponseInitChain{}}, Value: &Response_InitChain{&res},
} }
} }
func ToResponseBeginBlock() *Response { func ToResponseBeginBlock(res ResponseBeginBlock) *Response {
return &Response{ return &Response{
Value: &Response_BeginBlock{&ResponseBeginBlock{}}, Value: &Response_BeginBlock{&res},
} }
} }
func ToResponseEndBlock(resEndBlock ResponseEndBlock) *Response { func ToResponseEndBlock(res ResponseEndBlock) *Response {
return &Response{ return &Response{
Value: &Response_EndBlock{&resEndBlock}, Value: &Response_EndBlock{&res},
} }
} }
//----------------------------------------
// Write proto message, length delimited
func WriteMessage(msg proto.Message, w io.Writer) error {
bz, err := proto.Marshal(msg)
if err != nil {
return err
}
var n int
wire.WriteByteSlice(bz, w, &n, &err)
return err
}
// Read proto message, length delimited
func ReadMessage(r io.Reader, msg proto.Message) error {
var n int
var err error
bz := wire.ReadByteSlice(r, 0, &n, &err)
if err != nil {
return err
}
err = proto.Unmarshal(bz, msg)
return err
}

38
types/messages_test.go Normal file
View File

@ -0,0 +1,38 @@
package types
import (
"bytes"
"encoding/json"
"strings"
"testing"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/assert"
)
func TestMarshalJSON(t *testing.T) {
b, err := json.Marshal(&ResponseDeliverTx{})
assert.Nil(t, err)
assert.True(t, strings.Contains(string(b), "code"))
}
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)
}
}

View File

@ -0,0 +1,55 @@
package main
// +build ignore
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)
}

View File

@ -3,135 +3,106 @@ package types
import ( import (
"fmt" "fmt"
"github.com/tendermint/go-wire/data" "github.com/gogo/protobuf/jsonpb"
) )
// Result is a common result object for ABCI calls. const (
// CONTRACT: a zero Result is OK. CodeTypeOK uint32 = 0
type Result struct { )
Code CodeType `json:"code"`
Data data.Bytes `json:"data"` // IsOK returns true if Code is OK.
Log string `json:"log"` // Can be non-deterministic func (r ResponseCheckTx) IsOK() bool {
return r.Code == CodeTypeOK
} }
func NewResult(code CodeType, data []byte, log string) Result { // IsErr returns true if Code is something other than OK.
return Result{ func (r ResponseCheckTx) IsErr() bool {
Code: code, return r.Code != CodeTypeOK
Data: data,
Log: log,
}
} }
func (res Result) IsOK() bool { // Error implements error interface by formatting response as string.
return res.Code == CodeType_OK func (r ResponseCheckTx) Error() string {
return fmtError(r.Code, r.Log)
} }
func (res Result) IsErr() bool { // IsOK returns true if Code is OK.
return res.Code != CodeType_OK func (r ResponseDeliverTx) IsOK() bool {
return r.Code == CodeTypeOK
} }
func (res Result) IsSameCode(compare Result) bool { // IsErr returns true if Code is something other than OK.
return res.Code == compare.Code func (r ResponseDeliverTx) IsErr() bool {
return r.Code != CodeTypeOK
} }
func (res Result) Error() string { // Error implements error interface by formatting response as string.
return fmt.Sprintf("ABCI{code:%v, data:%X, log:%v}", res.Code, res.Data, res.Log) func (r ResponseDeliverTx) Error() string {
return fmtError(r.Code, r.Log)
} }
func (res Result) String() string { // IsOK returns true if Code is OK.
return fmt.Sprintf("ABCI{code:%v, data:%X, log:%v}", res.Code, res.Data, res.Log) func (r ResponseCommit) IsOK() bool {
return r.Code == CodeTypeOK
} }
func (res Result) PrependLog(log string) Result { // IsErr returns true if Code is something other than OK.
return Result{ func (r ResponseCommit) IsErr() bool {
Code: res.Code, return r.Code != CodeTypeOK
Data: res.Data,
Log: log + ";" + res.Log,
}
} }
func (res Result) AppendLog(log string) Result { // Error implements error interface by formatting response as string.
return Result{ func (r ResponseCommit) Error() string {
Code: res.Code, return fmtError(r.Code, r.Log)
Data: res.Data,
Log: res.Log + ";" + log,
}
} }
func (res Result) SetLog(log string) Result { // IsOK returns true if Code is OK.
return Result{ func (r ResponseQuery) IsOK() bool {
Code: res.Code, return r.Code == CodeTypeOK
Data: res.Data,
Log: log,
}
} }
func (res Result) SetData(data []byte) Result { // IsErr returns true if Code is something other than OK.
return Result{ func (r ResponseQuery) IsErr() bool {
Code: res.Code, return r.Code != CodeTypeOK
Data: data,
Log: res.Log,
}
} }
//---------------------------------------- // Error implements error interface by formatting response as string.
func (r ResponseQuery) Error() string {
// NOTE: if data == nil and log == "", same as zero Result. return fmtError(r.Code, r.Log)
func NewResultOK(data []byte, log string) Result {
return Result{
Code: CodeType_OK,
Data: data,
Log: log,
}
} }
func NewError(code CodeType, log string) Result { func fmtError(code uint32, log string) string {
return Result{ return fmt.Sprintf("Error code (%d): %s", code, log)
Code: code,
Log: log,
}
} }
//---------------------------------------- //---------------------------------------------------------------------------
// Convenience methods for turning the // override JSON marshalling so we dont emit defaults (ie. disable omitempty)
// pb type into one using data.Bytes
// Convert ResponseCheckTx to standard Result func (r *ResponseSetOption) MarshalJSON() ([]byte, error) {
func (r *ResponseCheckTx) Result() Result { m := jsonpb.Marshaler{EmitDefaults: true}
return Result{ s, err := m.MarshalToString(r)
Code: r.Code, return []byte(s), err
Data: r.Data,
Log: r.Log,
}
} }
// Convert ResponseDeliverTx to standard Result func (r *ResponseCheckTx) MarshalJSON() ([]byte, error) {
func (r *ResponseDeliverTx) Result() Result { m := jsonpb.Marshaler{EmitDefaults: true}
return Result{ s, err := m.MarshalToString(r)
Code: r.Code, return []byte(s), err
Data: r.Data,
Log: r.Log,
}
} }
type ResultQuery struct { func (r *ResponseDeliverTx) MarshalJSON() ([]byte, error) {
Code CodeType `json:"code"` m := jsonpb.Marshaler{EmitDefaults: true}
Index int64 `json:"index"` s, err := m.MarshalToString(r)
Key data.Bytes `json:"key"` return []byte(s), err
Value data.Bytes `json:"value"`
Proof data.Bytes `json:"proof"`
Height uint64 `json:"height"`
Log string `json:"log"`
} }
func (r *ResponseQuery) Result() *ResultQuery { func (r *ResponseQuery) MarshalJSON() ([]byte, error) {
return &ResultQuery{ m := jsonpb.Marshaler{EmitDefaults: true}
Code: r.Code, s, err := m.MarshalToString(r)
Index: r.Index, return []byte(s), err
Key: r.Key, }
Value: r.Value,
Proof: r.Proof, func (r *ResponseCommit) MarshalJSON() ([]byte, error) {
Height: r.Height, m := jsonpb.Marshaler{EmitDefaults: true}
Log: r.Log, s, err := m.MarshalToString(r)
} return []byte(s), err
} }

74
types/result_test.go Normal file
View File

@ -0,0 +1,74 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestResponseQuery(t *testing.T) {
res := ResponseQuery{
Code: CodeTypeOK,
Index: 0,
Key: []byte("hello"),
Value: []byte("world"),
Height: 1,
}
assert.False(t, res.IsErr())
res = ResponseQuery{
Code: 1,
Index: 0,
Key: []byte("hello"),
Value: []byte("world"),
Height: 1,
Log: "bad",
}
assert.True(t, res.IsErr())
assert.Equal(t, "Error code (1): bad", res.Error())
}
func TestResponseDeliverTx(t *testing.T) {
res := ResponseDeliverTx{
Code: CodeTypeOK,
Data: []byte("Victor Mancha"),
}
assert.False(t, res.IsErr())
res = ResponseDeliverTx{
Code: 1,
Log: "bad",
}
assert.True(t, res.IsErr())
assert.Equal(t, "Error code (1): bad", res.Error())
}
func TestResponseCheckTx(t *testing.T) {
res := ResponseCheckTx{
Code: CodeTypeOK,
Data: []byte("Talos"),
}
assert.False(t, res.IsErr())
res = ResponseCheckTx{
Code: 1,
Log: "bad",
}
assert.True(t, res.IsErr())
assert.Equal(t, "Error code (1): bad", res.Error())
}
func TestResponseCommit(t *testing.T) {
res := ResponseCommit{
Code: CodeTypeOK,
Data: []byte("Old Lace"),
}
assert.False(t, res.IsErr())
res = ResponseCommit{
Code: 1,
Log: "bad",
}
assert.True(t, res.IsErr())
assert.Equal(t, "Error code (1): bad", res.Error())
}

File diff suppressed because it is too large Load Diff

View File

@ -1,115 +1,77 @@
syntax = "proto3"; syntax = "proto3";
package types; package types;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// This file is copied from http://github.com/tendermint/abci // This file is copied from http://github.com/tendermint/abci
//----------------------------------------
// Code types
enum CodeType {
OK = 0;
// General response codes, 0 ~ 99
InternalError = 1;
EncodingError = 2;
BadNonce = 3;
Unauthorized = 4;
InsufficientFunds = 5;
UnknownRequest = 6;
// Reserved for basecoin, 100 ~ 199
BaseDuplicateAddress = 101;
BaseEncodingError = 102;
BaseInsufficientFees = 103;
BaseInsufficientFunds = 104;
BaseInsufficientGasPrice = 105;
BaseInvalidInput = 106;
BaseInvalidOutput = 107;
BaseInvalidPubKey = 108;
BaseInvalidSequence = 109;
BaseInvalidSignature = 110;
BaseUnknownAddress = 111;
BaseUnknownPubKey = 112;
BaseUnknownPlugin = 113;
// Reserved for governance, 200 ~ 299
GovUnknownEntity = 201;
GovUnknownGroup = 202;
GovUnknownProposal = 203;
GovDuplicateGroup = 204;
GovDuplicateMember = 205;
GovDuplicateProposal = 206;
GovDuplicateVote = 207;
GovInvalidMember = 208;
GovInvalidVote = 209;
GovInvalidVotingPower = 210;
}
//---------------------------------------- //----------------------------------------
// Request types // Request types
message Request { message Request {
oneof value{ oneof value{
RequestEcho echo = 1; RequestEcho echo = 1;
RequestFlush flush = 2; RequestFlush flush = 2;
RequestInfo info = 3; RequestInfo info = 3;
RequestSetOption set_option = 4; RequestSetOption set_option = 4;
RequestDeliverTx deliver_tx = 5; RequestDeliverTx deliver_tx = 5;
RequestCheckTx check_tx = 6; RequestCheckTx check_tx = 6;
RequestCommit commit = 7; RequestCommit commit = 7;
RequestQuery query = 8; RequestQuery query = 8;
RequestInitChain init_chain = 9; RequestInitChain init_chain = 9;
RequestBeginBlock begin_block = 10; RequestBeginBlock begin_block = 10;
RequestEndBlock end_block = 11; RequestEndBlock end_block = 11;
} }
} }
message RequestEcho { message RequestEcho {
string message = 1; string message = 1;
} }
message RequestFlush { message RequestFlush {
} }
message RequestInfo { message RequestInfo {
string version = 1; string version = 1;
} }
message RequestSetOption{ message RequestSetOption{
string key = 1; string key = 1;
string value = 2; string value = 2;
} }
message RequestDeliverTx{ message RequestDeliverTx{
bytes tx = 1; bytes tx = 1;
} }
message RequestCheckTx{ message RequestCheckTx{
bytes tx = 1; bytes tx = 1;
} }
message RequestQuery{ message RequestQuery{
bytes data = 1; bytes data = 1;
string path = 2; string path = 2;
uint64 height = 3; int64 height = 3;
bool prove = 4; bool prove = 4;
} }
message RequestCommit{ message RequestCommit{
} }
message RequestInitChain{ message RequestInitChain{
repeated Validator validators = 1; repeated Validator validators = 1;
} }
message RequestBeginBlock{ message RequestBeginBlock{
bytes hash = 1; bytes hash = 1;
Header header = 2; Header header = 2;
repeated int32 absent_validators = 3;
repeated Evidence byzantine_validators = 4;
} }
message RequestEndBlock{ message RequestEndBlock{
uint64 height = 1; int64 height = 1;
} }
//---------------------------------------- //----------------------------------------
@ -117,70 +79,74 @@ message RequestEndBlock{
message Response { message Response {
oneof value{ oneof value{
ResponseException exception = 1; ResponseException exception = 1;
ResponseEcho echo = 2; ResponseEcho echo = 2;
ResponseFlush flush = 3; ResponseFlush flush = 3;
ResponseInfo info = 4; ResponseInfo info = 4;
ResponseSetOption set_option = 5; ResponseSetOption set_option = 5;
ResponseDeliverTx deliver_tx = 6; ResponseDeliverTx deliver_tx = 6;
ResponseCheckTx check_tx = 7; ResponseCheckTx check_tx = 7;
ResponseCommit commit = 8; ResponseCommit commit = 8;
ResponseQuery query = 9; ResponseQuery query = 9;
ResponseInitChain init_chain = 10; ResponseInitChain init_chain = 10;
ResponseBeginBlock begin_block = 11; ResponseBeginBlock begin_block = 11;
ResponseEndBlock end_block = 12; ResponseEndBlock end_block = 12;
} }
} }
message ResponseException{ message ResponseException{
string error = 1; string error = 1;
} }
message ResponseEcho { message ResponseEcho {
string message = 1; string message = 1;
} }
message ResponseFlush{ message ResponseFlush{
} }
message ResponseInfo { message ResponseInfo {
string data = 1; string data = 1;
string version = 2; string version = 2;
uint64 last_block_height = 3; int64 last_block_height = 3;
bytes last_block_app_hash = 4; bytes last_block_app_hash = 4;
} }
message ResponseSetOption{ message ResponseSetOption{
string log = 1; uint32 code = 1;
string log = 2;
} }
message ResponseDeliverTx{ message ResponseDeliverTx{
CodeType code = 1; uint32 code = 1;
bytes data = 2; bytes data = 2 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
string log = 3; string log = 3;
repeated KVPair tags = 4;
} }
message ResponseCheckTx{ message ResponseCheckTx{
CodeType code = 1; uint32 code = 1;
bytes data = 2; bytes data = 2 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
string log = 3; string log = 3;
int64 gas = 4;
int64 fee = 5;
} }
message ResponseQuery{ message ResponseQuery{
CodeType code = 1; uint32 code = 1;
int64 index = 2; int64 index = 2;
bytes key = 3; bytes key = 3 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
bytes value = 4; bytes value = 4 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
bytes proof = 5; bytes proof = 5 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
uint64 height = 6; int64 height = 6;
string log = 7; string log = 7;
} }
message ResponseCommit{ message ResponseCommit{
CodeType code = 1; uint32 code = 1;
bytes data = 2; bytes data = 2 [(gogoproto.customtype) = "github.com/tendermint/go-wire/data.Bytes", (gogoproto.nullable) = false];
string log = 3; string log = 3;
} }
@ -191,52 +157,71 @@ message ResponseBeginBlock{
} }
message ResponseEndBlock{ message ResponseEndBlock{
repeated Validator diffs = 1; repeated Validator diffs = 1;
} }
//---------------------------------------- //----------------------------------------
// Blockchain Types // Blockchain Types
message Header { message Header {
string chain_id = 1; string chain_id = 1;
uint64 height = 2; int64 height = 2;
uint64 time = 3; int64 time = 3;
uint64 num_txs = 4; int32 num_txs = 4;
BlockID last_block_id = 5; BlockID last_block_id = 5;
bytes last_commit_hash = 6; bytes last_commit_hash = 6;
bytes data_hash = 7; bytes data_hash = 7;
bytes validators_hash = 8; bytes validators_hash = 8;
bytes app_hash = 9; bytes app_hash = 9;
} }
message BlockID { message BlockID {
bytes hash = 1; bytes hash = 1;
PartSetHeader parts = 2; PartSetHeader parts = 2;
} }
message PartSetHeader { message PartSetHeader {
uint64 total = 1; int32 total = 1;
bytes hash = 2; bytes hash = 2;
} }
message Validator { message Validator {
bytes pubKey = 1; bytes pub_key = 1;
uint64 power = 2; int64 power = 2;
}
message Evidence {
bytes pub_key = 1;
int64 height = 2;
}
//----------------------------------------
// Abstract types
message KVPair {
string key = 1;
enum Type {
STRING = 0;
INT = 1;
}
Type value_type = 2;
string value_string = 3;
int64 value_int = 4;
} }
//---------------------------------------- //----------------------------------------
// Service Definition // Service Definition
service ABCIApplication { service ABCIApplication {
rpc Echo(RequestEcho) returns (ResponseEcho) ; rpc Echo(RequestEcho) returns (ResponseEcho) ;
rpc Flush(RequestFlush) returns (ResponseFlush); rpc Flush(RequestFlush) returns (ResponseFlush);
rpc Info(RequestInfo) returns (ResponseInfo); rpc Info(RequestInfo) returns (ResponseInfo);
rpc SetOption(RequestSetOption) returns (ResponseSetOption); rpc SetOption(RequestSetOption) returns (ResponseSetOption);
rpc DeliverTx(RequestDeliverTx) returns (ResponseDeliverTx); rpc DeliverTx(RequestDeliverTx) returns (ResponseDeliverTx);
rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx);
rpc Query(RequestQuery) returns (ResponseQuery); rpc Query(RequestQuery) returns (ResponseQuery);
rpc Commit(RequestCommit) returns (ResponseCommit); rpc Commit(RequestCommit) returns (ResponseCommit);
rpc InitChain(RequestInitChain) returns (ResponseInitChain); rpc InitChain(RequestInitChain) returns (ResponseInitChain);
rpc BeginBlock(RequestBeginBlock) returns (ResponseBeginBlock); rpc BeginBlock(RequestBeginBlock) returns (ResponseBeginBlock);
rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock); rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock);
} }

View File

@ -8,8 +8,9 @@ import (
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
// validators implements sort //------------------------------------------------------------------------------
// Validators is a list of validators that implements the Sort interface
type Validators []*Validator type Validators []*Validator
func (v Validators) Len() int { func (v Validators) Len() int {
@ -27,13 +28,6 @@ func (v Validators) Swap(i, j int) {
v[j] = v1 v[j] = v1
} }
//-------------------------------------
type validatorPretty struct {
PubKey data.Bytes `json:"pub_key"`
Power uint64 `json:"power"`
}
func ValidatorsString(vs Validators) string { func ValidatorsString(vs Validators) string {
s := make([]validatorPretty, len(vs)) s := make([]validatorPretty, len(vs))
for i, v := range vs { for i, v := range vs {
@ -45,3 +39,28 @@ func ValidatorsString(vs Validators) string {
} }
return string(b) return string(b)
} }
type validatorPretty struct {
PubKey data.Bytes `json:"pub_key"`
Power int64 `json:"power"`
}
//------------------------------------------------------------------------------
// KVPairInt is a helper method to build KV pair with an integer value.
func KVPairInt(key string, val int64) *KVPair {
return &KVPair{
Key: key,
ValueInt: val,
ValueType: KVPair_INT,
}
}
// KVPairString is a helper method to build KV pair with a string value.
func KVPairString(key, val string) *KVPair {
return &KVPair{
Key: key,
ValueString: val,
ValueType: KVPair_STRING,
}
}

15
types/util_test.go Normal file
View File

@ -0,0 +1,15 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestKVPairInt(t *testing.T) {
assert.Equal(t, KVPairInt("a", 1), &KVPair{Key: "a", ValueType: KVPair_INT, ValueInt: 1})
}
func TestKVPairString(t *testing.T) {
assert.Equal(t, KVPairString("a", "b"), &KVPair{Key: "a", ValueType: KVPair_STRING, ValueString: "b"})
}

View File

@ -3,7 +3,7 @@ package version
// NOTE: we should probably be versioning the ABCI and the abci-cli separately // NOTE: we should probably be versioning the ABCI and the abci-cli separately
const Maj = "0" const Maj = "0"
const Min = "7" const Min = "8"
const Fix = "1" const Fix = "0"
const Version = "0.7.1" const Version = "0.8.0"