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