Merge pull request #4402 from tendermint/release/v0.33.1

Release/v0.33.1
This commit is contained in:
Anton Kaliaev
2020-02-14 12:56:26 +01:00
committed by GitHub
128 changed files with 4995 additions and 2777 deletions
+18
View File
@@ -14,6 +14,9 @@ executors:
- image: tendermintdev/docker-website-deployment
environment:
AWS_REGION: us-east-1
protoc:
docker:
- image: tendermintdev/docker-protoc
commands:
run_test:
@@ -72,6 +75,19 @@ jobs:
root: "/tmp/bin"
paths:
- "."
proto-lint:
executor: protoc
steps:
- checkout
- run:
command: make proto-lint
proto-breakage:
executor: protoc
steps:
- checkout
- run:
command: make proto-check-breaking
test_abci_apps:
executor: golang
@@ -391,6 +407,8 @@ workflows:
- test_abci_apps:
requires:
- setup_dependencies
- proto-breakage
- proto-lint
- test_abci_cli:
requires:
- setup_dependencies
+24
View File
@@ -1,5 +1,29 @@
# Changelog
## v0.33.1
*Feburary 13, 2020*
Special thanks to external contributors on this release:
@princesinha19
Friendly reminder, we have a [bug bounty
program](https://hackerone.com/tendermint).
### FEATURES:
- [rpc] [\#3333](https://github.com/tendermint/tendermint/issues/3333) Add `order_by` to `/tx_search` endpoint, allowing to change default ordering from asc to desc (@princesinha19)
### IMPROVEMENTS:
- [proto] [\#4369](https://github.com/tendermint/tendermint/issues/4369) Add [buf](https://buf.build/) for usage with linting and checking if there are breaking changes with the master branch.
- [proto] [\#4369](https://github.com/tendermint/tendermint/issues/4369) Add `make proto-gen` cmd to generate proto stubs outside of GOPATH.
### BUG FIXES:
- [node] [\#4311](https://github.com/tendermint/tendermint/issues/4311) Use `GRPCMaxOpenConnections` when creating the gRPC server, not `MaxOpenConnections`
- [rpc] [\#4319](https://github.com/tendermint/tendermint/issues/4319) Check `BlockMeta` is not nil in `/block` & `/block_by_hash`
## v0.33
Special thanks to external contributors on this release: @mrekucci, @PSalant726, @princesinha19, @greg-szabo, @dongsam, @cuonglm, @jgimeno, @yenkhoon
+1 -3
View File
@@ -1,4 +1,4 @@
## v0.33.1
## v0.33.2
\*\*
@@ -20,5 +20,3 @@ program](https://hackerone.com/tendermint).
### IMPROVEMENTS:
### BUG FIXES:
+5 -1
View File
@@ -31,7 +31,7 @@ at the RFC stage will build collective understanding of the dimensions
of the problems and help structure conversations around trade-offs.
When the problem is well understood but the solution leads to large
strucural changes to the code base, these changes should be proposed in
structural changes to the code base, these changes should be proposed in
the form of an [Architectural Decision Record
(ADR)](./docs/architecture/). The ADR will help build consensus on an
overall strategy to ensure the code base maintains coherence
@@ -98,6 +98,10 @@ need. Instead of running `go get -u=patch`, which will update anything,
specify exactly the dependency you want to update, eg.
`GO111MODULE=on go get -u github.com/tendermint/go-amino@master`.
## Protobuf
When working with [protobuf](https://developers.google.com/protocol-buffers) there are a few things you should know. We use [buf](https://buf.build/) for our linting and breaking changes checking. If you would like to run linting and check if the changes you have made are breaking then you will have to install the needed dependencies with `make buf`. Then the linting cmd will be `make proto-lint` and the breaking changes check will be `make proto-check-breaking`. To generate new stubs based off of your changes you can run `make proto-gen` (you can do this outside of GOPATH).
## Vagrant
If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started
+37 -65
View File
@@ -1,7 +1,6 @@
PACKAGES=$(shell go list ./...)
OUTPUT?=build/tendermint
INCLUDE = -I=${GOPATH}/src/github.com/tendermint/tendermint -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf
BUILD_TAGS?='tendermint'
LD_FLAGS = -X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD` -s -w
BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)"
@@ -12,8 +11,9 @@ all: check build test install
include tools.mk
include tests.mk
########################################
### Build Tendermint
###############################################################################
### Build Tendermint ###
###############################################################################
build:
CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o $(OUTPUT) ./cmd/tendermint/
@@ -30,26 +30,31 @@ install:
install_c:
CGO_ENABLED=1 go install $(BUILD_FLAGS) -tags "$(BUILD_TAGS) cleveldb" ./cmd/tendermint
########################################
### Protobuf
###############################################################################
### Protobuf ###
###############################################################################
protoc_all: protoc_libs protoc_merkle protoc_abci protoc_grpc protoc_proto3types
proto-all: proto-gen proto-lint proto-check-breaking
%.pb.go: %.proto
proto-gen:
## If you get the following error,
## "error while loading shared libraries: libprotobuf.so.14: cannot open shared object file: No such file or directory"
## See https://stackoverflow.com/a/25518702
## Note the $< here is substituted for the %.proto
## Note the $@ here is substituted for the %.pb.go
protoc $(INCLUDE) $< --gogo_out=Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration,plugins=grpc:../../..
@sh scripts/protocgen.sh
########################################
### Build ABCI
proto-lint:
@buf check lint --error-format=json
# see protobuf section above
protoc_abci: abci/types/types.pb.go
proto-check-breaking:
@buf check breaking --against-input '.git#branch=master'
protoc_proto3types: types/proto3/block.pb.go
.PHONY: proto-all proto-gen proto-lint proto-check-breaking
###############################################################################
### Build ABCI ###
###############################################################################
build_abci:
@go build -mod=readonly -i ./abci/cmd/...
@@ -57,8 +62,9 @@ build_abci:
install_abci:
@go install -mod=readonly ./abci/cmd/...
########################################
### Distribution
###############################################################################
### Distribution ###
###############################################################################
# dist builds binaries for all platforms and packages them for distribution
# TODO add abci to these scripts
@@ -86,18 +92,13 @@ get_deps_bin_size:
@find $(WORK) -type f -name "*.a" | xargs -I{} du -hxs "{}" | sort -rh | sed -e s:${WORK}/::g > deps_bin_size.log
@echo "Results can be found here: $(CURDIR)/deps_bin_size.log"
########################################
### Libs
protoc_libs: libs/kv/types.pb.go
###############################################################################
### Libs ###
###############################################################################
# generates certificates for TLS testing in remotedb and RPC server
gen_certs: clean_certs
certstrap init --common-name "tendermint.com" --passphrase ""
certstrap request-cert --common-name "remotedb" -ip "127.0.0.1" --passphrase ""
certstrap sign "remotedb" --CA "tendermint.com" --passphrase ""
mv out/remotedb.crt libs/db/remotedb/test.crt
mv out/remotedb.key libs/db/remotedb/test.key
certstrap request-cert --common-name "server" -ip "127.0.0.1" --passphrase ""
certstrap sign "server" --CA "tendermint.com" --passphrase ""
mv out/server.crt rpc/lib/server/test.crt
@@ -106,24 +107,12 @@ gen_certs: clean_certs
# deletes generated certificates
clean_certs:
rm -f libs/db/remotedb/test.crt
rm -f libs/db/remotedb/test.key
rm -f rpc/lib/server/test.crt
rm -f rpc/lib/server/test.key
test_libs:
go test -tags clevedb boltdb $(PACKAGES)
grpc_dbserver:
protoc -I libs/db/remotedb/proto/ libs/db/remotedb/proto/defs.proto --go_out=plugins=grpc:libs/db/remotedb/proto
protoc_grpc: rpc/grpc/types.pb.go
protoc_merkle: crypto/merkle/merkle.pb.go
########################################
### Formatting, linting, and vetting
###############################################################################
### Formatting, linting, and vetting ###
###############################################################################
fmt:
@go fmt ./...
@@ -134,8 +123,9 @@ lint:
DESTINATION = ./index.html.md
###########################################################
### Documentation
###############################################################################
### Documentation ###
###############################################################################
build-docs:
cd docs && \
@@ -154,16 +144,18 @@ sync-docs:
aws cloudfront create-invalidation --distribution-id ${CF_DISTRIBUTION_ID} --profile terraform --path "/*" ;
.PHONY: sync-docs
###########################################################
### Docker image
###############################################################################
### Docker image ###
###############################################################################
build-docker:
cp $(OUTPUT) DOCKER/tendermint
docker build --label=tendermint --tag="tendermint/tendermint" DOCKER
rm -rf DOCKER/tendermint
###########################################################
### Local testnet using docker
###############################################################################
### Local testnet using docker ###
###############################################################################
# Build linux binary on other platforms
build-linux: tools
@@ -188,26 +180,6 @@ localnet-start: localnet-stop build-docker-localnode
localnet-stop:
docker-compose down
###########################################################
### Remote full-nodes (sentry) using terraform and ansible
# Server management
sentry-start:
@if [ -z "$(DO_API_TOKEN)" ]; then echo "DO_API_TOKEN environment variable not set." ; false ; fi
@if ! [ -f $(HOME)/.ssh/id_rsa.pub ]; then ssh-keygen ; fi
cd networks/remote/terraform && terraform init && terraform apply -var DO_API_TOKEN="$(DO_API_TOKEN)" -var SSH_KEY_FILE="$(HOME)/.ssh/id_rsa.pub"
@if ! [ -f $(CURDIR)/build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 0 --n 4 --o . ; fi
cd networks/remote/ansible && ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i inventory/digital_ocean.py -l sentrynet install.yml
@echo "Next step: Add your validator setup in the genesis.json and config.tml files and run \"make sentry-config\". (Public key of validator, chain ID, peer IP and node ID.)"
# Configuration management
sentry-config:
cd networks/remote/ansible && ansible-playbook -i inventory/digital_ocean.py -l sentrynet config.yml -e BINARY=$(CURDIR)/build/tendermint -e CONFIGDIR=$(CURDIR)/build
sentry-stop:
@if [ -z "$(DO_API_TOKEN)" ]; then echo "DO_API_TOKEN environment variable not set." ; false ; fi
cd networks/remote/terraform && terraform destroy -var DO_API_TOKEN="$(DO_API_TOKEN)" -var SSH_KEY_FILE="$(HOME)/.ssh/id_rsa.pub"
# Build hooks for dredd, to skip or add information on some steps
build-contract-tests-hooks:
ifeq ($(OS),Windows_NT)
@@ -229,6 +201,6 @@ contract-tests:
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: check build build_race build_abci dist install install_abci check_tools tools update_tools draw_deps \
protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver fmt build-linux localnet-start \
localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop protoc_grpc protoc_all \
localnet-stop build-docker build-docker-localnode protoc_grpc protoc_all \
build_c install_c test_with_deadlock cleanup_after_test_with_deadlock lint build-contract-tests-hooks contract-tests \
build_c-amazonlinux
+5 -6
View File
@@ -47,9 +47,9 @@ For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY.
## Minimum requirements
| Requirement | Notes |
| ----------- | ------------------ |
| Go version | Go1.13 or higher |
| Requirement | Notes |
| ----------- | ---------------- |
| Go version | Go1.13 or higher |
## Documentation
@@ -129,7 +129,7 @@ For more information on upgrading, see [UPGRADING.md](./UPGRADING.md)
### Tendermint Core
For details about the blockchain data structures and the p2p protocols, see the
[Tendermint specification](/docs/spec).
[Tendermint specification](https://docs.tendermint.com/master/spec/).
For details on using the software, see the [documentation](/docs/) which is also
hosted at: https://docs.tendermint.com/master/
@@ -157,6 +157,5 @@ Additional documentation is found [here](/docs/tools).
- [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)
- [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769)
- [Original Whitepaper](https://github.com/tendermint/spec)
- You can find the link at the bottom of the readme
- [Original Whitepaper: "Tendermint: Consensus Without Mining"](https://tendermint.com/static/docs/tendermint.pdf)
- [Blog](https://blog.cosmos.network/tendermint/home)
+15 -3
View File
@@ -3,6 +3,18 @@
This guide provides steps to be followed when you upgrade your applications to
a newer version of Tendermint Core.
## Unreleased
<Overview>
## v0.33.1
This release is compatible with the previous version. The only change that is required is if you are fetching the protobuf files for application use.
### Protobuf Changes
When upgrading to version 0.33.1 you will have to fetch the `third_party` directory along with the updated proto files.
## v0.33.0
This release is not compatible with previous blockchains due to commit becoming signatures only and fields in the header have been removed.
@@ -224,14 +236,14 @@ due to changes in how various data structures are hashed.
Any implementations of Tendermint blockchain verification, including lite clients,
will need to be updated. For specific details:
- [Merkle tree](./docs/spec/blockchain/encoding.md#merkle-trees)
- [ConsensusParams](./docs/spec/blockchain/state.md#consensusparams)
- [Merkle tree](https://github.com/tendermint/spec/blob/master/spec/blockchain/encoding.md#merkle-trees)
- [ConsensusParams](https://github.com/tendermint/spec/blob/master/spec/blockchain/state.md#consensusparams)
There was also a small change to field ordering in the vote struct. Any
implementations of an out-of-process validator (like a Key-Management Server)
will need to be updated. For specific details:
- [Vote](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/signing.md#votes)
- [Vote](https://github.com/tendermint/spec/blob/master/spec/consensus/signing.md#votes)
Finally, the proposer selection algorithm continues to evolve. See the
[work-in-progress
+1 -1
View File
@@ -19,7 +19,7 @@ To get up and running quickly, see the [getting started guide](../docs/app-dev/g
A detailed description of the ABCI methods and message types is contained in:
- [The main spec](../docs/spec/abci/abci.md)
- [The main spec](https://github.com/tendermint/spec/blob/master/spec/abci/abci.md)
- [A protobuf file](./types/types.proto)
- [A Go interface](./types/application.go)
+149 -149
View File
@@ -2960,156 +2960,156 @@ func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_9f1eaa
func init() { golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_9f1eaa49c51fa1ac) }
var fileDescriptor_9f1eaa49c51fa1ac = []byte{
// 2371 bytes of a gzipped FileDescriptorProto
// 2370 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x59, 0x4d, 0x90, 0x1b, 0x47,
0x15, 0xde, 0xd1, 0x6a, 0xf5, 0xf3, 0xb4, 0xbb, 0x52, 0x3a, 0x4e, 0x22, 0x0b, 0x67, 0xd7, 0x35,
0xfe, 0x5b, 0xe7, 0x47, 0x0e, 0x0b, 0xa1, 0x62, 0xec, 0x0a, 0xb5, 0x5a, 0x3b, 0x48, 0x15, 0xdb,
0xd9, 0x8c, 0xed, 0xc5, 0x40, 0x55, 0xa6, 0x5a, 0x9a, 0xb6, 0x34, 0xb5, 0xd2, 0xcc, 0x64, 0xa6,
0x25, 0x6b, 0x29, 0xee, 0x14, 0x55, 0x1c, 0xb8, 0x50, 0xc5, 0x85, 0x3b, 0x47, 0x0e, 0x1c, 0x72,
0xe4, 0x98, 0x03, 0x07, 0x0e, 0x9c, 0x0d, 0x2c, 0x9c, 0xa8, 0x1c, 0x29, 0x8a, 0x23, 0xd5, 0xaf,
0x7b, 0xfe, 0xb4, 0xd2, 0x6a, 0x1c, 0x7c, 0xe3, 0x22, 0x4d, 0x77, 0xbf, 0xf7, 0xba, 0xfb, 0xf5,
0xeb, 0xf7, 0xbd, 0xf7, 0x1a, 0x5e, 0xa7, 0xdd, 0x9e, 0x7d, 0x83, 0x1f, 0x7b, 0x2c, 0x90, 0xbf,
0x4d, 0xcf, 0x77, 0xb9, 0x4b, 0x5e, 0xe3, 0xcc, 0xb1, 0x98, 0x3f, 0xb2, 0x1d, 0xde, 0x14, 0x24,
0x4d, 0x1c, 0x6c, 0xbc, 0xdb, 0xb7, 0xf9, 0x60, 0xdc, 0x6d, 0xf6, 0xdc, 0xd1, 0x8d, 0xbe, 0xdb,
0x77, 0x6f, 0x20, 0x75, 0x77, 0xfc, 0x14, 0x5b, 0xd8, 0xc0, 0x2f, 0x29, 0xa5, 0x71, 0x2b, 0x41,
0x1e, 0x0b, 0x4c, 0x7e, 0xf6, 0xfc, 0x63, 0x8f, 0xbb, 0x37, 0x46, 0xcc, 0x3f, 0x1a, 0x32, 0xf5,
0xa7, 0x98, 0xbf, 0xbd, 0x94, 0x79, 0x68, 0x77, 0x83, 0x1b, 0x47, 0x93, 0xe4, 0xc2, 0x1b, 0xdb,
0x7d, 0xd7, 0xed, 0x0f, 0x59, 0xbc, 0x30, 0x6e, 0x8f, 0x58, 0xc0, 0xe9, 0xc8, 0x53, 0x04, 0x5b,
0xb3, 0x04, 0xd6, 0xd8, 0xa7, 0xdc, 0x76, 0x1d, 0x39, 0xae, 0xff, 0x7b, 0x0d, 0x8a, 0x06, 0xfb,
0x7c, 0xcc, 0x02, 0x4e, 0x3e, 0x80, 0x3c, 0xeb, 0x0d, 0xdc, 0x7a, 0xee, 0xa2, 0xb6, 0x53, 0xd9,
0xd5, 0x9b, 0x73, 0x95, 0xd2, 0x54, 0xd4, 0x77, 0x7b, 0x03, 0xb7, 0xbd, 0x62, 0x20, 0x07, 0xb9,
0x05, 0x6b, 0x4f, 0x87, 0xe3, 0x60, 0x50, 0x5f, 0x45, 0xd6, 0x4b, 0x67, 0xb3, 0x7e, 0x24, 0x48,
0xdb, 0x2b, 0x86, 0xe4, 0x11, 0xd3, 0xda, 0xce, 0x53, 0xb7, 0x9e, 0xcf, 0x32, 0x6d, 0xc7, 0x79,
0x8a, 0xd3, 0x0a, 0x0e, 0xd2, 0x06, 0x08, 0x18, 0x37, 0x5d, 0x4f, 0x6c, 0xa8, 0xbe, 0x86, 0xfc,
0xd7, 0xce, 0xe6, 0x7f, 0xc8, 0xf8, 0x27, 0x48, 0xde, 0x5e, 0x31, 0xca, 0x41, 0xd8, 0x10, 0x92,
0x6c, 0xc7, 0xe6, 0x66, 0x6f, 0x40, 0x6d, 0xa7, 0x5e, 0xc8, 0x22, 0xa9, 0xe3, 0xd8, 0x7c, 0x5f,
0x90, 0x0b, 0x49, 0x76, 0xd8, 0x10, 0xaa, 0xf8, 0x7c, 0xcc, 0xfc, 0xe3, 0x7a, 0x31, 0x8b, 0x2a,
0x3e, 0x15, 0xa4, 0x42, 0x15, 0xc8, 0x43, 0x3e, 0x86, 0x4a, 0x97, 0xf5, 0x6d, 0xc7, 0xec, 0x0e,
0xdd, 0xde, 0x51, 0xbd, 0x84, 0x22, 0x76, 0xce, 0x16, 0xd1, 0x12, 0x0c, 0x2d, 0x41, 0xdf, 0x5e,
0x31, 0xa0, 0x1b, 0xb5, 0x48, 0x0b, 0x4a, 0xbd, 0x01, 0xeb, 0x1d, 0x99, 0x7c, 0x5a, 0x2f, 0xa3,
0xa4, 0x2b, 0x67, 0x4b, 0xda, 0x17, 0xd4, 0x8f, 0xa6, 0xed, 0x15, 0xa3, 0xd8, 0x93, 0x9f, 0x42,
0x2f, 0x16, 0x1b, 0xda, 0x13, 0xe6, 0x0b, 0x29, 0xaf, 0x66, 0xd1, 0xcb, 0x1d, 0x49, 0x8f, 0x72,
0xca, 0x56, 0xd8, 0x20, 0x77, 0xa1, 0xcc, 0x1c, 0x4b, 0x6d, 0xac, 0x82, 0x82, 0xae, 0x2e, 0xb1,
0x30, 0xc7, 0x0a, 0xb7, 0x55, 0x62, 0xea, 0x9b, 0x7c, 0x08, 0x85, 0x9e, 0x3b, 0x1a, 0xd9, 0xbc,
0xbe, 0x8e, 0x32, 0x2e, 0x2f, 0xd9, 0x12, 0xd2, 0xb6, 0x57, 0x0c, 0xc5, 0xd5, 0x2a, 0xc2, 0xda,
0x84, 0x0e, 0xc7, 0x4c, 0xbf, 0x06, 0x95, 0x84, 0x25, 0x93, 0x3a, 0x14, 0x47, 0x2c, 0x08, 0x68,
0x9f, 0xd5, 0xb5, 0x8b, 0xda, 0x4e, 0xd9, 0x08, 0x9b, 0xfa, 0x26, 0xac, 0x27, 0xed, 0x56, 0x1f,
0x45, 0x8c, 0xc2, 0x16, 0x05, 0xe3, 0x84, 0xf9, 0x81, 0x30, 0x40, 0xc5, 0xa8, 0x9a, 0xe4, 0x12,
0x6c, 0xe0, 0x6e, 0xcd, 0x70, 0x5c, 0xdc, 0xab, 0xbc, 0xb1, 0x8e, 0x9d, 0x87, 0x8a, 0x68, 0x1b,
0x2a, 0xde, 0xae, 0x17, 0x91, 0xac, 0x22, 0x09, 0x78, 0xbb, 0x9e, 0x22, 0xd0, 0xbf, 0x0b, 0xb5,
0x59, 0xd3, 0x25, 0x35, 0x58, 0x3d, 0x62, 0xc7, 0x6a, 0x3e, 0xf1, 0x49, 0xce, 0xa9, 0x6d, 0xe1,
0x1c, 0x65, 0x43, 0xed, 0xf1, 0x77, 0xb9, 0x88, 0x39, 0xb2, 0x56, 0x71, 0xdd, 0x84, 0x93, 0x40,
0xee, 0xca, 0x6e, 0xa3, 0x29, 0x1d, 0x44, 0x33, 0x74, 0x10, 0xcd, 0x47, 0xa1, 0x07, 0x69, 0x95,
0xbe, 0x7c, 0xbe, 0xbd, 0xf2, 0xcb, 0xbf, 0x6c, 0x6b, 0x06, 0x72, 0x90, 0xf3, 0xc2, 0xa0, 0xa8,
0xed, 0x98, 0xb6, 0xa5, 0xe6, 0x29, 0x62, 0xbb, 0x63, 0x91, 0x4f, 0xa1, 0xd6, 0x73, 0x9d, 0x80,
0x39, 0xc1, 0x38, 0x30, 0x3d, 0xea, 0xd3, 0x51, 0xa0, 0x7c, 0xc1, 0xa2, 0x43, 0xde, 0x0f, 0xc9,
0x0f, 0x90, 0xda, 0xa8, 0xf6, 0xd2, 0x1d, 0xe4, 0x1e, 0xc0, 0x84, 0x0e, 0x6d, 0x8b, 0x72, 0xd7,
0x0f, 0xea, 0xf9, 0x8b, 0xab, 0x67, 0x08, 0x3b, 0x0c, 0x09, 0x1f, 0x7b, 0x16, 0xe5, 0xac, 0x95,
0x17, 0x2b, 0x37, 0x12, 0xfc, 0xe4, 0x2a, 0x54, 0xa9, 0xe7, 0x99, 0x01, 0xa7, 0x9c, 0x99, 0xdd,
0x63, 0xce, 0x02, 0xf4, 0x17, 0xeb, 0xc6, 0x06, 0xf5, 0xbc, 0x87, 0xa2, 0xb7, 0x25, 0x3a, 0x75,
0x2b, 0x3a, 0x6d, 0xbc, 0x9a, 0x84, 0x40, 0xde, 0xa2, 0x9c, 0xa2, 0xb6, 0xd6, 0x0d, 0xfc, 0x16,
0x7d, 0x1e, 0xe5, 0x03, 0xa5, 0x03, 0xfc, 0x26, 0xaf, 0x43, 0x61, 0xc0, 0xec, 0xfe, 0x80, 0xe3,
0xb6, 0x57, 0x0d, 0xd5, 0x12, 0x07, 0xe3, 0xf9, 0xee, 0x84, 0xa1, 0x77, 0x2b, 0x19, 0xb2, 0xa1,
0xff, 0x2a, 0x07, 0xaf, 0x9c, 0xba, 0xbe, 0x42, 0xee, 0x80, 0x06, 0x83, 0x70, 0x2e, 0xf1, 0x4d,
0x6e, 0x09, 0xb9, 0xd4, 0x62, 0xbe, 0xf2, 0xca, 0x6f, 0x2e, 0xd0, 0x40, 0x1b, 0x89, 0xd4, 0xc6,
0x15, 0x0b, 0x79, 0x0c, 0xb5, 0x21, 0x0d, 0xb8, 0x29, 0x6d, 0xdf, 0x44, 0x2f, 0xbb, 0x7a, 0xa6,
0x27, 0xb8, 0x47, 0xc3, 0x3b, 0x23, 0x8c, 0x5b, 0x89, 0xdb, 0x1c, 0xa6, 0x7a, 0xc9, 0x13, 0x38,
0xd7, 0x3d, 0xfe, 0x09, 0x75, 0xb8, 0xed, 0x30, 0xf3, 0xd4, 0x19, 0x6d, 0x2f, 0x10, 0x7d, 0x77,
0x62, 0x5b, 0xcc, 0xe9, 0x85, 0x87, 0xf3, 0x6a, 0x24, 0x22, 0x3a, 0xbc, 0x40, 0x7f, 0x02, 0x9b,
0x69, 0x5f, 0x44, 0x36, 0x21, 0xc7, 0xa7, 0x4a, 0x23, 0x39, 0x3e, 0x25, 0xdf, 0x81, 0xbc, 0x10,
0x87, 0xda, 0xd8, 0x5c, 0x08, 0x16, 0x8a, 0xfb, 0xd1, 0xb1, 0xc7, 0x0c, 0xa4, 0xd7, 0xf5, 0xe8,
0x26, 0x44, 0xfe, 0x69, 0x56, 0xb6, 0x7e, 0x1d, 0xaa, 0x33, 0xae, 0x27, 0x71, 0xac, 0x5a, 0xf2,
0x58, 0xf5, 0x2a, 0x6c, 0xa4, 0x3c, 0x8c, 0xfe, 0xc7, 0x02, 0x94, 0x0c, 0x16, 0x78, 0xc2, 0x88,
0x49, 0x1b, 0xca, 0x6c, 0xda, 0x63, 0x12, 0x96, 0xb4, 0x25, 0x4e, 0x5c, 0xf2, 0xdc, 0x0d, 0xe9,
0x85, 0xd7, 0x8c, 0x98, 0xc9, 0xcd, 0x14, 0x24, 0x5f, 0x5a, 0x26, 0x24, 0x89, 0xc9, 0xb7, 0xd3,
0x98, 0x7c, 0x79, 0x09, 0xef, 0x0c, 0x28, 0xdf, 0x4c, 0x81, 0xf2, 0xb2, 0x89, 0x53, 0xa8, 0xdc,
0x99, 0x83, 0xca, 0xcb, 0xb6, 0xbf, 0x00, 0x96, 0x3b, 0x73, 0x60, 0x79, 0x67, 0xe9, 0x5a, 0xe6,
0xe2, 0xf2, 0xed, 0x34, 0x2e, 0x2f, 0x53, 0xc7, 0x0c, 0x30, 0xdf, 0x9b, 0x07, 0xcc, 0xd7, 0x97,
0xc8, 0x58, 0x88, 0xcc, 0xfb, 0xa7, 0x90, 0xf9, 0xea, 0x12, 0x51, 0x73, 0xa0, 0xb9, 0x93, 0x82,
0x66, 0xc8, 0xa4, 0x9b, 0x05, 0xd8, 0xfc, 0xd1, 0x69, 0x6c, 0xbe, 0xb6, 0xcc, 0xd4, 0xe6, 0x81,
0xf3, 0xf7, 0x66, 0xc0, 0xf9, 0xca, 0xb2, 0x5d, 0x2d, 0x44, 0xe7, 0xeb, 0xc2, 0x3f, 0xce, 0xdc,
0x0c, 0xe1, 0x4b, 0x99, 0xef, 0xbb, 0xbe, 0x02, 0x3e, 0xd9, 0xd0, 0x77, 0x84, 0xc7, 0x8e, 0xed,
0xff, 0x0c, 0x24, 0xc7, 0x4b, 0x9b, 0xb0, 0x76, 0xfd, 0x0b, 0x2d, 0xe6, 0x45, 0xcf, 0x96, 0xf4,
0xf6, 0x65, 0xe5, 0xed, 0x13, 0x00, 0x9f, 0x4b, 0x03, 0xfc, 0x36, 0x54, 0x04, 0xa6, 0xcc, 0x60,
0x37, 0xf5, 0x42, 0xec, 0x26, 0x6f, 0xc1, 0x2b, 0xe8, 0x7f, 0x65, 0x18, 0xa0, 0x1c, 0x49, 0x1e,
0x1d, 0x49, 0x55, 0x0c, 0x48, 0x0d, 0x4a, 0xa0, 0x78, 0x17, 0x5e, 0x4d, 0xd0, 0x0a, 0xb9, 0x88,
0x05, 0x12, 0xa4, 0x6a, 0x11, 0xf5, 0x9e, 0xe7, 0xb5, 0x69, 0x30, 0xd0, 0xef, 0xc7, 0x0a, 0x8a,
0xe3, 0x02, 0x02, 0xf9, 0x9e, 0x6b, 0xc9, 0x7d, 0x6f, 0x18, 0xf8, 0x2d, 0x62, 0x85, 0xa1, 0xdb,
0xc7, 0xc5, 0x95, 0x0d, 0xf1, 0x29, 0xa8, 0xa2, 0xab, 0x5d, 0x96, 0x77, 0x56, 0xff, 0xbd, 0x16,
0xcb, 0x8b, 0x43, 0x85, 0x79, 0xa8, 0xae, 0xbd, 0x4c, 0x54, 0xcf, 0xfd, 0x6f, 0xa8, 0xae, 0xff,
0x4b, 0x8b, 0x8f, 0x34, 0xc2, 0xeb, 0xaf, 0xa7, 0x02, 0x61, 0x5d, 0xb6, 0x63, 0xb1, 0x29, 0xaa,
0x7c, 0xd5, 0x90, 0x8d, 0x30, 0xd4, 0x2a, 0xe0, 0x31, 0xa4, 0x43, 0xad, 0x22, 0xf6, 0xc9, 0x06,
0x79, 0x1f, 0x71, 0xde, 0x7d, 0xaa, 0x5c, 0x43, 0x0a, 0x04, 0x65, 0xd6, 0xd7, 0x54, 0xe9, 0xde,
0x81, 0x20, 0x33, 0x24, 0x75, 0x02, 0x5f, 0xca, 0xa9, 0xb0, 0xe1, 0x02, 0x94, 0xc5, 0xd2, 0x03,
0x8f, 0xf6, 0x18, 0xde, 0xed, 0xb2, 0x11, 0x77, 0xe8, 0x16, 0x90, 0xd3, 0x3e, 0x86, 0x3c, 0x80,
0x02, 0x9b, 0x30, 0x87, 0x8b, 0x33, 0x12, 0x6a, 0xbd, 0xb0, 0x10, 0x88, 0x99, 0xc3, 0x5b, 0x75,
0xa1, 0xcc, 0x7f, 0x3e, 0xdf, 0xae, 0x49, 0x9e, 0x77, 0xdc, 0x91, 0xcd, 0xd9, 0xc8, 0xe3, 0xc7,
0x86, 0x92, 0xa2, 0xff, 0x2c, 0x27, 0xf0, 0x30, 0xe5, 0x7f, 0xe6, 0xaa, 0x37, 0xbc, 0x34, 0xb9,
0x44, 0x88, 0x94, 0x4d, 0xe5, 0x6f, 0x02, 0xf4, 0x69, 0x60, 0x3e, 0xa3, 0x0e, 0x67, 0x96, 0xd2,
0x7b, 0xb9, 0x4f, 0x83, 0x1f, 0x60, 0x87, 0x88, 0x37, 0xc5, 0xf0, 0x38, 0x60, 0x16, 0x1e, 0xc0,
0xaa, 0x51, 0xec, 0xd3, 0xe0, 0x71, 0xc0, 0xac, 0xc4, 0x5e, 0x8b, 0x2f, 0x63, 0xaf, 0x69, 0x7d,
0x97, 0x66, 0xf5, 0xfd, 0xf3, 0x5c, 0x7c, 0x3b, 0xe2, 0xf0, 0xe1, 0xff, 0x53, 0x17, 0xbf, 0xc1,
0x9c, 0x22, 0x0d, 0x02, 0xe4, 0x87, 0xf0, 0x4a, 0x74, 0x2b, 0xcd, 0x31, 0xde, 0xd6, 0xd0, 0x0a,
0x5f, 0xec, 0x72, 0xd7, 0x26, 0xe9, 0xee, 0x80, 0x7c, 0x06, 0x6f, 0xcc, 0xf8, 0xa0, 0x68, 0x82,
0xdc, 0x0b, 0xb9, 0xa2, 0xd7, 0xd2, 0xae, 0x28, 0x94, 0x1f, 0x6b, 0x6f, 0xf5, 0xa5, 0xdc, 0x9a,
0xcb, 0x22, 0x84, 0x4d, 0xc2, 0xdb, 0x3c, 0x9b, 0xd0, 0xff, 0xac, 0x41, 0x75, 0x66, 0x81, 0xe4,
0x03, 0x58, 0x93, 0x08, 0xac, 0x9d, 0x59, 0x08, 0x41, 0x8d, 0xab, 0x3d, 0x49, 0x06, 0xb2, 0x07,
0x25, 0xa6, 0xa2, 0x6b, 0xa5, 0x94, 0x2b, 0x4b, 0x82, 0x70, 0xc5, 0x1f, 0xb1, 0x91, 0x3b, 0x50,
0x8e, 0x54, 0xbf, 0x24, 0x73, 0x8b, 0x4e, 0x4e, 0x09, 0x89, 0x19, 0xf5, 0x7d, 0xa8, 0x24, 0x96,
0x47, 0xbe, 0x01, 0xe5, 0x11, 0x9d, 0xaa, 0x74, 0x4b, 0x06, 0xd0, 0xa5, 0x11, 0x9d, 0x62, 0xa6,
0x45, 0xde, 0x80, 0xa2, 0x18, 0xec, 0x53, 0x79, 0x90, 0xab, 0x46, 0x61, 0x44, 0xa7, 0xdf, 0xa7,
0x81, 0xfe, 0x0b, 0x0d, 0x36, 0xd3, 0xeb, 0x24, 0x6f, 0x03, 0x11, 0xb4, 0xb4, 0xcf, 0x4c, 0x67,
0x3c, 0x92, 0x18, 0x19, 0x4a, 0xac, 0x8e, 0xe8, 0x74, 0xaf, 0xcf, 0x1e, 0x8c, 0x47, 0x38, 0x75,
0x40, 0xee, 0x43, 0x2d, 0x24, 0x0e, 0x8b, 0x5d, 0x4a, 0x2b, 0xe7, 0x4f, 0x25, 0xbb, 0x77, 0x14,
0x81, 0xcc, 0x75, 0x7f, 0x2d, 0x72, 0xdd, 0x4d, 0x29, 0x2f, 0x1c, 0xd1, 0xdf, 0x87, 0xea, 0xcc,
0x8e, 0x89, 0x0e, 0x1b, 0xde, 0xb8, 0x6b, 0x1e, 0xb1, 0x63, 0x13, 0x55, 0x82, 0xa6, 0x5e, 0x36,
0x2a, 0xde, 0xb8, 0xfb, 0x31, 0x3b, 0x16, 0x59, 0x47, 0xa0, 0xf7, 0x60, 0x33, 0x9d, 0x4c, 0x09,
0xe0, 0xf0, 0xdd, 0xb1, 0x63, 0xe1, 0xba, 0xd7, 0x0c, 0xd9, 0x20, 0xb7, 0x60, 0x6d, 0xe2, 0x4a,
0x6b, 0x3e, 0x2b, 0x7b, 0x3a, 0x74, 0x39, 0x4b, 0xa4, 0x64, 0x92, 0x47, 0x0f, 0x60, 0x0d, 0xed,
0x52, 0xd8, 0x18, 0xa6, 0x45, 0x2a, 0x70, 0x11, 0xdf, 0xe4, 0x10, 0x80, 0x72, 0xee, 0xdb, 0xdd,
0x71, 0x2c, 0xbe, 0x9e, 0x14, 0x3f, 0xb4, 0xbb, 0x41, 0xf3, 0x68, 0xd2, 0x3c, 0xa0, 0xb6, 0xdf,
0xba, 0xa0, 0x2c, 0xfb, 0x5c, 0xcc, 0x93, 0xb0, 0xee, 0x84, 0x24, 0xfd, 0xab, 0x3c, 0x14, 0x64,
0xba, 0x49, 0x3e, 0x4c, 0x17, 0x3f, 0x2a, 0xbb, 0x5b, 0x8b, 0x96, 0x2f, 0xa9, 0xd4, 0xea, 0xa3,
0x08, 0xea, 0xea, 0x6c, 0x45, 0xa1, 0x55, 0x39, 0x79, 0xbe, 0x5d, 0xc4, 0xe8, 0xa3, 0x73, 0x27,
0x2e, 0x2f, 0x2c, 0xca, 0xae, 0xc3, 0x5a, 0x46, 0xfe, 0x85, 0x6b, 0x19, 0x6d, 0xd8, 0x48, 0x84,
0x5b, 0xb6, 0xa5, 0xf2, 0x94, 0xad, 0xb3, 0x2e, 0x5d, 0xe7, 0x8e, 0x5a, 0x7f, 0x25, 0x0a, 0xc7,
0x3a, 0x16, 0xd9, 0x49, 0x27, 0xd9, 0x18, 0xb5, 0xc9, 0x70, 0x21, 0x91, 0x37, 0x8b, 0x98, 0x4d,
0x5c, 0x07, 0x71, 0xf9, 0x25, 0x89, 0x8c, 0x1e, 0x4a, 0xa2, 0x03, 0x07, 0xaf, 0x41, 0x35, 0x0e,
0x6c, 0x24, 0x49, 0x49, 0x4a, 0x89, 0xbb, 0x91, 0xf0, 0x3d, 0x38, 0xe7, 0xb0, 0x29, 0x37, 0x67,
0xa9, 0xcb, 0x48, 0x4d, 0xc4, 0xd8, 0x61, 0x9a, 0xe3, 0x0a, 0x6c, 0xc6, 0x2e, 0x14, 0x69, 0x41,
0x96, 0x3e, 0xa2, 0x5e, 0x24, 0x3b, 0x0f, 0xa5, 0x28, 0xec, 0xac, 0x20, 0x41, 0x91, 0xca, 0x68,
0x33, 0x0a, 0x64, 0x7d, 0x16, 0x8c, 0x87, 0x5c, 0x09, 0x59, 0x47, 0x1a, 0x0c, 0x64, 0x0d, 0xd9,
0x8f, 0xb4, 0x97, 0x60, 0x23, 0xf4, 0x2a, 0x92, 0x6e, 0x03, 0xe9, 0xd6, 0xc3, 0x4e, 0x24, 0xba,
0x0e, 0x35, 0xcf, 0x77, 0x3d, 0x37, 0x60, 0xbe, 0x49, 0x2d, 0xcb, 0x67, 0x41, 0x50, 0xdf, 0x94,
0xf2, 0xc2, 0xfe, 0x3d, 0xd9, 0xad, 0x7f, 0x13, 0x8a, 0x61, 0x3c, 0x7d, 0x0e, 0xd6, 0x5a, 0x91,
0x87, 0xcc, 0x1b, 0xb2, 0x21, 0xf0, 0x75, 0xcf, 0xf3, 0x54, 0x75, 0x4d, 0x7c, 0xea, 0x43, 0x28,
0xaa, 0x03, 0x9b, 0x5b, 0x53, 0xb9, 0x0f, 0xeb, 0x1e, 0xf5, 0xc5, 0x36, 0x92, 0x95, 0x95, 0x45,
0x19, 0xe1, 0x01, 0xf5, 0xf9, 0x43, 0xc6, 0x53, 0x05, 0x96, 0x0a, 0xf2, 0xcb, 0x2e, 0xfd, 0x26,
0x6c, 0xa4, 0x68, 0xc4, 0x32, 0xb9, 0xcb, 0xe9, 0x30, 0xbc, 0xe8, 0xd8, 0x88, 0x56, 0x92, 0x8b,
0x57, 0xa2, 0xdf, 0x82, 0x72, 0x74, 0x56, 0x22, 0xd1, 0x08, 0x55, 0xa1, 0x29, 0xf5, 0xcb, 0x26,
0x16, 0x91, 0xdc, 0x67, 0xcc, 0x57, 0xd6, 0x2f, 0x1b, 0x3a, 0x4b, 0x38, 0x26, 0x89, 0x66, 0xe4,
0x36, 0x14, 0x95, 0x63, 0x52, 0xf7, 0x71, 0x51, 0xb9, 0xe8, 0x00, 0x3d, 0x55, 0x58, 0x2e, 0x92,
0x7e, 0x2b, 0x9e, 0x26, 0x97, 0x9c, 0xe6, 0xa7, 0x50, 0x0a, 0x9d, 0x4f, 0x1a, 0x25, 0xe4, 0x0c,
0x17, 0x97, 0xa1, 0x84, 0x9a, 0x24, 0x66, 0x14, 0xd6, 0x14, 0xd8, 0x7d, 0x87, 0x59, 0x66, 0x7c,
0x05, 0x71, 0xce, 0x92, 0x51, 0x95, 0x03, 0xf7, 0xc2, 0xfb, 0xa5, 0xbf, 0x07, 0x05, 0xb9, 0xd6,
0xb9, 0x2e, 0x6e, 0x1e, 0xb4, 0xfe, 0x43, 0x83, 0x52, 0x08, 0x1f, 0x73, 0x99, 0x52, 0x9b, 0xc8,
0x7d, 0xdd, 0x4d, 0xbc, 0x7c, 0x97, 0xf4, 0x0e, 0x10, 0xb4, 0x14, 0x73, 0xe2, 0x72, 0xdb, 0xe9,
0x9b, 0xf2, 0x2c, 0x64, 0x24, 0x58, 0xc3, 0x91, 0x43, 0x1c, 0x38, 0x10, 0xfd, 0x6f, 0x5d, 0x82,
0x4a, 0xa2, 0xca, 0x45, 0x8a, 0xb0, 0xfa, 0x80, 0x3d, 0xab, 0xad, 0x90, 0x0a, 0x14, 0x0d, 0x86,
0x35, 0x82, 0x9a, 0xb6, 0xfb, 0x55, 0x11, 0xaa, 0x7b, 0xad, 0xfd, 0xce, 0x9e, 0xe7, 0x0d, 0xed,
0x1e, 0xe2, 0x19, 0xf9, 0x04, 0xf2, 0x98, 0x27, 0x67, 0x78, 0xdf, 0x69, 0x64, 0x29, 0x38, 0x11,
0x03, 0xd6, 0x30, 0x9d, 0x26, 0x59, 0x9e, 0x7d, 0x1a, 0x99, 0xea, 0x50, 0x62, 0x91, 0x68, 0x70,
0x19, 0x5e, 0x83, 0x1a, 0x59, 0x8a, 0x53, 0xe4, 0x33, 0x28, 0xc7, 0x79, 0x72, 0xd6, 0x37, 0xa2,
0x46, 0xe6, 0xb2, 0x95, 0x90, 0x1f, 0x67, 0x06, 0x59, 0x5f, 0x48, 0x1a, 0x99, 0xeb, 0x35, 0xe4,
0x09, 0x14, 0xc3, 0x1c, 0x2c, 0xdb, 0x2b, 0x4e, 0x23, 0x63, 0x49, 0x49, 0x1c, 0x9f, 0x4c, 0x9d,
0xb3, 0x3c, 0x55, 0x35, 0x32, 0xd5, 0xcd, 0xc8, 0x63, 0x28, 0xa8, 0xe0, 0x37, 0xd3, 0xfb, 0x4c,
0x23, 0x5b, 0xa1, 0x48, 0x28, 0x39, 0x2e, 0x4e, 0x64, 0x7d, 0x9e, 0x6b, 0x64, 0x2e, 0x18, 0x12,
0x0a, 0x90, 0xc8, 0xa7, 0x33, 0xbf, 0xbb, 0x35, 0xb2, 0x17, 0x02, 0xc9, 0x8f, 0xa1, 0x14, 0x65,
0x4d, 0x19, 0xdf, 0xbf, 0x1a, 0x59, 0x6b, 0x71, 0xad, 0xce, 0x7f, 0xfe, 0xb6, 0xa5, 0xfd, 0xf6,
0x64, 0x4b, 0xfb, 0xe2, 0x64, 0x4b, 0xfb, 0xf2, 0x64, 0x4b, 0xfb, 0xd3, 0xc9, 0x96, 0xf6, 0xd7,
0x93, 0x2d, 0xed, 0x0f, 0x7f, 0xdf, 0xd2, 0x7e, 0xf4, 0xf6, 0xd2, 0x17, 0xe6, 0xf8, 0x75, 0xbc,
0x5b, 0x40, 0x87, 0xf5, 0xad, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x5a, 0xa5, 0xa9, 0x6d, 0x32,
0x1f, 0x00, 0x00,
0x15, 0xde, 0xd1, 0x6a, 0x57, 0xd2, 0xd3, 0xee, 0x4a, 0x69, 0x3b, 0x89, 0x22, 0x92, 0x5d, 0xd7,
0xf8, 0x6f, 0x9d, 0x04, 0x6d, 0x58, 0x2a, 0x54, 0x8c, 0x5d, 0xa1, 0x56, 0x6b, 0x07, 0xa9, 0x62,
0x3b, 0x9b, 0xb1, 0xbd, 0x18, 0xa8, 0xca, 0x54, 0x4b, 0xd3, 0x96, 0xa6, 0x56, 0x9a, 0x99, 0xcc,
0xb4, 0x64, 0x89, 0xe2, 0x4e, 0x51, 0xc5, 0x81, 0x0b, 0x55, 0x5c, 0xb8, 0x73, 0xe4, 0xc0, 0x21,
0x47, 0x8e, 0x39, 0x70, 0xe0, 0xc0, 0xd9, 0xc0, 0xc2, 0x89, 0xca, 0x91, 0xa2, 0x38, 0x52, 0xfd,
0xba, 0xe7, 0x4f, 0x2b, 0xad, 0xc6, 0xc1, 0x37, 0x2e, 0xd2, 0x74, 0xf7, 0x7b, 0xaf, 0xbb, 0x5f,
0xbf, 0x7e, 0xdf, 0x7b, 0xaf, 0xe1, 0x35, 0xda, 0xe9, 0xda, 0x7b, 0x7c, 0xea, 0xb1, 0x40, 0xfe,
0x36, 0x3c, 0xdf, 0xe5, 0x2e, 0x79, 0x95, 0x33, 0xc7, 0x62, 0xfe, 0xd0, 0x76, 0x78, 0x43, 0x90,
0x34, 0x70, 0xb0, 0x7e, 0x8d, 0xf7, 0x6d, 0xdf, 0x32, 0x3d, 0xea, 0xf3, 0xe9, 0x1e, 0x52, 0xee,
0xf5, 0xdc, 0x9e, 0x1b, 0x7f, 0x49, 0xf6, 0x7a, 0xbd, 0xeb, 0x4f, 0x3d, 0xee, 0xee, 0x0d, 0x99,
0x7f, 0x32, 0x60, 0xea, 0x4f, 0x8d, 0x5d, 0x18, 0xd8, 0x9d, 0x60, 0xef, 0x64, 0x9c, 0x9c, 0xaf,
0xbe, 0xd3, 0x73, 0xdd, 0xde, 0x80, 0x49, 0x99, 0x9d, 0xd1, 0xd3, 0x3d, 0x6e, 0x0f, 0x59, 0xc0,
0xe9, 0xd0, 0x53, 0x04, 0xdb, 0xb3, 0x04, 0xd6, 0xc8, 0xa7, 0xdc, 0x76, 0x1d, 0x39, 0xae, 0xff,
0x7b, 0x0d, 0x0a, 0x06, 0xfb, 0x7c, 0xc4, 0x02, 0x4e, 0x3e, 0x80, 0x3c, 0xeb, 0xf6, 0xdd, 0x5a,
0xee, 0x92, 0xb6, 0x5b, 0xde, 0xd7, 0x1b, 0x73, 0xf7, 0xd2, 0x50, 0xd4, 0x77, 0xbb, 0x7d, 0xb7,
0xb5, 0x62, 0x20, 0x07, 0xb9, 0x05, 0x6b, 0x4f, 0x07, 0xa3, 0xa0, 0x5f, 0x5b, 0x45, 0xd6, 0xcb,
0xe7, 0xb3, 0x7e, 0x24, 0x48, 0x5b, 0x2b, 0x86, 0xe4, 0x11, 0xd3, 0xda, 0xce, 0x53, 0xb7, 0x96,
0xcf, 0x32, 0x6d, 0xdb, 0x79, 0x8a, 0xd3, 0x0a, 0x0e, 0xd2, 0x02, 0x08, 0x18, 0x37, 0x5d, 0x4f,
0x6c, 0xa8, 0xb6, 0x86, 0xfc, 0xd7, 0xcf, 0xe7, 0x7f, 0xc8, 0xf8, 0x27, 0x48, 0xde, 0x5a, 0x31,
0x4a, 0x41, 0xd8, 0x10, 0x92, 0x6c, 0xc7, 0xe6, 0x66, 0xb7, 0x4f, 0x6d, 0xa7, 0xb6, 0x9e, 0x45,
0x52, 0xdb, 0xb1, 0xf9, 0xa1, 0x20, 0x17, 0x92, 0xec, 0xb0, 0x21, 0x54, 0xf1, 0xf9, 0x88, 0xf9,
0xd3, 0x5a, 0x21, 0x8b, 0x2a, 0x3e, 0x15, 0xa4, 0x42, 0x15, 0xc8, 0x43, 0x3e, 0x86, 0x72, 0x87,
0xf5, 0x6c, 0xc7, 0xec, 0x0c, 0xdc, 0xee, 0x49, 0xad, 0x88, 0x22, 0x76, 0xcf, 0x17, 0xd1, 0x14,
0x0c, 0x4d, 0x41, 0xdf, 0x5a, 0x31, 0xa0, 0x13, 0xb5, 0x48, 0x13, 0x8a, 0xdd, 0x3e, 0xeb, 0x9e,
0x98, 0x7c, 0x52, 0x2b, 0xa1, 0xa4, 0xab, 0xe7, 0x4b, 0x3a, 0x14, 0xd4, 0x8f, 0x26, 0xad, 0x15,
0xa3, 0xd0, 0x95, 0x9f, 0x42, 0x2f, 0x16, 0x1b, 0xd8, 0x63, 0xe6, 0x0b, 0x29, 0x17, 0xb2, 0xe8,
0xe5, 0x8e, 0xa4, 0x47, 0x39, 0x25, 0x2b, 0x6c, 0x90, 0xbb, 0x50, 0x62, 0x8e, 0xa5, 0x36, 0x56,
0x46, 0x41, 0xd7, 0x96, 0x58, 0x98, 0x63, 0x85, 0xdb, 0x2a, 0x32, 0xf5, 0x4d, 0x3e, 0x84, 0xf5,
0xae, 0x3b, 0x1c, 0xda, 0xbc, 0xb6, 0x81, 0x32, 0xae, 0x2c, 0xd9, 0x12, 0xd2, 0xb6, 0x56, 0x0c,
0xc5, 0xd5, 0x2c, 0xc0, 0xda, 0x98, 0x0e, 0x46, 0x4c, 0xbf, 0x0e, 0xe5, 0x84, 0x25, 0x93, 0x1a,
0x14, 0x86, 0x2c, 0x08, 0x68, 0x8f, 0xd5, 0xb4, 0x4b, 0xda, 0x6e, 0xc9, 0x08, 0x9b, 0xfa, 0x16,
0x6c, 0x24, 0xed, 0x56, 0x1f, 0x46, 0x8c, 0xc2, 0x16, 0x05, 0xe3, 0x98, 0xf9, 0x81, 0x30, 0x40,
0xc5, 0xa8, 0x9a, 0xe4, 0x32, 0x6c, 0xe2, 0x6e, 0xcd, 0x70, 0x5c, 0xdc, 0xab, 0xbc, 0xb1, 0x81,
0x9d, 0xc7, 0x8a, 0x68, 0x07, 0xca, 0xde, 0xbe, 0x17, 0x91, 0xac, 0x22, 0x09, 0x78, 0xfb, 0x9e,
0x22, 0xd0, 0xbf, 0x0b, 0xd5, 0x59, 0xd3, 0x25, 0x55, 0x58, 0x3d, 0x61, 0x53, 0x35, 0x9f, 0xf8,
0x24, 0x17, 0xd5, 0xb6, 0x70, 0x8e, 0x92, 0xa1, 0xf6, 0xf8, 0xbb, 0x5c, 0xc4, 0x1c, 0x59, 0xab,
0xb8, 0x6e, 0xc2, 0x49, 0x20, 0x77, 0x79, 0xbf, 0xde, 0x90, 0x0e, 0xa2, 0x11, 0x3a, 0x88, 0xc6,
0xa3, 0xd0, 0x83, 0x34, 0x8b, 0x5f, 0x3e, 0xdf, 0x59, 0xf9, 0xe5, 0x5f, 0x76, 0x34, 0x03, 0x39,
0xc8, 0x1b, 0xc2, 0xa0, 0xa8, 0xed, 0x98, 0xb6, 0xa5, 0xe6, 0x29, 0x60, 0xbb, 0x6d, 0x91, 0x4f,
0xa1, 0xda, 0x75, 0x9d, 0x80, 0x39, 0xc1, 0x28, 0x10, 0x6e, 0x8e, 0x0e, 0x03, 0xe5, 0x0b, 0x16,
0x1d, 0xf2, 0x61, 0x48, 0x7e, 0x84, 0xd4, 0x46, 0xa5, 0x9b, 0xee, 0x20, 0xf7, 0x00, 0xc6, 0x74,
0x60, 0x5b, 0x94, 0xbb, 0x7e, 0x50, 0xcb, 0x5f, 0x5a, 0x3d, 0x47, 0xd8, 0x71, 0x48, 0xf8, 0xd8,
0xb3, 0x28, 0x67, 0xcd, 0xbc, 0x58, 0xb9, 0x91, 0xe0, 0x27, 0xd7, 0xa0, 0x42, 0x3d, 0xcf, 0x0c,
0x38, 0xe5, 0xcc, 0xec, 0x4c, 0x39, 0x0b, 0xd0, 0x5f, 0x6c, 0x18, 0x9b, 0xd4, 0xf3, 0x1e, 0x8a,
0xde, 0xa6, 0xe8, 0xd4, 0xad, 0xe8, 0xb4, 0xf1, 0x6a, 0x12, 0x02, 0x79, 0x8b, 0x72, 0x8a, 0xda,
0xda, 0x30, 0xf0, 0x5b, 0xf4, 0x79, 0x94, 0xf7, 0x95, 0x0e, 0xf0, 0x9b, 0xbc, 0x06, 0xeb, 0x7d,
0x66, 0xf7, 0xfa, 0x1c, 0xb7, 0xbd, 0x6a, 0xa8, 0x96, 0x38, 0x18, 0xcf, 0x77, 0xc7, 0x0c, 0xbd,
0x5b, 0xd1, 0x90, 0x0d, 0xfd, 0x57, 0x39, 0x78, 0xe5, 0xcc, 0xf5, 0x15, 0x72, 0xfb, 0x34, 0xe8,
0x87, 0x73, 0x89, 0x6f, 0x72, 0x4b, 0xc8, 0xa5, 0x16, 0xf3, 0x95, 0x57, 0x7e, 0x6b, 0x81, 0x06,
0x5a, 0x48, 0xa4, 0x36, 0xae, 0x58, 0xc8, 0x63, 0xa8, 0x0e, 0x68, 0xc0, 0x4d, 0x69, 0xfb, 0x26,
0x7a, 0xd9, 0xd5, 0x73, 0x3d, 0xc1, 0x3d, 0x1a, 0xde, 0x19, 0x61, 0xdc, 0x4a, 0xdc, 0xd6, 0x20,
0xd5, 0x4b, 0x9e, 0xc0, 0xc5, 0xce, 0xf4, 0x27, 0xd4, 0xe1, 0xb6, 0xc3, 0xcc, 0x33, 0x67, 0xb4,
0xb3, 0x40, 0xf4, 0xdd, 0xb1, 0x6d, 0x31, 0xa7, 0x1b, 0x1e, 0xce, 0x85, 0x48, 0x44, 0x74, 0x78,
0x81, 0xfe, 0x04, 0xb6, 0xd2, 0xbe, 0x88, 0x6c, 0x41, 0x8e, 0x4f, 0x94, 0x46, 0x72, 0x7c, 0x42,
0xbe, 0x03, 0x79, 0x21, 0x0e, 0xb5, 0xb1, 0xb5, 0x10, 0x2c, 0x14, 0xf7, 0xa3, 0xa9, 0xc7, 0x0c,
0xa4, 0xd7, 0xf5, 0xe8, 0x26, 0x44, 0xfe, 0x69, 0x56, 0xb6, 0x7e, 0x03, 0x2a, 0x33, 0xae, 0x27,
0x71, 0xac, 0x5a, 0xf2, 0x58, 0xf5, 0x0a, 0x6c, 0xa6, 0x3c, 0x8c, 0xfe, 0xc7, 0x75, 0x28, 0x1a,
0x2c, 0xf0, 0x84, 0x11, 0x93, 0x16, 0x94, 0xd8, 0xa4, 0xcb, 0x24, 0x2c, 0x69, 0x4b, 0x9c, 0xb8,
0xe4, 0xb9, 0x1b, 0xd2, 0x0b, 0xaf, 0x19, 0x31, 0x93, 0x9b, 0x29, 0x48, 0xbe, 0xbc, 0x4c, 0x48,
0x12, 0x93, 0x6f, 0xa7, 0x31, 0xf9, 0xca, 0x12, 0xde, 0x19, 0x50, 0xbe, 0x99, 0x02, 0xe5, 0x65,
0x13, 0xa7, 0x50, 0xb9, 0x3d, 0x07, 0x95, 0x97, 0x6d, 0x7f, 0x01, 0x2c, 0xb7, 0xe7, 0xc0, 0xf2,
0xee, 0xd2, 0xb5, 0xcc, 0xc5, 0xe5, 0xdb, 0x69, 0x5c, 0x5e, 0xa6, 0x8e, 0x19, 0x60, 0xbe, 0x37,
0x0f, 0x98, 0x6f, 0x2c, 0x91, 0xb1, 0x10, 0x99, 0x0f, 0xcf, 0x20, 0xf3, 0xb5, 0x25, 0xa2, 0xe6,
0x40, 0x73, 0x3b, 0x05, 0xcd, 0x90, 0x49, 0x37, 0x0b, 0xb0, 0xf9, 0xa3, 0xb3, 0xd8, 0x7c, 0x7d,
0x99, 0xa9, 0xcd, 0x03, 0xe7, 0xef, 0xcd, 0x80, 0xf3, 0xd5, 0x65, 0xbb, 0x5a, 0x88, 0xce, 0x37,
0x84, 0x7f, 0x9c, 0xb9, 0x19, 0xc2, 0x97, 0x32, 0xdf, 0x77, 0x7d, 0x05, 0x7c, 0xb2, 0xa1, 0xef,
0x0a, 0x8f, 0x1d, 0xdb, 0xff, 0x39, 0x48, 0x8e, 0x97, 0x36, 0x61, 0xed, 0xfa, 0x17, 0x5a, 0xcc,
0x8b, 0x9e, 0x2d, 0xe9, 0xed, 0x4b, 0xca, 0xdb, 0x27, 0x00, 0x3e, 0x97, 0x06, 0xf8, 0x1d, 0x28,
0x0b, 0x4c, 0x99, 0xc1, 0x6e, 0xea, 0x85, 0xd8, 0x4d, 0xde, 0x86, 0x57, 0xd0, 0xff, 0xca, 0x30,
0x40, 0x39, 0x92, 0x3c, 0x3a, 0x92, 0x8a, 0x18, 0x90, 0x1a, 0x94, 0x40, 0xf1, 0x4d, 0xb8, 0x90,
0xa0, 0x15, 0x72, 0x11, 0x0b, 0x24, 0x48, 0x55, 0x23, 0xea, 0x03, 0xcf, 0x6b, 0xd1, 0xa0, 0xaf,
0xdf, 0x8f, 0x15, 0x14, 0xc7, 0x05, 0x04, 0xf2, 0x5d, 0xd7, 0x92, 0xfb, 0xde, 0x34, 0xf0, 0x5b,
0xc4, 0x0a, 0x03, 0xb7, 0x87, 0x8b, 0x2b, 0x19, 0xe2, 0x53, 0x50, 0x45, 0x57, 0xbb, 0x24, 0xef,
0xac, 0xfe, 0x7b, 0x2d, 0x96, 0x17, 0x87, 0x0a, 0xf3, 0x50, 0x5d, 0x7b, 0x99, 0xa8, 0x9e, 0xfb,
0xdf, 0x50, 0x5d, 0xff, 0x97, 0x16, 0x1f, 0x69, 0x84, 0xd7, 0x5f, 0x4f, 0x05, 0xc2, 0xba, 0x6c,
0xc7, 0x62, 0x13, 0x54, 0xf9, 0xaa, 0x21, 0x1b, 0x61, 0xa8, 0xb5, 0x8e, 0xc7, 0x90, 0x0e, 0xb5,
0x0a, 0xd8, 0x27, 0x1b, 0xe4, 0x7d, 0xc4, 0x79, 0xf7, 0xa9, 0x72, 0x0d, 0x29, 0x10, 0x94, 0x49,
0x5d, 0x43, 0x65, 0x73, 0x47, 0x82, 0xcc, 0x90, 0xd4, 0x09, 0x7c, 0x29, 0xa5, 0xc2, 0x86, 0x37,
0xa1, 0x24, 0x96, 0x1e, 0x78, 0xb4, 0xcb, 0xf0, 0x6e, 0x97, 0x8c, 0xb8, 0x43, 0xb7, 0x80, 0x9c,
0xf5, 0x31, 0xe4, 0x01, 0xac, 0xb3, 0x31, 0x73, 0xb8, 0x38, 0x23, 0xa1, 0xd6, 0x37, 0x17, 0x02,
0x31, 0x73, 0x78, 0xb3, 0x26, 0x94, 0xf9, 0xcf, 0xe7, 0x3b, 0x55, 0xc9, 0xf3, 0xae, 0x3b, 0xb4,
0x39, 0x1b, 0x7a, 0x7c, 0x6a, 0x28, 0x29, 0xfa, 0xcf, 0x72, 0x02, 0x0f, 0x53, 0xfe, 0x67, 0xae,
0x7a, 0xc3, 0x4b, 0x93, 0x4b, 0x84, 0x48, 0xd9, 0x54, 0xfe, 0x16, 0x40, 0x8f, 0x06, 0xe6, 0x33,
0xea, 0x70, 0x66, 0x29, 0xbd, 0x97, 0x7a, 0x34, 0xf8, 0x01, 0x76, 0x88, 0x78, 0x53, 0x0c, 0x8f,
0x02, 0x66, 0xe1, 0x01, 0xac, 0x1a, 0x85, 0x1e, 0x0d, 0x1e, 0x07, 0xcc, 0x4a, 0xec, 0xb5, 0xf0,
0x32, 0xf6, 0x9a, 0xd6, 0x77, 0x71, 0x56, 0xdf, 0x3f, 0xcf, 0xc5, 0xb7, 0x23, 0x0e, 0x1f, 0xfe,
0x3f, 0x75, 0xf1, 0x1b, 0xcc, 0x29, 0xd2, 0x20, 0x40, 0x7e, 0x08, 0xaf, 0x44, 0xb7, 0xd2, 0x1c,
0xe1, 0x6d, 0x0d, 0xad, 0xf0, 0xc5, 0x2e, 0x77, 0x75, 0x9c, 0xee, 0x0e, 0xc8, 0x67, 0xf0, 0xfa,
0x8c, 0x0f, 0x8a, 0x26, 0xc8, 0xbd, 0x90, 0x2b, 0x7a, 0x35, 0xed, 0x8a, 0x42, 0xf9, 0xb1, 0xf6,
0x56, 0x5f, 0xca, 0xad, 0xb9, 0x22, 0x42, 0xd8, 0x24, 0xbc, 0xcd, 0xb3, 0x09, 0xfd, 0xcf, 0x1a,
0x54, 0x66, 0x16, 0x48, 0x3e, 0x80, 0x35, 0x89, 0xc0, 0xda, 0xb9, 0x85, 0x10, 0xd4, 0xb8, 0xda,
0x93, 0x64, 0x20, 0x07, 0x50, 0x64, 0x2a, 0xba, 0x56, 0x4a, 0xb9, 0xba, 0x24, 0x08, 0x57, 0xfc,
0x11, 0x1b, 0xb9, 0x03, 0xa5, 0x48, 0xf5, 0x4b, 0x32, 0xb7, 0xe8, 0xe4, 0x94, 0x90, 0x98, 0x51,
0x3f, 0x84, 0x72, 0x62, 0x79, 0xe4, 0x1b, 0x50, 0x1a, 0xd2, 0x89, 0x4a, 0xb7, 0x64, 0x00, 0x5d,
0x1c, 0xd2, 0x09, 0x66, 0x5a, 0xe4, 0x75, 0x28, 0x88, 0xc1, 0x1e, 0x95, 0x07, 0xb9, 0x6a, 0xac,
0x0f, 0xe9, 0xe4, 0xfb, 0x34, 0xd0, 0x7f, 0xa1, 0xc1, 0x56, 0x7a, 0x9d, 0xe4, 0x1d, 0x20, 0x82,
0x96, 0xf6, 0x98, 0xe9, 0x8c, 0x86, 0x12, 0x23, 0x43, 0x89, 0x95, 0x21, 0x9d, 0x1c, 0xf4, 0xd8,
0x83, 0xd1, 0x10, 0xa7, 0x0e, 0xc8, 0x7d, 0xa8, 0x86, 0xc4, 0x61, 0xb1, 0x4b, 0x69, 0xe5, 0x8d,
0x33, 0xc9, 0xee, 0x1d, 0x45, 0x20, 0x73, 0xdd, 0x5f, 0x8b, 0x5c, 0x77, 0x4b, 0xca, 0x0b, 0x47,
0xf4, 0xf7, 0xa1, 0x32, 0xb3, 0x63, 0xa2, 0xc3, 0xa6, 0x37, 0xea, 0x98, 0x27, 0x6c, 0x6a, 0xa2,
0x4a, 0xd0, 0xd4, 0x4b, 0x46, 0xd9, 0x1b, 0x75, 0x3e, 0x66, 0x53, 0x91, 0x75, 0x04, 0x7a, 0x17,
0xb6, 0xd2, 0xc9, 0x94, 0x00, 0x0e, 0xdf, 0x1d, 0x39, 0x16, 0xae, 0x7b, 0xcd, 0x90, 0x0d, 0x72,
0x0b, 0xd6, 0xc6, 0xae, 0xb4, 0xe6, 0xf3, 0xb2, 0xa7, 0x63, 0x97, 0xb3, 0x44, 0x4a, 0x26, 0x79,
0xf4, 0x00, 0xd6, 0xd0, 0x2e, 0x85, 0x8d, 0x61, 0x5a, 0xa4, 0x02, 0x17, 0xf1, 0x4d, 0x8e, 0x01,
0x28, 0xe7, 0xbe, 0xdd, 0x19, 0xc5, 0xe2, 0x6b, 0x49, 0xf1, 0x03, 0xbb, 0x13, 0x34, 0x4e, 0xc6,
0x8d, 0x23, 0x6a, 0xfb, 0xcd, 0x37, 0x95, 0x65, 0x5f, 0x8c, 0x79, 0x12, 0xd6, 0x9d, 0x90, 0xa4,
0x7f, 0x95, 0x87, 0x75, 0x99, 0x6e, 0x92, 0x0f, 0xd3, 0xc5, 0x8f, 0xf2, 0xfe, 0xf6, 0xa2, 0xe5,
0x4b, 0x2a, 0xb5, 0xfa, 0x28, 0x82, 0xba, 0x36, 0x5b, 0x51, 0x68, 0x96, 0x4f, 0x9f, 0xef, 0x14,
0x30, 0xfa, 0x68, 0xdf, 0x89, 0xcb, 0x0b, 0x8b, 0xb2, 0xeb, 0xb0, 0x96, 0x91, 0x7f, 0xe1, 0x5a,
0x46, 0x0b, 0x36, 0x13, 0xe1, 0x96, 0x6d, 0xa9, 0x3c, 0x65, 0xfb, 0xbc, 0x4b, 0xd7, 0xbe, 0xa3,
0xd6, 0x5f, 0x8e, 0xc2, 0xb1, 0xb6, 0x45, 0x76, 0xd3, 0x49, 0x36, 0x46, 0x6d, 0x32, 0x5c, 0x48,
0xe4, 0xcd, 0x22, 0x66, 0x13, 0xd7, 0x41, 0x5c, 0x7e, 0x49, 0x22, 0xa3, 0x87, 0xa2, 0xe8, 0xc0,
0xc1, 0xeb, 0x50, 0x89, 0x03, 0x1b, 0x49, 0x52, 0x94, 0x52, 0xe2, 0x6e, 0x24, 0x7c, 0x0f, 0x2e,
0x3a, 0x6c, 0xc2, 0xcd, 0x59, 0xea, 0x12, 0x52, 0x13, 0x31, 0x76, 0x9c, 0xe6, 0xb8, 0x0a, 0x5b,
0xb1, 0x0b, 0x45, 0x5a, 0x90, 0xa5, 0x8f, 0xa8, 0x17, 0xc9, 0xde, 0x80, 0x62, 0x14, 0x76, 0x96,
0x91, 0xa0, 0x40, 0x65, 0xb4, 0x19, 0x05, 0xb2, 0x3e, 0x0b, 0x46, 0x03, 0xae, 0x84, 0x6c, 0x20,
0x0d, 0x06, 0xb2, 0x86, 0xec, 0x47, 0xda, 0xcb, 0xb0, 0x19, 0x7a, 0x15, 0x49, 0xb7, 0x89, 0x74,
0x1b, 0x61, 0x27, 0x12, 0xdd, 0x80, 0xaa, 0xe7, 0xbb, 0x9e, 0x1b, 0x30, 0xdf, 0xa4, 0x96, 0xe5,
0xb3, 0x20, 0xa8, 0x6d, 0x49, 0x79, 0x61, 0xff, 0x81, 0xec, 0xd6, 0xbf, 0x05, 0x85, 0x30, 0x9e,
0xbe, 0x08, 0x6b, 0xcd, 0xc8, 0x43, 0xe6, 0x0d, 0xd9, 0x10, 0xf8, 0x7a, 0xe0, 0x79, 0xaa, 0xba,
0x26, 0x3e, 0xf5, 0x01, 0x14, 0xd4, 0x81, 0xcd, 0xad, 0xa9, 0xdc, 0x87, 0x0d, 0x8f, 0xfa, 0x62,
0x1b, 0xc9, 0xca, 0xca, 0xa2, 0x8c, 0xf0, 0x88, 0xfa, 0xfc, 0x21, 0xe3, 0xa9, 0x02, 0x4b, 0x19,
0xf9, 0x65, 0x97, 0x7e, 0x13, 0x36, 0x53, 0x34, 0x62, 0x99, 0xdc, 0xe5, 0x74, 0x10, 0x5e, 0x74,
0x6c, 0x44, 0x2b, 0xc9, 0xc5, 0x2b, 0xd1, 0x6f, 0x41, 0x29, 0x3a, 0x2b, 0x91, 0x68, 0x84, 0xaa,
0xd0, 0x94, 0xfa, 0x65, 0x13, 0x8b, 0x48, 0xee, 0x33, 0xe6, 0x2b, 0xeb, 0x97, 0x0d, 0x9d, 0x25,
0x1c, 0x93, 0x44, 0x33, 0x72, 0x1b, 0x0a, 0xca, 0x31, 0xa9, 0xfb, 0xb8, 0xa8, 0x5c, 0x74, 0x84,
0x9e, 0x2a, 0x2c, 0x17, 0x49, 0xbf, 0x15, 0x4f, 0x93, 0x4b, 0x4e, 0xf3, 0x53, 0x28, 0x86, 0xce,
0x27, 0x8d, 0x12, 0x72, 0x86, 0x4b, 0xcb, 0x50, 0x42, 0x4d, 0x12, 0x33, 0x0a, 0x6b, 0x0a, 0xec,
0x9e, 0xc3, 0x2c, 0x33, 0xbe, 0x82, 0x38, 0x67, 0xd1, 0xa8, 0xc8, 0x81, 0x7b, 0xe1, 0xfd, 0xd2,
0xdf, 0x83, 0x75, 0xb9, 0xd6, 0xb9, 0x2e, 0x6e, 0x1e, 0xb4, 0xfe, 0x43, 0x83, 0x62, 0x08, 0x1f,
0x73, 0x99, 0x52, 0x9b, 0xc8, 0x7d, 0xdd, 0x4d, 0xbc, 0x7c, 0x97, 0xf4, 0x2e, 0x10, 0xb4, 0x14,
0x73, 0xec, 0x72, 0xdb, 0xe9, 0x99, 0xf2, 0x2c, 0x64, 0x24, 0x58, 0xc5, 0x91, 0x63, 0x1c, 0x38,
0x12, 0xfd, 0x6f, 0x5f, 0x86, 0x72, 0xa2, 0xca, 0x45, 0x0a, 0xb0, 0xfa, 0x80, 0x3d, 0xab, 0xae,
0x90, 0x32, 0x14, 0x0c, 0x86, 0x35, 0x82, 0xaa, 0xb6, 0xff, 0x55, 0x01, 0x2a, 0x07, 0xcd, 0xc3,
0xf6, 0x81, 0xe7, 0x0d, 0xec, 0x2e, 0xe2, 0x19, 0xf9, 0x04, 0xf2, 0x98, 0x27, 0x67, 0x78, 0xdf,
0xa9, 0x67, 0x29, 0x38, 0x11, 0x03, 0xd6, 0x30, 0x9d, 0x26, 0x59, 0x9e, 0x7d, 0xea, 0x99, 0xea,
0x50, 0x62, 0x91, 0x68, 0x70, 0x19, 0x5e, 0x83, 0xea, 0x59, 0x8a, 0x53, 0xe4, 0x33, 0x28, 0xc5,
0x79, 0x72, 0xd6, 0x37, 0xa2, 0x7a, 0xe6, 0xb2, 0x95, 0x90, 0x1f, 0x67, 0x06, 0x59, 0x5f, 0x48,
0xea, 0x99, 0xeb, 0x35, 0xe4, 0x09, 0x14, 0xc2, 0x1c, 0x2c, 0xdb, 0x2b, 0x4e, 0x3d, 0x63, 0x49,
0x49, 0x1c, 0x9f, 0x4c, 0x9d, 0xb3, 0x3c, 0x55, 0xd5, 0x33, 0xd5, 0xcd, 0xc8, 0x63, 0x58, 0x57,
0xc1, 0x6f, 0xa6, 0xf7, 0x99, 0x7a, 0xb6, 0x42, 0x91, 0x50, 0x72, 0x5c, 0x9c, 0xc8, 0xfa, 0x3c,
0x57, 0xcf, 0x5c, 0x30, 0x24, 0x14, 0x20, 0x91, 0x4f, 0x67, 0x7e, 0x77, 0xab, 0x67, 0x2f, 0x04,
0x92, 0x1f, 0x43, 0x31, 0xca, 0x9a, 0x32, 0xbe, 0x7f, 0xd5, 0xb3, 0xd6, 0xe2, 0x9a, 0xed, 0xff,
0xfc, 0x6d, 0x5b, 0xfb, 0xed, 0xe9, 0xb6, 0xf6, 0xc5, 0xe9, 0xb6, 0xf6, 0xe5, 0xe9, 0xb6, 0xf6,
0xa7, 0xd3, 0x6d, 0xed, 0xaf, 0xa7, 0xdb, 0xda, 0x1f, 0xfe, 0xbe, 0xad, 0xfd, 0xe8, 0x9d, 0x9e,
0xcd, 0xfb, 0xa3, 0x4e, 0xa3, 0xeb, 0x0e, 0xf7, 0x62, 0x81, 0xc9, 0xcf, 0xf8, 0x51, 0xbb, 0xb3,
0x8e, 0x0e, 0xeb, 0xdb, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xce, 0x64, 0xb9, 0xe4, 0xe9, 0x1e,
0x00, 0x00,
}
func (this *Request) Equal(that interface{}) bool {
+124 -122
View File
@@ -1,12 +1,12 @@
syntax = "proto3";
package tendermint.abci.types;
option go_package = "github.com/tendermint/tendermint/abci/types";
option go_package = "github.com/tendermint/tendermint/abci/types";
// For more information on gogo.proto, see:
// https://github.com/gogo/protobuf/blob/master/extensions.md
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "github.com/tendermint/tendermint/crypto/merkle/merkle.proto";
import "github.com/tendermint/tendermint/libs/kv/types.proto";
import "third_party/proto/gogoproto/gogo.proto";
import "crypto/merkle/merkle.proto";
import "libs/kv/types.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
@@ -14,31 +14,31 @@ import "google/protobuf/duration.proto";
// NOTE: When using custom types, mind the warnings.
// https://github.com/gogo/protobuf/blob/master/custom_types.md#warnings-and-issues
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.goproto_registration) = true;
// Generate tests
option (gogoproto.populate_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
//----------------------------------------
// Request types
message Request {
oneof value {
RequestEcho echo = 2;
RequestFlush flush = 3;
RequestInfo info = 4;
RequestSetOption set_option = 5;
RequestInitChain init_chain = 6;
RequestQuery query = 7;
RequestEcho echo = 2;
RequestFlush flush = 3;
RequestInfo info = 4;
RequestSetOption set_option = 5;
RequestInitChain init_chain = 6;
RequestQuery query = 7;
RequestBeginBlock begin_block = 8;
RequestCheckTx check_tx = 9;
RequestDeliverTx deliver_tx = 19;
RequestEndBlock end_block = 11;
RequestCommit commit = 12;
RequestCheckTx check_tx = 9;
RequestDeliverTx deliver_tx = 19;
RequestEndBlock end_block = 11;
RequestCommit commit = 12;
}
}
@@ -46,50 +46,49 @@ message RequestEcho {
string message = 1;
}
message RequestFlush {
}
message RequestFlush {}
message RequestInfo {
string version = 1;
string version = 1;
uint64 block_version = 2;
uint64 p2p_version = 3;
uint64 p2p_version = 3;
}
// nondeterministic
message RequestSetOption {
string key = 1;
string key = 1;
string value = 2;
}
message RequestInitChain {
google.protobuf.Timestamp time = 1 [(gogoproto.nullable)=false, (gogoproto.stdtime)=true];
string chain_id = 2;
ConsensusParams consensus_params = 3;
repeated ValidatorUpdate validators = 4 [(gogoproto.nullable)=false];
bytes app_state_bytes = 5;
google.protobuf.Timestamp time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
string chain_id = 2;
ConsensusParams consensus_params = 3;
repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false];
bytes app_state_bytes = 5;
}
message RequestQuery {
bytes data = 1;
string path = 2;
int64 height = 3;
bool prove = 4;
bytes data = 1;
string path = 2;
int64 height = 3;
bool prove = 4;
}
message RequestBeginBlock {
bytes hash = 1;
Header header = 2 [(gogoproto.nullable)=false];
LastCommitInfo last_commit_info = 3 [(gogoproto.nullable)=false];
repeated Evidence byzantine_validators = 4 [(gogoproto.nullable)=false];
bytes hash = 1;
Header header = 2 [(gogoproto.nullable) = false];
LastCommitInfo last_commit_info = 3 [(gogoproto.nullable) = false];
repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false];
}
enum CheckTxType {
New = 0;
New = 0;
Recheck = 1;
}
message RequestCheckTx {
bytes tx = 1;
bytes tx = 1;
CheckTxType type = 2;
}
@@ -101,26 +100,25 @@ message RequestEndBlock {
int64 height = 1;
}
message RequestCommit {
}
message RequestCommit {}
//----------------------------------------
// Response types
message Response {
oneof value {
ResponseException exception = 1;
ResponseEcho echo = 2;
ResponseFlush flush = 3;
ResponseInfo info = 4;
ResponseSetOption set_option = 5;
ResponseInitChain init_chain = 6;
ResponseQuery query = 7;
ResponseException exception = 1;
ResponseEcho echo = 2;
ResponseFlush flush = 3;
ResponseInfo info = 4;
ResponseSetOption set_option = 5;
ResponseInitChain init_chain = 6;
ResponseQuery query = 7;
ResponseBeginBlock begin_block = 8;
ResponseCheckTx check_tx = 9;
ResponseDeliverTx deliver_tx = 10;
ResponseEndBlock end_block = 11;
ResponseCommit commit = 12;
ResponseCheckTx check_tx = 9;
ResponseDeliverTx deliver_tx = 10;
ResponseEndBlock end_block = 11;
ResponseCommit commit = 12;
}
}
@@ -133,16 +131,15 @@ message ResponseEcho {
string message = 1;
}
message ResponseFlush {
}
message ResponseFlush {}
message ResponseInfo {
string data = 1;
string version = 2;
string version = 2;
uint64 app_version = 3;
int64 last_block_height = 4;
int64 last_block_height = 4;
bytes last_block_app_hash = 5;
}
@@ -150,58 +147,62 @@ message ResponseInfo {
message ResponseSetOption {
uint32 code = 1;
// bytes data = 2;
string log = 3;
string log = 3;
string info = 4;
}
message ResponseInitChain {
ConsensusParams consensus_params = 1;
repeated ValidatorUpdate validators = 2 [(gogoproto.nullable)=false];
ConsensusParams consensus_params = 1;
repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false];
}
message ResponseQuery {
uint32 code = 1;
// bytes data = 2; // use "value" instead.
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 index = 5;
bytes key = 6;
bytes value = 7;
tendermint.crypto.merkle.Proof proof = 8;
int64 height = 9;
string codespace = 10;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 index = 5;
bytes key = 6;
bytes value = 7;
tendermint.crypto.merkle.Proof proof = 8;
int64 height = 9;
string codespace = 10;
}
message ResponseBeginBlock {
repeated Event events = 1 [(gogoproto.nullable)=false, (gogoproto.jsontag)="events,omitempty"];
repeated Event events = 1
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
}
message ResponseCheckTx {
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event events = 7 [(gogoproto.nullable)=false, (gogoproto.jsontag)="events,omitempty"];
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
string codespace = 8;
}
message ResponseDeliverTx {
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event events = 7 [(gogoproto.nullable)=false, (gogoproto.jsontag)="events,omitempty"];
uint32 code = 1;
bytes data = 2;
string log = 3; // nondeterministic
string info = 4; // nondeterministic
int64 gas_wanted = 5;
int64 gas_used = 6;
repeated Event events = 7
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
string codespace = 8;
}
message ResponseEndBlock {
repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable)=false];
ConsensusParams consensus_param_updates = 2;
repeated Event events = 3 [(gogoproto.nullable)=false, (gogoproto.jsontag)="events,omitempty"];
repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false];
ConsensusParams consensus_param_updates = 2;
repeated Event events = 3
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"];
}
message ResponseCommit {
@@ -215,8 +216,8 @@ message ResponseCommit {
// ConsensusParams contains all consensus-relevant parameters
// that can be adjusted by the abci app
message ConsensusParams {
BlockParams block = 1;
EvidenceParams evidence = 2;
BlockParams block = 1;
EvidenceParams evidence = 2;
ValidatorParams validator = 3;
}
@@ -230,8 +231,9 @@ message BlockParams {
message EvidenceParams {
// Note: must be greater than 0
int64 max_age_num_blocks = 1;
google.protobuf.Duration max_age_duration = 2 [(gogoproto.nullable)=false, (gogoproto.stdduration)=true];
int64 max_age_num_blocks = 1;
google.protobuf.Duration max_age_duration = 2
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
}
// ValidatorParams contains limits on validators.
@@ -240,13 +242,14 @@ message ValidatorParams {
}
message LastCommitInfo {
int32 round = 1;
repeated VoteInfo votes = 2 [(gogoproto.nullable)=false];
int32 round = 1;
repeated VoteInfo votes = 2 [(gogoproto.nullable) = false];
}
message Event {
string type = 1;
repeated tendermint.libs.kv.Pair attributes = 2 [(gogoproto.nullable)=false, (gogoproto.jsontag)="attributes,omitempty"];
string type = 1;
repeated tendermint.libs.kv.Pair attributes = 2
[(gogoproto.nullable) = false, (gogoproto.jsontag) = "attributes,omitempty"];
}
//----------------------------------------
@@ -254,63 +257,62 @@ message Event {
message Header {
// basic block info
Version version = 1 [(gogoproto.nullable)=false];
string chain_id = 2 [(gogoproto.customname)="ChainID"];
int64 height = 3;
google.protobuf.Timestamp time = 4 [(gogoproto.nullable)=false, (gogoproto.stdtime)=true];
Version version = 1 [(gogoproto.nullable) = false];
string chain_id = 2 [(gogoproto.customname) = "ChainID"];
int64 height = 3;
google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// prev block info
BlockID last_block_id = 5 [(gogoproto.nullable)=false];
BlockID last_block_id = 5 [(gogoproto.nullable) = false];
// hashes of block data
bytes last_commit_hash = 6; // commit from validators from the last block
bytes data_hash = 7; // transactions
bytes last_commit_hash = 6; // commit from validators from the last block
bytes data_hash = 7; // transactions
// hashes from the app output from the prev block
bytes validators_hash = 8; // validators for the current block
bytes validators_hash = 8; // validators for the current block
bytes next_validators_hash = 9; // validators for the next block
bytes consensus_hash = 10; // consensus params for current block
bytes app_hash = 11; // state after txs from the previous block
bytes last_results_hash = 12;// root hash of all results from the txs from the previous block
bytes consensus_hash = 10; // consensus params for current block
bytes app_hash = 11; // state after txs from the previous block
bytes last_results_hash = 12; // root hash of all results from the txs from the previous block
// consensus info
bytes evidence_hash = 13; // evidence included in the block
bytes proposer_address = 14; // original proposer of the block
bytes evidence_hash = 13; // evidence included in the block
bytes proposer_address = 14; // original proposer of the block
}
message Version {
uint64 Block = 1;
uint64 App = 2;
uint64 App = 2;
}
message BlockID {
bytes hash = 1;
PartSetHeader parts_header = 2 [(gogoproto.nullable)=false];
bytes hash = 1;
PartSetHeader parts_header = 2 [(gogoproto.nullable) = false];
}
message PartSetHeader {
int32 total = 1;
bytes hash = 2;
bytes hash = 2;
}
// Validator
message Validator {
bytes address = 1;
//PubKey pub_key = 2 [(gogoproto.nullable)=false];
// PubKey pub_key = 2 [(gogoproto.nullable)=false];
int64 power = 3;
}
// ValidatorUpdate
message ValidatorUpdate {
PubKey pub_key = 1 [(gogoproto.nullable)=false];
int64 power = 2;
PubKey pub_key = 1 [(gogoproto.nullable) = false];
int64 power = 2;
}
// VoteInfo
message VoteInfo {
Validator validator = 1 [(gogoproto.nullable)=false];
bool signed_last_block = 2;
Validator validator = 1 [(gogoproto.nullable) = false];
bool signed_last_block = 2;
}
message PubKey {
@@ -319,18 +321,18 @@ message PubKey {
}
message Evidence {
string type = 1;
Validator validator = 2 [(gogoproto.nullable)=false];
int64 height = 3;
google.protobuf.Timestamp time = 4 [(gogoproto.nullable)=false, (gogoproto.stdtime)=true];
int64 total_voting_power = 5;
string type = 1;
Validator validator = 2 [(gogoproto.nullable) = false];
int64 height = 3;
google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
int64 total_voting_power = 5;
}
//----------------------------------------
// Service Definition
service ABCIApplication {
rpc Echo(RequestEcho) returns (ResponseEcho) ;
rpc Echo(RequestEcho) returns (ResponseEcho);
rpc Flush(RequestFlush) returns (ResponseFlush);
rpc Info(RequestInfo) returns (ResponseInfo);
rpc SetOption(RequestSetOption) returns (ResponseSetOption);
+16
View File
@@ -0,0 +1,16 @@
build:
roots:
- .
lint:
use:
- MINIMAL
- FILE_LOWER_SNAKE_CASE
- UNARY_RPC
except:
- PACKAGE_DIRECTORY_MATCH
ignore:
- third_party
breaking:
use:
- FILE
- PACKAGE
+37 -11
View File
@@ -2,6 +2,7 @@ package commands
import (
"net/http"
"strings"
"time"
"github.com/pkg/errors"
@@ -12,6 +13,7 @@ import (
tmos "github.com/tendermint/tendermint/libs/os"
lite "github.com/tendermint/tendermint/lite2"
"github.com/tendermint/tendermint/lite2/provider"
httpp "github.com/tendermint/tendermint/lite2/provider/http"
lproxy "github.com/tendermint/tendermint/lite2/proxy"
lrpc "github.com/tendermint/tendermint/lite2/rpc"
@@ -36,9 +38,10 @@ just with added trust and running locally.`,
var (
listenAddr string
nodeAddr string
primaryAddr string
chainID string
home string
witnessesAddrs string
maxOpenConnections int
trustingPeriod time.Duration
@@ -47,9 +50,17 @@ var (
)
func init() {
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888", "Serve the proxy on the given address")
LiteCmd.Flags().StringVar(&nodeAddr, "node", "tcp://localhost:26657", "Connect to a Tendermint node at this address")
LiteCmd.Flags().StringVar(&listenAddr, "laddr", "tcp://localhost:8888",
"Serve the proxy on the given address")
LiteCmd.Flags().StringVar(&primaryAddr, "primary", "tcp://localhost:26657",
"Connect to a Tendermint node at this address")
LiteCmd.Flags().StringVar(&witnessesAddrs, "witnesses", "",
"Tendermint nodes to cross-check the primary node, comma-separated")
LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID")
LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory")
LiteCmd.Flags().IntVar(
&maxOpenConnections,
@@ -57,19 +68,33 @@ func init() {
900,
"Maximum number of simultaneous connections (including WebSocket).")
LiteCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, "Trusting period. Should be significantly less than the unbonding period")
LiteCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
"Trusting period. Should be significantly less than the unbonding period")
LiteCmd.Flags().Int64Var(&trustedHeight, "trusted-height", 1, "Trusted header's height")
LiteCmd.Flags().BytesHexVar(&trustedHash, "trusted-hash", []byte{}, "Trusted header's hash")
}
func runProxy(cmd *cobra.Command, args []string) error {
liteLogger := logger.With("module", "lite")
logger.Info("Connecting to Tendermint node...")
// First, connect a client
node, err := rpcclient.NewHTTP(nodeAddr, "/websocket")
logger.Info("Connecting to the primary node...")
rpcClient, err := rpcclient.NewHTTP(chainID, primaryAddr)
if err != nil {
return errors.Wrap(err, "new HTTP client")
return errors.Wrapf(err, "http client for %s", primaryAddr)
}
primary := httpp.NewWithClient(chainID, rpcClient)
logger.Info("Connecting to the witness nodes...")
addrs := strings.Split(witnessesAddrs, ",")
witnesses := make([]provider.Provider, len(addrs))
for i, addr := range addrs {
p, err := httpp.New(chainID, addr)
if err != nil {
return errors.Wrapf(err, "http provider for %s", addr)
}
witnesses[i] = p
}
logger.Info("Creating client...")
@@ -84,19 +109,20 @@ func runProxy(cmd *cobra.Command, args []string) error {
Height: trustedHeight,
Hash: trustedHash,
},
httpp.NewWithClient(chainID, node),
primary,
witnesses,
dbs.New(db, chainID),
lite.Logger(liteLogger),
)
if err != nil {
return err
}
c.SetLogger(liteLogger)
p := lproxy.Proxy{
Addr: listenAddr,
Config: &rpcserver.Config{MaxOpenConnections: maxOpenConnections},
Codec: amino.NewCodec(),
Client: lrpc.NewClient(node, c),
Client: lrpc.NewClient(rpcClient, c),
Logger: liteLogger,
}
// Stop upon receiving SIGTERM or CTRL-C.
+3 -3
View File
@@ -415,8 +415,8 @@ func DefaultRPCConfig() *RPCConfig {
// TestRPCConfig returns a configuration for testing the RPC server
func TestRPCConfig() *RPCConfig {
cfg := DefaultRPCConfig()
cfg.ListenAddress = "tcp://0.0.0.0:36657"
cfg.GRPCListenAddress = "tcp://0.0.0.0:36658"
cfg.ListenAddress = "tcp://127.0.0.1:36657"
cfg.GRPCListenAddress = "tcp://127.0.0.1:36658"
cfg.Unsafe = true
return cfg
}
@@ -584,7 +584,7 @@ func DefaultP2PConfig() *P2PConfig {
// TestP2PConfig returns a configuration for testing the peer-to-peer layer
func TestP2PConfig() *P2PConfig {
cfg := DefaultP2PConfig()
cfg.ListenAddress = "tcp://0.0.0.0:36656"
cfg.ListenAddress = "tcp://127.0.0.1:36656"
cfg.FlushThrottleTimeout = 10 * time.Millisecond
cfg.AllowDuplicateIP = true
return cfg
+2 -2
View File
@@ -1864,10 +1864,10 @@ func (cs *State) voteTime() time.Time {
now := tmtime.Now()
minVoteTime := now
// TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil,
// even if cs.LockedBlock != nil. See https://github.com/tendermint/spec.
// even if cs.LockedBlock != nil. See https://docs.tendermint.com/master/spec/.
timeIotaMs := time.Duration(cs.state.ConsensusParams.Block.TimeIotaMs) * time.Millisecond
if cs.LockedBlock != nil {
// See the BFT time spec https://tendermint.com/docs/spec/consensus/bft-time.html
// See the BFT time spec https://docs.tendermint.com/master/spec/consensus/bft-time.html
minVoteTime = cs.LockedBlock.Time.Add(timeIotaMs)
} else if cs.ProposalBlock != nil {
minVoteTime = cs.ProposalBlock.Time.Add(timeIotaMs)
+3 -3
View File
@@ -125,9 +125,9 @@ func randPort() int {
func makeAddrs() (string, string, string) {
start := randPort()
return fmt.Sprintf("tcp://0.0.0.0:%d", start),
fmt.Sprintf("tcp://0.0.0.0:%d", start+1),
fmt.Sprintf("tcp://0.0.0.0:%d", start+2)
return fmt.Sprintf("tcp://127.0.0.1:%d", start),
fmt.Sprintf("tcp://127.0.0.1:%d", start+1),
fmt.Sprintf("tcp://127.0.0.1:%d", start+2)
}
// getConfig returns a config for test cases
+1 -1
View File
@@ -14,7 +14,7 @@ If you want to decode bytes into one of the types, but don't care about the spec
## Binary encoding
For Binary encoding, please refer to the [Tendermint encoding specification](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md).
For Binary encoding, please refer to the [Tendermint encoding specification](https://docs.tendermint.com/master/spec/blockchain/encoding.html).
## JSON Encoding
+29 -55
View File
@@ -146,22 +146,22 @@ func init() {
func init() { proto.RegisterFile("crypto/merkle/merkle.proto", fileDescriptor_9c1c2162d560d38e) }
var fileDescriptor_9c1c2162d560d38e = []byte{
// 226 bytes of a gzipped FileDescriptorProto
// 230 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4a, 0x2e, 0xaa, 0x2c,
0x28, 0xc9, 0xd7, 0xcf, 0x4d, 0x2d, 0xca, 0xce, 0x49, 0x85, 0x52, 0x7a, 0x05, 0x45, 0xf9, 0x25,
0xf9, 0x42, 0x12, 0x25, 0xa9, 0x79, 0x29, 0xa9, 0x45, 0xb9, 0x99, 0x79, 0x25, 0x7a, 0x10, 0x65,
0x7a, 0x10, 0x79, 0x29, 0xdd, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd,
0xf4, 0xfc, 0xf4, 0x7c, 0x7d, 0xb0, 0x86, 0xa4, 0xd2, 0x34, 0x30, 0x0f, 0xcc, 0x01, 0xb3, 0x20,
0x06, 0x29, 0x39, 0x73, 0xb1, 0x07, 0x14, 0xe5, 0xe7, 0xa7, 0xf9, 0x17, 0x08, 0x09, 0x71, 0xb1,
0x94, 0x54, 0x16, 0xa4, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42, 0x02, 0x5c,
0xcc, 0xd9, 0xa9, 0x95, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x20, 0x26, 0x48, 0x55, 0x4a,
0x62, 0x49, 0xa2, 0x04, 0x33, 0x58, 0x08, 0xcc, 0x56, 0x72, 0xe2, 0x62, 0x05, 0x1b, 0x22, 0x64,
0xc9, 0xc5, 0x9c, 0x5f, 0x50, 0x2c, 0xc1, 0xa8, 0xc0, 0xac, 0xc1, 0x6d, 0xa4, 0xa8, 0x87, 0xcb,
0x91, 0x7a, 0x50, 0x2b, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x02, 0xe9, 0x71, 0x72, 0xf9,
0xf1, 0x50, 0x8e, 0x71, 0xc5, 0x23, 0x39, 0xc6, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63,
0x7c, 0xf0, 0x48, 0x8e, 0x31, 0x4a, 0x0f, 0xc9, 0x37, 0x08, 0xd3, 0x90, 0x99, 0x28, 0x81, 0x94,
0xc4, 0x06, 0xf6, 0x95, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x95, 0x22, 0x4e, 0x3c, 0x01,
0x00, 0x00,
0x7a, 0x10, 0x79, 0x29, 0xb5, 0x92, 0x8c, 0xcc, 0xa2, 0x94, 0xf8, 0x82, 0xc4, 0xa2, 0x92, 0x4a,
0x7d, 0xb0, 0x62, 0xfd, 0xf4, 0xfc, 0xf4, 0x7c, 0x04, 0x0b, 0x62, 0x82, 0x92, 0x33, 0x17, 0x7b,
0x40, 0x51, 0x7e, 0x7e, 0x9a, 0x7f, 0x81, 0x90, 0x10, 0x17, 0x4b, 0x49, 0x65, 0x41, 0xaa, 0x04,
0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x98, 0x2d, 0x24, 0xc0, 0xc5, 0x9c, 0x9d, 0x5a, 0x29, 0xc1,
0xa4, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0x62, 0x82, 0x54, 0xa5, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x83,
0x85, 0xc0, 0x6c, 0x25, 0x27, 0x2e, 0x56, 0xb0, 0x21, 0x42, 0x96, 0x5c, 0xcc, 0xf9, 0x05, 0xc5,
0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0x8a, 0x7a, 0xb8, 0x5c, 0xa7, 0x07, 0xb5, 0xd2, 0x89,
0xe5, 0xc4, 0x3d, 0x79, 0x86, 0x20, 0x90, 0x1e, 0x27, 0x97, 0x1f, 0x0f, 0xe5, 0x18, 0x57, 0x3c,
0x92, 0x63, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0xa3,
0xf4, 0xd2, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x11, 0xa6, 0x21, 0x33,
0x51, 0x42, 0x27, 0x89, 0x0d, 0xec, 0x2b, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xcc,
0x2c, 0x91, 0x35, 0x01, 0x00, 0x00,
}
func (this *ProofOp) Equal(that interface{}) bool {
@@ -729,6 +729,7 @@ func (m *Proof) Unmarshal(dAtA []byte) error {
func skipMerkle(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -760,10 +761,8 @@ func skipMerkle(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -784,55 +783,30 @@ func skipMerkle(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthMerkle
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthMerkle
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowMerkle
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipMerkle(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthMerkle
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupMerkle
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthMerkle
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthMerkle = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowMerkle = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthMerkle = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowMerkle = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupMerkle = fmt.Errorf("proto: unexpected end of group")
)
+8 -8
View File
@@ -1,17 +1,17 @@
syntax = "proto3";
package tendermint.crypto.merkle;
option go_package = "github.com/tendermint/tendermint/crypto/merkle";
option go_package = "github.com/tendermint/tendermint/crypto/merkle";
// For more information on gogo.proto, see:
// https://github.com/gogo/protobuf/blob/master/extensions.md
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "third_party/proto/gogoproto/gogo.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.populate_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.equal_all) = true;
//----------------------------------------
// Message types
@@ -21,11 +21,11 @@ option (gogoproto.equal_all) = true;
// for example neighbouring node hash
message ProofOp {
string type = 1;
bytes key = 2;
bytes data = 3;
bytes key = 2;
bytes data = 3;
}
// Proof is Merkle proof defined by the list of ProofOps
message Proof {
repeated ProofOp ops = 1 [(gogoproto.nullable)=false];
repeated ProofOp ops = 1 [(gogoproto.nullable) = false];
}
+1 -1
View File
@@ -56,6 +56,6 @@ Tendermint.
See the following for more extensive documentation:
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
- [Tendermint RPC Docs](https://tendermint.com/rpc/)
- [Tendermint RPC Docs](https://docs.tendermint.com/master/rpc/)
- [Tendermint in Production](../tendermint-core/running-in-production.md)
- [ABCI spec](https://github.com/tendermint/spec/tree/95cf253b6df623066ff7cd4074a94e7a3f147c7a/spec/abci)
+2 -2
View File
@@ -106,7 +106,7 @@ You can query the transaction results by calling `/tx_search` RPC endpoint:
curl "localhost:26657/tx_search?query=\"account.name='igor'\"&prove=true"
```
Check out [API docs](https://tendermint.com/rpc/#txsearch) for more information
Check out [API docs](https://docs.tendermint.com/master/rpc/#/Info/tx_search) for more information
on query syntax and other options.
## Subscribing to Transactions
@@ -125,5 +125,5 @@ a query to `/subscribe` RPC endpoint.
}
```
Check out [API docs](https://tendermint.com/rpc/#subscribe) for more information
Check out [API docs](https://docs.tendermint.com/master/rpc/#subscribe) for more information
on query syntax and other options.
@@ -24,7 +24,7 @@ method via Websocket.
}
```
Check out [API docs](https://tendermint.com/rpc/) for
Check out [API docs](https://docs.tendermint.com/master/rpc/) for
more information on query syntax and other options.
You can also use tags, given you had included them into DeliverTx
+2
View File
@@ -64,3 +64,5 @@ Note the context/background should be written in the present tense.
- [ADR-039-Peer-Behaviour](./adr-039-peer-behaviour.md)
- [ADR-041-Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md)
- [ADR-043-Blockchain-RiRi-Org](./adr-043-blockchain-riri-org.md)
- [ADR-051-Double-Signing-Risk-Reduction](./adr-051-double-signing-risk-reduction.md)
- [ADR-052-Tendermint-Mode](./adr-052-tendermint-mode.md)
+4 -4
View File
@@ -21,12 +21,12 @@ representation of the pubkey.
This has two significant drawbacks.
Amino encoding is less space-efficient, due to requiring support for upgradability.
Amino encoding support requires forking protobuf and adding this new interface support
option in the langauge of choice.
option in the language of choice.
The reason for continuing to use amino however is that people can create code
more easily in languages that already have an up to date amino library.
It is possible that this will change in the future, if it is deemed that
requiring amino for interacting with tendermint cryptography is unneccessary.
requiring amino for interacting with Tendermint cryptography is unnecessary.
The arguments for space efficiency here are refuted on the basis that there are
far more egregious wastages of space in the SDK.
@@ -35,9 +35,9 @@ increasing the space attached to each validator / account.
The alternative to using amino here would be for us to create an enum type.
Switching to just an enum type is worthy of investigation post-launch.
For referrence, part of amino encoding interfaces is basically a 4 byte enum
For reference, part of amino encoding interfaces is basically a 4 byte enum
type definition.
Enum types would just change that 4 bytes to be a varuint, and it would remove
Enum types would just change that 4 bytes to be a variant, and it would remove
the protobuf overhead, but it would be hard to integrate into the existing API.
### Signatures
@@ -84,7 +84,7 @@ The linear verification algorithm requires downloading all headers
between the `TrustHeight` and the `LatestHeight`. The lite client downloads the
full header for the provided `TrustHeight` and then proceeds to download `N+1`
headers and applies the [Tendermint validation
rules](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#validation)
rules](https://docs.tendermint.com/master/spec/blockchain/blockchain.html#validation)
to each block.
### Bisecting Verification
@@ -119,7 +119,7 @@ network usage.
---
Check out the formal specification
[here](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/light-client.md).
[here](https://docs.tendermint.com/master/spec/consensus/light-client.html).
## Status
+1 -1
View File
@@ -18,7 +18,7 @@ graceful here, but that's for another day.
It's possible to fool lite clients without there being a fork on the
main chain - so called Fork-Lite. See the
[fork accountability](https://github.com/tendermint/tendermint/blob/master/docs/spec/consensus/fork-accountability.md)
[fork accountability](https://docs.tendermint.com/master/spec/consensus/fork-accountability.html)
document for more details. For a sequential lite client, this can happen via
equivocation or amnesia attacks. For a skipping lite client this can also happen
via lunatic validator attacks. There must be some way for applications to punish
@@ -0,0 +1,53 @@
# ADR 051: Double Signing Risk Reduction
## Changelog
* 27-11-2019: Initial draft
* 13-01-2020: Separate into 2 ADR, This ADR will only cover Double signing Protection and ADR-052 handle Tendermint Mode
* 22-01-2020: change the title from "Double signing Protection" to "Double Signing Risk Reduction"
## Context
To provide a risk reduction method for double signing incidents mistakenly executed by validators
- Validators often mistakenly run duplicated validators to cause double-signing incident
- This proposed feature is to reduce the risk of mistaken double-signing incident by checking recent N blocks before voting begins
- When we think of such serious impact on double-signing incident, it is very reasonable to have multiple risk reduction algorithm built in node daemon
## Decision
We would like to suggest a double signing risk reduction method.
- Methodology : query recent consensus results to find out whether node's consensus key is used on consensus recently or not
- When to check
- When the state machine starts `ConsensusReactor` after fully synced
- When the node is validator ( with privValidator )
- When `cs.config.DoubleSignCheckHeight > 0`
- How to check
1. When a validator is transformed from syncing status to fully synced status, the state machine check recent N blocks (`latest_height - double_sign_check_height`) to find out whether there exists consensus votes using the validator's consensus key
2. If there exists votes from the validator's consensus key, exit state machine program
- Configuration
- We would like to suggest by introducing `double_sign_check_height` parameter in `config.toml` and cli, how many blocks state machine looks back to check votes
- `double_sign_check_height = {{ .Consensus.DoubleSignCheckHeight }}` in `config.toml`
- `tendermint node --double_sign_check_height` in cli
- State machine ignore checking procedure when `vote-check-height == 0`
## Status
Proposed
## Consequences
### Positive
- Validators can avoid double signing incident by mistakes. (eg. If another validator node is voting on consensus, starting new validator node with same consensus key will cause panic stop of the state machine because consensus votes with the consensus key are found in recent blocks)
- We expect this method will prevent majority of double signing incident by mistakes.
### Negative
- When the risk reduction method is on, restarting a validator node will panic because the node itself voted on consensus with the same consensus key. So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic stop.
### Neutral
## References
- Issue [#4059](https://github.com/tendermint/tendermint/issues/4059) : double-signing protection
@@ -0,0 +1,81 @@
# ADR 052: Tendermint Mode
## Changelog
* 27-11-2019: Initial draft from ADR-051
* 13-01-2020: Separate ADR Tendermint Mode from ADR-051
## Context
- Fullnode mode: fullnode mode does not have the capability to become a validator.
- Validator mode : this mode is exactly same as existing state machine behavior. sync without voting on consensus, and participate consensus when fully synced
- Seed mode : lightweight seed mode maintaining an address book, p2p like [TenderSeed](https://gitlab.com/polychainlabs/tenderseed)
## Decision
We would like to suggest a simple Tendermint mode abstraction. These modes will live under one binary, and when initializing a node the user will be able to specify which node they would like to create.
- Which reactor, component to include for each node
- fullnode *(default)*
- switch, transport
- reactors
- mempool
- consensus
- evidence
- blockchain
- p2p/pex
- rpc (safe connections only)
- *~~no privValidator(priv_validator_key.json, priv_validator_state.json)~~*
- validator
- switch, transport
- reactors
- mempool
- consensus
- evidence
- blockchain
  - p2p/pex
- rpc (safe connections only)
- with privValidator(priv_validator_key.json, priv_validator_state.json)
- seed
- switch, transport
- reactor
- p2p/pex
- Configuration, cli command
- We would like to suggest by introducing `mode` parameter in `config.toml` and cli
- `mode = "{{ .BaseConfig.Mode }}"` in `config.toml`
- `tendermint node --mode validator` in cli
- fullnode | validator | seed (default: "fullnode")
- RPC modification
- `host:26657/status`
- return empty `validator_info` when fullnode mode
- no rpc server in seed mode
- Where to modify in codebase
- Add switch for `config.Mode` on `node/node.go:DefaultNewNode`
- If `config.Mode==validator`, call default `NewNode` (current logic)
- If `config.Mode==fullnode`, call `NewNode` with `nil` `privValidator` (do not load or generation)
- Need to add exception routine for `nil` `privValidator` to related functions
- If `config.Mode==seed`, call `NewSeedNode` (seed version of `node/node.go:NewNode`)
- Need to add exception routine for `nil` `reactor`, `component` to related functions
## Status
Proposed
## Consequences
### Positive
- Node operators can choose mode when they run state machine according to the purpose of the node.
- Mode can prevent mistakes because users have to specify which mode they want to run via flag. (eg. If a user want to run a validator node, she/he should explicitly write down validator as mode)
- Different mode needs different reactors, resulting in efficient resource usage.
### Negative
- Users need to study how each mode operate and which capability it has.
### Neutral
## References
- Issue [#2237](https://github.com/tendermint/tendermint/issues/2237) : Tendermint "mode"
- [TenderSeed](https://gitlab.com/polychainlabs/tenderseed) : A lightweight Tendermint Seed Node.
@@ -0,0 +1,209 @@
# ADR 053: State Sync Prototype
This ADR outlines the plan for an initial state sync prototype, and is subject to change as we gain feedback and experience. It builds on discussions and findings in [ADR-042](./adr-042-state-sync.md), see that for background information.
## Changelog
* 2020-01-28: Initial draft (Erik Grinaker)
## Context
State sync will allow a new node to receive a snapshot of the application state without downloading blocks or going through consensus. This bootstraps the node significantly faster than the current fast sync system, which replays all historical blocks.
Background discussions and justifications are detailed in [ADR-042](./adr-042-state-sync.md). Its recommendations can be summarized as:
* The application periodically takes full state snapshots (i.e. eager snapshots).
* The application splits snapshots into smaller chunks that can be individually verified against a chain app hash.
* Tendermint uses the light client to obtain a trusted chain app hash for verification.
* Tendermint discovers and downloads snapshot chunks in parallel from multiple peers, and passes them to the application via ABCI to be applied and verified against the chain app hash.
* Historical blocks are not backfilled, so state synced nodes will have a truncated block history.
## Tendermint Proposal
This describes the snapshot/restore process seen from Tendermint. The interface is kept as small and general as possible to give applications maximum flexibility.
### Snapshot Data Structure
A node can have multiple snapshots taken at various heights. Snapshots can be taken in different application-specified formats (e.g. MessagePack as format `1` and Protobuf as format `2`, or similarly for schema versioning). Each snapshot consists of multiple chunks containing the actual state data, allowing parallel downloads and reduced memory usage.
```proto
message Snapshot {
uint64 height = 1; // The height at which the snapshot was taken
uint32 format = 2; // The application-specific snapshot format
uint64 chunks = 3; // The number of chunks in the snapshot
bytes metadata = 4; // Arbitrary application metadata
}
message SnapshotChunk {
uint64 height = 1; // The height of the corresponding snapshot
uint32 format = 2; // The application-specific snapshot format
uint64 chunk = 3; // The chunk index (zero-based)
bytes data = 4; // Serialized application state in an arbitrary format
bytes checksum = 5; // SHA-1 checksum of data
}
```
Chunk verification data must be encoded along with the state data in the `data` field.
Chunk `data` cannot be larger than 64 MB, and snapshot `metadata` cannot be larger than 64 KB.
### ABCI Interface
```proto
// Lists available snapshots
message RequestListSnapshots {}
message ResponseListSnapshots {
repeated Snapshot snapshots = 1;
}
// Offers a snapshot to the application
message RequestOfferSnapshot {
Snapshot snapshot = 1;
}
message ResponseOfferSnapshot {
bool accepted = 1;
enum reason { // Reason why snapshot was rejected
unknown = 0; // Unknown or generic reason
invalid_height = 1; // Height is rejected: avoid this height
invalid_format = 2; // Format is rejected: avoid this format
}
}
// Fetches a snapshot chunk
message RequestGetSnapshotChunk {
uint64 height = 1;
uint32 format = 2;
uint64 chunk = 3;
}
message ResponseGetSnapshotChunk {
SnapshotChunk chunk = 1;
}
// Applies a snapshot chunk
message RequestApplySnapshotChunk {
SnapshotChunk chunk = 1;
bytes chain_hash = 2;
}
message ResponseApplySnapshotChunk {
bool applied = 1;
enum reason { // Reason why chunk failed
unknown = 0; // Unknown or generic reason
verify_failed = 1; // Chunk verification failed
}
}
```
### Taking Snapshots
Tendermint is not aware of the snapshotting process at all, it is entirely an application concern. The following guarantees must be provided:
* **Periodic:** snapshots must be taken periodically, not on-demand, for faster restores, lower load, and less DoS risk.
* **Deterministic:** snapshots must be deterministic, and identical across all nodes - typically by taking a snapshot at given height intervals.
* **Consistent:** snapshots must be consistent, i.e. not affected by concurrent writes - typically by using a data store that supports versioning and/or snapshot isolation.
* **Asynchronous:** snapshots must be asynchronous, i.e. not halt block processing and state transitions.
* **Chunked:** snapshots must be split into chunks of reasonable size (on the order of megabytes), and each chunk must be verifiable against the chain app hash.
* **Garbage collected:** snapshots must be garbage collected periodically.
### Restoring Snapshots
Nodes should have options for enabling state sync and/or fast sync, and be provided a trusted header hash for the light client.
When starting an empty node with state sync and fast sync enabled, snapshots are restored as follows:
1. The node checks that it is empty, i.e. that it has no state nor blocks.
2. The node contacts the given seeds to discover peers.
3. The node contacts a set of full nodes, and verifies the trusted block header using the given hash via the light client.
4. The node requests available snapshots via `RequestListSnapshots`. Snapshots with `metadata` greater than 64 KB are rejected.
5. The node iterates over all snapshots in reverse order by height and format until it finds one that satisfies all of the following conditions:
* The snapshot height's block is considered trustworthy by the light client (i.e. snapshot height is greater than trusted header and within unbonding period of the latest trustworthy block).
* The snapshot's height or format hasn't been explicitly rejected by an earlier `RequestOffsetSnapshot` call (via `invalid_height` or `invalid_format`).
* The application accepts the `RequestOfferSnapshot` call.
6. The node downloads chunks in parallel from multiple peers via `RequestGetSnapshotChunk`, and both the sender and receiver verifies their checksums. Chunks with `data` greater than 64 MB are rejected.
7. The node passes chunks sequentially to the app via `RequestApplySnapshotChunk`, along with the chain's app hash at the snapshot height for verification. If the chunk is rejected the node should retry it. If it was rejected with `verify_failed`, it should be refetched from a different source. If an internal error occurred, `ResponseException` should be returned and state sync should be aborted.
8. Once all chunks have been applied, the node compares the app hash to the chain app hash, and if they do not match it either errors or discards the state and starts over.
9. The node switches to fast sync to catch up blocks that were committed while restoring the snapshot.
10. The node switches to normal consensus mode.
## Gaia Proposal
This describes the snapshot process seen from Gaia, using format version `1`. The serialization format is unspecified, but likely to be compressed Amino or Protobuf.
### Snapshot Metadata
In the initial version there is no snapshot metadata, so it is set to an empty byte buffer.
Once all chunks have been successfully built, snapshot metadata should be serialized and stored in the file system as e.g. `snapshots/<height>/<format>/metadata`, and served via `RequestListSnapshots`.
### Snapshot Chunk Format
The Gaia data structure consists of a set of named IAVL trees. A root hash is constructed by taking the root hashes of each of the IAVL trees, then constructing a Merkle tree of the sorted name/hash map.
IAVL trees are versioned, but a snapshot only contains the version relevant for the snapshot height. All historical versions are ignored.
IAVL trees are insertion-order dependent, so key/value pairs must be stored in an appropriate insertion order to produce the same tree branching structure and thus the same Merkle hashes.
A chunk corresponds to a subtree key range within an IAVL tree, in insertion order, along with the Merkle proof from the root of the subtree all the way up to the root of the multistore (including the Merkle proof for the surrounding multistore.
```go
struct SnapshotChunk {
Store string // Name (key) of IAVL store in outer MultiStore
Keys [][]byte // Snapshotted keys in insertion order
Values [][]byte // Snapshotted values corresponding to Keys
Proof []merkle.ProofOp // Merkle proof from subtree root to MultiStore root
}
```
This chunk structure is believed to be sufficient to reconstruct an identical IAVL tree by applying separate chunks, but this may require the chunks to be ordered in a certain way; further research is needed.
We do not use IAVL RangeProofs, since these include redundant data such as proofs for intermediate and leaf nodes that can be derived from the above data.
Chunks should be built greedily by collecting key/value pairs constituting a complete subtree up to some size limit (e.g. 32 MB), then serialized. Chunk data is stored in the file system as `snapshots/<height>/<format>/<chunk>/data`, along with a SHA-1 checksum in `snapshots/<height>/<format>/<chunk>/checksum`, and served via `RequestGetSnapshotChunk`.
### Snapshot Scheduling
Snapshots should be taken at some configurable height interval, e.g. every 1000 blocks. All nodes should preferably have the same snapshot schedule, such that all nodes can serve chunks for a given snapshot.
Taking consistent snapshots of IAVL trees is greatly simplified by them being versioned: simply snapshot the version that corresponds to the snapshot height, while concurrent writes create new versions. IAVL pruning must not prune a version that is being snapshotted.
Snapshots must also be garbage collected after some configurable time, e.g. by keeping the latest `n` snapshots.
## Open Questions
* Is it possible to reconstruct an identical IAVL tree given separate subtrees in an appropriate order, or is more data needed about the branch structure?
* Should we punish nodes that provide invalid snapshots?
* Is it OK for state-synced nodes to not have historical blocks nor historical IAVL versions?
## Status
Proposed
## References
* [ADR-042](./adr-042-state-sync.md) and its references
+5 -7
View File
@@ -1,6 +1,6 @@
---
<!---
order: 2
---
--->
# Creating a built-in application in Go
@@ -55,8 +55,8 @@ $ echo $GOPATH
We'll start by creating a new Go project.
```sh
$ mkdir -p $GOPATH/src/github.com/me/kvstore
$ cd $GOPATH/src/github.com/me/kvstore
$ mkdir kvstore
$ cd kvstore
```
Inside the example directory create a `main.go` file with the following content:
@@ -569,7 +569,6 @@ We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
dependency management.
```sh
$ export GO111MODULE=on
$ go mod init github.com/me/example
$ go build
```
@@ -580,8 +579,7 @@ To create a default configuration, nodeKey and private validator files, let's
execute `tendermint init`. But before we do that, we will need to install
Tendermint Core. Please refer to [the official
guide](https://docs.tendermint.com/master/introduction/install.html). If you're
installing from source, don't forget to checkout the latest release (`git
checkout vX.Y.Z`).
installing from source, don't forget to checkout the latest release (`git checkout vX.Y.Z`).
```sh
$ rm -rf /tmp/example
+7 -8
View File
@@ -1,6 +1,6 @@
---
<!---
order: 1
---
--->
# Creating an application in Go
@@ -58,8 +58,8 @@ $ echo $GOPATH
We'll start by creating a new Go project.
```sh
$ mkdir -p $GOPATH/src/github.com/me/kvstore
$ cd $GOPATH/src/github.com/me/kvstore
$ mkdir kvstore
$ cd kvstore
```
Inside the example directory create a `main.go` file with the following content:
@@ -228,9 +228,9 @@ func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
When Tendermint Core has decided on the block, it's transfered to the
When Tendermint Core has decided on the block, it's transferred to the
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
`EndBlock` in the end. DeliverTx are being transfered asynchronously, but the
`EndBlock` in the end. DeliverTx are being transferred asynchronously, but the
responses are expected to come in order.
```
@@ -437,8 +437,7 @@ To create a default configuration, nodeKey and private validator files, let's
execute `tendermint init`. But before we do that, we will need to install
Tendermint Core. Please refer to [the official
guide](https://docs.tendermint.com/master/introduction/install.html). If you're
installing from source, don't forget to checkout the latest release (`git
checkout vX.Y.Z`).
installing from source, don't forget to checkout the latest release (`git checkout vX.Y.Z`).
```sh
$ rm -rf /tmp/example
+5 -5
View File
@@ -1,6 +1,6 @@
---
<!---
order: 3
---
--->
# Creating an application in Java
@@ -170,15 +170,15 @@ Copy the necessary `.proto` files to your project:
mkdir -p \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/abci/types \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/crypto/merkle \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/common \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/kv \
$KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto
cp $GOPATH/src/github.com/tendermint/tendermint/abci/types/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/abci/types/types.proto
cp $GOPATH/src/github.com/tendermint/tendermint/crypto/merkle/merkle.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/crypto/merkle/merkle.proto
cp $GOPATH/src/github.com/tendermint/tendermint/libs/common/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/common/types.proto
cp $GOPATH/src/github.com/tendermint/tendermint/libs/kv/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/kv/types.proto
cp $GOPATH/src/github.com/gogo/protobuf/gogoproto/gogo.proto \
$KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto/gogo.proto
```
+6 -5
View File
@@ -1,6 +1,7 @@
---
<!---
order: 4
---
--->
# Creating an application in Kotlin
## Guide Assumptions
@@ -169,15 +170,15 @@ Copy the necessary `.proto` files to your project:
mkdir -p \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/abci/types \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/crypto/merkle \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/common \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/kv \
$KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto
cp $GOPATH/src/github.com/tendermint/tendermint/abci/types/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/abci/types/types.proto
cp $GOPATH/src/github.com/tendermint/tendermint/crypto/merkle/merkle.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/crypto/merkle/merkle.proto
cp $GOPATH/src/github.com/tendermint/tendermint/libs/common/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/common/types.proto
cp $GOPATH/src/github.com/tendermint/tendermint/libs/kv/types.proto \
$KVSTORE_HOME/src/main/proto/github.com/tendermint/tendermint/libs/kv/types.proto
cp $GOPATH/src/github.com/gogo/protobuf/gogoproto/gogo.proto \
$KVSTORE_HOME/src/main/proto/github.com/gogo/protobuf/gogoproto/gogo.proto
```
+1 -1
View File
@@ -247,4 +247,4 @@ keep the list of new nodes it discovers, and when you need to establish
connection to a peer, you'll look to address book and get some addresses from
there. There's categorization/ranking of nodes there.
[1]: https://github.com/tendermint/tendermint/blob/master/docs/spec/reactors/consensus/proposer-selection.md
[1]: https://docs.tendermint.com/master/spec/reactors/consensus/proposer-selection.html
+2
View File
@@ -4,6 +4,8 @@ order: 3
# Terraform & Ansible
> Note: These commands/files are not being maintained by the tendermint team currently. Please use them carefully.
Automated deployments are done using
[Terraform](https://www.terraform.io/) to create servers on Digital
Ocean then [Ansible](http://www.ansible.com/) to create and manage
+2241 -1207
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Welcome to the Tendermint Core documentation!",
"main": "index.js",
"dependencies": {
"vuepress-theme-cosmos": "^1.0.73"
"vuepress-theme-cosmos": "^1.0.148"
},
"devDependencies": {
"@vuepress/plugin-google-analytics": "^1.2.0"
+1 -1
View File
@@ -11,6 +11,6 @@ nodes. This blockchain is accessible via various rpc endpoints, mainly
`/blockchain?minHeight=_&maxHeight=_` to get a list of headers. But what
exactly is stored in these blocks?
The [specification](../spec/blockchain/blockchain.md) contains a detailed description of each component - that's the best place to get started.
The [specification](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md) contains a detailed description of each component - that's the best place to get started.
To dig deeper, check out the [types package documentation](https://godoc.org/github.com/tendermint/tendermint/types).
@@ -18,7 +18,7 @@ commit for a recent block
hash where the commit includes a
majority of signatures from the last known validator set. From there,
all the application state is verifiable with [merkle
proofs](../spec/blockchain/encoding.md#iavl-tree).
proofs](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/encoding.md#iavl-tree).
## Properties
+12 -12
View File
@@ -34,21 +34,21 @@ The following metrics are available:
| consensus_rounds | Gauge | 0.21.0 | | Number of rounds |
| consensus_num_txs | Gauge | 0.21.0 | | Number of transactions |
| consensus_total_txs | Gauge | 0.21.0 | | Total number of transactions committed |
| consensus_block_parts | counter | on dev | peer_id | number of blockparts transmitted by peer |
| consensus_latest_block_height | gauge | on dev | | /status sync_info number |
| consensus_fast_syncing | gauge | on dev | | either 0 (not fast syncing) or 1 (syncing) |
| consensus_block_parts | counter | 0.25.0 | peer_id | number of blockparts transmitted by peer |
| consensus_latest_block_height | gauge | 0.25.0 | | /status sync_info number |
| consensus_fast_syncing | gauge | 0.25.0 | | either 0 (not fast syncing) or 1 (syncing) |
| consensus_block_size_bytes | Gauge | 0.21.0 | | Block size in bytes |
| p2p_peers | Gauge | 0.21.0 | | Number of peers node's connected to |
| p2p_peer_receive_bytes_total | counter | on dev | peer_id, chID | number of bytes per channel received from a given peer |
| p2p_peer_send_bytes_total | counter | on dev | peer_id, chID | number of bytes per channel sent to a given peer |
| p2p_peer_pending_send_bytes | gauge | on dev | peer_id | number of pending bytes to be sent to a given peer |
| p2p_num_txs | gauge | on dev | peer_id | number of transactions submitted by each peer_id |
| p2p_pending_send_bytes | gauge | on dev | peer_id | amount of data pending to be sent to peer |
| p2p_peer_receive_bytes_total | counter | 0.25.0 | peer_id, chID | number of bytes per channel received from a given peer |
| p2p_peer_send_bytes_total | counter | 0.25.0 | peer_id, chID | number of bytes per channel sent to a given peer |
| p2p_peer_pending_send_bytes | gauge | 0.25.0 | peer_id | number of pending bytes to be sent to a given peer |
| p2p_num_txs | gauge | 0.25.0 | peer_id | number of transactions submitted by each peer_id |
| p2p_pending_send_bytes | gauge | 0.25.0 | peer_id | amount of data pending to be sent to peer |
| mempool_size | Gauge | 0.21.0 | | Number of uncommitted transactions |
| mempool_tx_size_bytes | histogram | on dev | | transaction sizes in bytes |
| mempool_failed_txs | counter | on dev | | number of failed transactions |
| mempool_recheck_times | counter | on dev | | number of transactions rechecked in the mempool |
| state_block_processing_time | histogram | on dev | | time between BeginBlock and EndBlock in ms |
| mempool_tx_size_bytes | histogram | 0.25.0 | | transaction sizes in bytes |
| mempool_failed_txs | counter | 0.25.0 | | number of failed transactions |
| mempool_recheck_times | counter | 0.25.0 | | number of transactions rechecked in the mempool |
| state_block_processing_time | histogram | 0.25.0 | | time between BeginBlock and EndBlock in ms |
## Useful queries
@@ -99,7 +99,7 @@ send & receive rate per connection (`SendRate`, `RecvRate`).
### RPC
Endpoints returning multiple entries are limited by default to return 30
elements (100 max). See the [RPC Documentation](https://tendermint.com/rpc/)
elements (100 max). See the [RPC Documentation](https://docs.tendermint.com/master/rpc/)
for more information.
Rate-limiting and authentication are another key aspects to help protect
+1 -1
View File
@@ -67,7 +67,7 @@ Authenticated encryption is enabled by default.
## Specification
The full p2p specification can be found [here](https://github.com/tendermint/tendermint/tree/master/docs/spec/p2p).
The full p2p specification can be found [here](https://docs.tendermint.com/master/spec/p2p/).
## Additional Reading
+1 -1
View File
@@ -160,7 +160,7 @@ endpoints. Some take no arguments (like `/status`), while others specify
the argument name and use `_` as a placeholder.
::: tip
Find the RPC Documentation [here](https://tendermint.com/rpc/)
Find the RPC Documentation [here](https://docs.tendermint.com/master/rpc/)
:::
### Formatting
+3 -3
View File
@@ -31,9 +31,9 @@ There are two ways to become validator.
_+2/3 is short for "more than 2/3"_
A block is committed when +2/3 of the validator set sign [precommit
votes](../spec/blockchain/blockchain.md#vote) for that block at the same `round`.
votes](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#vote) for that block at the same `round`.
The +2/3 set of precommit votes is called a
[_commit_](../spec/blockchain/blockchain.md#commit). While any +2/3 set of
[_commit_](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#commit). While any +2/3 set of
precommits for the same block at the same height&round can serve as
validation, the canonical commit is included in the next block (see
[LastCommit](../spec/blockchain/blockchain.md#lastcommit)).
[LastCommit](https://github.com/tendermint/spec/blob/953523c3cb99fdb8c8f7a2d21e3a99094279e9de/spec/blockchain/blockchain.md#lastcommit)).
+13 -3
View File
@@ -7,8 +7,18 @@ parent:
# Overview
Tendermint comes with some tools for:
Tendermint has some tools that are associated with it for:
- [Benchmarking](./benchmarking.md)
- [Monitoring](./monitoring.md)
- [Benchmarking](#benchmarking)
- [Validation of remote signers](./remote-signer-validation.md)
- [Testnets](#testnets)
## Benchmarking
Benchmarking is done with tm-load-test, for information on how to use the tool please visit the docs: https://github.com/interchainio/tm-load-test
## Testnets
The testnets tool is aimed at testing Tendermint with different configurations. For more information please visit: https://github.com/interchainio/testnets.
-4
View File
@@ -1,4 +0,0 @@
# tm-load-test
Tm-bench has been removed in favor of [tm-load-test](https://github.com/interchainio/tm-load-test).
for information on how to use tm-load-test please visit the docs: https://github.com/interchainio/tm-load-test
+3 -3
View File
@@ -12,7 +12,7 @@ require (
github.com/go-kit/kit v0.9.0
github.com/go-logfmt/logfmt v0.5.0
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.3.2
github.com/golang/protobuf v1.3.3
github.com/google/gofuzz v1.0.0 // indirect
github.com/gorilla/websocket v1.4.1
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f
@@ -25,12 +25,12 @@ require (
github.com/rs/cors v1.7.0
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
github.com/spf13/cobra v0.0.1
github.com/spf13/viper v1.6.1
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.4.0
github.com/tendermint/go-amino v0.14.1
github.com/tendermint/tm-db v0.4.0
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
google.golang.org/grpc v1.26.0
google.golang.org/grpc v1.27.1
)
+8
View File
@@ -82,6 +82,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -199,6 +201,8 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -284,6 +288,10 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+6
View File
@@ -3,6 +3,7 @@ package autofile
import (
"os"
"os/signal"
"path/filepath"
"sync"
"syscall"
"time"
@@ -57,6 +58,11 @@ type AutoFile struct {
// an error, it will be of type *PathError or *ErrPermissionsChanged (if file's
// permissions got changed (should be 0600)).
func OpenAutoFile(path string) (*AutoFile, error) {
var err error
path, err = filepath.Abs(path)
if err != nil {
return nil, err
}
af := &AutoFile{
ID: tmrand.Str(12) + ":" + path,
Path: path,
+28 -8
View File
@@ -3,26 +3,34 @@ package autofile
import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tmos "github.com/tendermint/tendermint/libs/os"
)
func TestSIGHUP(t *testing.T) {
// First, create an AutoFile writing to a tempfile dir
file, err := ioutil.TempFile("", "sighup_test")
origDir, err := os.Getwd()
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
name := file.Name()
defer os.Chdir(origDir)
// Here is the actual AutoFile
// First, create a temporary directory and move into it
dir, err := ioutil.TempDir("", "sighup_test")
require.NoError(t, err)
defer os.RemoveAll(dir)
err = os.Chdir(dir)
require.NoError(t, err)
// Create an AutoFile in the temporary directory
name := "sighup_test"
af, err := OpenAutoFile(name)
require.NoError(t, err)
require.True(t, filepath.IsAbs(af.Path))
// Write to the file.
_, err = af.Write([]byte("Line 1\n"))
@@ -34,6 +42,13 @@ func TestSIGHUP(t *testing.T) {
err = os.Rename(name, name+"_old")
require.NoError(t, err)
// Move into a different temporary directory
otherDir, err := ioutil.TempDir("", "sighup_test_other")
require.NoError(t, err)
defer os.RemoveAll(otherDir)
err = os.Chdir(otherDir)
require.NoError(t, err)
// Send SIGHUP to self.
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
@@ -49,12 +64,17 @@ func TestSIGHUP(t *testing.T) {
require.NoError(t, err)
// Both files should exist
if body := tmos.MustReadFile(name + "_old"); string(body) != "Line 1\nLine 2\n" {
if body := tmos.MustReadFile(filepath.Join(dir, name+"_old")); string(body) != "Line 1\nLine 2\n" {
t.Errorf("unexpected body %s", body)
}
if body := tmos.MustReadFile(name); string(body) != "Line 3\nLine 4\n" {
if body := tmos.MustReadFile(filepath.Join(dir, name)); string(body) != "Line 3\nLine 4\n" {
t.Errorf("unexpected body %s", body)
}
// The current directory should be empty
files, err := ioutil.ReadDir(".")
require.NoError(t, err)
assert.Empty(t, files)
}
// // Manually modify file permissions, close, and reopen using autofile:
+4 -2
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
@@ -79,7 +78,10 @@ type Group struct {
// OpenGroup creates a new Group with head at headPath. It returns an error if
// it fails to open head file.
func OpenGroup(headPath string, groupOptions ...func(*Group)) (g *Group, err error) {
dir := path.Dir(headPath)
dir, err := filepath.Abs(filepath.Dir(headPath))
if err != nil {
return nil, err
}
head, err := OpenAutoFile(headPath)
if err != nil {
return nil, err
+23
View File
@@ -4,6 +4,7 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -105,6 +106,23 @@ func TestCheckHeadSizeLimit(t *testing.T) {
func TestRotateFile(t *testing.T) {
g := createTestGroupWithHeadSizeLimit(t, 0)
// Create a different temporary directory and move into it, to make sure
// relative paths are resolved at Group creation
origDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(origDir)
dir, err := ioutil.TempDir("", "rotate_test")
require.NoError(t, err)
defer os.RemoveAll(dir)
err = os.Chdir(dir)
require.NoError(t, err)
require.True(t, filepath.IsAbs(g.Head.Path))
require.True(t, filepath.IsAbs(g.Dir))
// Create and rotate files
g.WriteLine("Line 1")
g.WriteLine("Line 2")
g.WriteLine("Line 3")
@@ -129,6 +147,11 @@ func TestRotateFile(t *testing.T) {
t.Errorf("got unexpected contents: [%v]", string(body2))
}
// Make sure there are no files in the current, temporary directory
files, err := ioutil.ReadDir(".")
require.NoError(t, err)
assert.Empty(t, files)
// Cleanup
destroyTestGroup(t, g)
}
+11 -11
View File
@@ -149,20 +149,20 @@ func init() { proto.RegisterFile("libs/kv/types.proto", fileDescriptor_31432671d
func init() { golang_proto.RegisterFile("libs/kv/types.proto", fileDescriptor_31432671d164f444) }
var fileDescriptor_31432671d164f444 = []byte{
// 193 bytes of a gzipped FileDescriptorProto
// 196 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0xc9, 0x4c, 0x2a,
0xd6, 0xcf, 0x2e, 0xd3, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
0x12, 0x2a, 0x49, 0xcd, 0x4b, 0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x03, 0xc9, 0xeb, 0x65,
0x97, 0x49, 0xe9, 0xa6, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0xa7, 0xe7,
0xa7, 0xe7, 0xeb, 0x83, 0x95, 0x26, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0x31, 0x42,
0x49, 0x8f, 0x8b, 0x25, 0x20, 0x31, 0xb3, 0x48, 0x48, 0x80, 0x8b, 0x39, 0x3b, 0xb5, 0x52, 0x82,
0x51, 0x81, 0x51, 0x83, 0x27, 0x08, 0xc4, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d,
0x95, 0x60, 0x02, 0x8b, 0x41, 0x38, 0x4a, 0x46, 0x5c, 0x1c, 0xde, 0x9e, 0x66, 0x26, 0xc4, 0xe8,
0x61, 0x86, 0xea, 0x71, 0x72, 0xfb, 0xf1, 0x50, 0x8e, 0x71, 0xc5, 0x23, 0x39, 0xc6, 0x1d, 0x8f,
0xe4, 0x18, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x03,
0x8f, 0xe5, 0x18, 0xa3, 0x34, 0x90, 0x1c, 0x8c, 0xf0, 0x0f, 0x32, 0x13, 0xea, 0xf5, 0x24, 0x36,
0xb0, 0x93, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x01, 0x3f, 0x5a, 0xca, 0x0c, 0x01, 0x00,
0x00,
0x97, 0x49, 0xa9, 0x95, 0x64, 0x64, 0x16, 0xa5, 0xc4, 0x17, 0x24, 0x16, 0x95, 0x54, 0xea, 0x83,
0x95, 0xe9, 0xa7, 0xe7, 0xa7, 0xe7, 0x23, 0x58, 0x10, 0xbd, 0x4a, 0x7a, 0x5c, 0x2c, 0x01, 0x89,
0x99, 0x45, 0x42, 0x02, 0x5c, 0xcc, 0xd9, 0xa9, 0x95, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41,
0x20, 0xa6, 0x90, 0x08, 0x17, 0x6b, 0x59, 0x62, 0x4e, 0x69, 0xaa, 0x04, 0x13, 0x58, 0x0c, 0xc2,
0x51, 0x32, 0xe2, 0xe2, 0xf0, 0xf6, 0x34, 0x33, 0x21, 0x46, 0x0f, 0x33, 0x54, 0x8f, 0x93, 0xdb,
0x8f, 0x87, 0x72, 0x8c, 0x2b, 0x1e, 0xc9, 0x31, 0xee, 0x78, 0x24, 0xc7, 0x78, 0xe2, 0x91, 0x1c,
0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x1e, 0x78, 0x2c, 0xc7, 0x18, 0xa5, 0x91,
0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0xf0, 0x08, 0x32, 0x13, 0xea,
0xe7, 0x24, 0x36, 0xb0, 0x93, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xc5, 0x5f, 0x67, 0xcb,
0x05, 0x01, 0x00, 0x00,
}
func (this *Pair) Equal(that interface{}) bool {
+11 -11
View File
@@ -1,29 +1,29 @@
syntax = "proto3";
package tendermint.libs.kv;
option go_package = "github.com/tendermint/tendermint/libs/kv";
option go_package = "github.com/tendermint/tendermint/libs/kv";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "third_party/proto/gogoproto/gogo.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.goproto_registration) = true;
// Generate tests
option (gogoproto.populate_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
//----------------------------------------
// Abstract types
// Define these here for compatibility but use tmlibs/common.KVPair.
// Define these here for compatibility but use tmlibs/kv.Pair.
message Pair {
bytes key = 1;
bytes key = 1;
bytes value = 2;
}
// Define these here for compatibility but use tmlibs/common.KI64Pair.
// Define these here for compatibility but use tmlibs/kv.KI64Pair.
message KI64Pair {
bytes key = 1;
bytes key = 1;
int64 value = 2;
}
-76
View File
@@ -1,76 +0,0 @@
package lite
import (
"time"
"github.com/tendermint/tendermint/types"
)
// AutoClient can auto update itself by fetching headers every N seconds.
type AutoClient struct {
base *Client
updatePeriod time.Duration
quit chan struct{}
trustedHeaders chan *types.SignedHeader
errs chan error
}
// NewAutoClient creates a new client and starts a polling goroutine.
func NewAutoClient(base *Client, updatePeriod time.Duration) *AutoClient {
c := &AutoClient{
base: base,
updatePeriod: updatePeriod,
quit: make(chan struct{}),
trustedHeaders: make(chan *types.SignedHeader),
errs: make(chan error),
}
go c.autoUpdate()
return c
}
// TrustedHeaders returns a channel onto which new trusted headers are posted.
func (c *AutoClient) TrustedHeaders() <-chan *types.SignedHeader {
return c.trustedHeaders
}
// Err returns a channel onto which errors are posted.
func (c *AutoClient) Errs() <-chan error {
return c.errs
}
// Stop stops the client.
func (c *AutoClient) Stop() {
close(c.quit)
}
func (c *AutoClient) autoUpdate() {
ticker := time.NewTicker(c.updatePeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
lastTrustedHeight, err := c.base.LastTrustedHeight()
if err != nil {
c.errs <- err
continue
}
if lastTrustedHeight == -1 {
// no headers yet => wait
continue
}
h, err := c.base.VerifyHeaderAtHeight(lastTrustedHeight+1, time.Now())
if err != nil {
// no header yet or verification error => try again after updatePeriod
c.errs <- err
continue
}
c.trustedHeaders <- h
case <-c.quit:
return
}
}
}
-76
View File
@@ -1,76 +0,0 @@
package lite
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/libs/log"
mockp "github.com/tendermint/tendermint/lite2/provider/mock"
dbs "github.com/tendermint/tendermint/lite2/store/db"
"github.com/tendermint/tendermint/types"
)
func TestAutoClient(t *testing.T) {
const (
chainID = "TestAutoClient"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime = time.Now().Add(-1 * time.Hour)
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
base, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
dbs.New(dbm.NewMemDB(), chainID),
)
require.NoError(t, err)
base.SetLogger(log.TestingLogger())
c := NewAutoClient(base, 1*time.Second)
defer c.Stop()
for i := 2; i <= 3; i++ {
select {
case h := <-c.TrustedHeaders():
assert.EqualValues(t, i, h.Height)
case err := <-c.Errs():
require.NoError(t, err)
case <-time.After(2 * time.Second):
t.Fatal("no headers/errors received in 2 sec")
}
}
}
+508 -97
View File
@@ -3,13 +3,15 @@ package lite
import (
"bytes"
"fmt"
"math/rand"
"sync"
"time"
"github.com/pkg/errors"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/libs/log"
tmmath "github.com/tendermint/tendermint/libs/math"
tmrand "github.com/tendermint/tendermint/libs/rand"
"github.com/tendermint/tendermint/lite2/provider"
"github.com/tendermint/tendermint/lite2/store"
"github.com/tendermint/tendermint/types"
@@ -42,13 +44,32 @@ type TrustOptions struct {
Hash []byte
}
// ValidateBasic performs basic validation.
func (opts TrustOptions) ValidateBasic() error {
if opts.Period <= 0 {
return errors.New("negative or zero period")
}
if opts.Height <= 0 {
return errors.New("negative or zero height")
}
if len(opts.Hash) != tmhash.Size {
return errors.Errorf("expected hash size to be %d bytes, got %d bytes",
tmhash.Size,
len(opts.Hash),
)
}
return nil
}
type mode byte
const (
sequential mode = iota + 1
skipping
defaultUpdatePeriod = 5 * time.Second
defaultRemoveNoLongerTrustedHeadersPeriod = 24 * time.Hour
defaultMaxRetryAttempts = 10
)
// Option sets a parameter for the light client.
@@ -73,20 +94,16 @@ func SequentialVerification() Option {
// applies to non-adjacent headers. For adjacent headers, sequential
// verification is used.
func SkippingVerification(trustLevel tmmath.Fraction) Option {
if err := ValidateTrustLevel(trustLevel); err != nil {
panic(err)
}
return func(c *Client) {
c.verificationMode = skipping
c.trustLevel = trustLevel
}
}
// AlternativeSources option can be used to supply alternative providers, which
// will be used for cross-checking the primary provider.
func AlternativeSources(providers []provider.Provider) Option {
// UpdatePeriod option can be used to change default polling period (5s).
func UpdatePeriod(d time.Duration) Option {
return func(c *Client) {
c.alternatives = providers
c.updatePeriod = d
}
}
@@ -109,23 +126,42 @@ func ConfirmationFunction(fn func(action string) bool) Option {
}
}
// Logger option can be used to set a logger for the client.
func Logger(l log.Logger) Option {
return func(c *Client) {
c.logger = l
}
}
// MaxRetryAttempts option can be used to set max attempts before replacing
// primary with a witness.
func MaxRetryAttempts(max uint16) Option {
return func(c *Client) {
c.maxRetryAttempts = max
}
}
// Client represents a light client, connected to a single chain, which gets
// headers from a primary provider, verifies them either sequentially or by
// skipping some and stores them in a trusted store (usually, a local FS).
//
// By default, the client will poll the primary provider for new headers every
// 5s (UpdatePeriod). If there are any, it will try to advance the state.
//
// Default verification: SkippingVerification(DefaultTrustLevel)
type Client struct {
chainID string
trustingPeriod time.Duration // see TrustOptions.Period
verificationMode mode
trustLevel tmmath.Fraction
maxRetryAttempts uint16 // see MaxRetryAttempts option
// Mutex for locking during changes of the lite clients providers
providerMutex sync.Mutex
// Primary provider of new headers.
primary provider.Provider
// Alternative providers for checking the primary for misbehavior by
// comparing data.
alternatives []provider.Provider
// See Witnesses option
witnesses []provider.Provider
// Where trusted headers are stored.
trustedStore store.Store
@@ -134,11 +170,15 @@ type Client struct {
// Highest next validator set from the store (height=H+1).
trustedNextVals *types.ValidatorSet
// See UpdatePeriod option
updatePeriod time.Duration
// See RemoveNoLongerTrustedHeadersPeriod option
removeNoLongerTrustedHeadersPeriod time.Duration
// See ConfirmationFunction option
confirmationFn func(action string) bool
quit chan struct{}
routinesWaitGroup sync.WaitGroup
quit chan struct{}
logger log.Logger
}
@@ -147,34 +187,28 @@ type Client struct {
// obtain the header & vals from the primary or they are invalid (e.g. trust
// hash does not match with the one from the header).
//
// Witnesses are providers, which will be used for cross-checking the primary
// provider. At least one witness must be given. A witness can become a primary
// iff the current primary is unavailable.
//
// See all Option(s) for the additional configuration.
func NewClient(
chainID string,
trustOptions TrustOptions,
primary provider.Provider,
witnesses []provider.Provider,
trustedStore store.Store,
options ...Option) (*Client, error) {
c := &Client{
chainID: chainID,
trustingPeriod: trustOptions.Period,
verificationMode: skipping,
trustLevel: DefaultTrustLevel,
primary: primary,
trustedStore: trustedStore,
removeNoLongerTrustedHeadersPeriod: defaultRemoveNoLongerTrustedHeadersPeriod,
confirmationFn: func(action string) bool { return true },
quit: make(chan struct{}),
logger: log.NewNopLogger(),
if err := trustOptions.ValidateBasic(); err != nil {
return nil, errors.Wrap(err, "invalid TrustOptions")
}
for _, o := range options {
o(c)
}
if err := c.restoreTrustedHeaderAndNextVals(); err != nil {
c, err := NewClientFromTrustedStore(chainID, trustOptions.Period, primary, witnesses, trustedStore, options...)
if err != nil {
return nil, err
}
if c.trustedHeader != nil {
if err := c.checkTrustedHeaderUsingOptions(trustOptions); err != nil {
return nil, err
@@ -187,8 +221,60 @@ func NewClient(
}
}
if c.removeNoLongerTrustedHeadersPeriod > 0 {
go c.removeNoLongerTrustedHeadersRoutine()
return c, err
}
// NewClientFromTrustedStore initializes existing client from the trusted store.
//
// See NewClient
func NewClientFromTrustedStore(
chainID string,
trustingPeriod time.Duration,
primary provider.Provider,
witnesses []provider.Provider,
trustedStore store.Store,
options ...Option) (*Client, error) {
c := &Client{
chainID: chainID,
trustingPeriod: trustingPeriod,
verificationMode: skipping,
trustLevel: DefaultTrustLevel,
maxRetryAttempts: defaultMaxRetryAttempts,
primary: primary,
witnesses: witnesses,
trustedStore: trustedStore,
updatePeriod: defaultUpdatePeriod,
removeNoLongerTrustedHeadersPeriod: defaultRemoveNoLongerTrustedHeadersPeriod,
confirmationFn: func(action string) bool { return true },
quit: make(chan struct{}),
logger: log.NewNopLogger(),
}
for _, o := range options {
o(c)
}
// Validate the number of witnesses.
if len(c.witnesses) < 1 {
return nil, errors.New("expected at least one witness")
}
// Verify witnesses are all on the same chain.
for i, w := range witnesses {
if w.ChainID() != chainID {
return nil, errors.Errorf("witness #%d: %v is on another chain %s, expected %s",
i, w, w.ChainID(), chainID)
}
}
// Validate trust level.
if err := ValidateTrustLevel(c.trustLevel); err != nil {
return nil, err
}
if err := c.restoreTrustedHeaderAndNextVals(); err != nil {
return nil, err
}
return c, nil
@@ -243,7 +329,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
var primaryHash []byte
switch {
case options.Height > c.trustedHeader.Height:
h, err := c.primary.SignedHeader(c.trustedHeader.Height)
h, err := c.signedHeaderFromPrimary(c.trustedHeader.Height)
if err != nil {
return err
}
@@ -262,8 +348,6 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
if c.confirmationFn(action) {
// remove all the headers ( options.Height, trustedHeader.Height ]
c.cleanup(options.Height + 1)
// set c.trustedHeader to one at options.Height
c.restoreTrustedHeaderAndNextVals()
c.logger.Info("Rolled back to older header (newer headers were removed)",
"old", options.Height)
@@ -297,7 +381,7 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
// Fetch trustedHeader and trustedNextVals from primary provider.
func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
// 1) Fetch and verify the header.
h, err := c.primary.SignedHeader(options.Height)
h, err := c.signedHeaderFromPrimary(options.Height)
if err != nil {
return err
}
@@ -312,7 +396,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
}
// 2) Fetch and verify the vals.
vals, err := c.primary.ValidatorSet(options.Height)
vals, err := c.validatorSetFromPrimary(options.Height)
if err != nil {
return err
}
@@ -330,7 +414,7 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
// 3) Fetch and verify the next vals (verification happens in
// updateTrustedHeaderAndVals).
nextVals, err := c.primary.ValidatorSet(options.Height + 1)
nextVals, err := c.validatorSetFromPrimary(options.Height + 1)
if err != nil {
return err
}
@@ -339,21 +423,39 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
return c.updateTrustedHeaderAndVals(h, nextVals)
}
// Stop stops the light client.
// Start starts two processes: 1) auto updating 2) removing outdated headers.
func (c *Client) Start() error {
c.logger.Info("Starting light client")
if c.removeNoLongerTrustedHeadersPeriod > 0 {
c.routinesWaitGroup.Add(1)
go c.removeNoLongerTrustedHeadersRoutine()
}
if c.updatePeriod > 0 {
c.routinesWaitGroup.Add(1)
go c.autoUpdateRoutine()
}
return nil
}
// Stop stops two processes: 1) auto updating 2) removing outdated headers.
// Stop only returns after both of them are finished running. If you wish to
// remove all the data, call Cleanup.
func (c *Client) Stop() {
c.logger.Info("Stopping light client")
close(c.quit)
c.routinesWaitGroup.Wait()
}
// SetLogger sets a logger.
func (c *Client) SetLogger(l log.Logger) {
c.logger = l
}
// TrustedHeader returns a trusted header at the given height (0 - the latest)
// or nil if no such header exist.
// TrustedHeader returns a trusted header at the given height (0 - the latest).
// If a header is missing in trustedStore (e.g. it was skipped during
// bisection), it will be downloaded from primary.
//
// Headers, which can't be trusted anymore, are removed once a day (can be
// changed with RemoveNoLongerTrustedHeadersPeriod option).
// Headers along with validator sets, which can't be trusted anymore, are
// removed once a day (can be changed with RemoveNoLongerTrustedHeadersPeriod
// option).
// .
// height must be >= 0.
//
@@ -361,29 +463,44 @@ func (c *Client) SetLogger(l log.Logger) {
// - header expired, therefore can't be trusted (ErrOldHeaderExpired);
// - there are some issues with the trusted store, although that should not
// happen normally;
// - negative height is passed.
// - negative height is passed;
// - header has not been verified yet
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader, error) {
if height < 0 {
return nil, errors.New("negative height")
}
if height == 0 {
var err error
height, err = c.LastTrustedHeight()
if err != nil {
return nil, err
}
}
h, err := c.trustedStore.SignedHeader(height)
// 1) Get latest height.
latestHeight, err := c.LastTrustedHeight()
if err != nil {
return nil, err
}
if h == nil {
return nil, nil
if latestHeight == -1 {
return nil, errors.New("no headers exist")
}
if height > latestHeight {
return nil, errors.Errorf("unverified header requested (latest: %d)", latestHeight)
}
if height == 0 {
height = latestHeight
}
// Ensure header can still be trusted.
// 2) Get header from store.
h, err := c.trustedStore.SignedHeader(height)
switch {
case errors.Is(err, store.ErrSignedHeaderNotFound):
// 2.1) If not found, try to fetch header from primary.
h, err = c.fetchMissingTrustedHeader(height, now)
if err != nil {
return nil, err
}
case err != nil:
return nil, err
}
// 3) Ensure header can still be trusted.
if HeaderExpired(h, c.trustingPeriod, now) {
return nil, ErrOldHeaderExpired{h.Time.Add(c.trustingPeriod), now}
}
@@ -391,13 +508,57 @@ func (c *Client) TrustedHeader(height int64, now time.Time) (*types.SignedHeader
return h, nil
}
// TrustedValidatorSet returns a trusted validator set at the given height. If
// a validator set is missing in trustedStore (e.g. the associated header was
// skipped during bisection), it will be downloaded from primary. The second
// return parameter is height validator set corresponds to (useful when you
// pass 0).
//
// height must be >= 0.
//
// Headers along with validator sets, which can't be trusted anymore, are
// removed once a day (can be changed with RemoveNoLongerTrustedHeadersPeriod
// option).
//
// It returns an error if:
// - header signed by that validator set expired (ErrOldHeaderExpired)
// - there are some issues with the trusted store, although that should not
// happen normally;
// - negative height is passed;
// - header signed by that validator set has not been verified yet
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) TrustedValidatorSet(height int64, now time.Time) (*types.ValidatorSet, error) {
// Checks height is positive and header (note: height - 1) is not expired.
// Additionally, it fetches validator set from primary if it's missing in
// store.
_, err := c.TrustedHeader(height-1, now)
if err != nil {
return nil, err
}
return c.trustedStore.ValidatorSet(height)
}
// LastTrustedHeight returns a last trusted height. -1 and nil are returned if
// there are no trusted headers.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) LastTrustedHeight() (int64, error) {
return c.trustedStore.LastSignedHeaderHeight()
}
// FirstTrustedHeight returns a first trusted height. -1 and nil are returned if
// there are no trusted headers.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) FirstTrustedHeight() (int64, error) {
return c.trustedStore.FirstSignedHeaderHeight()
}
// ChainID returns the chain ID the light client was configured with.
//
// Safe for concurrent use by multiple goroutines.
func (c *Client) ChainID() string {
return c.chainID
}
@@ -406,6 +567,8 @@ func (c *Client) ChainID() string {
// and calls VerifyHeader.
//
// If the trusted header is more recent than one here, an error is returned.
// If the header is not found by the primary provider,
// provider.ErrSignedHeaderNotFound error is returned.
func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.SignedHeader, error) {
c.logger.Info("VerifyHeaderAtHeight", "height", height)
@@ -435,17 +598,20 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
// https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md
//
// If the trusted header is more recent than one here, an error is returned.
//
// If, at any moment, SignedHeader or ValidatorSet are not found by the primary
// provider, provider.ErrSignedHeaderNotFound /
// provider.ErrValidatorSetNotFound error is returned.
func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
c.logger.Info("VerifyHeader", "height", newHeader.Hash(), "newVals", newVals.Hash())
c.logger.Info("VerifyHeader", "height", newHeader.Hash(), "newVals", fmt.Sprintf("%X", newVals.Hash()))
if c.trustedHeader.Height >= newHeader.Height {
return errors.Errorf("header at more recent height #%d exists", c.trustedHeader.Height)
}
if len(c.alternatives) > 0 {
if err := c.compareNewHeaderWithRandomAlternative(newHeader); err != nil {
return err
}
if err := c.compareNewHeaderWithWitnesses(newHeader); err != nil {
c.logger.Error("Error when comparing new header with one from a witness", "err", err)
return err
}
var err error
@@ -462,20 +628,39 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
}
// Update trusted header and vals.
nextVals, err := c.primary.ValidatorSet(newHeader.Height + 1)
nextVals, err := c.validatorSetFromPrimary(newHeader.Height + 1)
if err != nil {
return err
}
return c.updateTrustedHeaderAndVals(newHeader, nextVals)
}
// Cleanup removes all the data (headers and validator sets) stored.
// Primary returns the primary provider.
//
// NOTE: provider may be not safe for concurrent access.
func (c *Client) Primary() provider.Provider {
c.providerMutex.Lock()
defer c.providerMutex.Unlock()
return c.primary
}
// Witnesses returns the witness providers.
//
// NOTE: providers may be not safe for concurrent access.
func (c *Client) Witnesses() []provider.Provider {
c.providerMutex.Lock()
defer c.providerMutex.Unlock()
return c.witnesses
}
// Cleanup removes all the data (headers and validator sets) stored. Note: the
// client must be stopped at this point.
func (c *Client) Cleanup() error {
c.logger.Info("Cleanup everything")
c.logger.Info("Removing all the data")
return c.cleanup(0)
}
// stopHeight=0 -> remove all data
// cleanup deletes all headers & validator sets between +stopHeight+ and latest height included
func (c *Client) cleanup(stopHeight int64) error {
// 1) Get the oldest height.
oldestHeight, err := c.trustedStore.FirstSignedHeaderHeight()
@@ -490,7 +675,7 @@ func (c *Client) cleanup(stopHeight int64) error {
}
// 3) Remove all headers and validator sets.
if stopHeight == 0 {
if stopHeight < oldestHeight {
stopHeight = oldestHeight
}
for height := stopHeight; height <= latestHeight; height++ {
@@ -503,6 +688,10 @@ func (c *Client) cleanup(stopHeight int64) error {
c.trustedHeader = nil
c.trustedNextVals = nil
err = c.restoreTrustedHeaderAndNextVals()
if err != nil {
return err
}
return nil
}
@@ -516,7 +705,7 @@ func (c *Client) sequence(newHeader *types.SignedHeader, newVals *types.Validato
err error
)
for height := c.trustedHeader.Height + 1; height < newHeader.Height; height++ {
interimHeader, err = c.primary.SignedHeader(height)
interimHeader, err = c.signedHeaderFromPrimary(height)
if err != nil {
return errors.Wrapf(err, "failed to obtain the header #%d", height)
}
@@ -536,7 +725,7 @@ func (c *Client) sequence(newHeader *types.SignedHeader, newVals *types.Validato
if height == newHeader.Height-1 {
nextVals = newVals
} else {
nextVals, err = c.primary.ValidatorSet(height + 1)
nextVals, err = c.validatorSetFromPrimary(height + 1)
if err != nil {
return errors.Wrapf(err, "failed to obtain the vals #%d", height+1)
}
@@ -590,7 +779,7 @@ func (c *Client) bisection(
// right branch
{
nextVals, err := c.primary.ValidatorSet(pivot + 1)
nextVals, err := c.validatorSetFromPrimary(pivot + 1)
if err != nil {
return errors.Wrapf(err, "failed to obtain the vals #%d", pivot+1)
}
@@ -633,42 +822,140 @@ func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, nextVals *typ
// fetch header and validators for the given height from primary provider.
func (c *Client) fetchHeaderAndValsAtHeight(height int64) (*types.SignedHeader, *types.ValidatorSet, error) {
h, err := c.primary.SignedHeader(height)
h, err := c.signedHeaderFromPrimary(height)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to obtain the header #%d", height)
}
vals, err := c.primary.ValidatorSet(height)
vals, err := c.validatorSetFromPrimary(height)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to obtain the vals #%d", height)
}
return h, vals, nil
}
// compare header with one from a random alternative provider.
func (c *Client) compareNewHeaderWithRandomAlternative(h *types.SignedHeader) error {
// 1. Pick an alternative provider.
p := c.alternatives[tmrand.Intn(len(c.alternatives))]
// fetchMissingTrustedHeader finds the closest height after the
// requested height and does backwards verification.
func (c *Client) fetchMissingTrustedHeader(height int64, now time.Time) (*types.SignedHeader, error) {
c.logger.Info("Fetching missing header", "height", height)
// 2. Fetch the header.
altHeader, err := p.SignedHeader(h.Height)
closestHeader, err := c.trustedStore.SignedHeaderAfter(height)
if err != nil {
return errors.Wrapf(err,
"failed to obtain header #%d from alternative provider %v", h.Height, p)
return nil, errors.Wrapf(err, "can't get signed header after %d", height)
}
// 3. Compare hashes.
if !bytes.Equal(h.Hash(), altHeader.Hash()) {
// TODO: One of the providers is lying. Send the evidence to fork
// accountability server.
return errors.Errorf(
"new header hash %X does not match one from alternative provider %X",
h.Hash(), altHeader.Hash())
// Perform backwards verification from closestHeader to header at the given
// height.
h, err := c.backwards(height, closestHeader, now)
if err != nil {
return nil, err
}
return nil
// Fetch next validator set from primary and persist it.
nextVals, err := c.validatorSetFromPrimary(height + 1)
if err != nil {
return nil, errors.Wrapf(err, "failed to obtain the vals #%d", height)
}
if !bytes.Equal(h.NextValidatorsHash, nextVals.Hash()) {
return nil, errors.Errorf("expected next validator's hash %X, but got %X",
h.NextValidatorsHash, nextVals.Hash())
}
if err := c.trustedStore.SaveSignedHeaderAndNextValidatorSet(h, nextVals); err != nil {
return nil, errors.Wrap(err, "failed to save trusted header")
}
return h, nil
}
// Backwards verification (see VerifyHeaderBackwards func in the spec)
func (c *Client) backwards(toHeight int64, fromHeader *types.SignedHeader, now time.Time) (*types.SignedHeader, error) {
var (
trustedHeader = fromHeader
untrustedHeader *types.SignedHeader
err error
)
for i := trustedHeader.Height - 1; i >= toHeight; i-- {
untrustedHeader, err = c.signedHeaderFromPrimary(i)
if err != nil {
return nil, errors.Wrapf(err, "failed to obtain the header #%d", i)
}
if err := untrustedHeader.ValidateBasic(c.chainID); err != nil {
return nil, errors.Wrap(err, "untrustedHeader.ValidateBasic failed")
}
if !untrustedHeader.Time.Before(trustedHeader.Time) {
return nil, errors.Errorf("expected older header time %v to be before newer header time %v",
untrustedHeader.Time,
trustedHeader.Time)
}
if HeaderExpired(untrustedHeader, c.trustingPeriod, now) {
return nil, ErrOldHeaderExpired{untrustedHeader.Time.Add(c.trustingPeriod), now}
}
if !bytes.Equal(untrustedHeader.Hash(), trustedHeader.LastBlockID.Hash) {
return nil, errors.Errorf("older header hash %X does not match trusted header's last block %X",
untrustedHeader.Hash(),
trustedHeader.LastBlockID.Hash)
}
trustedHeader = untrustedHeader
}
return trustedHeader, nil
}
// compare header with all witnesses provided.
func (c *Client) compareNewHeaderWithWitnesses(h *types.SignedHeader) error {
c.providerMutex.Lock()
defer c.providerMutex.Unlock()
// 0. Check witnesses exist
if len(c.witnesses) == 0 {
return errors.New("could not find any witnesses")
}
matchedHeader := false
for attempt := uint16(1); attempt <= c.maxRetryAttempts; attempt++ {
// 1. Loop through all witnesses.
for _, witness := range c.witnesses {
// 2. Fetch the header.
altH, err := witness.SignedHeader(h.Height)
if err != nil {
c.logger.Info("No Response from witness ", "witness", witness)
continue
}
// 3. Compare hashes.
if !bytes.Equal(h.Hash(), altH.Hash()) {
// TODO: One of the providers is lying. Send the evidence to fork
// accountability server.
return errors.Errorf(
"header hash %X does not match one %X from the witness %v",
h.Hash(), altH.Hash(), witness)
}
matchedHeader = true
}
// 4. Check that one responding witness has returned a matching header
if matchedHeader {
return nil
}
time.Sleep(backoffTimeout(attempt))
}
return errors.New("awaiting response from all witnesses exceeded dropout time")
}
func (c *Client) removeNoLongerTrustedHeadersRoutine() {
defer c.routinesWaitGroup.Done()
ticker := time.NewTicker(c.removeNoLongerTrustedHeadersPeriod)
defer ticker.Stop()
@@ -688,11 +975,14 @@ func (c *Client) removeNoLongerTrustedHeadersRoutine() {
// Exposed for testing.
func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
// 1) Get the oldest height.
oldestHeight, err := c.trustedStore.FirstSignedHeaderHeight()
oldestHeight, err := c.FirstTrustedHeight()
if err != nil {
c.logger.Error("can't get first trusted height", "err", err)
return
}
if oldestHeight == -1 { // no headers yet => wait
return
}
// 2) Get the latest height.
latestHeight, err := c.LastTrustedHeight()
@@ -700,6 +990,9 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
c.logger.Error("can't get last trusted height", "err", err)
return
}
if latestHeight == -1 { // no headers yet => wait
return
}
// 3) Remove all headers that are outside of the trusting period.
for height := oldestHeight; height <= latestHeight; height++ {
@@ -708,10 +1001,6 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
c.logger.Error("can't get a trusted header", "err", err, "height", height)
continue
}
if h == nil {
c.logger.Debug("attempted to remove non-existing header", "height", height)
continue
}
// Stop if the header is within the trusting period.
if !HeaderExpired(h, c.trustingPeriod, now) {
@@ -725,3 +1014,125 @@ func (c *Client) RemoveNoLongerTrustedHeaders(now time.Time) {
}
}
}
func (c *Client) autoUpdateRoutine() {
defer c.routinesWaitGroup.Done()
ticker := time.NewTicker(c.updatePeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
err := c.Update(time.Now())
if err != nil {
c.logger.Error("Error during auto update", "err", err)
}
case <-c.quit:
return
}
}
}
// Update attempts to advance the state making exponential steps (note:
// when SequentialVerification is being used, the client will still be
// downloading all intermediate headers).
//
// Exposed for testing.
func (c *Client) Update(now time.Time) error {
lastTrustedHeight, err := c.LastTrustedHeight()
if err != nil {
return errors.Wrap(err, "can't get last trusted height")
}
if lastTrustedHeight == -1 {
// no headers yet => wait
return nil
}
latestHeader, latestVals, err := c.fetchHeaderAndValsAtHeight(0)
if err != nil {
return errors.Wrapf(err, "can't get latest header and vals")
}
if latestHeader.Height > lastTrustedHeight {
err = c.VerifyHeader(latestHeader, latestVals, now)
if err != nil {
return err
}
c.logger.Info("Advanced to new state", "height", latestHeader.Height, "hash", latestHeader.Hash())
}
return nil
}
// replaceProvider takes the first alternative provider and promotes it as the primary provider
func (c *Client) replacePrimaryProvider() error {
c.providerMutex.Lock()
defer c.providerMutex.Unlock()
if len(c.witnesses) == 0 {
return errors.Errorf("no witnesses left")
}
c.primary = c.witnesses[0]
c.witnesses = c.witnesses[1:]
c.logger.Info("New primary", "p", c.primary)
return nil
}
// signedHeaderFromPrimary retrieves the SignedHeader from the primary provider at the specified height.
// Handles dropout by the primary provider by swapping with an alternative provider
func (c *Client) signedHeaderFromPrimary(height int64) (*types.SignedHeader, error) {
for attempt := uint16(1); attempt <= c.maxRetryAttempts; attempt++ {
c.providerMutex.Lock()
h, err := c.primary.SignedHeader(height)
c.providerMutex.Unlock()
if err == nil {
// sanity check
if height > 0 && h.Height != height {
return nil, errors.Errorf("expected %d height, got %d", height, h.Height)
}
return h, nil
}
if err == provider.ErrSignedHeaderNotFound {
return nil, err
}
time.Sleep(backoffTimeout(attempt))
}
c.logger.Info("Primary is unavailable. Replacing with the first witness")
err := c.replacePrimaryProvider()
if err != nil {
return nil, err
}
return c.signedHeaderFromPrimary(height)
}
// validatorSetFromPrimary retrieves the ValidatorSet from the primary provider at the specified height.
// Handles dropout by the primary provider after 5 attempts by replacing it with an alternative provider
func (c *Client) validatorSetFromPrimary(height int64) (*types.ValidatorSet, error) {
for attempt := uint16(1); attempt <= c.maxRetryAttempts; attempt++ {
c.providerMutex.Lock()
vals, err := c.primary.ValidatorSet(height)
c.providerMutex.Unlock()
if err == nil || err == provider.ErrValidatorSetNotFound {
return vals, err
}
time.Sleep(backoffTimeout(attempt))
}
c.logger.Info("Primary is unavailable. Replacing with the first witness")
err := c.replacePrimaryProvider()
if err != nil {
return nil, err
}
return c.validatorSetFromPrimary(height)
}
// exponential backoff (with jitter)
// 0.5s -> 2s -> 4.5s -> 8s -> 12.5 with 1s variation
func backoffTimeout(attempt uint16) time.Duration {
return time.Duration(500*attempt*attempt)*time.Millisecond + time.Duration(rand.Intn(1000))*time.Millisecond
}
+359 -271
View File
@@ -1,6 +1,7 @@
package lite
import (
"sync"
"testing"
"time"
@@ -10,24 +11,50 @@ import (
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/lite2/provider"
mockp "github.com/tendermint/tendermint/lite2/provider/mock"
dbs "github.com/tendermint/tendermint/lite2/store/db"
"github.com/tendermint/tendermint/types"
)
func TestClient_SequentialVerification(t *testing.T) {
const (
chainID = "sequential-verification"
)
const (
chainID = "test"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
var (
keys = genPrivKeys(4)
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
h1 = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()})
h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()})
trustPeriod = 4 * time.Hour
trustOptions = TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: h1.Hash(),
}
fullNode = mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: h1,
2: h2,
3: h3,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
)
deadNode = mockp.NewDeadMock(chainID)
)
func TestClient_SequentialVerification(t *testing.T) {
testCases := []struct {
otherHeaders map[int64]*types.SignedHeader // all except ^
@@ -39,13 +66,11 @@ func TestClient_SequentialVerification(t *testing.T) {
{
map[int64]*types.SignedHeader{
// trusted header
1: header,
1: h1,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
2: h2,
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
3: h3,
},
map[int64]*types.ValidatorSet{
1: vals,
@@ -73,7 +98,7 @@ func TestClient_SequentialVerification(t *testing.T) {
{
map[int64]*types.SignedHeader{
// trusted header
1: header,
1: h1,
// interim header (1/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), len(keys)-1, len(keys)),
@@ -94,7 +119,7 @@ func TestClient_SequentialVerification(t *testing.T) {
{
map[int64]*types.SignedHeader{
// trusted header
1: header,
1: h1,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
@@ -116,16 +141,17 @@ func TestClient_SequentialVerification(t *testing.T) {
for _, tc := range testCases {
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
trustOptions,
mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SequentialVerification(),
)
@@ -136,6 +162,8 @@ func TestClient_SequentialVerification(t *testing.T) {
} else {
require.NoError(t, err)
}
err = c.Start()
require.NoError(t, err)
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
@@ -148,18 +176,6 @@ func TestClient_SequentialVerification(t *testing.T) {
}
func TestClient_SkippingVerification(t *testing.T) {
const (
chainID = "skipping-verification"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
// required for 2nd test case
newKeys := genPrivKeys(4)
@@ -168,17 +184,14 @@ func TestClient_SkippingVerification(t *testing.T) {
testCases := []struct {
otherHeaders map[int64]*types.SignedHeader // all except ^
vals map[int64]*types.ValidatorSet
initErr bool
verifyErr bool
}{
// good
{
map[int64]*types.SignedHeader{
// trusted header
1: header,
1: h1,
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
3: h3,
},
map[int64]*types.ValidatorSet{
1: vals,
@@ -186,14 +199,12 @@ func TestClient_SkippingVerification(t *testing.T) {
3: vals,
4: vals,
},
false,
false,
},
// good, val set changes 100% at height 2
{
map[int64]*types.SignedHeader{
// trusted header
1: header,
1: h1,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
@@ -207,89 +218,56 @@ func TestClient_SkippingVerification(t *testing.T) {
3: newVals,
4: newVals,
},
false,
false,
},
}
for _, tc := range testCases {
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
trustOptions,
mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SkippingVerification(DefaultTrustLevel),
)
if tc.initErr {
require.Error(t, err)
continue
} else {
require.NoError(t, err)
}
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
if tc.verifyErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.NoError(t, err)
}
}
func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
const (
chainID = "TestClientRemovesNoLongerTrustedHeaders"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
// interim header (3/3 signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
// last header (3/3 signed)
3: keys.GenSignedHeader(chainID, 3, bTime.Add(4*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
4: vals,
},
),
trustOptions,
fullNode,
[]provider.Provider{fullNode},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
assert.NotPanics(t, func() {
now := bTime.Add(4 * time.Hour).Add(1 * time.Second)
c.RemoveNoLongerTrustedHeaders(now)
})
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
c.SetLogger(log.TestingLogger())
// Verify new headers.
_, err = c.VerifyHeaderAtHeight(2, bTime.Add(2*time.Hour).Add(1*time.Second))
@@ -303,7 +281,7 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
// Check expired headers are no longer available.
h, err := c.TrustedHeader(1, now)
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
// Check not expired headers are available.
@@ -313,110 +291,78 @@ func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
}
func TestClient_Cleanup(t *testing.T) {
const (
chainID = "TestClient_Cleanup"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
trustOptions,
fullNode,
[]provider.Provider{fullNode},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
c.Stop()
c.Cleanup()
// Check no headers exist after Cleanup.
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
// trustedHeader.Height == options.Height
func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
const (
chainID = "TestClientRestoreTrustedHeaderAfterStartup1"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
// 1. options.Hash == trustedHeader.Hash
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
trustOptions,
fullNode,
[]provider.Provider{fullNode},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
assert.NotNil(t, h)
assert.Equal(t, h.Hash(), header.Hash())
assert.Equal(t, h.Hash(), h1.Hash())
}
// 2. options.Hash != trustedHeader.Hash
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
// header1 != header
header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@@ -424,21 +370,15 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
Height: 1,
Hash: header1.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
// trusted header
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
@@ -449,163 +389,137 @@ func TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
// trustedHeader.Height < options.Height
func TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
const (
chainID = "TestClientRestoreTrustedHeaderAfterStartup2"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
// 1. options.Hash == trustedHeader.Hash
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 2,
Hash: header2.Hash(),
Hash: h2.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
fullNode,
[]provider.Provider{fullNode},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// Check we still have the 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.NotNil(t, h)
assert.Equal(t, h.Hash(), header.Hash())
assert.Equal(t, h.Hash(), h1.Hash())
}
// 2. options.Hash != trustedHeader.Hash
// This could happen if previous provider was lying to us.
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
// header1 != header
header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
diffHeader1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
diffHeader2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: diffHeader1,
2: diffHeader2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 2,
Hash: header2.Hash(),
Hash: diffHeader2.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// Check we no longer have the invalid 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
}
// trustedHeader.Height > options.Height
func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
const (
chainID = "TestClientRestoreTrustedHeaderAfterStartup3"
)
var (
keys = genPrivKeys(4)
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
vals = keys.ToValidators(20, 10)
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
// 1. options.Hash == trustedHeader.Hash
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(header2, vals)
//header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
// []byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(h2, vals)
require.NoError(t, err)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: h1,
2: h2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
Period: 4 * time.Hour,
Height: 1,
Hash: header.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header,
2: header2,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: vals,
},
),
trustOptions,
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// Check we still have the 1st header (+header+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.NotNil(t, h)
assert.Equal(t, h.Hash(), header.Hash())
assert.Equal(t, h.Hash(), h1.Hash())
// Check we no longer have 2nd header (+header2+).
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
@@ -613,7 +527,7 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
// This could happen if previous provider was lying to us.
{
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
// header1 != header
@@ -625,6 +539,17 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(header2, vals)
require.NoError(t, err)
primary := mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
)
c, err := NewClient(
chainID,
TrustOptions{
@@ -632,20 +557,15 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
Height: 1,
Hash: header1.Hash(),
},
mockp.New(
chainID,
map[int64]*types.SignedHeader{
1: header1,
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
},
),
primary,
[]provider.Provider{primary},
trustedStore,
Logger(log.TestingLogger()),
)
require.NoError(t, err)
c.SetLogger(log.TestingLogger())
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// Check we have swapped invalid 1st header (+header+) with correct one (+header1+).
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
@@ -655,7 +575,175 @@ func TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
// Check we no longer have invalid 2nd header (+header2+).
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
assert.NoError(t, err)
assert.Error(t, err)
assert.Nil(t, h)
}
}
func TestClient_Update(t *testing.T) {
c, err := NewClient(
chainID,
trustOptions,
fullNode,
[]provider.Provider{fullNode},
dbs.New(dbm.NewMemDB(), chainID),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// should result in downloading & verifying header #3
err = c.Update(bTime.Add(2 * time.Hour))
require.NoError(t, err)
h, err := c.TrustedHeader(3, bTime.Add(2*time.Hour))
assert.NoError(t, err)
require.NotNil(t, h)
assert.EqualValues(t, 3, h.Height)
}
func TestClient_Concurrency(t *testing.T) {
c, err := NewClient(
chainID,
trustOptions,
fullNode,
[]provider.Provider{fullNode},
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(2, bTime.Add(2*time.Hour))
require.NoError(t, err)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// NOTE: Cleanup, Stop, VerifyHeaderAtHeight and Verify are not supposed
// to be concurrenly safe.
assert.Equal(t, chainID, c.ChainID())
_, err := c.LastTrustedHeight()
assert.NoError(t, err)
_, err = c.FirstTrustedHeight()
assert.NoError(t, err)
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour))
assert.NoError(t, err)
assert.NotNil(t, h)
vals, err := c.TrustedValidatorSet(2, bTime.Add(2*time.Hour))
assert.NoError(t, err)
assert.NotNil(t, vals)
}()
}
wg.Wait()
}
func TestProvider_Replacement(t *testing.T) {
c, err := NewClient(
chainID,
trustOptions,
deadNode,
[]provider.Provider{fullNode, fullNode},
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
MaxRetryAttempts(1),
)
require.NoError(t, err)
err = c.Update(bTime.Add(2 * time.Hour))
require.NoError(t, err)
assert.NotEqual(t, c.Primary(), deadNode)
assert.Equal(t, 1, len(c.Witnesses()))
}
func TestProvider_TrustedHeaderFetchesMissingHeader(t *testing.T) {
c, err := NewClient(
chainID,
TrustOptions{
Period: 1 * time.Hour,
Height: 3,
Hash: h3.Hash(),
},
fullNode,
[]provider.Provider{fullNode},
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
)
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
// 1) header is missing => expect no error
h, err := c.TrustedHeader(2, bTime.Add(1*time.Hour).Add(1*time.Second))
require.NoError(t, err)
if assert.NotNil(t, h) {
assert.EqualValues(t, 2, h.Height)
}
// 2) header is missing, but it's expired => expect error
h, err = c.TrustedHeader(1, bTime.Add(1*time.Hour).Add(1*time.Second))
assert.Error(t, err)
assert.Nil(t, h)
}
func Test_NewClientFromTrustedStore(t *testing.T) {
// 1) Initiate DB and fill with a "trusted" header
db := dbs.New(dbm.NewMemDB(), chainID)
err := db.SaveSignedHeaderAndNextValidatorSet(h1, vals)
require.NoError(t, err)
// 2) Initialize Lite Client from Trusted Store
c, err := NewClientFromTrustedStore(
chainID,
trustPeriod,
fullNode,
[]provider.Provider{fullNode},
db,
)
require.NoError(t, err)
// 3) Check header exists through the lite clients eyes
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
assert.NoError(t, err)
assert.EqualValues(t, 1, h.Height)
}
func TestCompareWithWitnesses(t *testing.T) {
c, err := NewClient(
chainID,
trustOptions,
fullNode,
[]provider.Provider{deadNode, deadNode, deadNode},
dbs.New(dbm.NewMemDB(), chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
MaxRetryAttempts(1),
)
require.NoError(t, err)
err = c.Update(time.Now())
assert.Error(t, err)
}
+84 -71
View File
@@ -8,18 +8,18 @@ import (
"testing"
"time"
"github.com/pkg/errors"
dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/lite2/provider"
httpp "github.com/tendermint/tendermint/lite2/provider/http"
dbs "github.com/tendermint/tendermint/lite2/store/db"
rpctest "github.com/tendermint/tendermint/rpc/test"
)
func TestExample_Client(t *testing.T) {
// Automatically getting new headers and verifying them.
func TestExample_Client_AutoUpdate(t *testing.T) {
// give Tendermint time to generate some blocks
time.Sleep(5 * time.Second)
@@ -34,12 +34,12 @@ func TestExample_Client(t *testing.T) {
chainID = config.ChainID()
)
provider, err := httpp.New(chainID, config.RPC.ListenAddress)
primary, err := httpp.New(chainID, config.RPC.ListenAddress)
if err != nil {
stdlog.Fatal(err)
}
header, err := provider.SignedHeader(2)
header, err := primary.SignedHeader(2)
if err != nil {
stdlog.Fatal(err)
}
@@ -56,13 +56,90 @@ func TestExample_Client(t *testing.T) {
Height: 2,
Hash: header.Hash(),
},
provider,
primary,
[]provider.Provider{primary}, // TODO: primary should not be used here
dbs.New(db, chainID),
UpdatePeriod(1*time.Second),
Logger(log.TestingLogger()),
)
if err != nil {
stdlog.Fatal(err)
}
c.SetLogger(log.TestingLogger())
err = c.Start()
if err != nil {
stdlog.Fatal(err)
}
defer func() {
c.Stop()
c.Cleanup()
}()
time.Sleep(2 * time.Second)
h, err := c.TrustedHeader(0, time.Now())
if err != nil {
stdlog.Fatal(err)
}
fmt.Println("got header", h.Height)
// Output: got header 3
}
// Manually getting headers and verifying them.
func TestExample_Client_ManualUpdate(t *testing.T) {
// give Tendermint time to generate some blocks
time.Sleep(5 * time.Second)
dbDir, err := ioutil.TempDir("", "lite-client-example")
if err != nil {
stdlog.Fatal(err)
}
defer os.RemoveAll(dbDir)
var (
config = rpctest.GetConfig()
chainID = config.ChainID()
)
primary, err := httpp.New(chainID, config.RPC.ListenAddress)
if err != nil {
stdlog.Fatal(err)
}
header, err := primary.SignedHeader(2)
if err != nil {
stdlog.Fatal(err)
}
db, err := dbm.NewGoLevelDB("lite-client-db", dbDir)
if err != nil {
stdlog.Fatal(err)
}
c, err := NewClient(
chainID,
TrustOptions{
Period: 504 * time.Hour, // 21 days
Height: 2,
Hash: header.Hash(),
},
primary,
[]provider.Provider{primary}, // TODO: primary should not be used here
dbs.New(db, chainID),
UpdatePeriod(0),
Logger(log.TestingLogger()),
)
if err != nil {
stdlog.Fatal(err)
}
err = c.Start()
if err != nil {
stdlog.Fatal(err)
}
defer func() {
c.Stop()
c.Cleanup()
}()
_, err = c.VerifyHeaderAtHeight(3, time.Now())
if err != nil {
@@ -78,70 +155,6 @@ func TestExample_Client(t *testing.T) {
// Output: got header 3
}
func TestExample_AutoClient(t *testing.T) {
// give Tendermint time to generate some blocks
time.Sleep(5 * time.Second)
dbDir, err := ioutil.TempDir("", "lite-client-example")
if err != nil {
stdlog.Fatal(err)
}
defer os.RemoveAll(dbDir)
var (
config = rpctest.GetConfig()
chainID = config.ChainID()
)
provider, err := httpp.New(chainID, config.RPC.ListenAddress)
if err != nil {
stdlog.Fatal(err)
}
header, err := provider.SignedHeader(2)
if err != nil {
stdlog.Fatal(err)
}
db, err := dbm.NewGoLevelDB("lite-client-db", dbDir)
if err != nil {
stdlog.Fatal(err)
}
base, err := NewClient(
chainID,
TrustOptions{
Period: 504 * time.Hour, // 21 days
Height: 2,
Hash: header.Hash(),
},
provider,
dbs.New(db, chainID),
)
if err != nil {
stdlog.Fatal(err)
}
base.SetLogger(log.TestingLogger())
c := NewAutoClient(base, 1*time.Second)
defer c.Stop()
select {
case h := <-c.TrustedHeaders():
fmt.Println("got header", h.Height)
// Output: got header 3
case err := <-c.Errs():
switch errors.Cause(err).(type) {
case ErrOldHeaderExpired:
// reobtain trust height and hash
stdlog.Fatal(err)
default:
// try with another full node
stdlog.Fatal(err)
}
}
}
func TestMain(m *testing.M) {
// start a tendermint node (and kvstore) in the background to test against
app := kvstore.NewApplication()
+12
View File
@@ -0,0 +1,12 @@
package provider
import "errors"
var (
// ErrSignedHeaderNotFound is returned when a provider can't find the
// requested header.
ErrSignedHeaderNotFound = errors.New("signed header not found")
// ErrValidatorSetNotFound is returned when a provider can't find the
// requested validator set.
ErrValidatorSetNotFound = errors.New("validator set not found")
)
+15
View File
@@ -2,6 +2,7 @@ package http
import (
"fmt"
"strings"
"github.com/tendermint/tendermint/lite2/provider"
rpcclient "github.com/tendermint/tendermint/rpc/client"
@@ -12,6 +13,8 @@ import (
type SignStatusClient interface {
rpcclient.SignClient
rpcclient.StatusClient
// Remote returns the remote network address in a string form.
Remote() string
}
// http provider uses an RPC client (or SignStatusClient more generally) to
@@ -44,6 +47,10 @@ func (p *http) ChainID() string {
return p.chainID
}
func (p *http) String() string {
return fmt.Sprintf("http{%s}", p.client.Remote())
}
// SignedHeader fetches a SignedHeader at the given height and checks the
// chainID matches.
func (p *http) SignedHeader(height int64) (*types.SignedHeader, error) {
@@ -54,6 +61,10 @@ func (p *http) SignedHeader(height int64) (*types.SignedHeader, error) {
commit, err := p.client.Commit(h)
if err != nil {
// TODO: standartise errors on the RPC side
if strings.Contains(err.Error(), "height must be less than or equal") {
return nil, provider.ErrSignedHeaderNotFound
}
return nil, err
}
@@ -76,6 +87,10 @@ func (p *http) ValidatorSet(height int64) (*types.ValidatorSet, error) {
const maxPerPage = 100
res, err := p.client.Validators(h, 0, maxPerPage)
if err != nil {
// TODO: standartise errors on the RPC side
if strings.Contains(err.Error(), "height must be less than or equal") {
return nil, provider.ErrValidatorSetNotFound
}
return nil, err
}
+33
View File
@@ -0,0 +1,33 @@
package mock
import (
"errors"
"github.com/tendermint/tendermint/lite2/provider"
"github.com/tendermint/tendermint/types"
)
type deadMock struct {
chainID string
}
// NewDeadMock creates a mock provider that always errors.
func NewDeadMock(chainID string) provider.Provider {
return &deadMock{chainID: chainID}
}
func (p *deadMock) ChainID() string {
return p.chainID
}
func (p *deadMock) String() string {
return "deadMock"
}
func (p *deadMock) SignedHeader(height int64) (*types.SignedHeader, error) {
return nil, errors.New("no response from provider")
}
func (p *deadMock) ValidatorSet(height int64) (*types.ValidatorSet, error) {
return nil, errors.New("no response from provider")
}
+27 -6
View File
@@ -1,21 +1,21 @@
package mock
import (
"github.com/pkg/errors"
"fmt"
"strings"
"github.com/tendermint/tendermint/lite2/provider"
"github.com/tendermint/tendermint/types"
)
// mock provider allows to directly set headers & vals, which can be handy when
// testing.
type mock struct {
chainID string
headers map[int64]*types.SignedHeader
vals map[int64]*types.ValidatorSet
}
// New creates a mock provider.
// New creates a mock provider with the given set of headers and validator
// sets.
func New(chainID string, headers map[int64]*types.SignedHeader, vals map[int64]*types.ValidatorSet) provider.Provider {
return &mock{
chainID: chainID,
@@ -24,20 +24,41 @@ func New(chainID string, headers map[int64]*types.SignedHeader, vals map[int64]*
}
}
// ChainID returns the blockchain ID.
func (p *mock) ChainID() string {
return p.chainID
}
func (p *mock) String() string {
var headers strings.Builder
for _, h := range p.headers {
fmt.Fprintf(&headers, " %d:%X", h.Height, h.Hash())
}
var vals strings.Builder
for _, v := range p.vals {
fmt.Fprintf(&vals, " %X", v.Hash())
}
return fmt.Sprintf("mock{headers: %s, vals: %v}", headers.String(), vals.String())
}
func (p *mock) SignedHeader(height int64) (*types.SignedHeader, error) {
if height == 0 && len(p.headers) > 0 {
return p.headers[int64(len(p.headers))], nil
}
if _, ok := p.headers[height]; ok {
return p.headers[height], nil
}
return nil, errors.Errorf("no header at height %d", height)
return nil, provider.ErrSignedHeaderNotFound
}
func (p *mock) ValidatorSet(height int64) (*types.ValidatorSet, error) {
if height == 0 && len(p.vals) > 0 {
return p.vals[int64(len(p.vals))], nil
}
if _, ok := p.vals[height]; ok {
return p.vals[height], nil
}
return nil, errors.Errorf("no vals for height %d", height)
return nil, provider.ErrValidatorSetNotFound
}
+5 -3
View File
@@ -1,6 +1,8 @@
package provider
import "github.com/tendermint/tendermint/types"
import (
"github.com/tendermint/tendermint/types"
)
// Provider provides information for the lite client to sync (verification
// happens in the client).
@@ -17,7 +19,7 @@ type Provider interface {
// If the provider fails to fetch the SignedHeader due to the IO or other
// issues, an error will be returned.
// If there's no SignedHeader for the given height, ErrSignedHeaderNotFound
// will be returned.
// error is returned.
SignedHeader(height int64) (*types.SignedHeader, error)
// ValidatorSet returns the ValidatorSet that corresponds to height.
@@ -28,6 +30,6 @@ type Provider interface {
// If the provider fails to fetch the ValidatorSet due to the IO or other
// issues, an error will be returned.
// If there's no ValidatorSet for the given height, ErrValidatorSetNotFound
// will be returned.
// error is returned.
ValidatorSet(height int64) (*types.ValidatorSet, error)
}
+5 -4
View File
@@ -26,7 +26,7 @@ func RPCRoutes(c *lrpc.Client) map[string]*rpcserver.RPCFunc {
"block_results": rpcserver.NewRPCFunc(makeBlockResultsFunc(c), "height"),
"commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"),
"tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"),
"tx_search": rpcserver.NewRPCFunc(makeTxSearchFunc(c), "query,prove,page,per_page"),
"tx_search": rpcserver.NewRPCFunc(makeTxSearchFunc(c), "query,prove,page,per_page,order_by"),
"validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height,page,per_page"),
"dump_consensus_state": rpcserver.NewRPCFunc(makeDumpConsensusStateFunc(c), ""),
"consensus_state": rpcserver.NewRPCFunc(makeConsensusStateFunc(c), ""),
@@ -122,11 +122,12 @@ func makeTxFunc(c *lrpc.Client) rpcTxFunc {
}
type rpcTxSearchFunc func(ctx *rpctypes.Context, query string, prove bool,
page, perPage int) (*ctypes.ResultTxSearch, error)
page, perPage int, orderBy string) (*ctypes.ResultTxSearch, error)
func makeTxSearchFunc(c *lrpc.Client) rpcTxSearchFunc {
return func(ctx *rpctypes.Context, query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
return c.TxSearch(query, prove, page, perPage)
return func(ctx *rpctypes.Context, query string, prove bool, page, perPage int, orderBy string) (
*ctypes.ResultTxSearch, error) {
return c.TxSearch(query, prove, page, perPage, orderBy)
}
}
+3 -2
View File
@@ -295,8 +295,9 @@ func (c *Client) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return res, res.Proof.Validate(h.DataHash)
}
func (c *Client) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
return c.next.TxSearch(query, prove, page, perPage)
func (c *Client) TxSearch(query string, prove bool, page, perPage int, orderBy string) (
*ctypes.ResultTxSearch, error) {
return c.next.TxSearch(query, prove, page, perPage, orderBy)
}
func (c *Client) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) {
+45 -16
View File
@@ -5,6 +5,7 @@ import (
"regexp"
"strconv"
"github.com/pkg/errors"
"github.com/tendermint/go-amino"
dbm "github.com/tendermint/tm-db"
@@ -37,20 +38,21 @@ func (s *dbs) SaveSignedHeaderAndNextValidatorSet(sh *types.SignedHeader, valSet
panic("negative or zero height")
}
// TODO: batch
bz, err := s.cdc.MarshalBinaryLengthPrefixed(sh)
shBz, err := s.cdc.MarshalBinaryLengthPrefixed(sh)
if err != nil {
return err
return errors.Wrap(err, "marshalling header")
}
s.db.Set(s.shKey(sh.Height), bz)
bz, err = s.cdc.MarshalBinaryLengthPrefixed(valSet)
valSetBz, err := s.cdc.MarshalBinaryLengthPrefixed(valSet)
if err != nil {
return err
return errors.Wrap(err, "marshalling validator set")
}
s.db.Set(s.vsKey(sh.Height+1), bz)
return nil
b := s.db.NewBatch()
b.Set(s.shKey(sh.Height), shBz)
b.Set(s.vsKey(sh.Height+1), valSetBz)
err = b.WriteSync()
b.Close()
return err
}
// DeleteSignedHeaderAndNextValidatorSet deletes SignedHeader and ValidatorSet
@@ -60,11 +62,12 @@ func (s *dbs) DeleteSignedHeaderAndNextValidatorSet(height int64) error {
panic("negative or zero height")
}
// TODO: batch
s.db.Delete(s.shKey(height))
s.db.Delete(s.vsKey(height + 1))
return nil
b := s.db.NewBatch()
b.Delete(s.shKey(height))
b.Delete(s.vsKey(height + 1))
err := b.WriteSync()
b.Close()
return err
}
// SignedHeader loads SignedHeader at the given height.
@@ -78,7 +81,7 @@ func (s *dbs) SignedHeader(height int64) (*types.SignedHeader, error) {
panic(err)
}
if len(bz) == 0 {
return nil, nil
return nil, store.ErrSignedHeaderNotFound
}
var signedHeader *types.SignedHeader
@@ -97,7 +100,7 @@ func (s *dbs) ValidatorSet(height int64) (*types.ValidatorSet, error) {
panic(err)
}
if len(bz) == 0 {
return nil, nil
return nil, store.ErrValidatorSetNotFound
}
var valSet *types.ValidatorSet
@@ -151,6 +154,32 @@ func (s *dbs) FirstSignedHeaderHeight() (int64, error) {
return -1, nil
}
func (s *dbs) SignedHeaderAfter(height int64) (*types.SignedHeader, error) {
if height <= 0 {
panic("negative or zero height")
}
itr, err := s.db.ReverseIterator(
s.shKey(height+1),
append(s.shKey(1<<63-1), byte(0x00)),
)
if err != nil {
panic(err)
}
defer itr.Close()
for itr.Valid() {
key := itr.Key()
_, existingHeight, ok := parseShKey(key)
if ok {
return s.SignedHeader(existingHeight)
}
itr.Next()
}
panic(fmt.Sprintf("no header after height %d. make sure height is not greater than latest existing height", height))
}
func (s *dbs) shKey(height int64) []byte {
return []byte(fmt.Sprintf("sh/%s/%020d", s.prefix, height))
}
+23 -4
View File
@@ -42,11 +42,11 @@ func Test_SaveSignedHeaderAndNextValidatorSet(t *testing.T) {
// Empty store
h, err := dbStore.SignedHeader(1)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, h)
valSet, err := dbStore.ValidatorSet(2)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, valSet)
// 1 key
@@ -67,10 +67,29 @@ func Test_SaveSignedHeaderAndNextValidatorSet(t *testing.T) {
require.NoError(t, err)
h, err = dbStore.SignedHeader(1)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, h)
valSet, err = dbStore.ValidatorSet(2)
require.NoError(t, err)
require.Error(t, err)
assert.Nil(t, valSet)
}
func Test_SignedHeaderAfter(t *testing.T) {
dbStore := New(dbm.NewMemDB(), "Test_SignedHeaderAfter")
assert.Panics(t, func() {
dbStore.SignedHeaderAfter(0)
dbStore.SignedHeaderAfter(100)
})
err := dbStore.SaveSignedHeaderAndNextValidatorSet(
&types.SignedHeader{Header: &types.Header{Height: 2}}, &types.ValidatorSet{})
require.NoError(t, err)
h, err := dbStore.SignedHeaderAfter(1)
require.NoError(t, err)
if assert.NotNil(t, h) {
assert.EqualValues(t, 2, h.Height)
}
}
+12
View File
@@ -0,0 +1,12 @@
package store
import "errors"
var (
// ErrSignedHeaderNotFound is returned when a store does not have the
// requested header.
ErrSignedHeaderNotFound = errors.New("signed header not found")
// ErrValidatorSetNotFound is returned when a store does not have the
// requested validator set.
ErrValidatorSetNotFound = errors.New("validator set not found")
)
+7 -4
View File
@@ -21,16 +21,14 @@ type Store interface {
//
// height must be > 0.
//
// If the store is empty and the latest SignedHeader is requested, an error
// is returned.
// If SignedHeader is not found, ErrSignedHeaderNotFound is returned.
SignedHeader(height int64) (*types.SignedHeader, error)
// ValidatorSet returns the ValidatorSet that corresponds to height.
//
// height must be > 0.
//
// If the store is empty and the latest ValidatorSet is requested, an error
// is returned.
// If ValidatorSet is not found, ErrValidatorSetNotFound is returned.
ValidatorSet(height int64) (*types.ValidatorSet, error)
// LastSignedHeaderHeight returns the last (newest) SignedHeader height.
@@ -42,4 +40,9 @@ type Store interface {
//
// If the store is empty, -1 and nil error are returned.
FirstSignedHeaderHeight() (int64, error)
// SignedHeaderAfter returns the SignedHeader after the certain height.
//
// height must be > 0 && <= LastSignedHeaderHeight.
SignedHeaderAfter(height int64) (*types.SignedHeader, error)
}
+13
View File
@@ -147,3 +147,16 @@ func (pkz privKeys) GenSignedHeader(chainID string, height int64, bTime time.Tim
Commit: pkz.signHeader(header, first, last),
}
}
// GenSignedHeaderLastBlockID calls genHeader and signHeader and combines them into a SignedHeader.
func (pkz privKeys) GenSignedHeaderLastBlockID(chainID string, height int64, bTime time.Time, txs types.Txs,
valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int,
lastBlockID types.BlockID) *types.SignedHeader {
header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash)
header.LastBlockID = lastBlockID
return &types.SignedHeader{
Header: header,
Commit: pkz.signHeader(header, first, last),
}
}
+1 -81
View File
@@ -1,83 +1,3 @@
# Local Cluster with Docker Compose
DEPRECATED!
See the [docs](https://tendermint.com/docs/networks/docker-compose.html).
## Requirements
- [Install tendermint](/docs/install.md)
- [Install docker](https://docs.docker.com/engine/installation/)
- [Install docker-compose](https://docs.docker.com/compose/install/)
## Build
Build the `tendermint` binary and the `tendermint/localnode` docker image.
Note the binary will be mounted into the container so it can be updated without
rebuilding the image.
```
cd $GOPATH/src/github.com/tendermint/tendermint
# Build the linux binary in ./build
make build-linux
# Build tendermint/localnode image
make build-docker-localnode
```
## Run a testnet
To start a 4 node testnet run:
```
make localnet-start
```
The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the host.
This file creates a 4-node network using the localnode image.
The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively.
To update the binary, just rebuild it and restart the nodes:
```
make build-linux
make localnet-stop
make localnet-start
```
## Configuration
The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `tendermint testnet` command.
The `./build` directory is mounted to the `/tendermint` mount point to attach the binary and config files to the container.
For instance, to create a single node testnet:
```
cd $GOPATH/src/github.com/tendermint/tendermint
# Clear the build folder
rm -rf ./build
# Build binary
make build-linux
# Create configuration
docker run -e LOG="stdout" -v `pwd`/build:/tendermint tendermint/localnode testnet --o . --v 1
#Run the node
docker run -v `pwd`/build:/tendermint tendermint/localnode
```
## Logging
Log is saved under the attached volume, in the `tendermint.log` file. If the `LOG` environment variable is set to `stdout` at start, the log is not saved, but printed on the screen.
## Special binaries
If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume.
See the [docs](https://docs.tendermint.com/master/networks/docker-compose.html).
+10 -1
View File
@@ -948,7 +948,16 @@ func (n *Node) startRPC() ([]net.Listener, error) {
grpcListenAddr := n.config.RPC.GRPCListenAddress
if grpcListenAddr != "" {
config := rpcserver.DefaultConfig()
config.MaxOpenConnections = n.config.RPC.MaxOpenConnections
config.MaxBodyBytes = n.config.RPC.MaxBodyBytes
config.MaxHeaderBytes = n.config.RPC.MaxHeaderBytes
// NOTE: GRPCMaxOpenConnections is used, not MaxOpenConnections
config.MaxOpenConnections = n.config.RPC.GRPCMaxOpenConnections
// If necessary adjust global WriteTimeout to ensure it's greater than
// TimeoutBroadcastTxCommit.
// See https://github.com/tendermint/tendermint/issues/3435
if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit {
config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second
}
listener, err := rpcserver.Listen(grpcListenAddr, config)
if err != nil {
return nil, err
+5 -5
View File
@@ -4,8 +4,8 @@ The p2p package provides an abstraction around peer-to-peer communication.
Docs:
- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/connection.md) for details on how connections and multiplexing work
- [Peer](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange
- [Node](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/node.md) for details about different types of nodes and how they should work
- [Pex](https://github.com/tendermint/tendermint/blob/master/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange
- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/config.md) for details on some config option
- [Connection](https://docs.tendermint.com/master/spec/p2p/connection.html) for details on how connections and multiplexing work
- [Peer](https://docs.tendermint.com/master/spec/p2p/node.html) for details on peer ID, handshakes, and peer exchange
- [Node](https://docs.tendermint.com/master/spec/p2p/node.html) for details about different types of nodes and how they should work
- [Pex](https://docs.tendermint.com/master/spec/reactors/pex/pex.html) for details on peer discovery and exchange
- [Config](https://docs.tendermint.com/master/spec/p2p/config.html) for details on some config option
+1 -1
View File
@@ -48,7 +48,7 @@ func NewNetAddress(id ID, addr net.Addr) *NetAddress {
if flag.Lookup("test.v") == nil { // normal run
panic(fmt.Sprintf("Only TCPAddrs are supported. Got: %v", addr))
} else { // in testing
netAddr := NewNetAddressIPPort(net.IP("0.0.0.0"), 0)
netAddr := NewNetAddressIPPort(net.IP("127.0.0.1"), 0)
netAddr.ID = id
return netAddr
}
+9 -1
View File
@@ -123,10 +123,16 @@ func NewHTTPWithClient(remote, wsEndpoint string, client *http.Client) (*HTTP, e
var _ Client = (*HTTP)(nil)
// SetLogger sets a logger.
func (c *HTTP) SetLogger(l log.Logger) {
c.WSEvents.SetLogger(l)
}
// Remote returns the remote network address in a string form.
func (c *HTTP) Remote() string {
return c.remote
}
// NewBatch creates a new batch client for this HTTP client.
func (c *HTTP) NewBatch() *BatchHTTP {
rpcBatch := c.rpc.NewRequestBatch()
@@ -348,13 +354,15 @@ func (c *baseRPCClient) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return result, nil
}
func (c *baseRPCClient) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
func (c *baseRPCClient) TxSearch(query string, prove bool, page, perPage int, orderBy string) (
*ctypes.ResultTxSearch, error) {
result := new(ctypes.ResultTxSearch)
params := map[string]interface{}{
"query": query,
"prove": prove,
"page": page,
"per_page": perPage,
"order_by": orderBy,
}
_, err := c.caller.Call("tx_search", params, result)
if err != nil {
+1 -1
View File
@@ -69,7 +69,7 @@ type SignClient interface {
Commit(height *int64) (*ctypes.ResultCommit, error)
Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error)
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error)
TxSearch(query string, prove bool, page, perPage int, orderBy string) (*ctypes.ResultTxSearch, error)
}
// HistoryClient provides access to data from genesis to now in large chunks.
+3 -2
View File
@@ -160,8 +160,9 @@ func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return core.Tx(c.ctx, hash, prove)
}
func (c *Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
return core.TxSearch(c.ctx, query, prove, page, perPage)
func (c *Local) TxSearch(query string, prove bool, page, perPage int, orderBy string) (
*ctypes.ResultTxSearch, error) {
return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
}
func (c *Local) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
+33 -13
View File
@@ -418,7 +418,7 @@ func TestTxSearch(t *testing.T) {
c := getHTTPClient()
_, _, tx := MakeTxKV()
bres, err := c.BroadcastTxCommit(tx)
require.Nil(t, err, "%+v", err)
require.Nil(t, err)
txHeight := bres.Height
txHash := bres.Hash
@@ -430,8 +430,8 @@ func TestTxSearch(t *testing.T) {
// now we query for the tx.
// since there's only one tx, we know index=0.
result, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true, 1, 30)
require.Nil(t, err, "%+v", err)
result, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true, 1, 30, "asc")
require.Nil(t, err)
require.Len(t, result.Txs, 1)
ptx := result.Txs[0]
@@ -448,33 +448,53 @@ func TestTxSearch(t *testing.T) {
}
// query by height
result, err = c.TxSearch(fmt.Sprintf("tx.height=%d", txHeight), true, 1, 30)
require.Nil(t, err, "%+v", err)
result, err = c.TxSearch(fmt.Sprintf("tx.height=%d", txHeight), true, 1, 30, "asc")
require.Nil(t, err)
require.Len(t, result.Txs, 1)
// query for non existing tx
result, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false, 1, 30)
require.Nil(t, err, "%+v", err)
result, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false, 1, 30, "asc")
require.Nil(t, err)
require.Len(t, result.Txs, 0)
// query using a compositeKey (see kvstore application)
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko'", false, 1, 30)
require.Nil(t, err, "%+v", err)
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko'", false, 1, 30, "asc")
require.Nil(t, err)
if len(result.Txs) == 0 {
t.Fatal("expected a lot of transactions")
}
// query using a compositeKey (see kvstore application) and height
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko' AND tx.height<10000", true, 1, 30)
require.Nil(t, err, "%+v", err)
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko' AND tx.height<10000", true, 1, 30, "asc")
require.Nil(t, err)
if len(result.Txs) == 0 {
t.Fatal("expected a lot of transactions")
}
// query a non existing tx with page 1 and txsPerPage 1
result, err = c.TxSearch("app.creator='Cosmoshi Neetowoko'", true, 1, 1)
require.Nil(t, err, "%+v", err)
result, err = c.TxSearch("app.creator='Cosmoshi Neetowoko'", true, 1, 1, "asc")
require.Nil(t, err)
require.Len(t, result.Txs, 0)
// broadcast another transaction to make sure we have at least two.
_, _, tx2 := MakeTxKV()
_, err = c.BroadcastTxCommit(tx2)
require.Nil(t, err)
// chech sorting
result, err = c.TxSearch(fmt.Sprintf("tx.height >= 1"), false, 1, 30, "asc")
require.Nil(t, err)
for k := 0; k < len(result.Txs)-1; k++ {
require.LessOrEqual(t, result.Txs[k].Height, result.Txs[k+1].Height)
require.LessOrEqual(t, result.Txs[k].Index, result.Txs[k+1].Index)
}
result, err = c.TxSearch(fmt.Sprintf("tx.height >= 1"), false, 1, 30, "desc")
require.Nil(t, err)
for k := 0; k < len(result.Txs)-1; k++ {
require.GreaterOrEqual(t, result.Txs[k].Height, result.Txs[k+1].Height)
require.GreaterOrEqual(t, result.Txs[k].Index, result.Txs[k+1].Index)
}
}
}
+2 -2
View File
@@ -9,7 +9,7 @@ import (
)
// ABCIQuery queries the application for some information.
// More: https://tendermint.com/rpc/#/ABCI/abci_query
// More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_query
func ABCIQuery(
ctx *rpctypes.Context,
path string,
@@ -31,7 +31,7 @@ func ABCIQuery(
}
// ABCIInfo gets some info about the application.
// More: https://tendermint.com/rpc/#/ABCI/abci_info
// More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_info
func ABCIInfo(ctx *rpctypes.Context) (*ctypes.ResultABCIInfo, error) {
resInfo, err := proxyAppQuery.InfoSync(proxy.RequestInfo)
if err != nil {
+19 -11
View File
@@ -12,9 +12,8 @@ import (
// BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
// Block headers are returned in descending order (highest first).
// More: https://tendermint.com/rpc/#/Info/blockchain
// More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
// maximum 20 block metas
const limit int64 = 20
var err error
@@ -68,7 +67,7 @@ func filterMinMax(height, min, max, limit int64) (int64, int64, error) {
// Block gets block at a given height.
// If no height is provided, it will fetch the latest block.
// More: https://tendermint.com/rpc/#/Info/block
// More: https://docs.tendermint.com/master/rpc/#/Info/block
func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
@@ -76,24 +75,29 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error)
return nil, err
}
blockMeta := blockStore.LoadBlockMeta(height)
block := blockStore.LoadBlock(height)
blockMeta := blockStore.LoadBlockMeta(height)
if blockMeta == nil {
return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil
}
return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
}
// BlockByHash gets block by hash.
// More: https://tendermint.com/rpc/#/Info/block_by_hash
// More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) {
block := blockStore.LoadBlockByHash(hash)
height := block.Height
blockMeta := blockStore.LoadBlockMeta(height)
if block == nil {
return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
}
// If block is not nil, then blockMeta can't be nil.
blockMeta := blockStore.LoadBlockMeta(block.Height)
return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
}
// Commit gets block commit at a given height.
// If no height is provided, it will fetch the commit for the latest block.
// More: https://tendermint.com/rpc/#/Info/commit
// More: https://docs.tendermint.com/master/rpc/#/Info/commit
func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
@@ -101,7 +105,11 @@ func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, erro
return nil, err
}
header := blockStore.LoadBlockMeta(height).Header
blockMeta := blockStore.LoadBlockMeta(height)
if blockMeta == nil {
return nil, nil
}
header := blockMeta.Header
// If the next block has not been committed yet,
// use a non-canonical commit
@@ -121,7 +129,7 @@ func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, erro
// Results are for the height of the block containing the txs.
// Thus response.results.deliver_tx[5] is the results of executing
// getBlock(h).Txs[5]
// More: https://tendermint.com/rpc/#/Info/block_results
// More: https://docs.tendermint.com/master/rpc/#/Info/block_results
func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
storeHeight := blockStore.Height()
height, err := getHeight(storeHeight, heightPtr)
+4 -4
View File
@@ -13,7 +13,7 @@ import (
// If no height is provided, it will fetch the current validator set.
// Note the validators are sorted by their address - this is the canonical
// order for the validators in the set as used in computing their Merkle root.
// More: https://tendermint.com/rpc/#/Info/validators
// More: https://docs.tendermint.com/master/rpc/#/Info/validators
func Validators(ctx *rpctypes.Context, heightPtr *int64, page, perPage int) (*ctypes.ResultValidators, error) {
// The latest validator that we know is the
// NextValidator of the last block.
@@ -46,7 +46,7 @@ func Validators(ctx *rpctypes.Context, heightPtr *int64, page, perPage int) (*ct
// DumpConsensusState dumps consensus state.
// UNSTABLE
// More: https://tendermint.com/rpc/#/Info/dump_consensus_state
// More: https://docs.tendermint.com/master/rpc/#/Info/dump_consensus_state
func DumpConsensusState(ctx *rpctypes.Context) (*ctypes.ResultDumpConsensusState, error) {
// Get Peer consensus states.
peers := p2pPeers.Peers().List()
@@ -79,7 +79,7 @@ func DumpConsensusState(ctx *rpctypes.Context) (*ctypes.ResultDumpConsensusState
// ConsensusState returns a concise summary of the consensus state.
// UNSTABLE
// More: https://tendermint.com/rpc/#/Info/consensus_state
// More: https://docs.tendermint.com/master/rpc/#/Info/consensus_state
func ConsensusState(ctx *rpctypes.Context) (*ctypes.ResultConsensusState, error) {
// Get self round state.
bz, err := consensusState.GetRoundStateSimpleJSON()
@@ -88,7 +88,7 @@ func ConsensusState(ctx *rpctypes.Context) (*ctypes.ResultConsensusState, error)
// ConsensusParams gets the consensus parameters at the given block height.
// If no height is provided, it will fetch the current consensus params.
// More: https://tendermint.com/rpc/#/Info/consensus_params
// More: https://docs.tendermint.com/master/rpc/#/Info/consensus_params
func ConsensusParams(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultConsensusParams, error) {
height := consensusState.GetState().LastBlockHeight + 1
height, err := getHeight(height, heightPtr)
+3 -3
View File
@@ -13,7 +13,7 @@ import (
)
// Subscribe for events via WebSocket.
// More: https://tendermint.com/rpc/#/Websocket/subscribe
// More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe
func Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
addr := ctx.RemoteAddr()
@@ -72,7 +72,7 @@ func Subscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, er
}
// Unsubscribe from events via WebSocket.
// More: https://tendermint.com/rpc/#/Websocket/unsubscribe
// More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe
func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
addr := ctx.RemoteAddr()
logger.Info("Unsubscribe from query", "remote", addr, "query", query)
@@ -88,7 +88,7 @@ func Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe
}
// UnsubscribeAll from all events via WebSocket.
// More: https://tendermint.com/rpc/#/Websocket/unsubscribe_all
// More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe_all
func UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
addr := ctx.RemoteAddr()
logger.Info("Unsubscribe from all", "remote", addr)
+1 -1
View File
@@ -7,7 +7,7 @@ import (
)
// BroadcastEvidence broadcasts evidence of the misbehavior.
// More: https://tendermint.com/rpc/#/Info/broadcast_evidence
// More: https://docs.tendermint.com/master/rpc/#/Info/broadcast_evidence
func BroadcastEvidence(ctx *rpctypes.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
err := evidencePool.AddEvidence(ev)
if err != nil {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
// Health gets node health. Returns empty result (200 OK) on success, no
// response - in case of an error.
// More: https://tendermint.com/rpc/#/Info/health
// More: https://docs.tendermint.com/master/rpc/#/Info/health
func Health(ctx *rpctypes.Context) (*ctypes.ResultHealth, error) {
return &ctypes.ResultHealth{}, nil
}
+5 -5
View File
@@ -19,7 +19,7 @@ import (
// BroadcastTxAsync returns right away, with no response. Does not wait for
// CheckTx nor DeliverTx results.
// More: https://tendermint.com/rpc/#/Tx/broadcast_tx_async
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async
func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
err := mempool.CheckTx(tx, nil, mempl.TxInfo{})
@@ -31,7 +31,7 @@ func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadca
// BroadcastTxSync returns with the response from CheckTx. Does not wait for
// DeliverTx result.
// More: https://tendermint.com/rpc/#/Tx/broadcast_tx_sync
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync
func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
resCh := make(chan *abci.Response, 1)
err := mempool.CheckTx(tx, func(res *abci.Response) {
@@ -51,7 +51,7 @@ func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcas
}
// BroadcastTxCommit returns with the responses from CheckTx and DeliverTx.
// More: https://tendermint.com/rpc/#/Tx/broadcast_tx_commit
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit
func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
subscriber := ctx.RemoteAddr()
@@ -129,7 +129,7 @@ func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadc
// UnconfirmedTxs gets unconfirmed transactions (maximum ?limit entries)
// including their number.
// More: https://tendermint.com/rpc/#/Info/unconfirmed_txs
// More: https://docs.tendermint.com/master/rpc/#/Info/unconfirmed_txs
func UnconfirmedTxs(ctx *rpctypes.Context, limit int) (*ctypes.ResultUnconfirmedTxs, error) {
// reuse per_page validator
limit = validatePerPage(limit)
@@ -143,7 +143,7 @@ func UnconfirmedTxs(ctx *rpctypes.Context, limit int) (*ctypes.ResultUnconfirmed
}
// NumUnconfirmedTxs gets number of unconfirmed transactions.
// More: https://tendermint.com/rpc/#/Info/num_unconfirmed_txs
// More: https://docs.tendermint.com/master/rpc/#/Info/num_unconfirmed_txs
func NumUnconfirmedTxs(ctx *rpctypes.Context) (*ctypes.ResultUnconfirmedTxs, error) {
return &ctypes.ResultUnconfirmedTxs{
Count: mempool.Size(),
+2 -2
View File
@@ -11,7 +11,7 @@ import (
)
// NetInfo returns network info.
// More: https://tendermint.com/rpc/#/Info/net_info
// More: https://docs.tendermint.com/master/rpc/#/Info/net_info
func NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) {
peersList := p2pPeers.Peers().List()
peers := make([]ctypes.Peer, 0, len(peersList))
@@ -69,7 +69,7 @@ func UnsafeDialPeers(ctx *rpctypes.Context, peers []string, persistent bool) (*c
}
// Genesis returns genesis file.
// More: https://tendermint.com/rpc/#/Info/genesis
// More: https://docs.tendermint.com/master/rpc/#/Info/genesis
func Genesis(ctx *rpctypes.Context) (*ctypes.ResultGenesis, error) {
return &ctypes.ResultGenesis{Genesis: genDoc}, nil
}
+1 -1
View File
@@ -23,7 +23,7 @@ var Routes = map[string]*rpc.RPCFunc{
"block_results": rpc.NewRPCFunc(BlockResults, "height"),
"commit": rpc.NewRPCFunc(Commit, "height"),
"tx": rpc.NewRPCFunc(Tx, "hash,prove"),
"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page"),
"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by"),
"validators": rpc.NewRPCFunc(Validators, "height,page,per_page"),
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""),
"consensus_state": rpc.NewRPCFunc(ConsensusState, ""),
+2 -1
View File
@@ -14,7 +14,7 @@ import (
// Status returns Tendermint status including node info, pubkey, latest block
// hash, app hash, block height and time.
// More: https://tendermint.com/rpc/#/Info/status
// More: https://docs.tendermint.com/master/rpc/#/Info/status
func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) {
var latestHeight int64
if consensusReactor.FastSync() {
@@ -22,6 +22,7 @@ func Status(ctx *rpctypes.Context) (*ctypes.ResultStatus, error) {
} else {
latestHeight = consensusState.GetLastHeight()
}
var (
latestBlockMeta *types.BlockMeta
latestBlockHash tmbytes.HexBytes
+29 -6
View File
@@ -2,9 +2,11 @@ package core
import (
"fmt"
"sort"
"github.com/pkg/errors"
tmmath "github.com/tendermint/tendermint/libs/math"
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
@@ -15,9 +17,8 @@ import (
// Tx allows you to query the transaction results. `nil` could mean the
// transaction is in the mempool, invalidated, or was not sent in the first
// place.
// More: https://tendermint.com/rpc/#/Info/tx
// More: https://docs.tendermint.com/master/rpc/#/Info/tx
func Tx(ctx *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
// if index is disabled, return error
if _, ok := txIndexer.(*null.TxIndex); ok {
return nil, fmt.Errorf("transaction indexing is disabled")
@@ -53,11 +54,12 @@ func Tx(ctx *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error
// TxSearch allows you to query for multiple transactions results. It returns a
// list of transactions (maximum ?per_page entries) and the total count.
// More: https://tendermint.com/rpc/#/Info/tx_search
func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
// More: https://docs.tendermint.com/master/rpc/#/Info/tx_search
func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int, orderBy string) (
*ctypes.ResultTxSearch, error) {
// if index is disabled, return error
if _, ok := txIndexer.(*null.TxIndex); ok {
return nil, fmt.Errorf("transaction indexing is disabled")
return nil, errors.New("transaction indexing is disabled")
}
q, err := tmquery.New(query)
@@ -101,5 +103,26 @@ func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int
}
}
if len(apiResults) > 1 {
switch orderBy {
case "desc":
sort.Slice(apiResults, func(i, j int) bool {
if apiResults[i].Height == apiResults[j].Height {
return apiResults[i].Index > apiResults[j].Index
}
return apiResults[i].Height > apiResults[j].Height
})
case "asc", "":
sort.Slice(apiResults, func(i, j int) bool {
if apiResults[i].Height == apiResults[j].Height {
return apiResults[i].Index < apiResults[j].Index
}
return apiResults[i].Height < apiResults[j].Height
})
default:
return nil, errors.New("expected order_by to be either `asc` or `desc` or empty")
}
}
return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil
}
+39 -65
View File
@@ -226,29 +226,29 @@ func init() { proto.RegisterFile("rpc/grpc/types.proto", fileDescriptor_15f63baa
func init() { golang_proto.RegisterFile("rpc/grpc/types.proto", fileDescriptor_15f63baabf91876a) }
var fileDescriptor_15f63baabf91876a = []byte{
// 346 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x29, 0x2a, 0x48, 0xd6,
0x4f, 0x07, 0x11, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xc2,
0x25, 0xa9, 0x79, 0x29, 0xa9, 0x45, 0xb9, 0x99, 0x79, 0x25, 0x7a, 0x45, 0x05, 0xc9, 0x7a, 0x20,
0x05, 0x52, 0xba, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xe9, 0xf9,
0xe9, 0xf9, 0xfa, 0x60, 0xb5, 0x49, 0xa5, 0x69, 0x60, 0x1e, 0x98, 0x03, 0x66, 0x41, 0xcc, 0x90,
0x32, 0x47, 0x52, 0x8e, 0x30, 0x0e, 0x99, 0x99, 0x98, 0x94, 0x9c, 0x09, 0xb1, 0x16, 0xd9, 0x72,
0x25, 0x5e, 0x2e, 0xee, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x92, 0x80, 0xcc, 0xbc, 0x74, 0x25,
0x15, 0x2e, 0x21, 0x28, 0xd7, 0xa9, 0x28, 0x3f, 0x31, 0x25, 0x39, 0xb1, 0xb8, 0x24, 0xa4, 0x42,
0x88, 0x8f, 0x8b, 0xa9, 0xa4, 0x42, 0x82, 0x51, 0x81, 0x51, 0x83, 0x27, 0x88, 0xa9, 0xa4, 0x42,
0x89, 0x8f, 0x8b, 0x27, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x15, 0xac, 0x6b, 0x21, 0x23,
0x97, 0x30, 0x4c, 0x00, 0x59, 0x9f, 0x23, 0x17, 0x47, 0x72, 0x46, 0x6a, 0x72, 0x76, 0x3c, 0x54,
0x37, 0xb7, 0x91, 0x9a, 0x1e, 0x92, 0x67, 0x41, 0x4e, 0xd2, 0x83, 0x38, 0x06, 0xa6, 0xdb, 0x19,
0xa4, 0x3c, 0xa4, 0x22, 0x88, 0x3d, 0x19, 0xc2, 0x10, 0x72, 0xe7, 0xe2, 0x4a, 0x49, 0xcd, 0xc9,
0x2c, 0x4b, 0x2d, 0x02, 0x19, 0xc2, 0x04, 0x36, 0x44, 0x83, 0x80, 0x21, 0x2e, 0x10, 0x0d, 0x21,
0x15, 0x41, 0x9c, 0x29, 0x30, 0xa6, 0xd1, 0x5e, 0x46, 0x2e, 0x1e, 0xb8, 0xdb, 0x1c, 0x03, 0x3c,
0x85, 0xbc, 0xb9, 0x58, 0x40, 0x8e, 0x17, 0x52, 0xd0, 0xc3, 0x12, 0xfe, 0x7a, 0x48, 0x81, 0x22,
0xa5, 0x88, 0x43, 0x05, 0x22, 0x04, 0x84, 0x12, 0xb8, 0xb8, 0x91, 0x3d, 0xae, 0x8e, 0xcf, 0x4c,
0x24, 0x85, 0x52, 0x1a, 0x78, 0x8d, 0x46, 0x52, 0xe9, 0x14, 0xf8, 0xe3, 0xa1, 0x1c, 0xe3, 0x8a,
0x47, 0x72, 0x8c, 0x3b, 0x1e, 0xc9, 0x31, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3,
0x83, 0x47, 0x72, 0x8c, 0x07, 0x1e, 0xcb, 0x31, 0x46, 0x19, 0x13, 0x8c, 0x7f, 0x58, 0xd2, 0xb3,
0x4e, 0xce, 0x2f, 0x4a, 0x8d, 0x07, 0xb1, 0x92, 0xd8, 0xc0, 0x49, 0xc0, 0x18, 0x10, 0x00, 0x00,
0xff, 0xff, 0xe9, 0x1d, 0x64, 0xc2, 0x97, 0x02, 0x00, 0x00,
// 344 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xb1, 0x4e, 0xf3, 0x30,
0x14, 0x85, 0xe5, 0xea, 0xd7, 0x0f, 0xdc, 0x96, 0x0e, 0x2e, 0x42, 0x28, 0x83, 0x55, 0x2a, 0x54,
0x3a, 0x39, 0x52, 0x19, 0x99, 0x5a, 0x90, 0x10, 0x62, 0xa9, 0xa2, 0x4e, 0x2c, 0x25, 0x75, 0xac,
0x34, 0x82, 0xc6, 0xc6, 0x71, 0x51, 0xfa, 0x38, 0x6c, 0x3c, 0x02, 0x0b, 0x12, 0x23, 0x23, 0x8f,
0x00, 0xe1, 0x25, 0x18, 0x91, 0x93, 0x86, 0x78, 0x80, 0xb2, 0x44, 0x27, 0xd6, 0x39, 0x9f, 0xce,
0xbd, 0xba, 0xb0, 0xa3, 0x24, 0x73, 0x43, 0xf3, 0xd1, 0x4b, 0xc9, 0x13, 0x2a, 0x95, 0xd0, 0x02,
0xb7, 0x34, 0x8f, 0x03, 0xae, 0xe6, 0x51, 0xac, 0xa9, 0x92, 0x8c, 0x1a, 0x83, 0xd3, 0xd5, 0xb3,
0x48, 0x05, 0x13, 0xe9, 0x2b, 0xbd, 0x74, 0x73, 0x9f, 0x1b, 0x8a, 0x50, 0x54, 0xaa, 0x08, 0x3b,
0xbb, 0xfe, 0x94, 0x45, 0x05, 0xce, 0x86, 0x76, 0xb6, 0xa1, 0xee, 0xf1, 0xdb, 0x05, 0x4f, 0xf4,
0x28, 0x8a, 0xc3, 0xce, 0x01, 0xe0, 0xd5, 0xef, 0x50, 0x09, 0x3f, 0x60, 0x7e, 0xa2, 0xc7, 0x29,
0x6e, 0x42, 0x4d, 0xa7, 0x7b, 0xa8, 0x8d, 0x7a, 0x0d, 0xaf, 0xa6, 0xd3, 0x4e, 0x13, 0x1a, 0x1e,
0x4f, 0xa4, 0x88, 0x13, 0x9e, 0xa7, 0xee, 0x11, 0xb4, 0xca, 0x07, 0x3b, 0x37, 0x80, 0x4d, 0x36,
0xe3, 0xec, 0x7a, 0xb2, 0x4a, 0xd7, 0xfb, 0x5d, 0x6a, 0x0d, 0x61, 0x2a, 0xd1, 0xa2, 0x4c, 0x99,
0x3e, 0x31, 0xf6, 0x71, 0xea, 0x6d, 0xb0, 0x42, 0xe0, 0x33, 0x80, 0x80, 0xdf, 0x44, 0x77, 0x5c,
0x19, 0x48, 0x2d, 0x87, 0xf4, 0xfe, 0x80, 0x9c, 0x16, 0x81, 0x71, 0xea, 0x6d, 0x05, 0xa5, 0xec,
0x3f, 0x21, 0x68, 0x7c, 0x77, 0x1b, 0x8c, 0xce, 0xf1, 0x05, 0xfc, 0x33, 0xe5, 0x71, 0x9b, 0xfe,
0xb0, 0x57, 0x6a, 0x2d, 0xc5, 0xd9, 0xff, 0xc5, 0x51, 0x6d, 0x00, 0x5f, 0x41, 0xdd, 0x1e, 0xfc,
0x70, 0x1d, 0xd3, 0x32, 0x3a, 0xbd, 0xb5, 0x68, 0xcb, 0x39, 0x1c, 0x7d, 0xbe, 0x13, 0xf4, 0x90,
0x11, 0xf4, 0x98, 0x11, 0xf4, 0x92, 0x11, 0xf4, 0x9a, 0x11, 0xf4, 0x96, 0x11, 0xf4, 0xfc, 0x41,
0xd0, 0x65, 0x3f, 0x8c, 0xf4, 0x6c, 0x31, 0xa5, 0x4c, 0xcc, 0xdd, 0x8a, 0x68, 0xcb, 0xf2, 0xa4,
0x8e, 0x99, 0x50, 0xdc, 0x88, 0xe9, 0xff, 0xfc, 0x02, 0x8e, 0xbe, 0x02, 0x00, 0x00, 0xff, 0xff,
0x30, 0xfd, 0xaa, 0xac, 0x6e, 0x02, 0x00, 0x00,
}
func (this *RequestPing) Equal(that interface{}) bool {
@@ -1129,6 +1129,7 @@ func (m *ResponseBroadcastTx) Unmarshal(dAtA []byte) error {
func skipTypes(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -1160,10 +1161,8 @@ func skipTypes(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -1184,55 +1183,30 @@ func skipTypes(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthTypes
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthTypes
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTypes
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipTypes(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthTypes
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupTypes
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthTypes
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupTypes = fmt.Errorf("proto: unexpected end of group")
)
+14 -16
View File
@@ -1,26 +1,25 @@
syntax = "proto3";
package tendermint.rpc.grpc;
option go_package = "github.com/tendermint/tendermint/rpc/grpc;core_grpc";
option go_package = "github.com/tendermint/tendermint/rpc/grpc;coregrpc";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "github.com/tendermint/tendermint/abci/types/types.proto";
import "third_party/proto/gogoproto/gogo.proto";
import "abci/types/types.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.goproto_registration) = true;
// Generate tests
option (gogoproto.populate_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.testgen_all) = true;
//----------------------------------------
// Message types
//----------------------------------------
// Request types
message RequestPing {
}
message RequestPing {}
message RequestBroadcastTx {
bytes tx = 1;
@@ -29,11 +28,10 @@ message RequestBroadcastTx {
//----------------------------------------
// Response types
message ResponsePing{
}
message ResponsePing {}
message ResponseBroadcastTx{
tendermint.abci.types.ResponseCheckTx check_tx = 1;
message ResponseBroadcastTx {
tendermint.abci.types.ResponseCheckTx check_tx = 1;
tendermint.abci.types.ResponseDeliverTx deliver_tx = 2;
}
@@ -41,6 +39,6 @@ message ResponseBroadcastTx{
// Service Definition
service BroadcastAPI {
rpc Ping(RequestPing) returns (ResponsePing) ;
rpc BroadcastTx(RequestBroadcastTx) returns (ResponseBroadcastTx) ;
rpc Ping(RequestPing) returns (ResponsePing);
rpc BroadcastTx(RequestBroadcastTx) returns (ResponseBroadcastTx);
}
+1 -1
View File
@@ -28,7 +28,7 @@ import (
// Client and Server should work over tcp or unix sockets
const (
tcpAddr = "tcp://0.0.0.0:47768"
tcpAddr = "tcp://127.0.0.1:47768"
unixSocket = "/tmp/rpc_test.sock"
unixAddr = "unix://" + unixSocket
+1 -1
View File
@@ -37,7 +37,7 @@ func main() {
rpcserver.RegisterRPCFuncs(mux, routes, cdc, logger)
config := rpcserver.DefaultConfig()
listener, err := rpcserver.Listen("0.0.0.0:8008", config)
listener, err := rpcserver.Listen("tcp://127.0.0.1:8008", config)
if err != nil {
tmos.Exit(err.Error())
}
+8
View File
@@ -859,6 +859,14 @@ paths:
type: number
default: 30
example: 30
- in: query
name: order_by
description: Order in which transactions are sorted ("asc" or "desc"), by height & index. If empty, default sorting will be still applied.
required: false
schema:
type: string
default: "asc"
example: "asc"
tags:
- Info
description: |
+3 -3
View File
@@ -85,9 +85,9 @@ func randPort() int {
}
func makeAddrs() (string, string, string) {
return fmt.Sprintf("tcp://0.0.0.0:%d", randPort()),
fmt.Sprintf("tcp://0.0.0.0:%d", randPort()),
fmt.Sprintf("tcp://0.0.0.0:%d", randPort())
return fmt.Sprintf("tcp://127.0.0.1:%d", randPort()),
fmt.Sprintf("tcp://127.0.0.1:%d", randPort()),
fmt.Sprintf("tcp://127.0.0.1:%d", randPort())
}
func createConfig() *cfg.Config {
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -eo pipefail
proto_dirs=$(find . -path ./third_party -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq)
for dir in $proto_dirs; do
protoc \
-I. \
--gogo_out=Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration,plugins=grpc,paths=source_relative:. \
$(find "${dir}" -name '*.proto')
done
+8 -15
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/hex"
"fmt"
"sort"
"strconv"
"strings"
"time"
@@ -160,12 +159,14 @@ func (txi *TxIndex) indexEvents(result *types.TxResult, hash []byte, store dbm.S
}
}
// Search performs a search using the given query. It breaks the query into
// conditions (like "tx.height > 5"). For each condition, it queries the DB
// index. One special use cases here: (1) if "tx.hash" is found, it returns tx
// result for it (2) for range queries it is better for the client to provide
// both lower and upper bounds, so we are not performing a full scan. Results
// from querying indexes are then intersected and returned to the caller.
// Search performs a search using the given query.
//
// It breaks the query into conditions (like "tx.height > 5"). For each
// condition, it queries the DB index. One special use cases here: (1) if
// "tx.hash" is found, it returns tx result for it (2) for range queries it is
// better for the client to provide both lower and upper bounds, so we are not
// performing a full scan. Results from querying indexes are then intersected
// and returned to the caller, in no particular order.
func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) {
var hashesInitialized bool
filteredHashes := make(map[string][]byte)
@@ -250,14 +251,6 @@ func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) {
results = append(results, res)
}
// sort by height & index by default
sort.Slice(results, func(i, j int) bool {
if results[i].Height == results[j].Height {
return results[i].Index < results[j].Index
}
return results[i].Height < results[j].Height
})
return results, nil
}
-23
View File
@@ -272,29 +272,6 @@ func TestTxSearchMultipleTxs(t *testing.T) {
assert.NoError(t, err)
require.Len(t, results, 3)
assert.Equal(t, []*types.TxResult{txResult3, txResult2, txResult}, results)
}
func TestIndexAllTags(t *testing.T) {
indexer := NewTxIndex(db.NewMemDB(), IndexAllEvents())
txResult := txResultWithEvents([]abci.Event{
{Type: "account", Attributes: []kv.Pair{{Key: []byte("owner"), Value: []byte("Ivan")}}},
{Type: "account", Attributes: []kv.Pair{{Key: []byte("number"), Value: []byte("1")}}},
})
err := indexer.Index(txResult)
require.NoError(t, err)
results, err := indexer.Search(query.MustParse("account.number >= 1"))
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, []*types.TxResult{txResult}, results)
results, err = indexer.Search(query.MustParse("account.owner = 'Ivan'"))
assert.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, []*types.TxResult{txResult}, results)
}
func txResultWithEvents(events []abci.Event) *types.TxResult {
+4 -4
View File
@@ -36,11 +36,11 @@ function getCode() {
# build grpc client if needed
if [[ "$GRPC_BROADCAST_TX" != "" ]]; then
if [ -f grpc_client ]; then
rm grpc_client
if [ -f test/app/grpc_client ]; then
rm test/app/grpc_client
fi
echo "... building grpc_client"
go build -mod=readonly -o grpc_client grpc_client.go
go build -mod=readonly -o test/app/grpc_client test/app/grpc_client.go
fi
function sendTx() {
@@ -59,7 +59,7 @@ function sendTx() {
RESPONSE=$(echo "$RESPONSE" | jq '.result')
else
RESPONSE=$(./grpc_client "$TX")
RESPONSE=$(./test/app/grpc_client "$TX")
IS_ERR=false
ERROR=""
fi
+5 -7
View File
@@ -22,7 +22,7 @@ function kvstore_over_socket(){
sleep 5
echo "running test"
bash kvstore_test.sh "KVStore over Socket"
bash test/app/kvstore_test.sh "KVStore over Socket"
kill -9 $pid_kvstore $pid_tendermint
}
@@ -40,7 +40,7 @@ function kvstore_over_socket_reorder(){
sleep 5
echo "running test"
bash kvstore_test.sh "KVStore over Socket"
bash test/app/kvstore_test.sh "KVStore over Socket"
kill -9 $pid_kvstore $pid_tendermint
}
@@ -57,7 +57,7 @@ function counter_over_socket() {
sleep 5
echo "running test"
bash counter_test.sh "Counter over Socket"
bash test/app/counter_test.sh "Counter over Socket"
kill -9 $pid_counter $pid_tendermint
}
@@ -73,7 +73,7 @@ function counter_over_grpc() {
sleep 5
echo "running test"
bash counter_test.sh "Counter over GRPC"
bash test/app/counter_test.sh "Counter over GRPC"
kill -9 $pid_counter $pid_tendermint
}
@@ -91,13 +91,11 @@ function counter_over_grpc_grpc() {
sleep 5
echo "running test"
GRPC_BROADCAST_TX=true bash counter_test.sh "Counter over GRPC via GRPC BroadcastTx"
GRPC_BROADCAST_TX=true bash test/app/counter_test.sh "Counter over GRPC via GRPC BroadcastTx"
kill -9 $pid_counter $pid_tendermint
}
cd $GOPATH/src/github.com/tendermint/tendermint/test/app
case "$1" in
"kvstore_over_socket")
kvstore_over_socket

Some files were not shown because too many files have changed in this diff Show More