Compare commits
76 Commits
v0.24.0-rc
...
master
Author | SHA1 | Date |
---|---|---|
Ethan Buchman | 0c9c3292c9 | |
Anton Kaliaev | d16f52eab3 | |
Ethan Buchman | 27ba6e8a42 | |
Ethan Buchman | a8eee4ab28 | |
Ethan Buchman | cd172acee8 | |
Ethan Buchman | 97b43d875a | |
Ethan Buchman | b394bd5b5c | |
Ethan Buchman | f5824bc837 | |
Dev Ojha | 111e627037 | |
Ethan Buchman | ee8b8bbefb | |
Ethan Buchman | dde0936fb8 | |
Ethan Buchman | 2dfde37f44 | |
Zarko Milosevic | f99e4010f2 | |
Alessio Treglia | f11db8c1b0 | |
Zach | 886a83dfb8 | |
Anton Kaliaev | 8d50bb9dad | |
Ethan Buchman | 7b727bf3d0 | |
Aravind | 84b518b8d3 | |
Anton Kaliaev | bd951171db | |
Dev Ojha | 0d6b75bd53 | |
ValarDragon | f76312ffe6 | |
bradyjoestar | 8aad09d9d4 | |
Ethan Buchman | faa3509646 | |
Ethan Buchman | a045c562a2 | |
bradyjoestar | 26aa978456 | |
Anton Kaliaev | aa5495f24e | |
Dev Ojha | c0cdb9d441 | |
Ethan Buchman | 91a8767083 | |
zhangzheng | 3e099f75c7 | |
Alexander Simmerl | bdd01310a0 | |
Alexander Simmerl | be5d68ea4f | |
Anton Kaliaev | 89462c52d9 | |
Zarko Milosevic | 2fbf810cd8 | |
Anton Kaliaev | 64fc8f8157 | |
Anton Kaliaev | e1bda36c6c | |
Anton Kaliaev | ff9d0cdfb6 | |
Anton Kaliaev | 788474d08d | |
Anton Kaliaev | 484194789f | |
Anton Kaliaev | 747797bf3b | |
Anton Kaliaev | 76302c651f | |
Anton Kaliaev | 5bfb9001eb | |
Anton Kaliaev | 38bced2440 | |
Zach | 4fe9906361 | |
Anton Kaliaev | fc7f9bcaf6 | |
Ismail Khoffi | 8ae3334423 | |
zhangzheng | c6c0b52d0c | |
Anton Kaliaev | e3e3c13741 | |
Dev Ojha | 1ea64fc27f | |
Anton Kaliaev | 0e1cd88863 | |
Zach | 33b4617e9a | |
Ethan Buchman | 503de8c9b8 | |
xiaoping | dea4e96f66 | |
Ethan Buchman | a57aae7072 | |
Zach | 0bec20a1e3 | |
Ethan Buchman | 83a7c04bce | |
Ethan Buchman | d419fffe18 | |
Ethan Buchman | c8895dab98 | |
Ethan Buchman | 8b94deca73 | |
Ethan Buchman | 4cd2e40fb1 | |
Anton Kaliaev | 47dc4e6428 | |
Ethan Buchman | 94288006ba | |
Ethan Buchman | 22445a5029 | |
Ethan Buchman | 299d46304d | |
Ethan Buchman | 5106af484f | |
Ethan Buchman | 114c405120 | |
Ethan Buchman | 246a56283a | |
Ethan Buchman | 1144e72c61 | |
Ethan Buchman | 3fd54c5df5 | |
Ethan Buchman | 20c55cffc4 | |
Ethan Buchman | dea34506fb | |
Ethan Buchman | 54fe6ef73c | |
Ethan Buchman | 6fd79d1545 | |
Ethan Buchman | 8fcabe8b05 | |
Ethan Buchman | e0fa827a53 | |
Ethan Buchman | bdf3238710 | |
Ethan Buchman | ed9e00a8a7 |
|
@ -14,6 +14,7 @@ test/p2p/data/
|
|||
test/logs
|
||||
coverage.txt
|
||||
docs/_build
|
||||
docs/dist
|
||||
*.log
|
||||
abci-cli
|
||||
docs/node_modules/
|
||||
|
@ -25,6 +26,8 @@ scripts/cutWALUntil/cutWALUntil
|
|||
.idea/
|
||||
*.iml
|
||||
|
||||
.vscode/
|
||||
|
||||
libs/pubsub/query/fuzz_test/output
|
||||
shunit2
|
||||
|
||||
|
@ -38,4 +41,4 @@ terraform.tfstate
|
|||
terraform.tfstate.backup
|
||||
terraform.tfstate.d
|
||||
|
||||
.vscode
|
||||
.vscode
|
||||
|
|
68
CHANGELOG.md
68
CHANGELOG.md
|
@ -1,5 +1,60 @@
|
|||
# Changelog
|
||||
|
||||
## v0.25.0
|
||||
|
||||
*September 22, 2018*
|
||||
|
||||
Special thanks to external contributors on this release:
|
||||
@scriptionist, @bradyjoestar, @WALL-E
|
||||
|
||||
This release is mostly about the ConsensusParams - removing fields and enforcing MaxGas.
|
||||
It also addresses some issues found via security audit, removes various unused
|
||||
functions from `libs/common`, and implements
|
||||
[ADR-012](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-012-peer-transport.md).
|
||||
|
||||
Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint).
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
* CLI/RPC/Config
|
||||
* [rpc] [\#2391](https://github.com/tendermint/tendermint/issues/2391) /status `result.node_info.other` became a map
|
||||
* [types] [\#2364](https://github.com/tendermint/tendermint/issues/2364) Remove `TxSize` and `BlockGossip` from `ConsensusParams`
|
||||
* Maximum tx size is now set implicitly via the `BlockSize.MaxBytes`
|
||||
* The size of block parts in the consensus is now fixed to 64kB
|
||||
|
||||
* Apps
|
||||
* [mempool] [\#2360](https://github.com/tendermint/tendermint/issues/2360) Mempool tracks the `ResponseCheckTx.GasWanted` and
|
||||
`ConsensusParams.BlockSize.MaxGas` and enforces:
|
||||
- `GasWanted <= MaxGas` for every tx
|
||||
- `(sum of GasWanted in block) <= MaxGas` for block proposal
|
||||
|
||||
* Go API
|
||||
* [libs/common] [\#2431](https://github.com/tendermint/tendermint/issues/2431) Remove Word256 due to lack of use
|
||||
* [libs/common] [\#2452](https://github.com/tendermint/tendermint/issues/2452) Remove the following functions due to lack of use:
|
||||
* byteslice.go: cmn.IsZeros, cmn.RightPadBytes, cmn.LeftPadBytes, cmn.PrefixEndBytes
|
||||
* strings.go: cmn.IsHex, cmn.StripHex
|
||||
* int.go: Uint64Slice, all put/get int64 methods
|
||||
|
||||
FEATURES:
|
||||
- [rpc] [\#2415](https://github.com/tendermint/tendermint/issues/2415) New `/consensus_params?height=X` endpoint to query the consensus
|
||||
params at any height (@scriptonist)
|
||||
- [types] [\#1714](https://github.com/tendermint/tendermint/issues/1714) Add Address to GenesisValidator
|
||||
- [metrics] [\#2337](https://github.com/tendermint/tendermint/issues/2337) `consensus.block_interval_metrics` is now gauge, not histogram (you will be able to see spikes, if any)
|
||||
- [libs] [\#2286](https://github.com/tendermint/tendermint/issues/2286) Panic if `autofile` or `db/fsdb` permissions change from 0600.
|
||||
|
||||
IMPROVEMENTS:
|
||||
- [libs/db] [\#2371](https://github.com/tendermint/tendermint/issues/2371) Output error instead of panic when the given `db_backend` is not initialised (@bradyjoestar)
|
||||
- [mempool] [\#2399](https://github.com/tendermint/tendermint/issues/2399) Make mempool cache a proper LRU (@bradyjoestar)
|
||||
- [p2p] [\#2126](https://github.com/tendermint/tendermint/issues/2126) Introduce PeerTransport interface to improve isolation of concerns
|
||||
- [libs/common] [\#2326](https://github.com/tendermint/tendermint/issues/2326) Service returns ErrNotStarted
|
||||
|
||||
BUG FIXES:
|
||||
- [node] [\#2294](https://github.com/tendermint/tendermint/issues/2294) Delay starting node until Genesis time
|
||||
- [consensus] [\#2048](https://github.com/tendermint/tendermint/issues/2048) Correct peer statistics for marking peer as good
|
||||
- [rpc] [\#2460](https://github.com/tendermint/tendermint/issues/2460) StartHTTPAndTLSServer() now passes StartTLS() errors back to the caller rather than hanging forever.
|
||||
- [p2p] [\#2047](https://github.com/tendermint/tendermint/issues/2047) Accept new connections asynchronously
|
||||
- [tm-bench] [\#2410](https://github.com/tendermint/tendermint/issues/2410) Enforce minimum transaction size (@WALL-E)
|
||||
|
||||
## 0.24.0
|
||||
|
||||
*September 6th, 2018*
|
||||
|
@ -9,9 +64,13 @@ peerlink, Ahmah2009, bluele, b00f.
|
|||
|
||||
This release includes breaking upgrades in the block header,
|
||||
including the long awaited changes for delaying validator set updates by one
|
||||
block. It also fixes enforcement on the max size of blocks, and includes a BFT
|
||||
timestamp in each block that can be safely used by applications. There are also some
|
||||
minor breaking changes to the rpc, config, and ABCI.
|
||||
block to better support light clients.
|
||||
It also fixes enforcement on the maximum size of blocks, and includes a BFT
|
||||
timestamp in each block that can be safely used by applications.
|
||||
There are also some minor breaking changes to the rpc, config, and ABCI.
|
||||
|
||||
See the [UPGRADING.md](UPGRADING.md#v0.24.0) for details on upgrading to the new
|
||||
version.
|
||||
|
||||
From here on, breaking changes will be broken down to better reflect how users
|
||||
are affected by a change.
|
||||
|
@ -20,6 +79,8 @@ A few more breaking changes are in the works - each will come with a clear
|
|||
Architecture Decision Record (ADR) explaining the change. You can review ADRs
|
||||
[here](https://github.com/tendermint/tendermint/tree/develop/docs/architecture)
|
||||
or in the [open Pull Requests](https://github.com/tendermint/tendermint/pulls).
|
||||
You can also check in on the [issues marked as
|
||||
breaking](https://github.com/tendermint/tendermint/issues?q=is%3Aopen+is%3Aissue+label%3Abreaking).
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
|
@ -84,6 +145,7 @@ FEATURES:
|
|||
|
||||
IMPROVEMENTS:
|
||||
- [docs] Lint documentation with `write-good` and `stop-words`.
|
||||
- [docs] [\#2249](https://github.com/tendermint/tendermint/issues/2249) Refactor, deduplicate, and improve the ABCI docs and spec (with thanks to @ttmc).
|
||||
- [scripts] [\#2196](https://github.com/tendermint/tendermint/issues/2196) Added json2wal tool, which is supposed to help our users restore (@bradyjoestar)
|
||||
corrupted WAL files and compose test WAL files (@bradyjoestar)
|
||||
- [mempool] [\#2234](https://github.com/tendermint/tendermint/issues/2234) Now stores txs by hash inside of the cache, to mitigate memory leakage
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Pending
|
||||
|
||||
Special thanks to external contributors with PRs included in this release:
|
||||
Special thanks to external contributors on this release:
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
|
@ -10,11 +10,6 @@ BREAKING CHANGES:
|
|||
|
||||
* Go API
|
||||
|
||||
* Blockchain Protocol
|
||||
|
||||
* P2P Protocol
|
||||
|
||||
|
||||
FEATURES:
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
|
5
Makefile
5
Makefile
|
@ -23,11 +23,14 @@ check: check_tools get_vendor_deps
|
|||
build:
|
||||
CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o build/tendermint ./cmd/tendermint/
|
||||
|
||||
build_c:
|
||||
CGO_ENABLED=1 go build $(BUILD_FLAGS) -tags "$(BUILD_TAGS) gcc" -o build/tendermint ./cmd/tendermint/
|
||||
|
||||
build_race:
|
||||
CGO_ENABLED=0 go build -race $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o build/tendermint ./cmd/tendermint
|
||||
|
||||
install:
|
||||
CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/tendermint
|
||||
CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/tendermint
|
||||
|
||||
########################################
|
||||
### Protobuf
|
||||
|
|
43
README.md
43
README.md
|
@ -8,7 +8,7 @@ Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)) for short.
|
|||
[![API Reference](
|
||||
https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667
|
||||
)](https://godoc.org/github.com/tendermint/tendermint)
|
||||
[![Go version](https://img.shields.io/badge/go-1.9.2-blue.svg)](https://github.com/moovweb/gvm)
|
||||
[![Go version](https://img.shields.io/badge/go-1.10.4-blue.svg)](https://github.com/moovweb/gvm)
|
||||
[![riot.im](https://img.shields.io/badge/riot.im-JOIN%20CHAT-green.svg)](https://riot.im/app/#/room/#tendermint:matrix.org)
|
||||
[![license](https://img.shields.io/github/license/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/blob/master/LICENSE)
|
||||
[![](https://tokei.rs/b1/github/tendermint/tendermint?category=lines)](https://github.com/tendermint/tendermint)
|
||||
|
@ -22,7 +22,10 @@ develop | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/deve
|
|||
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language -
|
||||
and securely replicates it on many machines.
|
||||
|
||||
For protocol details, see [the specification](/docs/spec). For a consensus proof and detailed protocol analysis checkout our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
|
||||
For protocol details, see [the specification](/docs/spec).
|
||||
|
||||
For detailed analysis of the consensus protocol, including safety and liveness proofs,
|
||||
see our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)".
|
||||
|
||||
## A Note on Production Readiness
|
||||
|
||||
|
@ -30,7 +33,7 @@ While Tendermint is being used in production in private, permissioned
|
|||
environments, we are still working actively to harden and audit it in preparation
|
||||
for use in public blockchains, such as the [Cosmos Network](https://cosmos.network/).
|
||||
We are also still making breaking changes to the protocol and the APIs.
|
||||
Thus we tag the releases as *alpha software*.
|
||||
Thus, we tag the releases as *alpha software*.
|
||||
|
||||
In any case, if you intend to run Tendermint in production,
|
||||
please [contact us](https://riot.im/app/#/room/#tendermint:matrix.org) :)
|
||||
|
@ -46,7 +49,7 @@ For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY.
|
|||
|
||||
Requirement|Notes
|
||||
---|---
|
||||
Go version | Go1.9 or higher
|
||||
Go version | Go1.10 or higher
|
||||
|
||||
## Install
|
||||
|
||||
|
@ -54,10 +57,10 @@ See the [install instructions](/docs/introduction/install.md)
|
|||
|
||||
## Quick Start
|
||||
|
||||
- [Single node](/docs/using-tendermint.md)
|
||||
- [Single node](/docs/tendermint-core/using-tendermint.md)
|
||||
- [Local cluster using docker-compose](/networks/local)
|
||||
- [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md)
|
||||
- [Join the public testnet](https://cosmos.network/testnet)
|
||||
- [Join the Cosmos testnet](https://cosmos.network/testnet)
|
||||
|
||||
## Resources
|
||||
|
||||
|
@ -66,30 +69,31 @@ See the [install instructions](/docs/introduction/install.md)
|
|||
For details about the blockchain data structures and the p2p protocols, see the
|
||||
the [Tendermint specification](/docs/spec).
|
||||
|
||||
For details on using the software, [Read The Docs](https://tendermint.readthedocs.io/en/master/).
|
||||
Additional information about some - and eventually all - of the sub-projects below, can be found at Read The Docs.
|
||||
For details on using the software, see the [documentation](/docs/) which is also
|
||||
hosted at: https://tendermint.com/docs/
|
||||
|
||||
### Tools
|
||||
|
||||
Benchmarking and monitoring is provided by `tm-bench` and `tm-monitor`, respectively.
|
||||
Their code is found [here](/tools) and these binaries need to be built seperately.
|
||||
Additional documentation is found [here](/docs/tools).
|
||||
|
||||
### Sub-projects
|
||||
|
||||
* [Amino](http://github.com/tendermint/go-amino), a reflection-based improvement on proto3
|
||||
* [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation
|
||||
|
||||
### Tools
|
||||
* [Deployment, Benchmarking, and Monitoring](http://tendermint.readthedocs.io/projects/tools/en/develop/index.html#tendermint-tools)
|
||||
|
||||
### Applications
|
||||
|
||||
* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework
|
||||
* [Ethermint](http://github.com/tendermint/ethermint); Ethereum on Tendermint
|
||||
* [Many more](https://tendermint.readthedocs.io/en/master/ecosystem.html#abci-applications)
|
||||
* [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint
|
||||
* [Many more](https://tendermint.com/ecosystem)
|
||||
|
||||
### More
|
||||
### Research
|
||||
|
||||
* [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769)
|
||||
* [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf)
|
||||
* [Tendermint Blog](https://blog.cosmos.network/tendermint/home)
|
||||
* [Cosmos Blog](https://blog.cosmos.network)
|
||||
* [Blog](https://blog.cosmos.network/tendermint/home)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -114,6 +118,11 @@ CHANGELOG even if they don't lead to MINOR version bumps:
|
|||
- rpc/client
|
||||
- config
|
||||
- node
|
||||
- libs/bech32
|
||||
- libs/common
|
||||
- libs/db
|
||||
- libs/errors
|
||||
- libs/log
|
||||
|
||||
Exported objects in these packages that are not covered by the versioning scheme
|
||||
are explicitly marked by `// UNSTABLE` in their go doc comment and may change at any
|
||||
|
@ -130,6 +139,8 @@ data into the new chain.
|
|||
However, any bump in the PATCH version should be compatible with existing histories
|
||||
(if not please open an [issue](https://github.com/tendermint/tendermint/issues)).
|
||||
|
||||
For more information on upgrading, see [here](./UPGRADING.md)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please read, understand and adhere to our [code of conduct](CODE_OF_CONDUCT.md).
|
||||
|
|
48
UPGRADING.md
48
UPGRADING.md
|
@ -3,10 +3,17 @@
|
|||
This guide provides steps to be followed when you upgrade your applications to
|
||||
a newer version of Tendermint Core.
|
||||
|
||||
## Upgrading from 0.23.0 to 0.24.0
|
||||
## v0.25.0
|
||||
|
||||
This release has minimal impact.
|
||||
|
||||
If you use GasWanted in ABCI and want to enforce it, set the MaxGas in the genesis file (default is no max).
|
||||
|
||||
## v0.24.0
|
||||
|
||||
New 0.24.0 release contains a lot of changes to the state and types. It's not
|
||||
compatible to the old versions.
|
||||
compatible to the old versions and there is no straight forward way to update
|
||||
old data to be compatible with the new version.
|
||||
|
||||
To reset the state do:
|
||||
|
||||
|
@ -14,10 +21,12 @@ To reset the state do:
|
|||
$ tendermint unsafe_reset_all
|
||||
```
|
||||
|
||||
Here we summarize some other notable changes to be mindful of.
|
||||
|
||||
### Config changes
|
||||
|
||||
`p2p.max_num_peers` was removed in favor of `p2p.max_num_inbound_peers` and
|
||||
`p2p.max_num_outbound_peers`.
|
||||
`p2p.max_num_outbound_peers`.
|
||||
|
||||
```
|
||||
# Maximum number of inbound peers
|
||||
|
@ -28,5 +37,36 @@ max_num_outbound_peers = 10
|
|||
```
|
||||
|
||||
As you can see, the default ratio of inbound/outbound peers is 4/1. The reason
|
||||
as we want it to be easier for new nodes to connect to the network. You can
|
||||
is we want it to be easier for new nodes to connect to the network. You can
|
||||
tweak these parameters to alter the network topology.
|
||||
|
||||
### RPC Changes
|
||||
|
||||
The result of `/commit` used to contain `header` and `commit` fields at the top level. These are now contained under the `signed_header` field.
|
||||
|
||||
### ABCI Changes
|
||||
|
||||
The header has been upgraded and contains new fields, but none of the existing
|
||||
fields were changed, except their order.
|
||||
|
||||
The `Validator` type was split into two, one containing an `Address` and one
|
||||
containing a `PubKey`. When processing `RequestBeginBlock`, use the `Validator`
|
||||
type, which contains just the `Address`. When returning `ResponseEndBlock`, use
|
||||
the `ValidatorUpdate` type, which contains just the `PubKey`.
|
||||
|
||||
### Validator Set Updates
|
||||
|
||||
Validator set updates returned in ResponseEndBlock for height `H` used to take
|
||||
effect immediately at height `H+1`. Now they will be delayed one block, to take
|
||||
effect at height `H+2`. Note this means that the change will be seen by the ABCI
|
||||
app in the `RequestBeginBlock.LastCommitInfo` at block `H+3`. Apps were already
|
||||
required to maintain a map from validator addresses to pubkeys since v0.23 (when
|
||||
pubkeys were removed from RequestBeginBlock), but now they may need to track
|
||||
multiple validator sets at once to accomodate this delay.
|
||||
|
||||
|
||||
### Block Size
|
||||
|
||||
The `ConsensusParams.BlockSize.MaxTxs` was removed in favour of
|
||||
`ConsensusParams.BlockSize.MaxBytes`, which is now enforced. This means blocks
|
||||
are limitted only by byte-size, not by number of transactions.
|
||||
|
|
|
@ -29,10 +29,10 @@ Vagrant.configure("2") do |config|
|
|||
usermod -a -G docker vagrant
|
||||
|
||||
# install go
|
||||
wget -q https://dl.google.com/go/go1.10.1.linux-amd64.tar.gz
|
||||
tar -xvf go1.10.1.linux-amd64.tar.gz
|
||||
wget -q https://dl.google.com/go/go1.11.linux-amd64.tar.gz
|
||||
tar -xvf go1.11.linux-amd64.tar.gz
|
||||
mv go /usr/local
|
||||
rm -f go1.10.1.linux-amd64.tar.gz
|
||||
rm -f go1.11.linux-amd64.tar.gz
|
||||
|
||||
# cleanup
|
||||
apt-get autoremove -y
|
||||
|
|
|
@ -88,7 +88,7 @@ func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
|
|||
}
|
||||
|
||||
func (app *KVStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx {
|
||||
return types.ResponseCheckTx{Code: code.CodeTypeOK}
|
||||
return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
|
||||
}
|
||||
|
||||
func (app *KVStoreApplication) Commit() types.ResponseCommit {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -200,27 +200,21 @@ message ResponseCommit {
|
|||
// that can be adjusted by the abci app
|
||||
message ConsensusParams {
|
||||
BlockSize block_size = 1;
|
||||
TxSize tx_size = 2;
|
||||
BlockGossip block_gossip = 3;
|
||||
EvidenceParams evidence_params = 2;
|
||||
}
|
||||
|
||||
// BlockSize contains limits on the block size.
|
||||
message BlockSize {
|
||||
int32 max_bytes = 1;
|
||||
// Note: must be greater than 0
|
||||
int64 max_bytes = 1;
|
||||
// Note: must be greater or equal to -1
|
||||
int64 max_gas = 2;
|
||||
}
|
||||
|
||||
// TxSize contains limits on the tx size.
|
||||
message TxSize {
|
||||
int32 max_bytes = 1;
|
||||
int64 max_gas = 2;
|
||||
}
|
||||
|
||||
// BlockGossip determine consensus critical
|
||||
// elements of how blocks are gossiped
|
||||
message BlockGossip {
|
||||
// Note: must not be 0
|
||||
int32 block_part_size_bytes = 1;
|
||||
// EvidenceParams contains limits on the evidence.
|
||||
message EvidenceParams {
|
||||
// Note: must be greater than 0
|
||||
int64 max_age = 1;
|
||||
}
|
||||
|
||||
message LastCommitInfo {
|
||||
|
|
|
@ -1534,15 +1534,15 @@ func TestBlockSizeMarshalTo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTxSizeProto(t *testing.T) {
|
||||
func TestEvidenceParamsProto(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, false)
|
||||
p := NewPopulatedEvidenceParams(popr, false)
|
||||
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &TxSize{}
|
||||
msg := &EvidenceParams{}
|
||||
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
|
@ -1565,10 +1565,10 @@ func TestTxSizeProto(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTxSizeMarshalTo(t *testing.T) {
|
||||
func TestEvidenceParamsMarshalTo(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, false)
|
||||
p := NewPopulatedEvidenceParams(popr, false)
|
||||
size := p.Size()
|
||||
dAtA := make([]byte, size)
|
||||
for i := range dAtA {
|
||||
|
@ -1578,63 +1578,7 @@ func TestTxSizeMarshalTo(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &TxSize{}
|
||||
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
for i := range dAtA {
|
||||
dAtA[i] = byte(popr.Intn(256))
|
||||
}
|
||||
if !p.Equal(msg) {
|
||||
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockGossipProto(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, false)
|
||||
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &BlockGossip{}
|
||||
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
littlefuzz := make([]byte, len(dAtA))
|
||||
copy(littlefuzz, dAtA)
|
||||
for i := range dAtA {
|
||||
dAtA[i] = byte(popr.Intn(256))
|
||||
}
|
||||
if !p.Equal(msg) {
|
||||
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
|
||||
}
|
||||
if len(littlefuzz) > 0 {
|
||||
fuzzamount := 100
|
||||
for i := 0; i < fuzzamount; i++ {
|
||||
littlefuzz[popr.Intn(len(littlefuzz))] = byte(popr.Intn(256))
|
||||
littlefuzz = append(littlefuzz, byte(popr.Intn(256)))
|
||||
}
|
||||
// shouldn't panic
|
||||
_ = github_com_gogo_protobuf_proto.Unmarshal(littlefuzz, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockGossipMarshalTo(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, false)
|
||||
size := p.Size()
|
||||
dAtA := make([]byte, size)
|
||||
for i := range dAtA {
|
||||
dAtA[i] = byte(popr.Intn(256))
|
||||
}
|
||||
_, err := p.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &BlockGossip{}
|
||||
msg := &EvidenceParams{}
|
||||
if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
|
@ -2636,34 +2580,16 @@ func TestBlockSizeJSON(t *testing.T) {
|
|||
t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p)
|
||||
}
|
||||
}
|
||||
func TestTxSizeJSON(t *testing.T) {
|
||||
func TestEvidenceParamsJSON(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, true)
|
||||
p := NewPopulatedEvidenceParams(popr, true)
|
||||
marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{}
|
||||
jsondata, err := marshaler.MarshalToString(p)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &TxSize{}
|
||||
err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
if !p.Equal(msg) {
|
||||
t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p)
|
||||
}
|
||||
}
|
||||
func TestBlockGossipJSON(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, true)
|
||||
marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{}
|
||||
jsondata, err := marshaler.MarshalToString(p)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
msg := &BlockGossip{}
|
||||
msg := &EvidenceParams{}
|
||||
err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
|
@ -3590,12 +3516,12 @@ func TestBlockSizeProtoCompactText(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTxSizeProtoText(t *testing.T) {
|
||||
func TestEvidenceParamsProtoText(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, true)
|
||||
p := NewPopulatedEvidenceParams(popr, true)
|
||||
dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p)
|
||||
msg := &TxSize{}
|
||||
msg := &EvidenceParams{}
|
||||
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
|
@ -3604,40 +3530,12 @@ func TestTxSizeProtoText(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTxSizeProtoCompactText(t *testing.T) {
|
||||
func TestEvidenceParamsProtoCompactText(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, true)
|
||||
p := NewPopulatedEvidenceParams(popr, true)
|
||||
dAtA := github_com_gogo_protobuf_proto.CompactTextString(p)
|
||||
msg := &TxSize{}
|
||||
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
if !p.Equal(msg) {
|
||||
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockGossipProtoText(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, true)
|
||||
dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p)
|
||||
msg := &BlockGossip{}
|
||||
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
if !p.Equal(msg) {
|
||||
t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockGossipProtoCompactText(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, true)
|
||||
dAtA := github_com_gogo_protobuf_proto.CompactTextString(p)
|
||||
msg := &BlockGossip{}
|
||||
msg := &EvidenceParams{}
|
||||
if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
|
@ -4492,32 +4390,10 @@ func TestBlockSizeSize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTxSizeSize(t *testing.T) {
|
||||
func TestEvidenceParamsSize(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedTxSize(popr, true)
|
||||
size2 := github_com_gogo_protobuf_proto.Size(p)
|
||||
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
|
||||
if err != nil {
|
||||
t.Fatalf("seed = %d, err = %v", seed, err)
|
||||
}
|
||||
size := p.Size()
|
||||
if len(dAtA) != size {
|
||||
t.Errorf("seed = %d, size %v != marshalled size %v", seed, size, len(dAtA))
|
||||
}
|
||||
if size2 != size {
|
||||
t.Errorf("seed = %d, size %v != before marshal proto.Size %v", seed, size, size2)
|
||||
}
|
||||
size3 := github_com_gogo_protobuf_proto.Size(p)
|
||||
if size3 != size {
|
||||
t.Errorf("seed = %d, size %v != after marshal proto.Size %v", seed, size, size3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockGossipSize(t *testing.T) {
|
||||
seed := time.Now().UnixNano()
|
||||
popr := math_rand.New(math_rand.NewSource(seed))
|
||||
p := NewPopulatedBlockGossip(popr, true)
|
||||
p := NewPopulatedEvidenceParams(popr, true)
|
||||
size2 := github_com_gogo_protobuf_proto.Size(p)
|
||||
dAtA, err := github_com_gogo_protobuf_proto.Marshal(p)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package version
|
||||
|
||||
// NOTE: we should probably be versioning the ABCI and the abci-cli separately
|
||||
import (
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
const Maj = "0"
|
||||
const Min = "12"
|
||||
const Fix = "0"
|
||||
// TODO: eliminate this after some version refactor
|
||||
|
||||
const Version = "0.12.0"
|
||||
const Version = version.ABCIVersion
|
||||
|
|
|
@ -24,7 +24,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) {
|
|||
Network: "SOMENAME",
|
||||
ListenAddr: "SOMEADDR",
|
||||
Version: "SOMEVER",
|
||||
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
||||
Other: p2p.NodeInfoOther{
|
||||
AminoVersion: "SOMESTRING",
|
||||
P2PVersion: "OTHERSTRING",
|
||||
},
|
||||
},
|
||||
SyncInfo: ctypes.SyncInfo{
|
||||
LatestBlockHash: []byte("SOMEBYTES"),
|
||||
|
@ -59,7 +62,10 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) {
|
|||
Network: "SOMENAME",
|
||||
ListenAddr: "SOMEADDR",
|
||||
Version: "SOMEVER",
|
||||
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
||||
Other: p2p.NodeInfoOther{
|
||||
AminoVersion: "SOMESTRING",
|
||||
P2PVersion: "OTHERSTRING",
|
||||
},
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
|
@ -84,7 +90,10 @@ func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
|
|||
Network: "SOMENAME",
|
||||
ListenAddr: "SOMEADDR",
|
||||
Version: "SOMEVER",
|
||||
Other: []string{"SOMESTRING", "OTHERSTRING"},
|
||||
Other: p2p.NodeInfoOther{
|
||||
AminoVersion: "SOMESTRING",
|
||||
P2PVersion: "OTHERSTRING",
|
||||
},
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ FOR_LOOP:
|
|||
didProcessCh <- struct{}{}
|
||||
}
|
||||
|
||||
firstParts := first.MakePartSet(state.ConsensusParams.BlockPartSizeBytes)
|
||||
firstParts := first.MakePartSet(types.BlockPartSizeBytes)
|
||||
firstPartsHeader := firstParts.Header()
|
||||
firstID := types.BlockID{first.Hash(), firstPartsHeader}
|
||||
// Finally, verify the first block using the second's commit
|
||||
|
|
|
@ -42,13 +42,13 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
|
|||
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
||||
|
||||
// Next: we need to set a switch in order for peers to be added in
|
||||
bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig())
|
||||
bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig(), nil)
|
||||
|
||||
// Lastly: let's add some blocks in
|
||||
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
||||
firstBlock := makeBlock(blockHeight, state)
|
||||
secondBlock := makeBlock(blockHeight+1, state)
|
||||
firstParts := firstBlock.MakePartSet(state.ConsensusParams.BlockGossip.BlockPartSizeBytes)
|
||||
firstParts := firstBlock.MakePartSet(types.BlockPartSizeBytes)
|
||||
blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit)
|
||||
}
|
||||
|
||||
|
|
|
@ -58,8 +58,9 @@ func initFilesWithConfig(config *cfg.Config) error {
|
|||
ConsensusParams: types.DefaultConsensusParams(),
|
||||
}
|
||||
genDoc.Validators = []types.GenesisValidator{{
|
||||
PubKey: pv.GetPubKey(),
|
||||
Power: 10,
|
||||
Address: pv.GetPubKey().Address(),
|
||||
PubKey: pv.GetPubKey(),
|
||||
Power: 10,
|
||||
}}
|
||||
|
||||
if err := genDoc.SaveAs(genFile); err != nil {
|
||||
|
|
|
@ -91,9 +91,10 @@ func testnetFiles(cmd *cobra.Command, args []string) error {
|
|||
pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator)
|
||||
pv := privval.LoadFilePV(pvFile)
|
||||
genVals[i] = types.GenesisValidator{
|
||||
PubKey: pv.GetPubKey(),
|
||||
Power: 1,
|
||||
Name: nodeDirName,
|
||||
Address: pv.GetPubKey().Address(),
|
||||
PubKey: pv.GetPubKey(),
|
||||
Power: 1,
|
||||
Name: nodeDirName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ type BaseConfig struct {
|
|||
// and verifying their commits
|
||||
FastSync bool `mapstructure:"fast_sync"`
|
||||
|
||||
// Database backend: leveldb | memdb
|
||||
// Database backend: leveldb | memdb | cleveldb
|
||||
DBBackend string `mapstructure:"db_backend"`
|
||||
|
||||
// Database directory
|
||||
|
@ -587,15 +587,15 @@ type TxIndexConfig struct {
|
|||
// Comma-separated list of tags to index (by default the only tag is "tx.hash")
|
||||
//
|
||||
// You can also index transactions by height by adding "tx.height" tag here.
|
||||
//
|
||||
//
|
||||
// It's recommended to index only a subset of tags due to possible memory
|
||||
// bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||
// transactions.
|
||||
IndexTags string `mapstructure:"index_tags"`
|
||||
|
||||
// When set to true, tells indexer to index all tags (predefined tags:
|
||||
// "tx.hash", "tx.height" and all tags from DeliverTx responses).
|
||||
//
|
||||
// "tx.hash", "tx.height" and all tags from DeliverTx responses).
|
||||
//
|
||||
// Note this may be not desirable (see the comment above). IndexTags has a
|
||||
// precedence over IndexAllTags (i.e. when given both, IndexTags will be
|
||||
// indexed).
|
||||
|
|
|
@ -77,7 +77,7 @@ moniker = "{{ .BaseConfig.Moniker }}"
|
|||
# and verifying their commits
|
||||
fast_sync = {{ .BaseConfig.FastSync }}
|
||||
|
||||
# Database backend: leveldb | memdb
|
||||
# Database backend: leveldb | memdb | cleveldb
|
||||
db_backend = "{{ .BaseConfig.DBBackend }}"
|
||||
|
||||
# Database directory
|
||||
|
|
|
@ -39,7 +39,13 @@ func TestByzantine(t *testing.T) {
|
|||
switches := make([]*p2p.Switch, N)
|
||||
p2pLogger := logger.With("module", "p2p")
|
||||
for i := 0; i < N; i++ {
|
||||
switches[i] = p2p.NewSwitch(config.P2P)
|
||||
switches[i] = p2p.MakeSwitch(
|
||||
config.P2P,
|
||||
i,
|
||||
"foo", "1.0.0",
|
||||
func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
return sw
|
||||
})
|
||||
switches[i].SetLogger(p2pLogger.With("validator", i))
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ func TestMempoolRmBadTx(t *testing.T) {
|
|||
|
||||
// check for the tx
|
||||
for {
|
||||
txs := cs.mempool.ReapMaxBytes(len(txBytes))
|
||||
txs := cs.mempool.ReapMaxBytesMaxGas(int64(len(txBytes)), -1)
|
||||
if len(txs) == 0 {
|
||||
emptyMempoolCh <- struct{}{}
|
||||
return
|
||||
|
|
|
@ -30,7 +30,7 @@ type Metrics struct {
|
|||
ByzantineValidatorsPower metrics.Gauge
|
||||
|
||||
// Time between this and the last block.
|
||||
BlockIntervalSeconds metrics.Histogram
|
||||
BlockIntervalSeconds metrics.Gauge
|
||||
|
||||
// Number of transactions.
|
||||
NumTxs metrics.Gauge
|
||||
|
@ -85,11 +85,10 @@ func PrometheusMetrics() *Metrics {
|
|||
Help: "Total power of the byzantine validators.",
|
||||
}, []string{}),
|
||||
|
||||
BlockIntervalSeconds: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{
|
||||
BlockIntervalSeconds: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||
Subsystem: "consensus",
|
||||
Name: "block_interval_seconds",
|
||||
Help: "Time between this and the last block.",
|
||||
Buckets: []float64{1, 2.5, 5, 10, 60},
|
||||
}, []string{}),
|
||||
|
||||
NumTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||
|
@ -124,7 +123,7 @@ func NopMetrics() *Metrics {
|
|||
ByzantineValidators: discard.NewGauge(),
|
||||
ByzantineValidatorsPower: discard.NewGauge(),
|
||||
|
||||
BlockIntervalSeconds: discard.NewHistogram(),
|
||||
BlockIntervalSeconds: discard.NewGauge(),
|
||||
|
||||
NumTxs: discard.NewGauge(),
|
||||
BlockSizeBytes: discard.NewGauge(),
|
||||
|
|
|
@ -29,6 +29,7 @@ const (
|
|||
maxMsgSize = 1048576 // 1MB; NOTE/TODO: keep in sync with types.PartSet sizes.
|
||||
|
||||
blocksToContributeToBecomeGoodPeer = 10000
|
||||
votesToContributeToBecomeGoodPeer = 10000
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -60,6 +61,9 @@ func NewConsensusReactor(consensusState *ConsensusState, fastSync bool) *Consens
|
|||
func (conR *ConsensusReactor) OnStart() error {
|
||||
conR.Logger.Info("ConsensusReactor ", "fastSync", conR.FastSync())
|
||||
|
||||
// start routine that computes peer statistics for evaluating peer quality
|
||||
go conR.peerStatsRoutine()
|
||||
|
||||
conR.subscribeToBroadcastEvents()
|
||||
|
||||
if !conR.FastSync() {
|
||||
|
@ -258,9 +262,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||
ps.ApplyProposalPOLMessage(msg)
|
||||
case *BlockPartMessage:
|
||||
ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index)
|
||||
if numBlocks := ps.RecordBlockPart(msg); numBlocks%blocksToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(src)
|
||||
}
|
||||
|
||||
conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()}
|
||||
default:
|
||||
conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
|
||||
|
@ -280,9 +282,6 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||
ps.EnsureVoteBitArrays(height, valSize)
|
||||
ps.EnsureVoteBitArrays(height-1, lastCommitSize)
|
||||
ps.SetHasVote(msg.Vote)
|
||||
if blocks := ps.RecordVote(msg.Vote); blocks%blocksToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(src)
|
||||
}
|
||||
|
||||
cs.peerMsgQueue <- msgInfo{msg, src.ID()}
|
||||
|
||||
|
@ -794,6 +793,43 @@ OUTER_LOOP:
|
|||
}
|
||||
}
|
||||
|
||||
func (conR *ConsensusReactor) peerStatsRoutine() {
|
||||
for {
|
||||
if !conR.IsRunning() {
|
||||
conR.Logger.Info("Stopping peerStatsRoutine")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case msg := <-conR.conS.statsMsgQueue:
|
||||
// Get peer
|
||||
peer := conR.Switch.Peers().Get(msg.PeerID)
|
||||
if peer == nil {
|
||||
conR.Logger.Debug("Attempt to update stats for non-existent peer",
|
||||
"peer", msg.PeerID)
|
||||
continue
|
||||
}
|
||||
// Get peer state
|
||||
ps := peer.Get(types.PeerStateKey).(*PeerState)
|
||||
switch msg.Msg.(type) {
|
||||
case *VoteMessage:
|
||||
if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(peer)
|
||||
}
|
||||
case *BlockPartMessage:
|
||||
if numParts := ps.RecordBlockPart(); numParts%blocksToContributeToBecomeGoodPeer == 0 {
|
||||
conR.Switch.MarkPeerAsGood(peer)
|
||||
}
|
||||
}
|
||||
case <-conR.conS.Quit():
|
||||
return
|
||||
|
||||
case <-conR.Quit():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the ConsensusReactor.
|
||||
// NOTE: For now, it is just a hard-coded string to avoid accessing unprotected shared variables.
|
||||
// TODO: improve!
|
||||
|
@ -836,15 +872,13 @@ type PeerState struct {
|
|||
|
||||
// peerStateStats holds internal statistics for a peer.
|
||||
type peerStateStats struct {
|
||||
LastVoteHeight int64 `json:"last_vote_height"`
|
||||
Votes int `json:"votes"`
|
||||
LastBlockPartHeight int64 `json:"last_block_part_height"`
|
||||
BlockParts int `json:"block_parts"`
|
||||
Votes int `json:"votes"`
|
||||
BlockParts int `json:"block_parts"`
|
||||
}
|
||||
|
||||
func (pss peerStateStats) String() string {
|
||||
return fmt.Sprintf("peerStateStats{lvh: %d, votes: %d, lbph: %d, blockParts: %d}",
|
||||
pss.LastVoteHeight, pss.Votes, pss.LastBlockPartHeight, pss.BlockParts)
|
||||
return fmt.Sprintf("peerStateStats{votes: %d, blockParts: %d}",
|
||||
pss.Votes, pss.BlockParts)
|
||||
}
|
||||
|
||||
// NewPeerState returns a new PeerState for the given Peer
|
||||
|
@ -1080,18 +1114,14 @@ func (ps *PeerState) ensureVoteBitArrays(height int64, numValidators int) {
|
|||
}
|
||||
}
|
||||
|
||||
// RecordVote updates internal statistics for this peer by recording the vote.
|
||||
// It returns the total number of votes (1 per block). This essentially means
|
||||
// the number of blocks for which peer has been sending us votes.
|
||||
func (ps *PeerState) RecordVote(vote *types.Vote) int {
|
||||
// RecordVote increments internal votes related statistics for this peer.
|
||||
// It returns the total number of added votes.
|
||||
func (ps *PeerState) RecordVote() int {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
|
||||
if ps.Stats.LastVoteHeight >= vote.Height {
|
||||
return ps.Stats.Votes
|
||||
}
|
||||
ps.Stats.LastVoteHeight = vote.Height
|
||||
ps.Stats.Votes++
|
||||
|
||||
return ps.Stats.Votes
|
||||
}
|
||||
|
||||
|
@ -1104,25 +1134,17 @@ func (ps *PeerState) VotesSent() int {
|
|||
return ps.Stats.Votes
|
||||
}
|
||||
|
||||
// RecordBlockPart updates internal statistics for this peer by recording the
|
||||
// block part. It returns the total number of block parts (1 per block). This
|
||||
// essentially means the number of blocks for which peer has been sending us
|
||||
// block parts.
|
||||
func (ps *PeerState) RecordBlockPart(bp *BlockPartMessage) int {
|
||||
// RecordBlockPart increments internal block part related statistics for this peer.
|
||||
// It returns the total number of added block parts.
|
||||
func (ps *PeerState) RecordBlockPart() int {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
|
||||
if ps.Stats.LastBlockPartHeight >= bp.Height {
|
||||
return ps.Stats.BlockParts
|
||||
}
|
||||
|
||||
ps.Stats.LastBlockPartHeight = bp.Height
|
||||
ps.Stats.BlockParts++
|
||||
return ps.Stats.BlockParts
|
||||
}
|
||||
|
||||
// BlockPartsSent returns the number of blocks for which peer has been sending
|
||||
// us block parts.
|
||||
// BlockPartsSent returns the number of useful block parts the peer has sent us.
|
||||
func (ps *PeerState) BlockPartsSent() int {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
|
|
|
@ -11,20 +11,16 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
abcicli "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
bc "github.com/tendermint/tendermint/blockchain"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
mempl "github.com/tendermint/tendermint/mempool"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
p2pdummy "github.com/tendermint/tendermint/p2p/dummy"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -196,7 +192,7 @@ func newMockEvidencePool(val []byte) *mockEvidencePool {
|
|||
}
|
||||
|
||||
// NOTE: maxBytes is ignored
|
||||
func (m *mockEvidencePool) PendingEvidence(maxBytes int) []types.Evidence {
|
||||
func (m *mockEvidencePool) PendingEvidence(maxBytes int64) []types.Evidence {
|
||||
if m.height > 0 {
|
||||
return m.ev
|
||||
}
|
||||
|
@ -246,110 +242,25 @@ func TestReactorProposalHeartbeats(t *testing.T) {
|
|||
}, css)
|
||||
}
|
||||
|
||||
// Test we record block parts from other peers
|
||||
func TestReactorRecordsBlockParts(t *testing.T) {
|
||||
// create dummy peer
|
||||
peer := p2pdummy.NewPeer()
|
||||
ps := NewPeerState(peer).SetLogger(log.TestingLogger())
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
// Test we record stats about votes and block parts from other peers.
|
||||
func TestReactorRecordsVotesAndBlockParts(t *testing.T) {
|
||||
N := 4
|
||||
css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
|
||||
reactors, eventChans, eventBuses := startConsensusNet(t, css, N)
|
||||
defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses)
|
||||
|
||||
// create reactor
|
||||
css := randConsensusNet(1, "consensus_reactor_records_block_parts_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states
|
||||
reactor.SetEventBus(css[0].eventBus)
|
||||
reactor.SetLogger(log.TestingLogger())
|
||||
sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
reactor.SetSwitch(sw)
|
||||
err := reactor.Start()
|
||||
require.NoError(t, err)
|
||||
defer reactor.Stop()
|
||||
// wait till everyone makes the first new block
|
||||
timeoutWaitGroup(t, N, func(j int) {
|
||||
<-eventChans[j]
|
||||
}, css)
|
||||
|
||||
// 1) new block part
|
||||
parts := types.NewPartSetFromData(cmn.RandBytes(100), 10)
|
||||
msg := &BlockPartMessage{
|
||||
Height: 2,
|
||||
Round: 0,
|
||||
Part: parts.GetPart(0),
|
||||
}
|
||||
bz, err := cdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
// Get peer
|
||||
peer := reactors[1].Switch.Peers().List()[0]
|
||||
// Get peer state
|
||||
ps := peer.Get(types.PeerStateKey).(*PeerState)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should have increased by 1")
|
||||
|
||||
// 2) block part with the same height, but different round
|
||||
msg.Round = 1
|
||||
|
||||
bz, err = cdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||
|
||||
// 3) block part from earlier height
|
||||
msg.Height = 1
|
||||
msg.Round = 0
|
||||
|
||||
bz, err = cdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(DataChannel, peer, bz)
|
||||
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||
}
|
||||
|
||||
// Test we record votes from other peers.
|
||||
func TestReactorRecordsVotes(t *testing.T) {
|
||||
// Create dummy peer.
|
||||
peer := p2pdummy.NewPeer()
|
||||
ps := NewPeerState(peer).SetLogger(log.TestingLogger())
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
|
||||
// Create reactor.
|
||||
css := randConsensusNet(1, "consensus_reactor_records_votes_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states
|
||||
reactor.SetEventBus(css[0].eventBus)
|
||||
reactor.SetLogger(log.TestingLogger())
|
||||
sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw })
|
||||
reactor.SetSwitch(sw)
|
||||
err := reactor.Start()
|
||||
require.NoError(t, err)
|
||||
defer reactor.Stop()
|
||||
_, val := css[0].state.Validators.GetByIndex(0)
|
||||
|
||||
// 1) new vote
|
||||
vote := &types.Vote{
|
||||
ValidatorIndex: 0,
|
||||
ValidatorAddress: val.Address,
|
||||
Height: 2,
|
||||
Round: 0,
|
||||
Timestamp: tmtime.Now(),
|
||||
Type: types.VoteTypePrevote,
|
||||
BlockID: types.BlockID{},
|
||||
}
|
||||
bz, err := cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should have increased by 1")
|
||||
|
||||
// 2) vote with the same height, but different round
|
||||
vote.Round = 1
|
||||
|
||||
bz, err = cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same")
|
||||
|
||||
// 3) vote from earlier height
|
||||
vote.Height = 1
|
||||
vote.Round = 0
|
||||
|
||||
bz, err = cdc.MarshalBinaryBare(&VoteMessage{vote})
|
||||
require.NoError(t, err)
|
||||
|
||||
reactor.Receive(VoteChannel, peer, bz)
|
||||
assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same")
|
||||
assert.Equal(t, true, ps.VotesSent() > 0, "number of votes sent should have increased")
|
||||
assert.Equal(t, true, ps.BlockPartsSent() > 0, "number of votes sent should have increased")
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------
|
||||
|
|
|
@ -298,13 +298,18 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo
|
|||
|
||||
// Create proxyAppConn connection (consensus, mempool, query)
|
||||
clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir())
|
||||
proxyApp := proxy.NewAppConns(clientCreator,
|
||||
NewHandshaker(stateDB, state, blockStore, gdoc))
|
||||
proxyApp := proxy.NewAppConns(clientCreator)
|
||||
err = proxyApp.Start()
|
||||
if err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Error starting proxy app conns: %v", err))
|
||||
}
|
||||
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore, gdoc)
|
||||
err = handshaker.Handshake(proxyApp)
|
||||
if err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Error on handshake: %v", err))
|
||||
}
|
||||
|
||||
eventBus := types.NewEventBus()
|
||||
if err := eventBus.Start(); err != nil {
|
||||
cmn.Exit(fmt.Sprintf("Failed to start event bus: %v", err))
|
||||
|
|
|
@ -102,14 +102,6 @@ func TestWALCrash(t *testing.T) {
|
|||
{"empty block",
|
||||
func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) {},
|
||||
1},
|
||||
{"block with a smaller part size",
|
||||
func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) {
|
||||
// XXX: is there a better way to change BlockPartSizeBytes?
|
||||
cs.state.ConsensusParams.BlockPartSizeBytes = 512
|
||||
sm.SaveState(stateDB, cs.state)
|
||||
go sendTxs(cs, ctx)
|
||||
},
|
||||
1},
|
||||
{"many non-empty blocks",
|
||||
func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) {
|
||||
go sendTxs(cs, ctx)
|
||||
|
@ -359,7 +351,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||
if nBlocks > 0 {
|
||||
// run nBlocks against a new client to build up the app state.
|
||||
// use a throwaway tendermint state
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, nil)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2)
|
||||
stateDB, state, _ := stateAndStore(config, privVal.GetPubKey())
|
||||
buildAppStateFromChain(proxyApp, stateDB, state, chain, nBlocks, mode)
|
||||
}
|
||||
|
@ -367,11 +359,14 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||
// now start the app using the handshake - it should sync
|
||||
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
handshaker := NewHandshaker(stateDB, state, store, genDoc)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2, handshaker)
|
||||
proxyApp := proxy.NewAppConns(clientCreator2)
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
if err := handshaker.Handshake(proxyApp); err != nil {
|
||||
t.Fatalf("Error on abci handshake: %v", err)
|
||||
}
|
||||
|
||||
// get the latest app hash from the app
|
||||
res, err := proxyApp.Query().InfoSync(abci.RequestInfo{Version: ""})
|
||||
|
@ -397,7 +392,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||
}
|
||||
|
||||
func applyBlock(stateDB dbm.DB, st sm.State, blk *types.Block, proxyApp proxy.AppConns) sm.State {
|
||||
testPartSize := st.ConsensusParams.BlockPartSizeBytes
|
||||
testPartSize := types.BlockPartSizeBytes
|
||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
|
||||
|
||||
blkID := types.BlockID{blk.Hash(), blk.MakePartSet(testPartSize).Header()}
|
||||
|
@ -447,7 +442,7 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB,
|
|||
func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, chain []*types.Block, mode uint) sm.State {
|
||||
// run the whole chain against this client to build up the tendermint state
|
||||
clientCreator := proxy.NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(path.Join(config.DBDir(), "1")))
|
||||
proxyApp := proxy.NewAppConns(clientCreator, nil) // sm.NewHandshaker(config, state, store, ReplayLastBlock))
|
||||
proxyApp := proxy.NewAppConns(clientCreator) // sm.NewHandshaker(config, state, store, ReplayLastBlock))
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -620,7 +615,7 @@ func (bs *mockBlockStore) LoadBlock(height int64) *types.Block { return bs.chain
|
|||
func (bs *mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
|
||||
block := bs.chain[height-1]
|
||||
return &types.BlockMeta{
|
||||
BlockID: types.BlockID{block.Hash(), block.MakePartSet(bs.params.BlockPartSizeBytes).Header()},
|
||||
BlockID: types.BlockID{block.Hash(), block.MakePartSet(types.BlockPartSizeBytes).Header()},
|
||||
Header: block.Header,
|
||||
}
|
||||
}
|
||||
|
@ -651,11 +646,14 @@ func TestInitChainUpdateValidators(t *testing.T) {
|
|||
// now start the app using the handshake - it should sync
|
||||
genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile())
|
||||
handshaker := NewHandshaker(stateDB, state, store, genDoc)
|
||||
proxyApp := proxy.NewAppConns(clientCreator, handshaker)
|
||||
proxyApp := proxy.NewAppConns(clientCreator)
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
t.Fatalf("Error starting proxy app connections: %v", err)
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
if err := handshaker.Handshake(proxyApp); err != nil {
|
||||
t.Fatalf("Error on abci handshake: %v", err)
|
||||
}
|
||||
|
||||
// reload the state, check the validator set was updated
|
||||
state = sm.LoadState(stateDB)
|
||||
|
|
|
@ -91,6 +91,10 @@ type ConsensusState struct {
|
|||
internalMsgQueue chan msgInfo
|
||||
timeoutTicker TimeoutTicker
|
||||
|
||||
// information about about added votes and block parts are written on this channel
|
||||
// so statistics can be computed by reactor
|
||||
statsMsgQueue chan msgInfo
|
||||
|
||||
// we use eventBus to trigger msg broadcasts in the reactor,
|
||||
// and to notify external subscribers, eg. through a websocket
|
||||
eventBus *types.EventBus
|
||||
|
@ -141,6 +145,7 @@ func NewConsensusState(
|
|||
peerMsgQueue: make(chan msgInfo, msgQueueSize),
|
||||
internalMsgQueue: make(chan msgInfo, msgQueueSize),
|
||||
timeoutTicker: NewTimeoutTicker(),
|
||||
statsMsgQueue: make(chan msgInfo, msgQueueSize),
|
||||
done: make(chan struct{}),
|
||||
doWALCatchup: true,
|
||||
wal: nilWAL{},
|
||||
|
@ -639,7 +644,11 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
|
|||
err = cs.setProposal(msg.Proposal)
|
||||
case *BlockPartMessage:
|
||||
// if the proposal is complete, we'll enterPrevote or tryFinalizeCommit
|
||||
_, err = cs.addProposalBlockPart(msg, peerID)
|
||||
added, err := cs.addProposalBlockPart(msg, peerID)
|
||||
if added {
|
||||
cs.statsMsgQueue <- mi
|
||||
}
|
||||
|
||||
if err != nil && msg.Round != cs.Round {
|
||||
cs.Logger.Debug("Received block part from wrong round", "height", cs.Height, "csRound", cs.Round, "blockRound", msg.Round)
|
||||
err = nil
|
||||
|
@ -647,7 +656,11 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
|
|||
case *VoteMessage:
|
||||
// attempt to add the vote and dupeout the validator if its a duplicate signature
|
||||
// if the vote gives us a 2/3-any or 2/3-one, we transition
|
||||
err := cs.tryAddVote(msg.Vote, peerID)
|
||||
added, err := cs.tryAddVote(msg.Vote, peerID)
|
||||
if added {
|
||||
cs.statsMsgQueue <- mi
|
||||
}
|
||||
|
||||
if err == ErrAddingVote {
|
||||
// TODO: punish peer
|
||||
// We probably don't want to stop the peer here. The vote does not
|
||||
|
@ -949,24 +962,21 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
|
|||
}
|
||||
|
||||
maxBytes := cs.state.ConsensusParams.BlockSize.MaxBytes
|
||||
maxGas := cs.state.ConsensusParams.BlockSize.MaxGas
|
||||
// bound evidence to 1/10th of the block
|
||||
evidence := cs.evpool.PendingEvidence(maxBytes / 10)
|
||||
evidence := cs.evpool.PendingEvidence(types.MaxEvidenceBytesPerBlock(maxBytes))
|
||||
// Mempool validated transactions
|
||||
txs := cs.mempool.ReapMaxBytes(maxDataBytes(maxBytes, cs.state.Validators.Size(), len(evidence)))
|
||||
txs := cs.mempool.ReapMaxBytesMaxGas(types.MaxDataBytes(
|
||||
maxBytes,
|
||||
cs.state.Validators.Size(),
|
||||
len(evidence),
|
||||
), maxGas)
|
||||
proposerAddr := cs.privValidator.GetAddress()
|
||||
block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr)
|
||||
|
||||
return block, parts
|
||||
}
|
||||
|
||||
func maxDataBytes(maxBytes, valsCount, evidenceCount int) int {
|
||||
return maxBytes -
|
||||
types.MaxAminoOverheadForBlock -
|
||||
types.MaxHeaderBytes -
|
||||
(valsCount * types.MaxVoteBytes) -
|
||||
(evidenceCount * types.MaxEvidenceBytes)
|
||||
}
|
||||
|
||||
// Enter: `timeoutPropose` after entering Propose.
|
||||
// Enter: proposal block and POL is ready.
|
||||
// Enter: any +2/3 prevotes for future round.
|
||||
|
@ -1379,7 +1389,7 @@ func (cs *ConsensusState) recordMetrics(height int64, block *types.Block) {
|
|||
|
||||
if height > 1 {
|
||||
lastBlockMeta := cs.blockStore.LoadBlockMeta(height - 1)
|
||||
cs.metrics.BlockIntervalSeconds.Observe(
|
||||
cs.metrics.BlockIntervalSeconds.Set(
|
||||
block.Time.Sub(lastBlockMeta.Header.Time).Seconds(),
|
||||
)
|
||||
}
|
||||
|
@ -1457,7 +1467,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p
|
|||
int64(cs.state.ConsensusParams.BlockSize.MaxBytes),
|
||||
)
|
||||
if err != nil {
|
||||
return true, err
|
||||
return added, err
|
||||
}
|
||||
// NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal
|
||||
cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash())
|
||||
|
@ -1487,35 +1497,35 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p
|
|||
// If we're waiting on the proposal block...
|
||||
cs.tryFinalizeCommit(height)
|
||||
}
|
||||
return true, nil
|
||||
return added, nil
|
||||
}
|
||||
return added, nil
|
||||
}
|
||||
|
||||
// Attempt to add the vote. if its a duplicate signature, dupeout the validator
|
||||
func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) error {
|
||||
_, err := cs.addVote(vote, peerID)
|
||||
func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) {
|
||||
added, err := cs.addVote(vote, peerID)
|
||||
if err != nil {
|
||||
// If the vote height is off, we'll just ignore it,
|
||||
// But if it's a conflicting sig, add it to the cs.evpool.
|
||||
// If it's otherwise invalid, punish peer.
|
||||
if err == ErrVoteHeightMismatch {
|
||||
return err
|
||||
return added, err
|
||||
} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
|
||||
if bytes.Equal(vote.ValidatorAddress, cs.privValidator.GetAddress()) {
|
||||
cs.Logger.Error("Found conflicting vote from ourselves. Did you unsafe_reset a validator?", "height", vote.Height, "round", vote.Round, "type", vote.Type)
|
||||
return err
|
||||
return added, err
|
||||
}
|
||||
cs.evpool.AddEvidence(voteErr.DuplicateVoteEvidence)
|
||||
return err
|
||||
return added, err
|
||||
} else {
|
||||
// Probably an invalid signature / Bad peer.
|
||||
// Seems this can also err sometimes with "Unexpected step" - perhaps not from a bad peer ?
|
||||
cs.Logger.Error("Error attempting to add vote", "err", err)
|
||||
return ErrAddingVote
|
||||
return added, ErrAddingVote
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return added, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -7,9 +7,13 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cstypes "github.com/tendermint/tendermint/consensus/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||
p2pdummy "github.com/tendermint/tendermint/p2p/dummy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
|
@ -184,7 +188,7 @@ func TestStateBadProposal(t *testing.T) {
|
|||
height, round := cs1.Height, cs1.Round
|
||||
vs2 := vss[1]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
voteCh := subscribe(cs1.eventBus, types.EventQueryVote)
|
||||
|
@ -339,7 +343,7 @@ func TestStateLockNoPOL(t *testing.T) {
|
|||
vs2 := vss[1]
|
||||
height := cs1.Height
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
@ -507,7 +511,7 @@ func TestStateLockPOLRelock(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
@ -622,7 +626,7 @@ func TestStateLockPOLUnlock(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -719,7 +723,7 @@ func TestStateLockPOLSafety1(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -842,7 +846,7 @@ func TestStateLockPOLSafety2(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -1021,7 +1025,7 @@ func TestStateHalt1(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
partSize := types.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
@ -1081,6 +1085,80 @@ func TestStateHalt1(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStateOutputsBlockPartsStats(t *testing.T) {
|
||||
// create dummy peer
|
||||
cs, _ := randConsensusState(1)
|
||||
peer := p2pdummy.NewPeer()
|
||||
|
||||
// 1) new block part
|
||||
parts := types.NewPartSetFromData(cmn.RandBytes(100), 10)
|
||||
msg := &BlockPartMessage{
|
||||
Height: 1,
|
||||
Round: 0,
|
||||
Part: parts.GetPart(0),
|
||||
}
|
||||
|
||||
cs.ProposalBlockParts = types.NewPartSetFromHeader(parts.Header())
|
||||
cs.handleMsg(msgInfo{msg, peer.ID()})
|
||||
|
||||
statsMessage := <-cs.statsMsgQueue
|
||||
require.Equal(t, msg, statsMessage.Msg, "")
|
||||
require.Equal(t, peer.ID(), statsMessage.PeerID, "")
|
||||
|
||||
// sending the same part from different peer
|
||||
cs.handleMsg(msgInfo{msg, "peer2"})
|
||||
|
||||
// sending the part with the same height, but different round
|
||||
msg.Round = 1
|
||||
cs.handleMsg(msgInfo{msg, peer.ID()})
|
||||
|
||||
// sending the part from the smaller height
|
||||
msg.Height = 0
|
||||
cs.handleMsg(msgInfo{msg, peer.ID()})
|
||||
|
||||
// sending the part from the bigger height
|
||||
msg.Height = 3
|
||||
cs.handleMsg(msgInfo{msg, peer.ID()})
|
||||
|
||||
select {
|
||||
case <-cs.statsMsgQueue:
|
||||
t.Errorf("Should not output stats message after receiving the known block part!")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStateOutputVoteStats(t *testing.T) {
|
||||
cs, vss := randConsensusState(2)
|
||||
// create dummy peer
|
||||
peer := p2pdummy.NewPeer()
|
||||
|
||||
vote := signVote(vss[1], types.VoteTypePrecommit, []byte("test"), types.PartSetHeader{})
|
||||
|
||||
voteMessage := &VoteMessage{vote}
|
||||
cs.handleMsg(msgInfo{voteMessage, peer.ID()})
|
||||
|
||||
statsMessage := <-cs.statsMsgQueue
|
||||
require.Equal(t, voteMessage, statsMessage.Msg, "")
|
||||
require.Equal(t, peer.ID(), statsMessage.PeerID, "")
|
||||
|
||||
// sending the same part from different peer
|
||||
cs.handleMsg(msgInfo{&VoteMessage{vote}, "peer2"})
|
||||
|
||||
// sending the vote for the bigger height
|
||||
incrementHeight(vss[1])
|
||||
vote = signVote(vss[1], types.VoteTypePrecommit, []byte("test"), types.PartSetHeader{})
|
||||
|
||||
cs.handleMsg(msgInfo{&VoteMessage{vote}, peer.ID()})
|
||||
|
||||
select {
|
||||
case <-cs.statsMsgQueue:
|
||||
t.Errorf("Should not output stats message after receiving the known vote or vote from bigger height")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// subscribe subscribes test client to the given query and returns a channel with cap = 1.
|
||||
func subscribe(eventBus *types.EventBus, q tmpubsub.Query) <-chan interface{} {
|
||||
out := make(chan interface{}, 1)
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// must be greater than params.BlockGossip.BlockPartSizeBytes + a few bytes
|
||||
// must be greater than types.BlockPartSizeBytes + a few bytes
|
||||
maxMsgSizeBytes = 1024 * 1024 // 1MB
|
||||
)
|
||||
|
||||
|
|
|
@ -52,13 +52,13 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) {
|
|||
return nil, errors.Wrap(err, "failed to make genesis state")
|
||||
}
|
||||
blockStore := bc.NewBlockStore(blockStoreDB)
|
||||
handshaker := NewHandshaker(stateDB, state, blockStore, genDoc)
|
||||
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app), handshaker)
|
||||
proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app))
|
||||
proxyApp.SetLogger(logger.With("module", "proxy"))
|
||||
if err := proxyApp.Start(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start proxy app connections")
|
||||
}
|
||||
defer proxyApp.Stop()
|
||||
|
||||
eventBus := types.NewEventBus()
|
||||
eventBus.SetLogger(logger.With("module", "events"))
|
||||
if err := eventBus.Start(); err != nil {
|
||||
|
|
|
@ -27,6 +27,7 @@ module.exports = {
|
|||
"/tendermint-core/configuration",
|
||||
"/tendermint-core/rpc",
|
||||
"/tendermint-core/running-in-production",
|
||||
"/tendermint-core/fast-sync",
|
||||
"/tendermint-core/how-to-read-logs",
|
||||
"/tendermint-core/block-structure",
|
||||
"/tendermint-core/light-client-protocol",
|
||||
|
@ -36,21 +37,23 @@ module.exports = {
|
|||
]
|
||||
},
|
||||
{
|
||||
title: "Tendermint Tools",
|
||||
title: "Tools",
|
||||
collapsable: false,
|
||||
children: ["tools/benchmarking", "tools/monitoring"]
|
||||
children: [
|
||||
"tools/benchmarking",
|
||||
"tools/monitoring"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Tendermint Networks",
|
||||
title: "Networks",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/networks/deploy-testnets",
|
||||
"/networks/terraform-and-ansible",
|
||||
"/networks/fast-sync"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Application Development",
|
||||
title: "Apps",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"/app-dev/getting-started",
|
||||
|
@ -64,9 +67,45 @@ module.exports = {
|
|||
]
|
||||
},
|
||||
{
|
||||
title: "Research",
|
||||
title: "Tendermint Spec",
|
||||
collapsable: true,
|
||||
children: [
|
||||
"/spec/",
|
||||
"/spec/blockchain/blockchain",
|
||||
"/spec/blockchain/encoding",
|
||||
"/spec/blockchain/state",
|
||||
"/spec/software/abci",
|
||||
"/spec/consensus/bft-time",
|
||||
"/spec/consensus/consensus",
|
||||
"/spec/consensus/light-client",
|
||||
"/spec/software/wal",
|
||||
"/spec/p2p/config",
|
||||
"/spec/p2p/connection",
|
||||
"/spec/p2p/node",
|
||||
"/spec/p2p/peer",
|
||||
"/spec/reactors/block_sync/reactor",
|
||||
"/spec/reactors/block_sync/impl",
|
||||
"/spec/reactors/consensus/consensus",
|
||||
"/spec/reactors/consensus/consensus-reactor",
|
||||
"/spec/reactors/consensus/proposer-selection",
|
||||
"/spec/reactors/evidence/reactor",
|
||||
"/spec/reactors/mempool/concurrency",
|
||||
"/spec/reactors/mempool/config",
|
||||
"/spec/reactors/mempool/functionality",
|
||||
"/spec/reactors/mempool/messages",
|
||||
"/spec/reactors/mempool/reactor",
|
||||
"/spec/reactors/pex/pex",
|
||||
"/spec/reactors/pex/reactor",
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "ABCI Specification",
|
||||
collapsable: false,
|
||||
children: ["/research/determinism", "/research/transactional-semantics"]
|
||||
children: [
|
||||
"/spec/abci/abci",
|
||||
"/spec/abci/apps",
|
||||
"/spec/abci/client-server"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>VuePress</title>
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/assets/css/1.styles.c01b7ee3.css" as="style"><link rel="preload" href="/assets/js/app.48f1ff5f.js" as="script"><link rel="prefetch" href="/assets/js/0.7c2695bf.js">
|
||||
<link rel="stylesheet" href="/assets/css/1.styles.c01b7ee3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container"><div class="content"><h1>404</h1><blockquote>Looks like we've got some broken links.</blockquote><a href="/" class="router-link-active">Take me home.</a></div></div></div>
|
||||
<script src="/assets/js/app.48f1ff5f.js" defer></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg>
|
After Width: | Height: | Size: 216 B |
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{136:function(e,t,s){"use strict";s.r(t);var n=s(0),r=Object(n.a)({},function(){this.$createElement;this._self._c;return this._m(0)},[function(){var e=this.$createElement,t=this._self._c||e;return t("div",{staticClass:"content"},[t("h1",{attrs:{id:"hello-vuepress"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#hello-vuepress","aria-hidden":"true"}},[this._v("#")]),this._v(" Hello VuePress")])])}],!1,null,null,null);t.default=r.exports}}]);
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Hello VuePress</title>
|
||||
<meta name="description" content="">
|
||||
|
||||
|
||||
<link rel="preload" href="/assets/css/1.styles.c01b7ee3.css" as="style"><link rel="preload" href="/assets/js/app.48f1ff5f.js" as="script"><link rel="preload" href="/assets/js/0.7c2695bf.js" as="script">
|
||||
<link rel="stylesheet" href="/assets/css/1.styles.c01b7ee3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" data-server-rendered="true"><div class="theme-container no-sidebar"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div><a href="/" class="home-link router-link-exact-active router-link-active"></a><div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><!----></div></header><div class="sidebar-mask"></div><div class="sidebar"><!----><!----></div><div class="page"><div class="content"><h1 id="hello-vuepress"><a href="#hello-vuepress" aria-hidden="true" class="header-anchor">#</a> Hello VuePress</h1></div><div class="page-edit"><!----><!----></div><!----></div></div></div>
|
||||
<script src="/assets/js/0.7c2695bf.js" defer></script><script src="/assets/js/app.48f1ff5f.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,17 +1,96 @@
|
|||
# Documentation Maintenance Overview
|
||||
# Docs Build Workflow
|
||||
|
||||
The documentation found in this directory is hosted at:
|
||||
The documentation for Tendermint Core is hosted at:
|
||||
|
||||
- https://tendermint.com/docs/
|
||||
- https://tendermint.com/docs/ and
|
||||
- https://tendermint-staging.interblock.io/docs/
|
||||
|
||||
and built using [VuePress](https://vuepress.vuejs.org/) from the tendermint website repo:
|
||||
built from the files in this (`/docs`) directory for
|
||||
[master](https://github.com/tendermint/tendermint/tree/master/docs)
|
||||
and [develop](https://github.com/tendermint/tendermint/tree/develop/docs),
|
||||
respectively.
|
||||
|
||||
- https://github.com/tendermint/tendermint.com
|
||||
## How It Works
|
||||
|
||||
Under the hood, Jenkins listens for changes (on develop or master) in ./docs then rebuilds
|
||||
either the staging or production site depending on which branch the changes were made.
|
||||
There is a Jenkins job listening for changes in the `/docs` directory, on both
|
||||
the `master` and `develop` branches. Any updates to files in this directory
|
||||
on those branches will automatically trigger a website deployment. Under the hood,
|
||||
a private website repository has make targets consumed by a standard Jenkins task.
|
||||
|
||||
To update the Table of Contents (layout of the documentation sidebar), edit the
|
||||
`config.js` in this directory, while the `README.md` is the landing page for the
|
||||
website documentation.
|
||||
## README
|
||||
|
||||
The [README.md](./README.md) is also the landing page for the documentation
|
||||
on the website.
|
||||
|
||||
## Config.js
|
||||
|
||||
The [config.js](./.vuepress/config.js) generates the sidebar and Table of Contents
|
||||
on the website docs. Note the use of relative links and the omission of
|
||||
file extensions. Additional features are available to improve the look
|
||||
of the sidebar.
|
||||
|
||||
## Links
|
||||
|
||||
**NOTE:** Strongly consider the existing links - both within this directory
|
||||
and to the website docs - when moving or deleting files.
|
||||
|
||||
Relative links should be used nearly everywhere, having discovered and weighed the following:
|
||||
|
||||
### Relative
|
||||
|
||||
Where is the other file, relative to the current one?
|
||||
|
||||
- works both on GitHub and for the VuePress build
|
||||
- confusing / annoying to have things like: `../../../../myfile.md`
|
||||
- requires more updates when files are re-shuffled
|
||||
|
||||
### Absolute
|
||||
|
||||
Where is the other file, given the root of the repo?
|
||||
|
||||
- works on GitHub, doesn't work for the VuePress build
|
||||
- this is much nicer: `/docs/hereitis/myfile.md`
|
||||
- if you move that file around, the links inside it are preserved (but not to it, of course)
|
||||
|
||||
### Full
|
||||
|
||||
The full GitHub URL to a file or directory. Used occasionally when it makes sense
|
||||
to send users to the GitHub.
|
||||
|
||||
## Building Locally
|
||||
|
||||
To build and serve the documentation locally, run:
|
||||
|
||||
```
|
||||
# from this directory
|
||||
npm install
|
||||
npm install -g vuepress
|
||||
```
|
||||
|
||||
then change the following line in the `config.js`:
|
||||
|
||||
```
|
||||
base: "/docs/",
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```
|
||||
base: "/",
|
||||
```
|
||||
|
||||
Finally, go up one directory to the root of the repo and run:
|
||||
|
||||
```
|
||||
# from root of repo
|
||||
vuepress build docs
|
||||
cd dist/docs
|
||||
python -m SimpleHTTPServer 8080
|
||||
```
|
||||
|
||||
then navigate to localhost:8080 in your browser.
|
||||
|
||||
## Consistency
|
||||
|
||||
Because the build processes are identical (as is the information contained herein), this file should be kept in sync as
|
||||
much as possible with its [counterpart in the Cosmos SDK repo](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/DOCS_README.md).
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# Tendermint
|
||||
|
||||
Welcome to the Tendermint Core documentation! The introduction below provides
|
||||
an overview to help you navigate to your area of interest.
|
||||
|
||||
## Introduction
|
||||
Welcome to the Tendermint Core documentation! Below you'll find an
|
||||
overview of the documentation.
|
||||
|
||||
Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state
|
||||
transition machine - written in any programming language - and securely
|
||||
|
@ -11,17 +9,33 @@ replicates it on many machines. In other words, a blockchain.
|
|||
|
||||
Tendermint requires an application running over the Application Blockchain
|
||||
Interface (ABCI) - and comes packaged with an example application to do so.
|
||||
Follow the [installation instructions](./introduction/install.md) to get up and running
|
||||
quickly. For more details on [using tendermint](./tendermint-core/using-tendermint.md) see that
|
||||
and the following sections.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Here you'll find quick start guides and links to more advanced "get up and running"
|
||||
documentation.
|
||||
|
||||
## Core
|
||||
|
||||
Details about the core functionality and configuration of Tendermint.
|
||||
|
||||
## Tools
|
||||
|
||||
Benchmarking and monitoring tools.
|
||||
|
||||
## Networks
|
||||
|
||||
Testnets can be setup manually on one or more machines, or automatically on one
|
||||
or more machine, using a variety of methods described in the [deploy testnets
|
||||
section](./networks/deploy-testnets.md).
|
||||
Setting up testnets manually or automated, local or in the cloud.
|
||||
|
||||
## Application Development
|
||||
## Apps
|
||||
|
||||
The first step to building application on Tendermint is to [install
|
||||
ABCI-CLI](./app-dev/getting-started.md) and play with the example applications.
|
||||
Building appplications with the ABCI.
|
||||
|
||||
## Specification
|
||||
|
||||
Dive deep into the spec. There's one for each Tendermint and the ABCI
|
||||
|
||||
## Edit the Documentation
|
||||
|
||||
See [this file](./DOCS_README.md) for details of the build process and
|
||||
considerations when making changes.
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# ABCI Specification
|
||||
|
||||
### XXX
|
||||
|
||||
DEPRECATED: Moved [here](../spec/abci/abci.md)
|
||||
|
||||
## Message Types
|
||||
|
||||
ABCI requests/responses are defined as simple Protobuf messages in [this
|
||||
|
@ -177,7 +181,8 @@ See below for more details on the message types and how they are used.
|
|||
- **Usage**:
|
||||
- Signals the beginning of a new block. Called prior to
|
||||
any DeliverTxs.
|
||||
- The header is expected to at least contain the Height.
|
||||
- The header contains the height, timestamp, and more - it exactly matches the
|
||||
Tendermint block header. We may seek to generalize this in the future.
|
||||
- The `LastCommitInfo` and `ByzantineValidators` can be used to determine
|
||||
rewards and punishments for the validators. NOTE validators here do not
|
||||
include pubkeys.
|
||||
|
@ -253,7 +258,11 @@ See below for more details on the message types and how they are used.
|
|||
- **Usage**:
|
||||
- Signals the end of a block.
|
||||
- Called prior to each Commit, after all transactions.
|
||||
- Validator set and consensus params are updated with the result.
|
||||
- Validator updates returned for block H:
|
||||
- apply to the NextValidatorsHash of block H+1
|
||||
- apply to the ValidatorsHash (and thus the validator set) for block H+2
|
||||
- apply to the RequestBeginBlock.LastCommitInfo (ie. the last validator set) for block H+3
|
||||
- Consensus params returned for block H apply for block H+1
|
||||
|
||||
### Commit
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Application Development Guide
|
||||
|
||||
## XXX
|
||||
|
||||
This page is undergoing deprecation. All content is being moved to the new [home
|
||||
of the ABCI specification](../spec/abci/README.md).
|
||||
|
||||
## ABCI Design
|
||||
|
||||
The purpose of ABCI is to provide a clean interface between state
|
||||
|
|
|
@ -7,7 +7,7 @@ application you want to run. So, to run a complete blockchain that does
|
|||
something useful, you must start two programs: one is Tendermint Core,
|
||||
the other is your application, which can be written in any programming
|
||||
language. Recall from [the intro to
|
||||
ABCI](./introduction.md#ABCI-Overview) that Tendermint Core handles all
|
||||
ABCI](../introduction/introduction.html#abci-overview) that Tendermint Core handles all
|
||||
the p2p and consensus stuff, and just forwards transactions to the
|
||||
application when they need to be validated, or when they're ready to be
|
||||
committed to a block.
|
||||
|
@ -64,7 +64,7 @@ tendermint node
|
|||
If you have used Tendermint, you may want to reset the data for a new
|
||||
blockchain by running `tendermint unsafe_reset_all`. Then you can run
|
||||
`tendermint node` to start Tendermint, and connect to the app. For more
|
||||
details, see [the guide on using Tendermint](./using-tendermint.md).
|
||||
details, see [the guide on using Tendermint](../tendermint-core/using-tendermint.md).
|
||||
|
||||
You should see Tendermint making blocks! We can get the status of our
|
||||
Tendermint node as follows:
|
||||
|
@ -244,7 +244,7 @@ But if we send a `1`, it works again:
|
|||
```
|
||||
|
||||
For more details on the `broadcast_tx` API, see [the guide on using
|
||||
Tendermint](./using-tendermint.md).
|
||||
Tendermint](../tendermint-core/using-tendermint.md).
|
||||
|
||||
## CounterJS - Example in Another Language
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ Let's take a look at the `[tx_index]` config section:
|
|||
# What indexer to use for transactions
|
||||
#
|
||||
# Options:
|
||||
# 1) "null" (default)
|
||||
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||
# 1) "null"
|
||||
# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||
indexer = "kv"
|
||||
|
||||
# Comma-separated list of tags to index (by default the only tag is "tx.hash")
|
||||
|
|
|
@ -4,9 +4,17 @@
|
|||
|
||||
- How to / should we version the authenticated encryption handshake itself (ie.
|
||||
upfront protocol negotiation for the P2PVersion)
|
||||
- How to / should we version ABCI itself? Should it just be absorbed by the
|
||||
BlockVersion?
|
||||
|
||||
## Changelog
|
||||
|
||||
- 18-09-2018: Updates after working a bit on implementation
|
||||
- ABCI Handshake needs to happen independently of starting the app
|
||||
conns so we can see the result
|
||||
- Add question about ABCI protocol version
|
||||
- 16-08-2018: Updates after discussion with SDK team
|
||||
- Remove signalling for next version from Header/ABCI
|
||||
- 03-08-2018: Updates from discussion with Jae:
|
||||
- ProtocolVersion contains Block/AppVersion, not Current/Next
|
||||
- signal upgrades to Tendermint using EndBlock fields
|
||||
|
@ -19,18 +27,18 @@
|
|||
|
||||
## Context
|
||||
|
||||
Here we focus on software-agnostic protocol versions.
|
||||
|
||||
The Software Version is covered by SemVer and described elsewhere.
|
||||
It is not relevant to the protocol description, suffice to say that if any protocol version
|
||||
changes, the software version changes, but not necessarily vice versa.
|
||||
|
||||
Software version shoudl be included in NodeInfo for convenience/diagnostics.
|
||||
Software version should be included in NodeInfo for convenience/diagnostics.
|
||||
|
||||
We are also interested in versioning across different blockchains in a
|
||||
meaningful way, for instance to differentiate branches of a contentious
|
||||
hard-fork. We leave that for a later ADR.
|
||||
|
||||
Here we focus on protocol versions.
|
||||
|
||||
## Requirements
|
||||
|
||||
We need to version components of the blockchain that may be independently upgraded.
|
||||
|
@ -86,11 +94,9 @@ to connect to peers with older version.
|
|||
|
||||
Each component of the software is independently versioned in a modular way and its easy to mix and match and upgrade.
|
||||
|
||||
Good luck pal ;)
|
||||
|
||||
## Proposal
|
||||
|
||||
Each of BlockVersion, AppVersion, P2PVersion is a monotonically increasing int64.
|
||||
Each of BlockVersion, AppVersion, P2PVersion, is a monotonically increasing int64.
|
||||
|
||||
To use these versions, we need to update the block Header, the p2p NodeInfo, and the ABCI.
|
||||
|
||||
|
@ -100,19 +106,16 @@ Block Header should include a `Version` struct as its first field like:
|
|||
|
||||
```
|
||||
type Version struct {
|
||||
CurrentVersion ProtocolVersion
|
||||
ChainID string
|
||||
|
||||
NextVersion ProtocolVersion
|
||||
}
|
||||
|
||||
type ProtocolVersion struct {
|
||||
BlockVersion int64
|
||||
AppVersion int64
|
||||
Block int64
|
||||
App int64
|
||||
}
|
||||
```
|
||||
|
||||
Note this effectively makes BlockVersion the first field in the block Header.
|
||||
Here, `Version.Block` defines the rules for the current block, while
|
||||
`Version.App` defines the app version that processed the last block and computed
|
||||
the `AppHash` in the current block. Together they provide a complete description
|
||||
of the consensus-critical protocol.
|
||||
|
||||
Since we have settled on a proto3 header, the ability to read the BlockVersion out of the serialized header is unanimous.
|
||||
|
||||
Using a Version struct gives us more flexibility to add fields without breaking
|
||||
|
@ -120,8 +123,6 @@ the header.
|
|||
|
||||
The ProtocolVersion struct includes both the Block and App versions - it should
|
||||
serve as a complete description of the consensus-critical protocol.
|
||||
Using the `NextVersion` field, proposer's can signal their readiness to upgrade
|
||||
to a new Block and/or App version.
|
||||
|
||||
### NodeInfo
|
||||
|
||||
|
@ -129,23 +130,21 @@ NodeInfo should include a Version struct as its first field like:
|
|||
|
||||
```
|
||||
type Version struct {
|
||||
P2PVersion int64
|
||||
P2P int64
|
||||
Block int64
|
||||
App int64
|
||||
|
||||
ChainID string
|
||||
BlockVersion int64
|
||||
AppVersion int64
|
||||
SoftwareVersion string
|
||||
Other []string
|
||||
}
|
||||
```
|
||||
|
||||
Note this effectively makes P2PVersion the first field in the NodeInfo, so it
|
||||
Note this effectively makes `Version.P2P` the first field in the NodeInfo, so it
|
||||
should be easy to read this out of the serialized header if need be to facilitate an upgrade.
|
||||
|
||||
The SoftwareVersion here should include the name of the software client and
|
||||
The `Version.Other` here should include additional information like the name of the software client and
|
||||
it's SemVer version - this is for convenience only. Eg.
|
||||
`tendermint-core/v0.22.8`.
|
||||
|
||||
The other versions and ChainID will determine peer compatibility (described below).
|
||||
`tendermint-core/v0.22.8`. It's a `[]string` so it can include information about
|
||||
the version of Tendermint, of the app, of Tendermint libraries, etc.
|
||||
|
||||
### ABCI
|
||||
|
||||
|
@ -158,6 +157,11 @@ version information.
|
|||
We also need to be able to update versions in the life of a blockchain. The
|
||||
natural place to do this is EndBlock.
|
||||
|
||||
Note that currently the result of the Handshake isn't exposed anywhere, as the
|
||||
handshaking happens inside the `proxy.AppConns` abstraction. We will need to
|
||||
remove the handshaking from the `proxy` package so we can call it independently
|
||||
and get the result, which should contain the application version.
|
||||
|
||||
#### Info
|
||||
|
||||
RequestInfo should add support for protocol versions like:
|
||||
|
@ -199,28 +203,24 @@ message ResponseEndBlock {
|
|||
ConsensusParams consensus_param_updates
|
||||
repeated common.KVPair tags
|
||||
|
||||
VersionUpdates version_updates
|
||||
VersionUpdate version_update
|
||||
}
|
||||
|
||||
message VersionUpdates {
|
||||
ProtocolVersion current_version
|
||||
ProtocolVersion next_version
|
||||
}
|
||||
|
||||
message ProtocolVersion {
|
||||
int64 block_version
|
||||
message VersionUpdate {
|
||||
int64 app_version
|
||||
}
|
||||
```
|
||||
|
||||
Tendermint will use the information in VersionUpdates for the next block it
|
||||
Tendermint will use the information in VersionUpdate for the next block it
|
||||
proposes.
|
||||
|
||||
### BlockVersion
|
||||
|
||||
BlockVersion is included in both the Header and the NodeInfo.
|
||||
|
||||
Changing BlockVersion should happen quite infrequently and ideally only for extreme emergency.
|
||||
Changing BlockVersion should happen quite infrequently and ideally only for
|
||||
critical upgrades. For now, it is not encoded in ABCI, though it's always
|
||||
possible to use tags to signal an external process to co-ordinate an upgrade.
|
||||
|
||||
Note Ethereum has not had to make an upgrade like this (everything has been at state machine level, AFAIK).
|
||||
|
||||
|
@ -251,7 +251,7 @@ this is the first byte of a 32-byte ed25519 pubkey.
|
|||
|
||||
AppVersion is also included in the block Header and the NodeInfo.
|
||||
|
||||
AppVersion essentially defines how the AppHash and Results are computed.
|
||||
AppVersion essentially defines how the AppHash and LastResults are computed.
|
||||
|
||||
### Peer Compatibility
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# ADR 012: ABCI Events
|
||||
|
||||
## Changelog
|
||||
|
||||
- *2018-09-02* Remove ABCI errors component. Update description for events
|
||||
- *2018-07-12* Initial version
|
||||
|
||||
## Context
|
||||
|
||||
ABCI tags were first described in [ADR 002](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-002-event-subscription.md).
|
||||
They are key-value pairs that can be used to index transactions.
|
||||
|
||||
Currently, ABCI messages return a list of tags to describe an
|
||||
"event" that took place during the Check/DeliverTx/Begin/EndBlock,
|
||||
where each tag refers to a different property of the event, like the sending and receiving account addresses.
|
||||
|
||||
Since there is only one list of tags, recording data for multiple such events in
|
||||
a single Check/DeliverTx/Begin/EndBlock must be done using prefixes in the key
|
||||
space.
|
||||
|
||||
Alternatively, groups of tags that constitute an event can be separated by a
|
||||
special tag that denotes a break between the events. This would allow
|
||||
straightforward encoding of multiple events into a single list of tags without
|
||||
prefixing, at the cost of these "special" tags to separate the different events.
|
||||
|
||||
TODO: brief description of how the indexing works
|
||||
|
||||
## Decision
|
||||
|
||||
Instead of returning a list of tags, return a list of events, where
|
||||
each event is a list of tags. This way we naturally capture the concept of
|
||||
multiple events happening during a single ABCI message.
|
||||
|
||||
TODO: describe impact on indexing and querying
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Ability to track distinct events separate from ABCI calls (DeliverTx/BeginBlock/EndBlock)
|
||||
- More powerful query abilities
|
||||
|
||||
### Negative
|
||||
|
||||
- More complex query syntax
|
||||
- More complex search implementation
|
||||
|
||||
### Neutral
|
|
@ -0,0 +1,64 @@
|
|||
# ADR 023: ABCI Codespaces
|
||||
|
||||
## Changelog
|
||||
|
||||
- *2018-09-01* Initial version
|
||||
|
||||
## Context
|
||||
|
||||
ABCI errors should provide an abstraction between application details
|
||||
and the client interface responsible for formatting & displaying errors to the user.
|
||||
|
||||
Currently, this abstraction consists of a single integer (the `code`), where any
|
||||
`code > 0` is considered an error (ie. invalid transaction) and all type
|
||||
information about the error is contained in the code. This integer is
|
||||
expected to be decoded by the client into a known error string, where any
|
||||
more specific data is contained in the `data`.
|
||||
|
||||
In a [previous conversation](https://github.com/tendermint/abci/issues/165#issuecomment-353704015),
|
||||
it was suggested that not all non-zero codes need to be errors, hence why it's called `code` and not `error code`.
|
||||
It is unclear exactly how the semantics of the `code` field will evolve, though
|
||||
better lite-client proofs (like discussed for tags
|
||||
[here](https://github.com/tendermint/tendermint/issues/1007#issuecomment-413917763))
|
||||
may play a role.
|
||||
|
||||
Note that having all type information in a single integer
|
||||
precludes an easy coordination method between "module implementers" and "client
|
||||
implementers", especially for apps with many "modules". With an unbounded error domain (such as a string), module
|
||||
implementers can pick a globally unique prefix & error code set, so client
|
||||
implementers could easily implement support for "module A" regardless of which
|
||||
particular blockchain network it was running in and which other modules were running with it. With
|
||||
only error codes, globally unique codes are difficult/impossible, as the space
|
||||
is finite and collisions are likely without an easy way to coordinate.
|
||||
|
||||
For instance, while trying to build an ecosystem of modules that can be composed into a single
|
||||
ABCI application, the Cosmos-SDK had to hack a higher level "codespace" into the
|
||||
single integer so that each module could have its own space to express its
|
||||
errors.
|
||||
|
||||
## Decision
|
||||
|
||||
Include a `string code_space` in all ABCI messages that have a `code`.
|
||||
This allows applications to namespace the codes so they can experiment with
|
||||
their own code schemes.
|
||||
|
||||
It is the responsibility of applications to limit the size of the `code_space`
|
||||
string.
|
||||
|
||||
How the codespace is hashed into block headers (ie. so it can be queried
|
||||
efficiently by lite clients) is left for a separate ADR.
|
||||
|
||||
## Consequences
|
||||
|
||||
## Positive
|
||||
|
||||
- No need for complex codespacing on a single integer
|
||||
- More expressive type system for errors
|
||||
|
||||
## Negative
|
||||
|
||||
- Another field in the response needs to be accounted for
|
||||
- Some redundancy with `code` field
|
||||
- May encourage more error/code type info to move to the `codespace` string, which
|
||||
could impact lite clients.
|
||||
|
|
@ -5,7 +5,7 @@ is to run [this script](https://github.com/tendermint/tendermint/blob/develop/sc
|
|||
a fresh Ubuntu instance,
|
||||
or [this script](https://github.com/tendermint/tendermint/blob/develop/scripts/install/install_tendermint_bsd.sh)
|
||||
on a fresh FreeBSD instance. Read the comments / instructions carefully (i.e., reset your terminal after running the script,
|
||||
make sure your okay with the network connections being made).
|
||||
make sure you are okay with the network connections being made).
|
||||
|
||||
## From Binary
|
||||
|
||||
|
@ -48,6 +48,15 @@ to put the binary in `./build`.
|
|||
|
||||
The latest `tendermint version` is now installed.
|
||||
|
||||
## Run
|
||||
|
||||
To start a one-node blockchain with a simple in-process application:
|
||||
|
||||
```
|
||||
tendermint init
|
||||
tendermint node --proxy_app=kvstore
|
||||
```
|
||||
|
||||
## Reinstall
|
||||
|
||||
If you already have Tendermint installed, and you make updates, simply
|
||||
|
@ -66,11 +75,42 @@ make get_vendor_deps
|
|||
make install
|
||||
```
|
||||
|
||||
## Run
|
||||
## Compile with CLevelDB support
|
||||
|
||||
To start a one-node blockchain with a simple in-process application:
|
||||
Install [LevelDB](https://github.com/google/leveldb) (minimum version is 1.7).
|
||||
|
||||
Build Tendermint with C libraries: `make build_c`.
|
||||
|
||||
### Ubuntu
|
||||
|
||||
Install LevelDB with snappy:
|
||||
|
||||
```
|
||||
tendermint init
|
||||
tendermint node --proxy_app=kvstore
|
||||
sudo apt-get update
|
||||
sudo apt install build-essential
|
||||
|
||||
sudo apt-get install libsnappy-dev
|
||||
|
||||
wget https://github.com/google/leveldb/archive/v1.20.tar.gz && \
|
||||
tar -zxvf v1.20.tar.gz && \
|
||||
cd leveldb-1.20/ && \
|
||||
make && \
|
||||
cp -r out-static/lib* out-shared/lib* /usr/local/lib/ && \
|
||||
cd include/ && \
|
||||
cp -r leveldb /usr/local/include/ && \
|
||||
sudo ldconfig && \
|
||||
rm -f v1.20.tar.gz
|
||||
```
|
||||
|
||||
Set database backend to cleveldb:
|
||||
|
||||
```
|
||||
# config/config.toml
|
||||
db_backend = "cleveldb"
|
||||
```
|
||||
|
||||
To build Tendermint, run
|
||||
|
||||
```
|
||||
CGO_LDFLAGS="-lsnappy" go build -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags "tendermint gcc" -o build/tendermint ./cmd/tendermint/
|
||||
```
|
||||
|
|
|
@ -36,7 +36,7 @@ tendermint node --proxy_app=kvstore --p2p.persistent_peers=96663a3dd0d7b9d17d4c8
|
|||
|
||||
After a few seconds, all the nodes should connect to each other and
|
||||
start making blocks! For more information, see the Tendermint Networks
|
||||
section of [the guide to using Tendermint](./using-tendermint.md).
|
||||
section of [the guide to using Tendermint](../tendermint-core/using-tendermint.md).
|
||||
|
||||
But wait! Steps 3, 4 and 5 are quite manual. Instead, use the `tendermint testnet` command. By default, running `tendermint testnet` will create all the
|
||||
required files, but it won't populate the list of persistent peers. It will do
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,3 @@
|
|||
# On Determinism
|
||||
|
||||
Arguably, the most difficult part of blockchain programming is determinism - that is, ensuring that sources of indeterminism do not creep into the design of such systems.
|
||||
See [Determinism](../spec/abci/abci.md#determinism).
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
# Transactional Semantics
|
||||
|
||||
In [Using Tendermint](./using-tendermint.md#broadcast-api) we
|
||||
discussed different API endpoints for sending transactions and
|
||||
differences between them.
|
||||
See details of the [broadcast API](../tendermint-core/using-tendermint.md#broadcast-api)
|
||||
and the [mempool WAL](../tendermint-core/running-in-production.md#mempool-wal).
|
||||
|
||||
What we have not yet covered is transactional semantics.
|
||||
|
||||
When you send a transaction using one of the available methods, it first
|
||||
goes to the mempool. Currently, it does not provide strong guarantees
|
||||
like "if the transaction were accepted, it would be eventually included
|
||||
in a block (given CheckTx passes)."
|
||||
|
||||
For instance a tx could enter the mempool, but before it can be sent to
|
||||
peers the node crashes.
|
||||
|
||||
We are planning to provide such guarantees by using a WAL and replaying
|
||||
transactions (See
|
||||
[this issue](https://github.com/tendermint/tendermint/issues/248)), but
|
||||
it's non-trivial to do this all efficiently.
|
||||
|
||||
The temporary solution is for clients to monitor the node and resubmit
|
||||
transaction(s) and/or send them to more nodes at once, so the
|
||||
probability of all of them crashing at the same time and losing the msg
|
||||
decreases substantially.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint Specification
|
||||
# Overview
|
||||
|
||||
This is a markdown specification of the Tendermint blockchain.
|
||||
It defines the base data structures, how they are validated,
|
||||
|
@ -21,6 +21,7 @@ please submit them to our [bug bounty](https://tendermint.com/security)!
|
|||
### Consensus Protocol
|
||||
|
||||
- [Consensus Algorithm](/docs/spec/consensus/consensus.md)
|
||||
- [Creating a proposal](/docs/spec/consensus/creating-proposal.md)
|
||||
- [Time](/docs/spec/consensus/bft-time.md)
|
||||
- [Light-Client](/docs/spec/consensus/light-client.md)
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# ABCI
|
||||
|
||||
ABCI is the interface between Tendermint (a state-machine replication engine)
|
||||
and an application (the actual state machine). It consists of a set of
|
||||
*methods*, where each method has a corresponding `Request` and `Response`
|
||||
message type. Tendermint calls the ABCI methods on the ABCI application by sending the `Request*`
|
||||
messages and receiving the `Response*` messages in return.
|
||||
|
||||
All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
This allows Tendermint to run applications written in any programming language.
|
||||
|
||||
This specification is split as follows:
|
||||
|
||||
- [Methods and Types](abci.md) - complete details on all ABCI methods and
|
||||
message types
|
||||
- [Applications](apps.md) - how to manage ABCI application state and other
|
||||
details about building ABCI applications
|
||||
- [Client and Server](client-server.md) - for those looking to implement their
|
||||
own ABCI application servers
|
|
@ -0,0 +1,415 @@
|
|||
# Methods and Types
|
||||
|
||||
## Overview
|
||||
|
||||
The ABCI message types are defined in a [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
|
||||
ABCI methods are split across 3 separate ABCI *connections*:
|
||||
|
||||
- `Consensus Connection: InitChain, BeginBlock, DeliverTx, EndBlock, Commit`
|
||||
- `Mempool Connection: CheckTx`
|
||||
- `Info Connection: Info, SetOption, Query`
|
||||
|
||||
The `Consensus Connection` is driven by a consensus protocol and is responsible
|
||||
for block execution.
|
||||
The `Mempool Connection` is for validating new transactions, before they're
|
||||
shared or included in a block.
|
||||
The `Info Connection` is for initialization and for queries from the user.
|
||||
|
||||
Additionally, there is a `Flush` method that is called on every connection,
|
||||
and an `Echo` method that is just for debugging.
|
||||
|
||||
More details on managing state across connections can be found in the section on
|
||||
[ABCI Applications](apps.md).
|
||||
|
||||
## Errors
|
||||
|
||||
Some methods (`Echo, Info, InitChain, BeginBlock, EndBlock, Commit`),
|
||||
don't return errors because an error would indicate a critical failure
|
||||
in the application and there's nothing Tendermint can do. The problem
|
||||
should be addressed and both Tendermint and the application restarted.
|
||||
All other methods (`SetOption, Query, CheckTx, DeliverTx`) return an
|
||||
application-specific response `Code uint32`, where only `0` is reserved
|
||||
for `OK`.
|
||||
|
||||
## Tags
|
||||
|
||||
Some methods (`CheckTx, BeginBlock, DeliverTx, EndBlock`)
|
||||
include a `Tags` field in their `Response*`. Each tag is key-value pair denoting
|
||||
something about what happened during the methods execution.
|
||||
|
||||
Tags can be used to index transactions and blocks according to what happened
|
||||
during their execution.
|
||||
|
||||
Keys and values in tags must be UTF-8 encoded strings (e.g.
|
||||
"account.owner": "Bob", "balance": "100.0",
|
||||
"time": "2018-01-02T12:30:00Z")
|
||||
|
||||
## Determinism
|
||||
|
||||
ABCI applications must implement deterministic finite-state machines to be
|
||||
securely replicated by the Tendermint consensus. This means block execution
|
||||
over the Consensus Connection must be strictly deterministic: given the same
|
||||
ordered set of requests, all nodes will compute identical responses, for all
|
||||
BeginBlock, DeliverTx, EndBlock, and Commit. This is critical, because the
|
||||
responses are included in the header of the next block, either via a Merkle root
|
||||
or directly, so all nodes must agree on exactly what they are.
|
||||
|
||||
For this reason, it is recommended that applications not be exposed to any
|
||||
external user or process except via the ABCI connections to a consensus engine
|
||||
like Tendermint Core. The application must only change its state based on input
|
||||
from block execution (BeginBlock, DeliverTx, EndBlock, Commit), and not through
|
||||
any other kind of request. This is the only way to ensure all nodes see the same
|
||||
transactions and compute the same results.
|
||||
|
||||
If there is some non-determinism in the state machine, consensus will eventually
|
||||
fail as nodes disagree over the correct values for the block header. The
|
||||
non-determinism must be fixed and the nodes restarted.
|
||||
|
||||
Sources of non-determinism in applications may include:
|
||||
|
||||
- Hardware failures
|
||||
- Cosmic rays, overheating, etc.
|
||||
- Node-dependent state
|
||||
- Random numbers
|
||||
- Time
|
||||
- Underspecification
|
||||
- Library version changes
|
||||
- Race conditions
|
||||
- Floating point numbers
|
||||
- JSON serialization
|
||||
- Iterating through hash-tables/maps/dictionaries
|
||||
- External Sources
|
||||
- Filesystem
|
||||
- Network calls (eg. some external REST API service)
|
||||
|
||||
See [#56](https://github.com/tendermint/abci/issues/56) for original discussion.
|
||||
|
||||
Note that some methods (`SetOption, Query, CheckTx, DeliverTx`) return
|
||||
explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is
|
||||
intended for the literal output from the application's logger, while the
|
||||
`Info` is any additional info that should be returned. These are the only fields
|
||||
that are not included in block header computations, so we don't need agreement
|
||||
on them. All other fields in the `Response*` must be strictly deterministic.
|
||||
|
||||
## Block Execution
|
||||
|
||||
The first time a new blockchain is started, Tendermint calls
|
||||
`InitChain`. From then on, the follow sequence of methods is executed for each
|
||||
block:
|
||||
|
||||
`BeginBlock, [DeliverTx], EndBlock, Commit`
|
||||
|
||||
where one `DeliverTx` is called for each transaction in the block.
|
||||
The result is an updated application state.
|
||||
Cryptographic commitments to the results of DeliverTx, EndBlock, and
|
||||
Commit are included in the header of the next block.
|
||||
|
||||
## Messages
|
||||
|
||||
### Echo
|
||||
|
||||
- **Request**:
|
||||
- `Message (string)`: A string to echo back
|
||||
- **Response**:
|
||||
- `Message (string)`: The input string
|
||||
- **Usage**:
|
||||
- Echo a string to test an abci client/server implementation
|
||||
|
||||
### Flush
|
||||
|
||||
- **Usage**:
|
||||
- Signals that messages queued on the client should be flushed to
|
||||
the server. It is called periodically by the client
|
||||
implementation to ensure asynchronous requests are actually
|
||||
sent, and is called immediately to make a synchronous request,
|
||||
which returns when the Flush response comes back.
|
||||
|
||||
### Info
|
||||
|
||||
- **Request**:
|
||||
- `Version (string)`: The Tendermint version
|
||||
- **Response**:
|
||||
- `Data (string)`: Some arbitrary information
|
||||
- `Version (Version)`: Version information
|
||||
- `LastBlockHeight (int64)`: Latest block for which the app has
|
||||
called Commit
|
||||
- `LastBlockAppHash ([]byte)`: Latest result of Commit
|
||||
- **Usage**:
|
||||
- Return information about the application state.
|
||||
- Used to sync Tendermint with the application during a handshake
|
||||
that happens on startup.
|
||||
- Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to
|
||||
be updated during `Commit`, ensuring that `Commit` is never
|
||||
called twice for the same block height.
|
||||
|
||||
### SetOption
|
||||
|
||||
- **Request**:
|
||||
- `Key (string)`: Key to set
|
||||
- `Value (string)`: Value to set for key
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- **Usage**:
|
||||
- Set non-consensus critical application specific options.
|
||||
- e.g. Key="min-fee", Value="100fermion" could set the minimum fee
|
||||
required for CheckTx (but not DeliverTx - that would be
|
||||
consensus critical).
|
||||
|
||||
### InitChain
|
||||
|
||||
- **Request**:
|
||||
- `Time (google.protobuf.Timestamp)`: Genesis time.
|
||||
- `ChainID (string)`: ID of the blockchain.
|
||||
- `ConsensusParams (ConsensusParams)`: Initial consensus-critical parameters.
|
||||
- `Validators ([]ValidatorUpdate)`: Initial genesis validators.
|
||||
- `AppStateBytes ([]byte)`: Serialized initial application state. Amino-encoded JSON bytes.
|
||||
- **Response**:
|
||||
- `ConsensusParams (ConsensusParams)`: Initial
|
||||
consensus-critical parameters.
|
||||
- `Validators ([]ValidatorUpdate)`: Initial validator set (if non empty).
|
||||
- **Usage**:
|
||||
- Called once upon genesis.
|
||||
- If ResponseInitChain.Validators is empty, the initial validator set will be the RequestInitChain.Validators
|
||||
- If ResponseInitChain.Validators is not empty, the initial validator set will be the
|
||||
ResponseInitChain.Validators (regardless of what is in RequestInitChain.Validators).
|
||||
- This allows the app to decide if it wants to accept the initial validator
|
||||
set proposed by tendermint (ie. in the genesis file), or if it wants to use
|
||||
a different one (perhaps computed based on some application specific
|
||||
information in the genesis file).
|
||||
|
||||
### Query
|
||||
|
||||
- **Request**:
|
||||
- `Data ([]byte)`: Raw query bytes. Can be used with or in lieu
|
||||
of Path.
|
||||
- `Path (string)`: Path of request, like an HTTP GET path. Can be
|
||||
used with or in liue of Data.
|
||||
- Apps MUST interpret '/store' as a query by key on the
|
||||
underlying store. The key SHOULD be specified in the Data field.
|
||||
- Apps SHOULD allow queries over specific types like
|
||||
'/accounts/...' or '/votes/...'
|
||||
- `Height (int64)`: The block height for which you want the query
|
||||
(default=0 returns data for the latest committed block). Note
|
||||
that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- `Prove (bool)`: Return Merkle proof with response if possible
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `Index (int64)`: The index of the key in the tree.
|
||||
- `Key ([]byte)`: The key of the matching data.
|
||||
- `Value ([]byte)`: The value of the matching data.
|
||||
- `Proof ([]byte)`: Serialized proof for the data, if requested, to be
|
||||
verified against the `AppHash` for the given Height.
|
||||
- `Height (int64)`: The block height from which data was derived.
|
||||
Note that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- **Usage**:
|
||||
- Query for data from the application at current or past height.
|
||||
- Optionally return Merkle proof.
|
||||
|
||||
### BeginBlock
|
||||
|
||||
- **Request**:
|
||||
- `Hash ([]byte)`: The block's hash. This can be derived from the
|
||||
block header.
|
||||
- `Header (struct{})`: The block header.
|
||||
- `LastCommitInfo (LastCommitInfo)`: Info about the last commit, including the
|
||||
round, and the list of validators and which ones signed the last block.
|
||||
- `ByzantineValidators ([]Evidence)`: List of evidence of
|
||||
validators that acted maliciously.
|
||||
- **Response**:
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the beginning of a new block. Called prior to
|
||||
any DeliverTxs.
|
||||
- The header contains the height, timestamp, and more - it exactly matches the
|
||||
Tendermint block header. We may seek to generalize this in the future.
|
||||
- The `LastCommitInfo` and `ByzantineValidators` can be used to determine
|
||||
rewards and punishments for the validators. NOTE validators here do not
|
||||
include pubkeys.
|
||||
|
||||
### CheckTx
|
||||
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas requested for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- **Usage**:
|
||||
- Technically optional - not involved in processing blocks.
|
||||
- Guardian of the mempool: every node runs CheckTx before letting a
|
||||
transaction into its local mempool.
|
||||
- The transaction may come from an external user or another node
|
||||
- CheckTx need not execute the transaction in full, but rather a light-weight
|
||||
yet stateful validation, like checking signatures and account balances, but
|
||||
not running code in a virtual machine.
|
||||
- Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to
|
||||
other nodes or included in a proposal block.
|
||||
- Tendermint attributes no other value to the response code
|
||||
|
||||
### DeliverTx
|
||||
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes.
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas requested for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- **Usage**:
|
||||
- The workhorse of the application - non-optional.
|
||||
- Execute the transaction in full.
|
||||
- `ResponseDeliverTx.Code == 0` only if the transaction is fully valid.
|
||||
|
||||
### EndBlock
|
||||
|
||||
- **Request**:
|
||||
- `Height (int64)`: Height of the block just executed.
|
||||
- **Response**:
|
||||
- `ValidatorUpdates ([]ValidatorUpdate)`: Changes to validator set (set
|
||||
voting power to 0 to remove).
|
||||
- `ConsensusParamUpdates (ConsensusParams)`: Changes to
|
||||
consensus-critical time, size, and other parameters.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the end of a block.
|
||||
- Called after all transactions, prior to each Commit.
|
||||
- Validator updates returned by block `H` impact blocks `H+1`, `H+2`, and
|
||||
`H+3`, but only effects changes on the validator set of `H+2`:
|
||||
- `H+1`: NextValidatorsHash
|
||||
- `H+2`: ValidatorsHash (and thus the validator set)
|
||||
- `H+3`: LastCommitInfo (ie. the last validator set)
|
||||
- Consensus params returned for block `H` apply for block `H+1`
|
||||
|
||||
### Commit
|
||||
|
||||
- **Response**:
|
||||
- `Data ([]byte)`: The Merkle root hash of the application state
|
||||
- **Usage**:
|
||||
- Persist the application state.
|
||||
- Return an (optional) Merkle root hash of the application state
|
||||
- `ResponseCommit.Data` is included as the `Header.AppHash` in the next block
|
||||
- it may be empty
|
||||
- Later calls to `Query` can return proofs about the application state anchored
|
||||
in this Merkle root hash
|
||||
- Note developers can return whatever they want here (could be nothing, or a
|
||||
constant string, etc.), so long as it is deterministic - it must not be a
|
||||
function of anything that did not come from the
|
||||
BeginBlock/DeliverTx/EndBlock methods.
|
||||
|
||||
## Data Types
|
||||
|
||||
### Header
|
||||
|
||||
- **Fields**:
|
||||
- `ChainID (string)`: ID of the blockchain
|
||||
- `Height (int64)`: Height of the block in the chain
|
||||
- `Time (google.protobuf.Timestamp)`: Time of the block. It is the proposer's
|
||||
local time when block was created.
|
||||
- `NumTxs (int32)`: Number of transactions in the block
|
||||
- `TotalTxs (int64)`: Total number of transactions in the blockchain until
|
||||
now
|
||||
- `LastBlockID (BlockID)`: Hash of the previous (parent) block
|
||||
- `LastCommitHash ([]byte)`: Hash of the previous block's commit
|
||||
- `ValidatorsHash ([]byte)`: Hash of the validator set for this block
|
||||
- `NextValidatorsHash ([]byte)`: Hash of the validator set for the next block
|
||||
- `ConsensusHash ([]byte)`: Hash of the consensus parameters for this block
|
||||
- `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the
|
||||
Merkle root of the application state after executing the previous block's
|
||||
transactions
|
||||
- `LastResultsHash ([]byte)`: Hash of the ABCI results returned by the last block
|
||||
- `EvidenceHash ([]byte)`: Hash of the evidence included in this block
|
||||
- `ProposerAddress ([]byte)`: Original proposer for the block
|
||||
- **Usage**:
|
||||
- Provided in RequestBeginBlock
|
||||
- Provides important context about the current state of the blockchain -
|
||||
especially height and time.
|
||||
- Provides the proposer of the current block, for use in proposer-based
|
||||
reward mechanisms.
|
||||
|
||||
### Validator
|
||||
|
||||
- **Fields**:
|
||||
- `Address ([]byte)`: Address of the validator (hash of the public key)
|
||||
- `Power (int64)`: Voting power of the validator
|
||||
- **Usage**:
|
||||
- Validator identified by address
|
||||
- Used in RequestBeginBlock as part of VoteInfo
|
||||
- Does not include PubKey to avoid sending potentially large quantum pubkeys
|
||||
over the ABCI
|
||||
|
||||
### ValidatorUpdate
|
||||
|
||||
- **Fields**:
|
||||
- `PubKey (PubKey)`: Public key of the validator
|
||||
- `Power (int64)`: Voting power of the validator
|
||||
- **Usage**:
|
||||
- Validator identified by PubKey
|
||||
- Used to tell Tendermint to update the validator set
|
||||
|
||||
### VoteInfo
|
||||
|
||||
- **Fields**:
|
||||
- `Validator (Validator)`: A validator
|
||||
- `SignedLastBlock (bool)`: Indicates whether or not the validator signed
|
||||
the last block
|
||||
- **Usage**:
|
||||
- Indicates whether a validator signed the last block, allowing for rewards
|
||||
based on validator availability
|
||||
|
||||
### PubKey
|
||||
|
||||
- **Fields**:
|
||||
- `Type (string)`: Type of the public key. A simple string like `"ed25519"`.
|
||||
In the future, may indicate a serialization algorithm to parse the `Data`,
|
||||
for instance `"amino"`.
|
||||
- `Data ([]byte)`: Public key data. For a simple public key, it's just the
|
||||
raw bytes. If the `Type` indicates an encoding algorithm, this is the
|
||||
encoded public key.
|
||||
- **Usage**:
|
||||
- A generic and extensible typed public key
|
||||
|
||||
### Evidence
|
||||
|
||||
- **Fields**:
|
||||
- `Type (string)`: Type of the evidence. A hierarchical path like
|
||||
"duplicate/vote".
|
||||
- `Validator (Validator`: The offending validator
|
||||
- `Height (int64)`: Height when the offense was committed
|
||||
- `Time (google.protobuf.Timestamp)`: Time of the block at height `Height`.
|
||||
It is the proposer's local time when block was created.
|
||||
- `TotalVotingPower (int64)`: Total voting power of the validator set at
|
||||
height `Height`
|
||||
|
||||
### LastCommitInfo
|
||||
|
||||
- **Fields**:
|
||||
- `Round (int32)`: Commit round.
|
||||
- `Votes ([]VoteInfo)`: List of validators addresses in the last validator set
|
||||
with their voting power and whether or not they signed a vote.
|
|
@ -0,0 +1,399 @@
|
|||
# Applications
|
||||
|
||||
Please ensure you've first read the spec for [ABCI Methods and Types](abci.md)
|
||||
|
||||
Here we cover the following components of ABCI applications:
|
||||
|
||||
- [Connection State](#state) - the interplay between ABCI connections and application state
|
||||
and the differences between `CheckTx` and `DeliverTx`.
|
||||
- [Transaction Results](#transaction-results) - rules around transaction
|
||||
results and validity
|
||||
- [Validator Set Updates](#validator-updates) - how validator sets are
|
||||
changed during `InitChain` and `EndBlock`
|
||||
- [Query](#query) - standards for using the `Query` method and proofs about the
|
||||
application state
|
||||
- [Crash Recovery](#crash-recovery) - handshake protocol to synchronize
|
||||
Tendermint and the application on startup.
|
||||
|
||||
## State
|
||||
|
||||
Since Tendermint maintains three concurrent ABCI connections, it is typical
|
||||
for an application to maintain a distinct state for each, and for the states to
|
||||
be synchronized during `Commit`.
|
||||
|
||||
### Commit
|
||||
|
||||
Application state should only be persisted to disk during `Commit`.
|
||||
|
||||
Before `Commit` is called, Tendermint locks and flushes the mempool so that no new messages will
|
||||
be received on the mempool connection. This provides an opportunity to safely update all three
|
||||
states to the latest committed state at once.
|
||||
|
||||
When `Commit` completes, it unlocks the mempool.
|
||||
|
||||
Note that it is not possible to send transactions to Tendermint during `Commit` - if your app
|
||||
tries to send a `/broadcast_tx` to Tendermint during Commit, it will deadlock.
|
||||
|
||||
### Consensus Connection
|
||||
|
||||
The Consensus Connection should maintain a `DeliverTxState` -
|
||||
the working state for block execution. It should be updated by the calls to
|
||||
`BeginBlock`, `DeliverTx`, and `EndBlock` during block execution and committed to
|
||||
disk as the "latest committed state" during `Commit`.
|
||||
|
||||
Updates made to the DeliverTxState by each method call must be readable by each subsequent method -
|
||||
ie. the updates are linearizable.
|
||||
|
||||
### Mempool Connection
|
||||
|
||||
The Mempool Connection should maintain a `CheckTxState`
|
||||
to sequentially process pending transactions in the mempool that have
|
||||
not yet been committed. It should be initialized to the latest committed state
|
||||
at the end of every `Commit`.
|
||||
|
||||
The CheckTxState may be updated concurrently with the DeliverTxState, as
|
||||
messages may be sent concurrently on the Consensus and Mempool connections. However,
|
||||
before calling `Commit`, Tendermint will lock and flush the mempool connection,
|
||||
ensuring that all existing CheckTx are responded to and no new ones can
|
||||
begin.
|
||||
|
||||
After `Commit`, CheckTx is run again on all transactions that remain in the
|
||||
node's local mempool after filtering those included in the block. To prevent the
|
||||
mempool from rechecking all transactions every time a block is committed, set
|
||||
the configuration option `mempool.recheck=false`.
|
||||
|
||||
Finally, the mempool will unlock and new transactions can be processed through CheckTx again.
|
||||
|
||||
Note that CheckTx doesn't have to check everything that affects transaction validity; the
|
||||
expensive things can be skipped. In fact, CheckTx doesn't have to check
|
||||
anything; it might say that any transaction is a valid transaction.
|
||||
Unlike DeliverTx, CheckTx is just there as
|
||||
a sort of weak filter to keep invalid transactions out of the blockchain. It's
|
||||
weak, because a Byzantine node doesn't care about CheckTx; it can propose a
|
||||
block full of invalid transactions if it wants.
|
||||
|
||||
### Info Connection
|
||||
|
||||
The Info Connection should maintain a `QueryState` for answering queries from the user,
|
||||
and for initialization when Tendermint first starts up (both described further
|
||||
below).
|
||||
It should always contain the latest committed state associated with the
|
||||
latest committed block.
|
||||
|
||||
QueryState should be set to the latest `DeliverTxState` at the end of every `Commit`,
|
||||
ie. after the full block has been processed and the state committed to disk.
|
||||
Otherwise it should never be modified.
|
||||
|
||||
## Transaction Results
|
||||
|
||||
`ResponseCheckTx` and `ResponseDeliverTx` contain the same fields.
|
||||
|
||||
The `Info` and `Log` fields are non-deterministic values for debugging/convenience purposes
|
||||
that are otherwise ignored.
|
||||
|
||||
The `Data` field must be strictly deterministic, but can be arbitrary data.
|
||||
|
||||
### Gas
|
||||
|
||||
Ethereum introduced the notion of `gas` as an abstract representation of the
|
||||
cost of resources used by nodes when processing transactions. Every operation in the
|
||||
Ethereum Virtual Machine uses some amount of gas, and gas can be accepted at a market-variable price.
|
||||
Users propose a maximum amount of gas for their transaction; if the tx uses less, they get
|
||||
the difference credited back. Tendermint adopts a similar abstraction,
|
||||
though uses it only optionally and weakly, allowing applications to define
|
||||
their own sense of the cost of execution.
|
||||
|
||||
In Tendermint, the `ConsensusParams.BlockSize.MaxGas` limits the amount of `gas` that can be used in a block.
|
||||
The default value is `-1`, meaning no limit, or that the concept of gas is
|
||||
meaningless.
|
||||
|
||||
Responses contain a `GasWanted` and `GasUsed` field. The former is the maximum
|
||||
amount of gas the sender of a tx is willing to use, and the later is how much it actually
|
||||
used. Applications should enforce that `GasUsed <= GasWanted` - ie. tx execution
|
||||
should halt before it can use more resources than it requested.
|
||||
|
||||
When `MaxGas > -1`, Tendermint enforces the following rules:
|
||||
|
||||
- `GasWanted <= MaxGas` for all txs in the mempool
|
||||
- `(sum of GasWanted in a block) <= MaxGas` when proposing a block
|
||||
|
||||
If `MaxGas == -1`, no rules about gas are enforced.
|
||||
|
||||
Note that Tendermint does not currently enforce anything about Gas in the consensus, only the mempool.
|
||||
This means it does not guarantee that committed blocks satisfy these rules!
|
||||
It is the application's responsibility to return non-zero response codes when gas limits are exceeded.
|
||||
|
||||
The `GasUsed` field is ignored completely by Tendermint. That said, applications should enforce:
|
||||
|
||||
- `GasUsed <= GasWanted` for any given transaction
|
||||
- `(sum of GasUsed in a block) <= MaxGas` for every block
|
||||
|
||||
In the future, we intend to add a `Priority` field to the responses that can be
|
||||
used to explicitly prioritize txs in the mempool for inclusion in a block
|
||||
proposal. See [#1861](https://github.com/tendermint/tendermint/issues/1861).
|
||||
|
||||
### CheckTx
|
||||
|
||||
If `Code != 0`, it will be rejected from the mempool and hence
|
||||
not broadcasted to other peers and not included in a proposal block.
|
||||
|
||||
`Data` contains the result of the CheckTx transaction execution, if any. It is
|
||||
semantically meaningless to Tendermint.
|
||||
|
||||
`Tags` include any tags for the execution, though since the transaction has not
|
||||
been committed yet, they are effectively ignored by Tendermint.
|
||||
|
||||
### DeliverTx
|
||||
|
||||
If DeliverTx returns `Code != 0`, the transaction will be considered invalid,
|
||||
though it is still included in the block.
|
||||
|
||||
`Data` contains the result of the CheckTx transaction execution, if any. It is
|
||||
semantically meaningless to Tendermint.
|
||||
|
||||
Both the `Code` and `Data` are included in a structure that is hashed into the
|
||||
`LastResultsHash` of the next block header.
|
||||
|
||||
`Tags` include any tags for the execution, which Tendermint will use to index
|
||||
the transaction by. This allows transactions to be queried according to what
|
||||
events took place during their execution.
|
||||
|
||||
See issue [#1007](https://github.com/tendermint/tendermint/issues/1007) for how
|
||||
the tags will be hashed into the next block header.
|
||||
|
||||
## Validator Updates
|
||||
|
||||
The application may set the validator set during InitChain, and update it during
|
||||
EndBlock.
|
||||
|
||||
### InitChain
|
||||
|
||||
ResponseInitChain can return a list of validators.
|
||||
If the list is empty, Tendermint will use the validators loaded in the genesis
|
||||
file.
|
||||
If the list is not empty, Tendermint will use it for the validator set.
|
||||
This way the application can determine the initial validator set for the
|
||||
blockchain.
|
||||
|
||||
### EndBlock
|
||||
|
||||
Updates to the Tendermint validator set can be made by returning
|
||||
`ValidatorUpdate` objects in the `ResponseEndBlock`:
|
||||
|
||||
```
|
||||
message ValidatorUpdate {
|
||||
PubKey pub_key
|
||||
int64 power
|
||||
}
|
||||
|
||||
message PubKey {
|
||||
string type
|
||||
bytes data
|
||||
}
|
||||
```
|
||||
|
||||
The `pub_key` currently supports only one type:
|
||||
|
||||
- `type = "ed25519" and`data = <raw 32-byte public key>`
|
||||
|
||||
The `power` is the new voting power for the validator, with the
|
||||
following rules:
|
||||
|
||||
- power must be non-negative
|
||||
- if power is 0, the validator must already exist, and will be removed from the
|
||||
validator set
|
||||
- if power is non-0:
|
||||
- if the validator does not already exist, it will be added to the validator
|
||||
set with the given power
|
||||
- if the validator does already exist, its power will be adjusted to the given power
|
||||
|
||||
Note the updates returned in block `H` will only take effect at block `H+2`.
|
||||
|
||||
## Consensus Parameters
|
||||
|
||||
ConsensusParams enforce certain limits in the blockchain, like the maximum size
|
||||
of blocks, amount of gas used in a block, and the maximum acceptable age of
|
||||
evidence. They can be set in InitChain and updated in EndBlock.
|
||||
|
||||
### BlockSize.MaxBytes
|
||||
|
||||
The maximum size of a complete Amino encoded block.
|
||||
This is enforced by Tendermint consensus.
|
||||
|
||||
This implies a maximum tx size that is this MaxBytes, less the expected size of
|
||||
the header, the validator set, and any included evidence in the block.
|
||||
|
||||
Must have `0 < MaxBytes < 100 MB`.
|
||||
|
||||
### BlockSize.MaxGas
|
||||
|
||||
The maximum of the sum of `GasWanted` in a proposed block.
|
||||
This is *not* enforced by Tendermint consensus.
|
||||
It is left to the app to enforce (ie. if txs are included past the
|
||||
limit, they should return non-zero codes). It is used by Tendermint to limit the
|
||||
txs included in a proposed block.
|
||||
|
||||
Must have `MaxGas >= -1`.
|
||||
If `MaxGas == -1`, no limit is enforced.
|
||||
|
||||
### EvidenceParams.MaxAge
|
||||
|
||||
This is the maximum age of evidence.
|
||||
This is enforced by Tendermint consensus.
|
||||
If a block includes evidence older than this, the block will be rejected
|
||||
(validators won't vote for it).
|
||||
|
||||
Must have `0 < MaxAge`.
|
||||
|
||||
### Updates
|
||||
|
||||
The application may set the consensus params during InitChain, and update them during
|
||||
EndBlock.
|
||||
|
||||
#### InitChain
|
||||
|
||||
ResponseInitChain includes a ConsensusParams.
|
||||
If its nil, Tendermint will use the params loaded in the genesis
|
||||
file. If it's not nil, Tendermint will use it.
|
||||
This way the application can determine the initial consensus params for the
|
||||
blockchain.
|
||||
|
||||
#### EndBlock
|
||||
|
||||
ResponseEndBlock includes a ConsensusParams.
|
||||
If its nil, Tendermint will do nothing.
|
||||
If it's not nil, Tendermint will use it.
|
||||
This way the application can update the consensus params over time.
|
||||
|
||||
Note the updates returned in block `H` will take effect right away for block
|
||||
`H+1`.
|
||||
|
||||
## Query
|
||||
|
||||
Query is a generic method with lots of flexibility to enable diverse sets
|
||||
of queries on application state. Tendermint makes use of Query to filter new peers
|
||||
based on ID and IP, and exposes Query to the user over RPC.
|
||||
|
||||
Note that calls to Query are not replicated across nodes, but rather query the
|
||||
local node's state - hence they may return stale reads. For reads that require
|
||||
consensus, use a transaction.
|
||||
|
||||
The most important use of Query is to return Merkle proofs of the application state at some height
|
||||
that can be used for efficient application-specific lite-clients.
|
||||
|
||||
Note Tendermint has technically no requirements from the Query
|
||||
message for normal operation - that is, the ABCI app developer need not implement
|
||||
Query functionality if they do not wish too.
|
||||
|
||||
### Query Proofs
|
||||
|
||||
The Tendermint block header includes a number of hashes, each providing an
|
||||
anchor for some type of proof about the blockchain. The `ValidatorsHash` enables
|
||||
quick verification of the validator set, the `DataHash` gives quick
|
||||
verification of the transactions included in the block, etc.
|
||||
|
||||
The `AppHash` is unique in that it is application specific, and allows for
|
||||
application-specific Merkle proofs about the state of the application.
|
||||
While some applications keep all relevant state in the transactions themselves
|
||||
(like Bitcoin and its UTXOs), others maintain a separated state that is
|
||||
computed deterministically *from* transactions, but is not contained directly in
|
||||
the transactions themselves (like Ethereum contracts and accounts).
|
||||
For such applications, the `AppHash` provides a much more efficient way to verify lite-client proofs.
|
||||
|
||||
ABCI applications can take advantage of more efficient lite-client proofs for
|
||||
their state as follows:
|
||||
|
||||
- return the Merkle root of the deterministic application state in
|
||||
`ResponseCommit.Data`.
|
||||
- it will be included as the `AppHash` in the next block.
|
||||
- return efficient Merkle proofs about that application state in `ResponseQuery.Proof`
|
||||
that can be verified using the `AppHash` of the corresponding block.
|
||||
|
||||
For instance, this allows an application's lite-client to verify proofs of
|
||||
absence in the application state, something which is much less efficient to do using the block hash.
|
||||
|
||||
### Peer Filtering
|
||||
|
||||
When Tendermint connects to a peer, it sends two queries to the ABCI application
|
||||
using the following paths, with no additional data:
|
||||
|
||||
- `/p2p/filter/addr/<IP:PORT>`, where `<IP:PORT>` denote the IP address and
|
||||
the port of the connection
|
||||
- `p2p/filter/id/<ID>`, where `<ID>` is the peer node ID (ie. the
|
||||
pubkey.Address() for the peer's PubKey)
|
||||
|
||||
If either of these queries return a non-zero ABCI code, Tendermint will refuse
|
||||
to connect to the peer.
|
||||
|
||||
### Paths
|
||||
|
||||
Queries are directed at paths, and may optionally include additional data.
|
||||
|
||||
The expectation is for there to be some number of high level paths
|
||||
differentiating concerns, like `/p2p`, `/store`, and `/app`. Currently,
|
||||
Tendermint only uses `/p2p`, for filtering peers. For more advanced use, see the
|
||||
implementation of
|
||||
[Query in the Cosmos-SDK](https://github.com/cosmos/cosmos-sdk/blob/v0.23.1/baseapp/baseapp.go#L333).
|
||||
|
||||
## Crash Recovery
|
||||
|
||||
On startup, Tendermint calls the `Info` method on the Info Connection to get the latest
|
||||
committed state of the app. The app MUST return information consistent with the
|
||||
last block it succesfully completed Commit for.
|
||||
|
||||
If the app succesfully committed block H but not H+1, then `last_block_height = H` and `last_block_app_hash = <hash returned by Commit for block H>`. If the app
|
||||
failed during the Commit of block H, then `last_block_height = H-1` and
|
||||
`last_block_app_hash = <hash returned by Commit for block H-1, which is the hash in the header of block H>`.
|
||||
|
||||
We now distinguish three heights, and describe how Tendermint syncs itself with
|
||||
the app.
|
||||
|
||||
```
|
||||
storeBlockHeight = height of the last block Tendermint saw a commit for
|
||||
stateBlockHeight = height of the last block for which Tendermint completed all
|
||||
block processing and saved all ABCI results to disk
|
||||
appBlockHeight = height of the last block for which ABCI app succesfully
|
||||
completed Commit
|
||||
```
|
||||
|
||||
Note we always have `storeBlockHeight >= stateBlockHeight` and `storeBlockHeight >= appBlockHeight`
|
||||
Note also we never call Commit on an ABCI app twice for the same height.
|
||||
|
||||
The procedure is as follows.
|
||||
|
||||
First, some simple start conditions:
|
||||
|
||||
If `appBlockHeight == 0`, then call InitChain.
|
||||
|
||||
If `storeBlockHeight == 0`, we're done.
|
||||
|
||||
Now, some sanity checks:
|
||||
|
||||
If `storeBlockHeight < appBlockHeight`, error
|
||||
If `storeBlockHeight < stateBlockHeight`, panic
|
||||
If `storeBlockHeight > stateBlockHeight+1`, panic
|
||||
|
||||
Now, the meat:
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`,
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
|
||||
This happens if we completed processing the block, but the app forgot its height.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done
|
||||
This happens if we crashed at an opportune spot.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight+1`
|
||||
This happens if we started processing the block but didn't finish.
|
||||
|
||||
If `appBlockHeight < stateBlockHeight`
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
||||
and replay the block at `storeBlockHeight` using the WAL.
|
||||
This happens if the app forgot the last block it committed.
|
||||
|
||||
If `appBlockHeight == stateBlockHeight`,
|
||||
replay the last block (storeBlockHeight) in full.
|
||||
This happens if we crashed before the app finished Commit
|
||||
|
||||
If appBlockHeight == storeBlockHeight {
|
||||
update the state using the saved ABCI responses but dont run the block against the real app.
|
||||
This happens if we crashed after the app finished Commit but before Tendermint saved the state.
|
|
@ -0,0 +1,104 @@
|
|||
# Client and Server
|
||||
|
||||
This section is for those looking to implement their own ABCI Server, perhaps in
|
||||
a new programming language.
|
||||
|
||||
You are expected to have read [ABCI Methods and Types](abci.md) and [ABCI
|
||||
Applications](apps.md).
|
||||
|
||||
See additional details in the [ABCI
|
||||
readme](https://github.com/tendermint/tendermint/blob/develop/abci/README.md)(TODO: deduplicate
|
||||
those details).
|
||||
|
||||
## Message Protocol
|
||||
|
||||
The message protocol consists of pairs of requests and responses defined in the
|
||||
[protobuf file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
|
||||
Some messages have no fields, while others may include byte-arrays, strings, integers,
|
||||
or custom protobuf types.
|
||||
|
||||
For more details on protobuf, see the [documentation](https://developers.google.com/protocol-buffers/docs/overview).
|
||||
|
||||
For each request, a server should respond with the corresponding
|
||||
response, where the order of requests is preserved in the order of
|
||||
responses.
|
||||
|
||||
## Server
|
||||
|
||||
To use ABCI in your programming language of choice, there must be a ABCI
|
||||
server in that language. Tendermint supports two kinds of implementation
|
||||
of the server:
|
||||
|
||||
- Asynchronous, raw socket server (Tendermint Socket Protocol, also
|
||||
known as TSP or Teaspoon)
|
||||
- GRPC
|
||||
|
||||
Both can be tested using the `abci-cli` by setting the `--abci` flag
|
||||
appropriately (ie. to `socket` or `grpc`).
|
||||
|
||||
See examples, in various stages of maintenance, in
|
||||
[Go](https://github.com/tendermint/tendermint/tree/develop/abci/server),
|
||||
[JavaScript](https://github.com/tendermint/js-abci),
|
||||
[Python](https://github.com/tendermint/tendermint/tree/develop/abci/example/python3/abci),
|
||||
[C++](https://github.com/mdyring/cpp-tmsp), and
|
||||
[Java](https://github.com/jTendermint/jabci).
|
||||
|
||||
### GRPC
|
||||
|
||||
If GRPC is available in your language, this is the easiest approach,
|
||||
though it will have significant performance overhead.
|
||||
|
||||
To get started with GRPC, copy in the [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto)
|
||||
and compile it using the GRPC plugin for your language. For instance,
|
||||
for golang, the command is `protoc --go_out=plugins=grpc:. types.proto`.
|
||||
See the [grpc documentation for more details](http://www.grpc.io/docs/).
|
||||
`protoc` will autogenerate all the necessary code for ABCI client and
|
||||
server in your language, including whatever interface your application
|
||||
must satisfy to be used by the ABCI server for handling requests.
|
||||
|
||||
### TSP
|
||||
|
||||
If GRPC is not available in your language, or you require higher
|
||||
performance, or otherwise enjoy programming, you may implement your own
|
||||
ABCI server using the Tendermint Socket Protocol, known affectionately
|
||||
as Teaspoon. The first step is still to auto-generate the relevant data
|
||||
types and codec in your language using `protoc`. Messages coming over
|
||||
the socket are proto3 encoded, but additionally length-prefixed to
|
||||
facilitate use as a streaming protocol. proto3 doesn't have an
|
||||
official length-prefix standard, so we use our own. The first byte in
|
||||
the prefix represents the length of the Big Endian encoded length. The
|
||||
remaining bytes in the prefix are the Big Endian encoded length.
|
||||
|
||||
For example, if the proto3 encoded ABCI message is 0xDEADBEEF (4
|
||||
bytes), the length-prefixed message is 0x0104DEADBEEF. If the proto3
|
||||
encoded ABCI message is 65535 bytes long, the length-prefixed message
|
||||
would be like 0x02FFFF....
|
||||
|
||||
Note this prefixing does not apply for grpc.
|
||||
|
||||
An ABCI server must also be able to support multiple connections, as
|
||||
Tendermint uses three connections.
|
||||
|
||||
|
||||
### Async vs Sync
|
||||
|
||||
The main ABCI server (ie. non-GRPC) provides ordered asynchronous messages.
|
||||
This is useful for DeliverTx and CheckTx, since it allows Tendermint to forward
|
||||
transactions to the app before it's finished processing previous ones.
|
||||
|
||||
Thus, DeliverTx and CheckTx messages are sent asynchronously, while all other
|
||||
messages are sent synchronously.
|
||||
|
||||
## Client
|
||||
|
||||
There are currently two use-cases for an ABCI client. One is a testing
|
||||
tool, as in the `abci-cli`, which allows ABCI requests to be sent via
|
||||
command line. The other is a consensus engine, such as Tendermint Core,
|
||||
which makes requests to the application every time a new transaction is
|
||||
received or a block is committed.
|
||||
|
||||
It is unlikely that you will need to implement a client. For details of
|
||||
our client, see
|
||||
[here](https://github.com/tendermint/tendermint/tree/develop/abci/client).
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint Blockchain
|
||||
# Blockchain
|
||||
|
||||
Here we describe the data structures in the Tendermint blockchain and the rules for validating them.
|
||||
|
||||
|
@ -10,29 +10,25 @@ The Tendermint blockchains consists of a short list of basic data types:
|
|||
- `Header`
|
||||
- `BlockID`
|
||||
- `Time`
|
||||
- `Vote`
|
||||
- `Evidence`
|
||||
- `Data` (for transactions)
|
||||
- `Commit` and `Vote`
|
||||
- `EvidenceData` and `Evidence`
|
||||
|
||||
## Block
|
||||
|
||||
A block consists of a header, a list of transactions, a list of votes (the commit),
|
||||
A block consists of a header, transactions, votes (the commit),
|
||||
and a list of evidence of malfeasance (ie. signing conflicting votes).
|
||||
|
||||
```go
|
||||
type Block struct {
|
||||
Header Header
|
||||
Txs [][]byte
|
||||
LastCommit []Vote
|
||||
Evidence []Evidence
|
||||
Txs Data
|
||||
Evidence EvidenceData
|
||||
LastCommit Commit
|
||||
}
|
||||
```
|
||||
|
||||
The signatures returned along with block `X` are those validating block
|
||||
`X-1`. This can be a little confusing, but consider that
|
||||
the `Header` also contains the `LastCommitHash`. It would be impossible
|
||||
for a Header to include the commits that sign it, as it would cause an
|
||||
infinite loop here. But when we get block `X`, we find
|
||||
`Header.LastCommitHash`, which must match the hash of `LastCommit`.
|
||||
Note the `LastCommit` is the set of votes that committed the last block.
|
||||
|
||||
## Header
|
||||
|
||||
|
@ -44,7 +40,7 @@ type Header struct {
|
|||
// basic block info
|
||||
ChainID string
|
||||
Height int64
|
||||
Time time.Time
|
||||
Time Time
|
||||
NumTxs int64
|
||||
TotalTxs int64
|
||||
|
||||
|
@ -90,15 +86,43 @@ type PartsHeader struct {
|
|||
}
|
||||
```
|
||||
|
||||
TODO: link to details of merkle sums.
|
||||
|
||||
## Time
|
||||
|
||||
Tendermint uses the
|
||||
[Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp)
|
||||
format, which uses two integers, one for Seconds and for Nanoseconds.
|
||||
|
||||
TODO: clarify exact format and reconcile [this
|
||||
comment](https://github.com/tendermint/tendermint/blob/892b170818cd3be4cd3f919d72dde1ad60c28bbb/types/proto3/block.proto#L43).
|
||||
NOTE: there is currently a small divergence between Tendermint and the
|
||||
Google.Protobuf.WellKnownTypes.Timestamp that should be resolved. See [this
|
||||
issue](https://github.com/tendermint/go-amino/issues/223) for details.
|
||||
|
||||
## Data
|
||||
|
||||
Data is just a wrapper for a list of transactions, where transactions are
|
||||
arbitrary byte arrays:
|
||||
|
||||
```
|
||||
type Data struct {
|
||||
Txs [][]byte
|
||||
}
|
||||
```
|
||||
|
||||
## Commit
|
||||
|
||||
Commit is a simple wrapper for a list of votes, with one vote for each
|
||||
validator. It also contains the relevant BlockID:
|
||||
|
||||
```
|
||||
type Commit struct {
|
||||
BlockID BlockID
|
||||
Precommits []Vote
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: this will likely change to reduce the commit size by eliminating redundant
|
||||
information - see [issue #1648](https://github.com/tendermint/tendermint/issues/1648).
|
||||
|
||||
## Vote
|
||||
|
||||
|
@ -128,9 +152,30 @@ Signatures in Tendermint are raw bytes representing the underlying signature.
|
|||
The only signature scheme currently supported for Tendermint validators is
|
||||
ED25519. The signature is the raw 64-byte ED25519 signature.
|
||||
|
||||
## EvidenceData
|
||||
|
||||
EvidenceData is a simple wrapper for a list of evidence:
|
||||
|
||||
```
|
||||
type EvidenceData struct {
|
||||
Evidence []Evidence
|
||||
}
|
||||
```
|
||||
|
||||
## Evidence
|
||||
|
||||
Forthcoming, see [this issue](https://github.com/tendermint/tendermint/issues/2329)
|
||||
Evidence in Tendermint is implemented as an interface.
|
||||
This means any evidence is encoded using its Amino prefix.
|
||||
There is currently only a single type, the `DuplicateVoteEvidence`.
|
||||
|
||||
```
|
||||
// amino name: "tendermint/DuplicateVoteEvidence"
|
||||
type DuplicateVoteEvidence struct {
|
||||
PubKey PubKey
|
||||
VoteA Vote
|
||||
VoteB Vote
|
||||
}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
|
@ -155,13 +200,13 @@ See [here](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockc
|
|||
|
||||
A Header is valid if its corresponding fields are valid.
|
||||
|
||||
### Version
|
||||
|
||||
Arbitrary string.
|
||||
|
||||
### ChainID
|
||||
|
||||
Arbitrary constant string.
|
||||
```
|
||||
len(block.ChainID) < 50
|
||||
```
|
||||
|
||||
ChainID must be maximum 50 UTF-8 symbols.
|
||||
|
||||
### Height
|
||||
|
||||
|
@ -174,50 +219,27 @@ The height is an incrementing integer. The first block has `block.Header.Height
|
|||
|
||||
### Time
|
||||
|
||||
The median of the timestamps of the valid votes in the block.LastCommit.
|
||||
Corresponds to the number of nanoseconds, with millisecond resolution, since January 1, 1970.
|
||||
```
|
||||
block.Header.Timestamp >= prevBlock.Header.Timestamp + 1 ms
|
||||
block.Header.Timestamp == MedianTime(block.LastCommit, state.LastValidators)
|
||||
```
|
||||
|
||||
The block timestamp must be monotonic.
|
||||
It must equal the weighted median of the timestamps of the valid votes in the block.LastCommit.
|
||||
|
||||
Note: the timestamp of a vote must be greater by at least one millisecond than that of the
|
||||
block being voted on.
|
||||
|
||||
See the section on [BFT time](../consensus/bft-time.md) for more details.
|
||||
|
||||
### NumTxs
|
||||
|
||||
```go
|
||||
block.Header.NumTxs == len(block.Txs)
|
||||
block.Header.NumTxs == len(block.Txs.Txs)
|
||||
```
|
||||
|
||||
Number of transactions included in the block.
|
||||
|
||||
### TxHash
|
||||
|
||||
```go
|
||||
block.Header.TxHash == SimpleMerkleRoot(block.Txs)
|
||||
```
|
||||
|
||||
Simple Merkle root of the transactions in the block.
|
||||
|
||||
### LastCommitHash
|
||||
|
||||
```go
|
||||
block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit)
|
||||
```
|
||||
|
||||
Simple Merkle root of the votes included in the block.
|
||||
These are the votes that committed the previous block.
|
||||
|
||||
The first block has `block.Header.LastCommitHash == []byte{}`
|
||||
|
||||
### DataHash
|
||||
|
||||
The `DataHash` can provide a nice check on the
|
||||
[Data](https://godoc.org/github.com/tendermint/tendermint/types#Data)
|
||||
returned in this same block. If you are subscribed to new blocks, via
|
||||
tendermint RPC, in order to display or process the new transactions you
|
||||
should at least validate that the `DataHash` is valid. If it is
|
||||
important to verify autheniticity, you must wait for the `LastCommit`
|
||||
from the next block to make sure the block header (including `DataHash`)
|
||||
was properly signed.
|
||||
|
||||
### TotalTxs
|
||||
|
||||
```go
|
||||
|
@ -248,25 +270,24 @@ which are held in the `state` and may be updated by the application.
|
|||
|
||||
The first block has `block.Header.LastBlockID == BlockID{}`.
|
||||
|
||||
### ResultsHash
|
||||
### LastCommitHash
|
||||
|
||||
```go
|
||||
block.ResultsHash == SimpleMerkleRoot(state.LastResults)
|
||||
block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit)
|
||||
```
|
||||
|
||||
Simple Merkle root of the results of the transactions in the previous block.
|
||||
Simple Merkle root of the votes included in the block.
|
||||
These are the votes that committed the previous block.
|
||||
|
||||
The first block has `block.Header.ResultsHash == []byte{}`.
|
||||
The first block has `block.Header.LastCommitHash == []byte{}`
|
||||
|
||||
### AppHash
|
||||
### DataHash
|
||||
|
||||
```go
|
||||
block.AppHash == state.AppHash
|
||||
block.Header.DataHash == SimpleMerkleRoot(block.Txs.Txs)
|
||||
```
|
||||
|
||||
Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself.
|
||||
|
||||
The first block has `block.Header.AppHash == []byte{}`.
|
||||
Simple Merkle root of the transactions included in the block.
|
||||
|
||||
### ValidatorsHash
|
||||
|
||||
|
@ -284,7 +305,8 @@ block.NextValidatorsHash == SimpleMerkleRoot(state.NextValidators)
|
|||
```
|
||||
|
||||
Simple Merkle root of the next validator set that will be the validator set that commits the next block.
|
||||
Modifications to the validator set are defined by the application.
|
||||
This is included so that the current validator set gets a chance to sign the
|
||||
next validator sets Merkle root.
|
||||
|
||||
### ConsensusParamsHash
|
||||
|
||||
|
@ -293,17 +315,26 @@ block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams)
|
|||
```
|
||||
|
||||
Simple Merkle root of the consensus parameters.
|
||||
May be updated by the application.
|
||||
|
||||
### ProposerAddress
|
||||
### AppHash
|
||||
|
||||
```go
|
||||
block.Header.ProposerAddress in state.Validators
|
||||
block.AppHash == state.AppHash
|
||||
```
|
||||
|
||||
Address of the original proposer of the block. Must be a current validator.
|
||||
Arbitrary byte array returned by the application after executing and commiting the previous block. It serves as the basis for validating any merkle proofs that comes from the ABCI application and represents the state of the actual application rather than the state of the blockchain itself.
|
||||
|
||||
NOTE: we also need to track the round.
|
||||
The first block has `block.Header.AppHash == []byte{}`.
|
||||
|
||||
### LastResultsHash
|
||||
|
||||
```go
|
||||
block.ResultsHash == SimpleMerkleRoot(state.LastResults)
|
||||
```
|
||||
|
||||
Simple Merkle root of the results of the transactions in the previous block.
|
||||
|
||||
The first block has `block.Header.ResultsHash == []byte{}`.
|
||||
|
||||
## EvidenceHash
|
||||
|
||||
|
@ -313,6 +344,14 @@ block.EvidenceHash == SimpleMerkleRoot(block.Evidence)
|
|||
|
||||
Simple Merkle root of the evidence of Byzantine behaviour included in this block.
|
||||
|
||||
### ProposerAddress
|
||||
|
||||
```go
|
||||
block.Header.ProposerAddress in state.Validators
|
||||
```
|
||||
|
||||
Address of the original proposer of the block. Must be a current validator.
|
||||
|
||||
## Txs
|
||||
|
||||
Arbitrary length array of arbitrary length byte-arrays.
|
||||
|
@ -361,7 +400,7 @@ must be greater than 2/3 of the total voting power of the complete validator set
|
|||
### Vote
|
||||
|
||||
A vote is a signed message broadcast in the consensus for a particular block at a particular height and round.
|
||||
When stored in the blockchain or propagated over the network, votes are encoded in TMBIN.
|
||||
When stored in the blockchain or propagated over the network, votes are encoded in Amino.
|
||||
For signing, votes are encoded in JSON, and the ChainID is included, in the form of the `CanonicalSignBytes`.
|
||||
|
||||
We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the CanonicalSignBytes
|
||||
|
@ -378,16 +417,7 @@ against the given signature and message bytes.
|
|||
|
||||
## Evidence
|
||||
|
||||
There is currently only one kind of evidence:
|
||||
|
||||
```
|
||||
// amino: "tendermint/DuplicateVoteEvidence"
|
||||
type DuplicateVoteEvidence struct {
|
||||
PubKey crypto.PubKey
|
||||
VoteA *Vote
|
||||
VoteB *Vote
|
||||
}
|
||||
```
|
||||
There is currently only one kind of evidence, `DuplicateVoteEvidence`.
|
||||
|
||||
DuplicateVoteEvidence `ev` is valid if
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint Encoding
|
||||
# Encoding
|
||||
|
||||
## Amino
|
||||
|
||||
|
@ -269,19 +269,17 @@ similarly derived.
|
|||
|
||||
### IAVL+ Tree
|
||||
|
||||
Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/core/multistore.md)
|
||||
Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/sdk/core/multistore.md)
|
||||
|
||||
## JSON
|
||||
|
||||
### Amino
|
||||
|
||||
This section is pending an update, see [this issue](https://github.com/tendermint/tendermint/issues/1749).
|
||||
|
||||
Amino also supports JSON encoding - registered types are simply encoded as:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "<DisfixBytes>",
|
||||
"type": "<amino type name>",
|
||||
"value": <JSON>
|
||||
}
|
||||
```
|
||||
|
@ -296,19 +294,18 @@ For instance, an ED25519 PubKey would look like:
|
|||
```
|
||||
|
||||
Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the
|
||||
`"type"` is the full disfix bytes for Ed25519 pubkeys.
|
||||
`"type"` is the amino name for Ed25519 pubkeys.
|
||||
|
||||
### Signed Messages
|
||||
|
||||
Signed messages (eg. votes, proposals) in the consensus are encoded using Amino-JSON, rather than in the standard binary format.
|
||||
Signed messages (eg. votes, proposals) in the consensus are encoded using Amino-JSON, rather than in the standard binary format
|
||||
(NOTE: this is subject to change: https://github.com/tendermint/tendermint/issues/1622)
|
||||
|
||||
When signing, the elements of a message are sorted by key and the sorted message is embedded in an
|
||||
outer JSON that includes a `chain_id` field.
|
||||
When signing, the elements of a message are sorted by key and prepended with
|
||||
a `@chain_id` and `@type` field.
|
||||
We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look
|
||||
like:
|
||||
|
||||
```json
|
||||
{"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2}
|
||||
{"@chain_id":"test_chain_id","@type":"vote","block_id":{"hash":"8B01023386C371778ECB6368573E539AFC3CC860","parts":{"hash":"72DB3D959635DFF1BB567BEDAA70573392C51596","total":"1000000"}},"height":"12345","round":"2","timestamp":"2017-12-25T03:00:01.234Z","type":2}
|
||||
```
|
||||
|
||||
Note how the fields within each level are sorted.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint State
|
||||
# State
|
||||
|
||||
## State
|
||||
|
||||
|
@ -8,10 +8,10 @@ transactions are never included in blocks, but their Merkle roots are - the stat
|
|||
|
||||
Note that the `State` object itself is an implementation detail, since it is never
|
||||
included in a block or gossipped over the network, and we never compute
|
||||
its hash. However, the types it contains are part of the specification, since
|
||||
their Merkle roots are included in blocks.
|
||||
|
||||
Details on an implementation of `State` with persistence is forthcoming, see [this issue](https://github.com/tendermint/tendermint/issues/1152)
|
||||
its hash. Thus we do not include here details of how the `State` object is
|
||||
persisted or queried. That said, the types it contains are part of the specification, since
|
||||
their Merkle roots are included in blocks and their values are used in
|
||||
validation.
|
||||
|
||||
```go
|
||||
type State struct {
|
||||
|
@ -32,20 +32,15 @@ type State struct {
|
|||
type Result struct {
|
||||
Code uint32
|
||||
Data []byte
|
||||
Tags []KVPair
|
||||
}
|
||||
|
||||
type KVPair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
```
|
||||
|
||||
`Result` is the result of executing a transaction against the application.
|
||||
It returns a result code, an arbitrary byte array (ie. a return value),
|
||||
and a list of key-value pairs ordered by key. The key-value pairs, or tags,
|
||||
can be used to index transactions according to their "effects", which are
|
||||
represented in the tags.
|
||||
It returns a result code and an arbitrary byte array (ie. a return value).
|
||||
|
||||
NOTE: the Result needs to be updated to include more fields returned from
|
||||
processing transactions, like gas variables and tags - see
|
||||
[issue 1007](https://github.com/tendermint/tendermint/issues/1007).
|
||||
|
||||
### Validator
|
||||
|
||||
|
@ -60,7 +55,7 @@ type Validator struct {
|
|||
}
|
||||
```
|
||||
|
||||
The `state.Validators` and `state.LastValidators` must always by sorted by validator address,
|
||||
The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always by sorted by validator address,
|
||||
so that there is a canonical order for computing the SimpleMerkleRoot.
|
||||
|
||||
We also define a `TotalVotingPower` function, to return the total voting power:
|
||||
|
@ -77,4 +72,59 @@ func TotalVotingPower(vals []Validators) int64{
|
|||
|
||||
### ConsensusParams
|
||||
|
||||
This section is forthcoming. See [this issue](https://github.com/tendermint/tendermint/issues/1152).
|
||||
ConsensusParams define various limits for blockchain data structures.
|
||||
Like validator sets, they are set during genesis and can be updated by the application through ABCI.
|
||||
|
||||
```
|
||||
type ConsensusParams struct {
|
||||
BlockSize
|
||||
TxSize
|
||||
BlockGossip
|
||||
EvidenceParams
|
||||
}
|
||||
|
||||
type BlockSize struct {
|
||||
MaxBytes int
|
||||
MaxGas int64
|
||||
}
|
||||
|
||||
type TxSize struct {
|
||||
MaxBytes int
|
||||
MaxGas int64
|
||||
}
|
||||
|
||||
type BlockGossip struct {
|
||||
BlockPartSizeBytes int
|
||||
}
|
||||
|
||||
type EvidenceParams struct {
|
||||
MaxAge int64
|
||||
}
|
||||
```
|
||||
|
||||
#### BlockSize
|
||||
|
||||
The total size of a block is limited in bytes by the `ConsensusParams.BlockSize.MaxBytes`.
|
||||
Proposed blocks must be less than this size, and will be considered invalid
|
||||
otherwise.
|
||||
|
||||
Blocks should additionally be limited by the amount of "gas" consumed by the
|
||||
transactions in the block, though this is not yet implemented.
|
||||
|
||||
#### TxSize
|
||||
|
||||
These parameters are not yet enforced and may disappear. See [issue
|
||||
#2347](https://github.com/tendermint/tendermint/issues/2347).
|
||||
|
||||
#### BlockGossip
|
||||
|
||||
When gossipping blocks in the consensus, they are first split into parts. The
|
||||
size of each part is `ConsensusParams.BlockGossip.BlockPartSizeBytes`.
|
||||
|
||||
#### EvidenceParams
|
||||
|
||||
For evidence in a block to be valid, it must satisfy:
|
||||
|
||||
```
|
||||
block.Header.Height - evidence.Height < ConsensusParams.EvidenceParams.MaxAge
|
||||
```
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# BFT time in Tendermint
|
||||
# BFT Time
|
||||
|
||||
Tendermint provides a deterministic, Byzantine fault-tolerant, source of time.
|
||||
Time in Tendermint is defined with the Time field of the block header.
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
vote](https://godoc.org/github.com/tendermint/tendermint/types#FirstPrecommit)
|
||||
for something.
|
||||
- A vote _at_ `(H,R)` is a vote signed with the bytes for `H` and `R`
|
||||
included in its [sign-bytes](../blockchain/blockchain.md).
|
||||
included in its [sign-bytes](../blockchain/blockchain.md#vote).
|
||||
- _+2/3_ is short for "more than 2/3"
|
||||
- _1/3+_ is short for "1/3 or more"
|
||||
- A set of +2/3 of prevotes for a particular block or `<nil>` at
|
||||
|
@ -282,7 +282,7 @@ may make JSet verification/gossip logic easier to implement.
|
|||
### Censorship Attacks
|
||||
|
||||
Due to the definition of a block
|
||||
[commit](../../tendermint-core/validator.md#commiting-a-block), any 1/3+ coalition of
|
||||
[commit](../../tendermint-core/validators.md#commit-a-block), any 1/3+ coalition of
|
||||
validators can halt the blockchain by not broadcasting their votes. Such
|
||||
a coalition can also censor particular transactions by rejecting blocks
|
||||
that include these transactions, though this would result in a
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Creating a proposal
|
||||
|
||||
A block consists of a header, transactions, votes (the commit),
|
||||
and a list of evidence of malfeasance (ie. signing conflicting votes).
|
||||
|
||||
We include no more than 1/10th of the maximum block size
|
||||
(`ConsensusParams.BlockSize.MaxBytes`) of evidence with each block.
|
||||
|
||||
## Reaping transactions from the mempool
|
||||
|
||||
When we reap transactions from the mempool, we calculate maximum data
|
||||
size by subtracting maximum header size (`MaxHeaderBytes`), the maximum
|
||||
amino overhead for a block (`MaxAminoOverheadForBlock`), the size of
|
||||
the last commit (if present) and evidence (if present). While reaping
|
||||
we account for amino overhead for each transaction.
|
||||
|
||||
```go
|
||||
func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 {
|
||||
return maxBytes -
|
||||
MaxAminoOverheadForBlock -
|
||||
MaxHeaderBytes -
|
||||
int64(valsCount)*MaxVoteBytes -
|
||||
int64(evidenceCount)*MaxEvidenceBytes
|
||||
}
|
||||
```
|
||||
|
||||
## Validating transactions in the mempool
|
||||
|
||||
Before we accept a transaction in the mempool, we check if it's size is no more
|
||||
than {MaxDataSize}. {MaxDataSize} is calculated using the same formula as
|
||||
above, except because the evidence size is unknown at the moment, we subtract
|
||||
maximum evidence size (1/10th of the maximum block size).
|
||||
|
||||
```go
|
||||
func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 {
|
||||
return maxBytes -
|
||||
MaxAminoOverheadForBlock -
|
||||
MaxHeaderBytes -
|
||||
int64(valsCount)*MaxVoteBytes -
|
||||
MaxEvidenceBytesPerBlock(maxBytes)
|
||||
}
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
# Light client
|
||||
# Light Client
|
||||
|
||||
A light client is a process that connects to the Tendermint Full Node(s) and then tries to verify the Merkle proofs
|
||||
about the blockchain application. In this document we describe mechanisms that ensures that the Tendermint light client
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint Peer Discovery
|
||||
# Peer Discovery
|
||||
|
||||
A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to one another.
|
||||
This document describes what kind of nodes Tendermint should enable and how they should work.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Tendermint Peers
|
||||
# Peers
|
||||
|
||||
This document explains how Tendermint Peers are identified and how they connect to one another.
|
||||
|
||||
|
@ -40,7 +40,7 @@ It goes as follows:
|
|||
- get 96 bytes of output from hkdf-sha256
|
||||
- if we had the smaller ephemeral pubkey, use the first 32 bytes for the key for receiving, the second 32 bytes for sending; else the opposite
|
||||
- use the last 32 bytes of output for the challenge
|
||||
- use a seperate nonce for receiving and sending. Both nonces start at 0, and should support the full 96 bit nonce range
|
||||
- use a separate nonce for receiving and sending. Both nonces start at 0, and should support the full 96 bit nonce range
|
||||
- all communications from now on are encrypted in 1024 byte frames,
|
||||
using the respective secret and nonce. Each nonce is incremented by one after each use.
|
||||
- we now have an encrypted channel, but still need to authenticate
|
||||
|
@ -83,7 +83,16 @@ type NodeInfo struct {
|
|||
Channels []int8
|
||||
|
||||
Moniker string
|
||||
Other []string
|
||||
Other NodeInfoOther
|
||||
}
|
||||
|
||||
type NodeInfoOther struct {
|
||||
AminoVersion string
|
||||
P2PVersion string
|
||||
ConsensusVersion string
|
||||
RPCVersion string
|
||||
TxIndex string
|
||||
RPCAddress string
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ to potentially untrusted actors.
|
|||
Internal functionality is exposed via method calls to other
|
||||
code compiled into the tendermint binary.
|
||||
|
||||
- Reap - get tx to propose in next block
|
||||
- ReapMaxBytesMaxGas - get txs to propose in the next block. Guarantees that the
|
||||
size of the txs is less than MaxBytes, and gas is less than MaxGas
|
||||
- Update - remove tx that were included in last block
|
||||
- ABCI.CheckTx - call ABCI app to validate the tx
|
||||
|
||||
|
|
|
@ -1,185 +1,3 @@
|
|||
# Application Blockchain Interface (ABCI)
|
||||
|
||||
ABCI is the interface between Tendermint (a state-machine replication engine)
|
||||
and an application (the actual state machine).
|
||||
|
||||
The ABCI message types are defined in a [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
|
||||
For full details on the ABCI message types and protocol, see the [ABCI
|
||||
specification](https://github.com/tendermint/tendermint/blob/develop/docs/app-dev/abci-spec.md).
|
||||
Be sure to read the specification if you're trying to build an ABCI app!
|
||||
|
||||
For additional details on server implementation, see the [ABCI
|
||||
readme](https://github.com/tendermint/tendermint/blob/develop/abci/README.md).
|
||||
|
||||
Here we provide some more details around the use of ABCI by Tendermint and
|
||||
clarify common "gotchas".
|
||||
|
||||
## ABCI connections
|
||||
|
||||
Tendermint opens 3 ABCI connections to the app: one for Consensus, one for
|
||||
Mempool, one for Queries.
|
||||
|
||||
## Async vs Sync
|
||||
|
||||
The main ABCI server (ie. non-GRPC) provides ordered asynchronous messages.
|
||||
This is useful for DeliverTx and CheckTx, since it allows Tendermint to forward
|
||||
transactions to the app before it's finished processing previous ones.
|
||||
|
||||
Thus, DeliverTx and CheckTx messages are sent asycnhronously, while all other
|
||||
messages are sent synchronously.
|
||||
|
||||
## CheckTx and Commit
|
||||
|
||||
It is typical to hold three distinct states in an ABCI app: CheckTxState, DeliverTxState,
|
||||
QueryState. The QueryState contains the latest committed state for a block.
|
||||
The CheckTxState and DeliverTxState may be updated concurrently with one another.
|
||||
Before Commit is called, Tendermint locks and flushes the mempool so that no new changes will happen
|
||||
to CheckTxState. When Commit completes, it unlocks the mempool.
|
||||
|
||||
Thus, during Commit, it is safe to reset the QueryState and the CheckTxState to the latest DeliverTxState
|
||||
(ie. the new state from executing all the txs in the block).
|
||||
|
||||
Note, however, that it is not possible to send transactions to Tendermint during Commit - if your app
|
||||
tries to send a `/broadcast_tx` to Tendermint during Commit, it will deadlock.
|
||||
|
||||
## EndBlock Validator Updates
|
||||
|
||||
Updates to the Tendermint validator set can be made by returning `Validator`
|
||||
objects in the `ResponseBeginBlock`:
|
||||
|
||||
```
|
||||
message Validator {
|
||||
PubKey pub_key
|
||||
int64 power
|
||||
}
|
||||
|
||||
message PubKey {
|
||||
string type
|
||||
bytes data
|
||||
}
|
||||
```
|
||||
|
||||
The `pub_key` currently supports two types:
|
||||
|
||||
- `type = "ed25519" and`data = <raw 32-byte public key>`
|
||||
- `type = "secp256k1" and `data = <33-byte OpenSSL compressed public key>`
|
||||
|
||||
If the address is provided, it must match the address of the pubkey, as
|
||||
specified [here](/docs/spec/blockchain/encoding.md#Addresses)
|
||||
|
||||
(Note: In the v0.19 series, the `pub_key` is the [Amino encoded public
|
||||
key](/docs/spec/blockchain/encoding.md#public-key-cryptography).
|
||||
For Ed25519 pubkeys, the Amino prefix is always "1624DE6220". For example, the 32-byte Ed25519 pubkey
|
||||
`76852933A4686A721442E931A8415F62F5F1AEDF4910F1F252FB393F74C40C85` would be
|
||||
Amino encoded as
|
||||
`1624DE622076852933A4686A721442E931A8415F62F5F1AEDF4910F1F252FB393F74C40C85`)
|
||||
|
||||
(Note: In old versions of Tendermint (pre-v0.19.0), the pubkey is just prefixed with a
|
||||
single type byte, so for ED25519 we'd have `pub_key = 0x1 | pub`)
|
||||
|
||||
The `power` is the new voting power for the validator, with the
|
||||
following rules:
|
||||
|
||||
- power must be non-negative
|
||||
- if power is 0, the validator must already exist, and will be removed from the
|
||||
validator set
|
||||
- if power is non-0:
|
||||
- if the validator does not already exist, it will be added to the validator
|
||||
set with the given power
|
||||
- if the validator does already exist, its power will be adjusted to the given power
|
||||
|
||||
## InitChain Validator Updates
|
||||
|
||||
ResponseInitChain has the option to return a list of validators.
|
||||
If the list is not empty, Tendermint will adopt it for the validator set.
|
||||
This way the application can determine the initial validator set for the
|
||||
blockchain.
|
||||
|
||||
ResponseInitChain also includes ConsensusParams, but these are presently
|
||||
ignored.
|
||||
|
||||
## Query
|
||||
|
||||
Query is a generic message type with lots of flexibility to enable diverse sets
|
||||
of queries from applications. Tendermint has no requirements from the Query
|
||||
message for normal operation - that is, the ABCI app developer need not implement Query functionality if they do not wish too.
|
||||
That said, Tendermint makes a number of queries to support some optional
|
||||
features. These are:
|
||||
|
||||
### Peer Filtering
|
||||
|
||||
When Tendermint connects to a peer, it sends two queries to the ABCI application
|
||||
using the following paths, with no additional data:
|
||||
|
||||
- `/p2p/filter/addr/<IP:PORT>`, where `<IP:PORT>` denote the IP address and
|
||||
the port of the connection
|
||||
- `p2p/filter/id/<ID>`, where `<ID>` is the peer node ID (ie. the
|
||||
pubkey.Address() for the peer's PubKey)
|
||||
|
||||
If either of these queries return a non-zero ABCI code, Tendermint will refuse
|
||||
to connect to the peer.
|
||||
|
||||
## Info and the Handshake/Replay
|
||||
|
||||
On startup, Tendermint calls Info on the Query connection to get the latest
|
||||
committed state of the app. The app MUST return information consistent with the
|
||||
last block it succesfully completed Commit for.
|
||||
|
||||
If the app succesfully committed block H but not H+1, then `last_block_height = H` and `last_block_app_hash = <hash returned by Commit for block H>`. If the app
|
||||
failed during the Commit of block H, then `last_block_height = H-1` and
|
||||
`last_block_app_hash = <hash returned by Commit for block H-1, which is the hash in the header of block H>`.
|
||||
|
||||
We now distinguish three heights, and describe how Tendermint syncs itself with
|
||||
the app.
|
||||
|
||||
```
|
||||
storeBlockHeight = height of the last block Tendermint saw a commit for
|
||||
stateBlockHeight = height of the last block for which Tendermint completed all
|
||||
block processing and saved all ABCI results to disk
|
||||
appBlockHeight = height of the last block for which ABCI app succesfully
|
||||
completely Commit
|
||||
```
|
||||
|
||||
Note we always have `storeBlockHeight >= stateBlockHeight` and `storeBlockHeight >= appBlockHeight`
|
||||
Note also we never call Commit on an ABCI app twice for the same height.
|
||||
|
||||
The procedure is as follows.
|
||||
|
||||
First, some simeple start conditions:
|
||||
|
||||
If `appBlockHeight == 0`, then call InitChain.
|
||||
|
||||
If `storeBlockHeight == 0`, we're done.
|
||||
|
||||
Now, some sanity checks:
|
||||
|
||||
If `storeBlockHeight < appBlockHeight`, error
|
||||
If `storeBlockHeight < stateBlockHeight`, panic
|
||||
If `storeBlockHeight > stateBlockHeight+1`, panic
|
||||
|
||||
Now, the meat:
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`,
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight`.
|
||||
This happens if we completed processing the block, but the app forgot its height.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done
|
||||
This happens if we crashed at an opportune spot.
|
||||
|
||||
If `storeBlockHeight == stateBlockHeight+1`
|
||||
This happens if we started processing the block but didn't finish.
|
||||
|
||||
If `appBlockHeight < stateBlockHeight`
|
||||
replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`,
|
||||
and replay the block at `storeBlockHeight` using the WAL.
|
||||
This happens if the app forgot the last block it committed.
|
||||
|
||||
If `appBlockHeight == stateBlockHeight`,
|
||||
replay the last block (storeBlockHeight) in full.
|
||||
This happens if we crashed before the app finished Commit
|
||||
|
||||
If appBlockHeight == storeBlockHeight {
|
||||
update the state using the saved ABCI responses but dont run the block against the real app.
|
||||
This happens if we crashed after the app finished Commit but before Tendermint saved the state.
|
||||
This page has [moved](../abci/apps.md).
|
||||
|
|
|
@ -30,7 +30,7 @@ moniker = "anonymous"
|
|||
# and verifying their commits
|
||||
fast_sync = true
|
||||
|
||||
# Database backend: leveldb | memdb
|
||||
# Database backend: leveldb | memdb | cleveldb
|
||||
db_backend = "leveldb"
|
||||
|
||||
# Database directory
|
||||
|
|
|
@ -127,7 +127,7 @@ little overview what they do.
|
|||
found
|
||||
[here](https://github.com/tendermint/tendermint/blob/master/types/events.go).
|
||||
You can subscribe to them by calling `subscribe` RPC method. Refer
|
||||
to [RPC docs](./specification/rpc.md) for additional information.
|
||||
to [RPC docs](./rpc.md) for additional information.
|
||||
- `mempool` Mempool module handles all incoming transactions, whenever
|
||||
they are coming from peers or the application.
|
||||
- `p2p` Provides an abstraction around peer-to-peer communication. For
|
||||
|
|
|
@ -10,11 +10,11 @@ package](https://godoc.org/github.com/tendermint/tendermint/lite).
|
|||
## Overview
|
||||
|
||||
The objective of the light client protocol is to get a
|
||||
[commit](./validators.md#committing-a-block) for a recent [block
|
||||
hash](../spec/consensus/consensus.md.md#block-hash) where the commit includes a
|
||||
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](./merkle.md#iavl-tree).
|
||||
proofs](../spec/blockchain/encoding.md#iavl-tree).
|
||||
|
||||
## Properties
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# RPC
|
||||
|
||||
The RPC documentation is hosted [here](https://tendermint.github.io/slate) and is generated by the CI from our [Slate repo](https://github.com/tendermint/slate). To update the documentation, edit the relevant `godoc` comments in the [rpc/core directory](https://github.com/tendermint/tendermint/tree/develop/rpc/core).
|
||||
The RPC documentation is hosted here:
|
||||
|
||||
NOTE: We will be moving the RPC documentation into the website in the near future. Stay tuned!
|
||||
- https://tendermint.com/rpc/
|
||||
|
||||
To update the documentation, edit the relevant `godoc` comments in the [rpc/core directory](https://github.com/tendermint/tendermint/tree/develop/rpc/core).
|
||||
|
|
|
@ -1,5 +1,41 @@
|
|||
# Running in production
|
||||
|
||||
## Database
|
||||
|
||||
By default, Tendermint uses the `syndtr/goleveldb` package for it's in-process
|
||||
key-value database. Unfortunately, this implementation of LevelDB seems to suffer under heavy load (see
|
||||
[#226](https://github.com/syndtr/goleveldb/issues/226)). It may be best to
|
||||
install the real C-implementation of LevelDB and compile Tendermint to use
|
||||
that using `make build_c`. See the [install instructions](../introduction/install.md) for details.
|
||||
|
||||
Tendermint keeps multiple distinct LevelDB databases in the `$TMROOT/data`:
|
||||
|
||||
- `blockstore.db`: Keeps the entire blockchain - stores blocks,
|
||||
block commits, and block meta data, each indexed by height. Used to sync new
|
||||
peers.
|
||||
- `evidence.db`: Stores all verified evidence of misbehaviour.
|
||||
- `state.db`: Stores the current blockchain state (ie. height, validators,
|
||||
consensus params). Only grows if consensus params or validators change. Also
|
||||
used to temporarily store intermediate results during block processing.
|
||||
- `tx_index.db`: Indexes txs (and their results) by tx hash and by DeliverTx result tags.
|
||||
|
||||
By default, Tendermint will only index txs by their hash, not by their DeliverTx
|
||||
result tags. See [indexing transactions](../app-dev/indexing-transactions.md) for
|
||||
details.
|
||||
|
||||
There is no current strategy for pruning the databases. Consider reducing
|
||||
block production by [controlling empty blocks](../tendermint-core/using-tendermint.md#no-empty-blocks)
|
||||
or by increasing the `consensus.timeout_commit` param. Note both of these are
|
||||
local settings and not enforced by the consensus.
|
||||
|
||||
We're working on [state
|
||||
syncing](https://github.com/tendermint/tendermint/issues/828),
|
||||
which will enable history to be thrown away
|
||||
and recent application state to be directly synced. We'll need to develop solutions
|
||||
for archival nodes that allow queries on historical transactions and states.
|
||||
The Cosmos project has had much success just dumping the latest state of a
|
||||
blockchain to disk and starting a new chain from that state.
|
||||
|
||||
## Logging
|
||||
|
||||
Default logging level (`main:info,state:info,*:`) should suffice for
|
||||
|
@ -11,6 +47,33 @@ you're trying to debug Tendermint or asked to provide logs with debug
|
|||
logging level, you can do so by running tendermint with
|
||||
`--log_level="*:debug"`.
|
||||
|
||||
## Write Ahead Logs (WAL)
|
||||
|
||||
Tendermint uses write ahead logs for the consensus (`cs.wal`) and the mempool
|
||||
(`mempool.wal`). Both WALs have a max size of 1GB and are automatically rotated.
|
||||
|
||||
### Consensus WAL
|
||||
|
||||
The `consensus.wal` is used to ensure we can recover from a crash at any point
|
||||
in the consensus state machine.
|
||||
It writes all consensus messages (timeouts, proposals, block part, or vote)
|
||||
to a single file, flushing to disk before processing messages from its own
|
||||
validator. Since Tendermint validators are expected to never sign a conflicting vote, the
|
||||
WAL ensures we can always recover deterministically to the latest state of the consensus without
|
||||
using the network or re-signing any consensus messages.
|
||||
|
||||
If your `consensus.wal` is corrupted, see [below](#wal-corruption).
|
||||
|
||||
### Mempool WAL
|
||||
|
||||
The `mempool.wal` logs all incoming txs before running CheckTx, but is
|
||||
otherwise not used in any programmatic way. It's just a kind of manual
|
||||
safe guard. Note the mempool provides no durability guarantees - a tx sent to one or many nodes
|
||||
may never make it into the blockchain if those nodes crash before being able to
|
||||
propose it. Clients must monitor their txs by subscribing over websockets,
|
||||
polling for them, or using `/broadcast_tx_commit`. In the worst case, txs can be
|
||||
resent from the mempool WAL manually.
|
||||
|
||||
## DOS Exposure and Mitigation
|
||||
|
||||
Validators are supposed to setup [Sentry Node
|
||||
|
@ -28,7 +91,8 @@ send & receive rate per connection (`SendRate`, `RecvRate`).
|
|||
### RPC
|
||||
|
||||
Endpoints returning multiple entries are limited by default to return 30
|
||||
elements (100 max). See [here](./rpc.md) for more information about the RPC.
|
||||
elements (100 max). See the [RPC Documentation](https://tendermint.com/rpc/)
|
||||
for more information.
|
||||
|
||||
Rate-limiting and authentication are another key aspects to help protect
|
||||
against DOS attacks. While in the future we may implement these
|
||||
|
|
|
@ -8,41 +8,43 @@ Each peer generates an ED25519 key-pair to use as a persistent
|
|||
(long-term) id.
|
||||
|
||||
When two peers establish a TCP connection, they first each generate an
|
||||
ephemeral ED25519 key-pair to use for this session, and send each other
|
||||
ephemeral X25519 key-pair to use for this session, and send each other
|
||||
their respective ephemeral public keys. This happens in the clear.
|
||||
|
||||
They then each compute the shared secret. The shared secret is the
|
||||
multiplication of the peer's ephemeral private key by the other peer's
|
||||
ephemeral public key. The result is the same for both peers by the magic
|
||||
of [elliptic
|
||||
curves](https://en.wikipedia.org/wiki/Elliptic_curve_cryptography). The
|
||||
shared secret is used as the symmetric key for the encryption algorithm.
|
||||
They then each compute the shared secret, as done in a [diffie hellman
|
||||
key exhange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange).
|
||||
The shared secret is used as the symmetric key for the encryption algorithm.
|
||||
|
||||
The two ephemeral public keys are sorted to establish a canonical order.
|
||||
Then a 24-byte nonce is generated by concatenating the public keys and
|
||||
hashing them with Ripemd160. Note Ripemd160 produces 20byte hashes, so
|
||||
the nonce ends with four 0s.
|
||||
We then run [hkdf-sha256](https://en.wikipedia.org/wiki/HKDF) to expand the
|
||||
shared secret to generate a symmetric key for sending data,
|
||||
a symmetric key for receiving data,
|
||||
a challenge to authenticate the other party.
|
||||
One peer will send data with their sending key, and the other peer
|
||||
would decode it using their own receiving key.
|
||||
We must ensure that both parties don't try to use the same key as the sending
|
||||
key, and the same key as the receiving key, as in that case nothing can be
|
||||
decoded.
|
||||
To ensure this, the peer with the canonically smaller ephemeral pubkey
|
||||
uses the first key as their receiving key, and the second key as their sending key.
|
||||
If the peer has the canonically larger ephemeral pubkey, they do the reverse.
|
||||
|
||||
The nonce is used to seed the encryption - it is critical that the same
|
||||
nonce never be used twice with the same private key. For convenience,
|
||||
the last bit of the nonce is flipped, giving us two nonces: one for
|
||||
encrypting our own messages, one for decrypting our peer's. Which ever
|
||||
peer has the higher public key uses the "bit-flipped" nonce for
|
||||
encryption.
|
||||
Each peer also keeps a received message counter and sent message counter, both
|
||||
are initialized to zero.
|
||||
All future communication is encrypted using chacha20poly1305.
|
||||
The key used to send the message is the sending key, and the key used to decode
|
||||
the message is the receiving key.
|
||||
The nonce for chacha20poly1305 is the relevant message counter.
|
||||
It is critical that the message counter is incremented every time you send a
|
||||
message and every time you receive a message that decodes correctly.
|
||||
|
||||
Now, a challenge is generated by concatenating the ephemeral public keys
|
||||
and taking the SHA256 hash.
|
||||
|
||||
Each peer signs the challenge with their persistent private key, and
|
||||
Each peer now signs the challenge with their persistent private key, and
|
||||
sends the other peer an AuthSigMsg, containing their persistent public
|
||||
key and the signature. On receiving an AuthSigMsg, the peer verifies the
|
||||
signature.
|
||||
|
||||
The peers are now authenticated.
|
||||
|
||||
All future communications can now be encrypted using the shared secret
|
||||
and the generated nonces, where each nonce is incremented by one each
|
||||
time it is used. The communications maintain Perfect Forward Secrecy, as
|
||||
The communication maintains Perfect Forward Secrecy, as
|
||||
the persistent key pair was not used for generating secrets - only for
|
||||
authenticating.
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ definition](https://github.com/tendermint/tendermint/blob/master/types/genesis.g
|
|||
- `genesis_time`: Official time of blockchain start.
|
||||
- `chain_id`: ID of the blockchain. This must be unique for
|
||||
every blockchain. If your testnet blockchains do not have unique
|
||||
chain IDs, you will have a bad time. The ChainID must be less than 50 bytes.
|
||||
chain IDs, you will have a bad time. The ChainID must be less than 50 symbols.
|
||||
- `validators`: List of initial validators. Note this may be overridden entirely by the
|
||||
application, and may be left empty to make explicit that the
|
||||
application will initialize the validator set with ResponseInitChain.
|
||||
|
@ -156,6 +156,10 @@ Visit http://localhost:26657 in your browser to see the list of other
|
|||
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/)
|
||||
:::
|
||||
|
||||
### Formatting
|
||||
|
||||
The following nuances when sending/formatting transactions should be
|
||||
|
@ -209,23 +213,19 @@ Note that raw hex cannot be used in `POST` transactions.
|
|||
**WARNING: UNSAFE** Only do this in development and only if you can
|
||||
afford to lose all blockchain data!
|
||||
|
||||
To reset a blockchain, stop the node, remove the `~/.tendermint/data`
|
||||
directory and run
|
||||
To reset a blockchain, stop the node and run:
|
||||
|
||||
```
|
||||
tendermint unsafe_reset_priv_validator
|
||||
tendermint unsafe_reset_all
|
||||
```
|
||||
|
||||
This final step is necessary to reset the `priv_validator.json`, which
|
||||
otherwise prevents you from making conflicting votes in the consensus
|
||||
(something that could get you in trouble if you do it on a real
|
||||
blockchain). If you don't reset the `priv_validator.json`, your fresh
|
||||
new blockchain will not make any blocks.
|
||||
This command will remove the data directory and reset private validator and
|
||||
address book files.
|
||||
|
||||
## Configuration
|
||||
|
||||
Tendermint uses a `config.toml` for configuration. For details, see [the
|
||||
config specification](./tendermint-core/configuration.md).
|
||||
config specification](./configuration.md).
|
||||
|
||||
Notable options include the socket address of the application
|
||||
(`proxy_app`), the listening address of the Tendermint peer
|
||||
|
@ -305,6 +305,12 @@ can take on the order of a second. For a quick result, use
|
|||
`broadcast_tx_sync`, but the transaction will not be committed until
|
||||
later, and by that point its effect on the state may change.
|
||||
|
||||
Note the mempool does not provide strong guarantees - just because a tx passed
|
||||
CheckTx (ie. was accepted into the mempool), doesn't mean it will be committed,
|
||||
as nodes with the tx in their mempool may crash before they get to propose.
|
||||
For more information, see the [mempool
|
||||
write-ahead-log](../tendermint-core/running-in-production.md#mempool-wal)
|
||||
|
||||
## Tendermint Networks
|
||||
|
||||
When `tendermint init` is run, both a `genesis.json` and
|
||||
|
|
|
@ -22,7 +22,7 @@ Validators have a cryptographic key-pair and an associated amount of
|
|||
|
||||
There are two ways to become validator.
|
||||
|
||||
1. They can be pre-established in the [genesis state](../../tendermint-core/using-tendermint.md#genesis)
|
||||
1. They can be pre-established in the [genesis state](./using-tendermint.md#genesis)
|
||||
2. The ABCI app responds to the EndBlock message with changes to the
|
||||
existing validator set.
|
||||
|
||||
|
@ -36,4 +36,4 @@ The +2/3 set of precommit votes is called a
|
|||
[_commit_](../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#last-commit)).
|
||||
[LastCommit](../spec/blockchain/blockchain.md#lastcommit)).
|
||||
|
|
|
@ -59,7 +59,7 @@ func (evpool *EvidencePool) PriorityEvidence() []types.Evidence {
|
|||
|
||||
// PendingEvidence returns uncommitted evidence up to maxBytes.
|
||||
// If maxBytes is -1, all evidence is returned.
|
||||
func (evpool *EvidencePool) PendingEvidence(maxBytes int) []types.Evidence {
|
||||
func (evpool *EvidencePool) PendingEvidence(maxBytes int64) []types.Evidence {
|
||||
return evpool.evidenceStore.PendingEvidence(maxBytes)
|
||||
}
|
||||
|
||||
|
|
|
@ -88,23 +88,23 @@ func (store *EvidenceStore) PriorityEvidence() (evidence []types.Evidence) {
|
|||
|
||||
// PendingEvidence returns known uncommitted evidence up to maxBytes.
|
||||
// If maxBytes is -1, all evidence is returned.
|
||||
func (store *EvidenceStore) PendingEvidence(maxBytes int) (evidence []types.Evidence) {
|
||||
func (store *EvidenceStore) PendingEvidence(maxBytes int64) (evidence []types.Evidence) {
|
||||
return store.listEvidence(baseKeyPending, maxBytes)
|
||||
}
|
||||
|
||||
// listEvidence lists the evidence for the given prefix key up to maxBytes.
|
||||
// It is wrapped by PriorityEvidence and PendingEvidence for convenience.
|
||||
// If maxBytes is -1, there's no cap on the size of returned evidence.
|
||||
func (store *EvidenceStore) listEvidence(prefixKey string, maxBytes int) (evidence []types.Evidence) {
|
||||
var bytes int
|
||||
func (store *EvidenceStore) listEvidence(prefixKey string, maxBytes int64) (evidence []types.Evidence) {
|
||||
var bytes int64
|
||||
iter := dbm.IteratePrefix(store.db, []byte(prefixKey))
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
val := iter.Value()
|
||||
|
||||
if maxBytes > 0 && bytes+len(val) > maxBytes {
|
||||
if maxBytes > 0 && bytes+int64(len(val)) > maxBytes {
|
||||
return evidence
|
||||
}
|
||||
bytes += len(val)
|
||||
bytes += int64(len(val))
|
||||
|
||||
var ei EvidenceInfo
|
||||
err := cdc.UnmarshalBinaryBare(val, &ei)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/errors"
|
||||
)
|
||||
|
||||
/* AutoFile usage
|
||||
|
@ -30,7 +31,10 @@ if err != nil {
|
|||
}
|
||||
*/
|
||||
|
||||
const autoFileOpenDuration = 1000 * time.Millisecond
|
||||
const (
|
||||
autoFileOpenDuration = 1000 * time.Millisecond
|
||||
autoFilePerms = os.FileMode(0600)
|
||||
)
|
||||
|
||||
// Automatically closes and re-opens file for writing.
|
||||
// This is useful for using a log file with the logrotate tool.
|
||||
|
@ -116,10 +120,17 @@ func (af *AutoFile) Sync() error {
|
|||
}
|
||||
|
||||
func (af *AutoFile) openFile() error {
|
||||
file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
|
||||
file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, autoFilePerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileInfo.Mode() != autoFilePerms {
|
||||
return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms)
|
||||
}
|
||||
af.file = file
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -8,41 +8,33 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/errors"
|
||||
)
|
||||
|
||||
func TestSIGHUP(t *testing.T) {
|
||||
|
||||
// First, create an AutoFile writing to a tempfile dir
|
||||
file, err := ioutil.TempFile("", "sighup_test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating tempfile: %v", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatalf("Error closing tempfile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
name := file.Name()
|
||||
|
||||
// Here is the actual AutoFile
|
||||
af, err := OpenAutoFile(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating autofile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write to the file.
|
||||
_, err = af.Write([]byte("Line 1\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to autofile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
_, err = af.Write([]byte("Line 2\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to autofile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Move the file over
|
||||
err = os.Rename(name, name+"_old")
|
||||
if err != nil {
|
||||
t.Fatalf("Error moving autofile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send SIGHUP to self.
|
||||
oldSighupCounter := atomic.LoadInt32(&sighupCounter)
|
||||
|
@ -55,16 +47,11 @@ func TestSIGHUP(t *testing.T) {
|
|||
|
||||
// Write more to the file.
|
||||
_, err = af.Write([]byte("Line 3\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to autofile: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
_, err = af.Write([]byte("Line 4\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to autofile: %v", err)
|
||||
}
|
||||
if err := af.Close(); err != nil {
|
||||
t.Fatalf("Error closing autofile")
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = af.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both files should exist
|
||||
if body := cmn.MustReadFile(name + "_old"); string(body) != "Line 1\nLine 2\n" {
|
||||
|
@ -74,3 +61,33 @@ func TestSIGHUP(t *testing.T) {
|
|||
t.Errorf("Unexpected body %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Manually modify file permissions, close, and reopen using autofile:
|
||||
// We expect the file permissions to be changed back to the intended perms.
|
||||
func TestOpenAutoFilePerms(t *testing.T) {
|
||||
file, err := ioutil.TempFile("", "permission_test")
|
||||
require.NoError(t, err)
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
name := file.Name()
|
||||
|
||||
// open and change permissions
|
||||
af, err := OpenAutoFile(name)
|
||||
require.NoError(t, err)
|
||||
err = af.file.Chmod(0755)
|
||||
require.NoError(t, err)
|
||||
err = af.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// reopen and expect an ErrPermissionsChanged as Cause
|
||||
af, err = OpenAutoFile(name)
|
||||
require.Error(t, err)
|
||||
if e, ok := err.(*errors.ErrPermissionsChanged); ok {
|
||||
t.Logf("%v", e)
|
||||
} else {
|
||||
t.Errorf("unexpected error %v", e)
|
||||
}
|
||||
|
||||
err = af.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Fingerprint returns the first 6 bytes of a byte slice.
|
||||
// If the slice is less than 6 bytes, the fingerprint
|
||||
// contains trailing zeroes.
|
||||
|
@ -12,62 +8,3 @@ func Fingerprint(slice []byte) []byte {
|
|||
copy(fingerprint, slice)
|
||||
return fingerprint
|
||||
}
|
||||
|
||||
func IsZeros(slice []byte) bool {
|
||||
for _, byt := range slice {
|
||||
if byt != byte(0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func RightPadBytes(slice []byte, l int) []byte {
|
||||
if l < len(slice) {
|
||||
return slice
|
||||
}
|
||||
padded := make([]byte, l)
|
||||
copy(padded[0:len(slice)], slice)
|
||||
return padded
|
||||
}
|
||||
|
||||
func LeftPadBytes(slice []byte, l int) []byte {
|
||||
if l < len(slice) {
|
||||
return slice
|
||||
}
|
||||
padded := make([]byte, l)
|
||||
copy(padded[l-len(slice):], slice)
|
||||
return padded
|
||||
}
|
||||
|
||||
func TrimmedString(b []byte) string {
|
||||
trimSet := string([]byte{0})
|
||||
return string(bytes.TrimLeft(b, trimSet))
|
||||
|
||||
}
|
||||
|
||||
// PrefixEndBytes returns the end byteslice for a noninclusive range
|
||||
// that would include all byte slices for which the input is the prefix
|
||||
func PrefixEndBytes(prefix []byte) []byte {
|
||||
if prefix == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
end := make([]byte, len(prefix))
|
||||
copy(end, prefix)
|
||||
finished := false
|
||||
|
||||
for !finished {
|
||||
if end[len(end)-1] != byte(255) {
|
||||
end[len(end)-1]++
|
||||
finished = true
|
||||
} else {
|
||||
end = end[:len(end)-1]
|
||||
if len(end) == 0 {
|
||||
end = nil
|
||||
finished = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPrefixEndBytes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var testCases = []struct {
|
||||
prefix []byte
|
||||
expected []byte
|
||||
}{
|
||||
{[]byte{byte(55), byte(255), byte(255), byte(0)}, []byte{byte(55), byte(255), byte(255), byte(1)}},
|
||||
{[]byte{byte(55), byte(255), byte(255), byte(15)}, []byte{byte(55), byte(255), byte(255), byte(16)}},
|
||||
{[]byte{byte(55), byte(200), byte(255)}, []byte{byte(55), byte(201)}},
|
||||
{[]byte{byte(55), byte(255), byte(255)}, []byte{byte(56)}},
|
||||
{[]byte{byte(255), byte(255), byte(255)}, nil},
|
||||
{nil, nil},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
end := PrefixEndBytes(test.prefix)
|
||||
assert.Equal(test.expected, end)
|
||||
}
|
||||
}
|
|
@ -1,59 +1,5 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sort for []uint64
|
||||
|
||||
type Uint64Slice []uint64
|
||||
|
||||
func (p Uint64Slice) Len() int { return len(p) }
|
||||
func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p Uint64Slice) Sort() { sort.Sort(p) }
|
||||
|
||||
func SearchUint64s(a []uint64, x uint64) int {
|
||||
return sort.Search(len(a), func(i int) bool { return a[i] >= x })
|
||||
}
|
||||
|
||||
func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) }
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func PutUint64LE(dest []byte, i uint64) {
|
||||
binary.LittleEndian.PutUint64(dest, i)
|
||||
}
|
||||
|
||||
func GetUint64LE(src []byte) uint64 {
|
||||
return binary.LittleEndian.Uint64(src)
|
||||
}
|
||||
|
||||
func PutUint64BE(dest []byte, i uint64) {
|
||||
binary.BigEndian.PutUint64(dest, i)
|
||||
}
|
||||
|
||||
func GetUint64BE(src []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(src)
|
||||
}
|
||||
|
||||
func PutInt64LE(dest []byte, i int64) {
|
||||
binary.LittleEndian.PutUint64(dest, uint64(i))
|
||||
}
|
||||
|
||||
func GetInt64LE(src []byte) int64 {
|
||||
return int64(binary.LittleEndian.Uint64(src))
|
||||
}
|
||||
|
||||
func PutInt64BE(dest []byte, i int64) {
|
||||
binary.BigEndian.PutUint64(dest, uint64(i))
|
||||
}
|
||||
|
||||
func GetInt64BE(src []byte) int64 {
|
||||
return int64(binary.BigEndian.Uint64(src))
|
||||
}
|
||||
|
||||
// IntInSlice returns true if a is found in the list.
|
||||
func IntInSlice(a int, list []int) bool {
|
||||
for _, b := range list {
|
||||
|
|
|
@ -9,8 +9,15 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyStarted is returned when somebody tries to start an already
|
||||
// running service.
|
||||
ErrAlreadyStarted = errors.New("already started")
|
||||
// ErrAlreadyStopped is returned when somebody tries to stop an already
|
||||
// stopped service (without resetting it).
|
||||
ErrAlreadyStopped = errors.New("already stopped")
|
||||
// ErrNotStarted is returned when somebody tries to stop a not running
|
||||
// service.
|
||||
ErrNotStarted = errors.New("not started")
|
||||
)
|
||||
|
||||
// Service defines a service that can be started, stopped, and reset.
|
||||
|
@ -124,6 +131,8 @@ func (bs *BaseService) Start() error {
|
|||
if atomic.CompareAndSwapUint32(&bs.started, 0, 1) {
|
||||
if atomic.LoadUint32(&bs.stopped) == 1 {
|
||||
bs.Logger.Error(fmt.Sprintf("Not starting %v -- already stopped", bs.name), "impl", bs.impl)
|
||||
// revert flag
|
||||
atomic.StoreUint32(&bs.started, 0)
|
||||
return ErrAlreadyStopped
|
||||
}
|
||||
bs.Logger.Info(fmt.Sprintf("Starting %v", bs.name), "impl", bs.impl)
|
||||
|
@ -148,6 +157,12 @@ func (bs *BaseService) OnStart() error { return nil }
|
|||
// channel. An error will be returned if the service is already stopped.
|
||||
func (bs *BaseService) Stop() error {
|
||||
if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) {
|
||||
if atomic.LoadUint32(&bs.started) == 0 {
|
||||
bs.Logger.Error(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl)
|
||||
// revert flag
|
||||
atomic.StoreUint32(&bs.stopped, 0)
|
||||
return ErrNotStarted
|
||||
}
|
||||
bs.Logger.Info(fmt.Sprintf("Stopping %v", bs.name), "impl", bs.impl)
|
||||
bs.impl.OnStop()
|
||||
close(bs.quit)
|
||||
|
|
|
@ -1,28 +1,10 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsHex returns true for non-empty hex-string prefixed with "0x"
|
||||
func IsHex(s string) bool {
|
||||
if len(s) > 2 && strings.EqualFold(s[:2], "0x") {
|
||||
_, err := hex.DecodeString(s[2:])
|
||||
return err == nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StripHex returns hex string without leading "0x"
|
||||
func StripHex(s string) string {
|
||||
if IsHex(s) {
|
||||
return s[2:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// StringInSlice returns true if a is found the list.
|
||||
func StringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
|
|
|
@ -13,30 +13,12 @@ func TestStringInSlice(t *testing.T) {
|
|||
assert.False(t, StringInSlice("", []string{}))
|
||||
}
|
||||
|
||||
func TestIsHex(t *testing.T) {
|
||||
notHex := []string{
|
||||
"", " ", "a", "x", "0", "0x", "0X", "0x ", "0X ", "0X a",
|
||||
"0xf ", "0x f", "0xp", "0x-",
|
||||
"0xf", "0XBED", "0xF", "0xbed", // Odd lengths
|
||||
}
|
||||
for _, v := range notHex {
|
||||
assert.False(t, IsHex(v), "%q is not hex", v)
|
||||
}
|
||||
hex := []string{
|
||||
"0x00", "0x0a", "0x0F", "0xFFFFFF", "0Xdeadbeef", "0x0BED",
|
||||
"0X12", "0X0A",
|
||||
}
|
||||
for _, v := range hex {
|
||||
assert.True(t, IsHex(v), "%q is hex", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIText(t *testing.T) {
|
||||
notASCIIText := []string{
|
||||
"", "\xC2", "\xC2\xA2", "\xFF", "\x80", "\xF0", "\n", "\t",
|
||||
}
|
||||
for _, v := range notASCIIText {
|
||||
assert.False(t, IsHex(v), "%q is not ascii-text", v)
|
||||
assert.False(t, IsASCIIText(v), "%q is not ascii-text", v)
|
||||
}
|
||||
asciiText := []string{
|
||||
" ", ".", "x", "$", "_", "abcdefg;", "-", "0x00", "0", "123",
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
Zero256 = Word256{0}
|
||||
One256 = Word256{1}
|
||||
)
|
||||
|
||||
type Word256 [32]byte
|
||||
|
||||
func (w Word256) String() string { return string(w[:]) }
|
||||
func (w Word256) TrimmedString() string { return TrimmedString(w.Bytes()) }
|
||||
func (w Word256) Copy() Word256 { return w }
|
||||
func (w Word256) Bytes() []byte { return w[:] } // copied.
|
||||
func (w Word256) Prefix(n int) []byte { return w[:n] }
|
||||
func (w Word256) Postfix(n int) []byte { return w[32-n:] }
|
||||
func (w Word256) IsZero() bool {
|
||||
accum := byte(0)
|
||||
for _, byt := range w {
|
||||
accum |= byt
|
||||
}
|
||||
return accum == 0
|
||||
}
|
||||
func (w Word256) Compare(other Word256) int {
|
||||
return bytes.Compare(w[:], other[:])
|
||||
}
|
||||
|
||||
func Uint64ToWord256(i uint64) Word256 {
|
||||
buf := [8]byte{}
|
||||
PutUint64BE(buf[:], i)
|
||||
return LeftPadWord256(buf[:])
|
||||
}
|
||||
|
||||
func Int64ToWord256(i int64) Word256 {
|
||||
buf := [8]byte{}
|
||||
PutInt64BE(buf[:], i)
|
||||
return LeftPadWord256(buf[:])
|
||||
}
|
||||
|
||||
func RightPadWord256(bz []byte) (word Word256) {
|
||||
copy(word[:], bz)
|
||||
return
|
||||
}
|
||||
|
||||
func LeftPadWord256(bz []byte) (word Word256) {
|
||||
copy(word[32-len(bz):], bz)
|
||||
return
|
||||
}
|
||||
|
||||
func Uint64FromWord256(word Word256) uint64 {
|
||||
buf := word.Postfix(8)
|
||||
return GetUint64BE(buf)
|
||||
}
|
||||
|
||||
func Int64FromWord256(word Word256) int64 {
|
||||
buf := word.Postfix(8)
|
||||
return GetInt64BE(buf)
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
type Tuple256 struct {
|
||||
First Word256
|
||||
Second Word256
|
||||
}
|
||||
|
||||
func (tuple Tuple256) Compare(other Tuple256) int {
|
||||
firstCompare := tuple.First.Compare(other.First)
|
||||
if firstCompare == 0 {
|
||||
return tuple.Second.Compare(other.Second)
|
||||
}
|
||||
return firstCompare
|
||||
}
|
||||
|
||||
func Tuple256Split(t Tuple256) (Word256, Word256) {
|
||||
return t.First, t.Second
|
||||
}
|
||||
|
||||
type Tuple256Slice []Tuple256
|
||||
|
||||
func (p Tuple256Slice) Len() int { return len(p) }
|
||||
func (p Tuple256Slice) Less(i, j int) bool {
|
||||
return p[i].Compare(p[j]) < 0
|
||||
}
|
||||
func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p Tuple256Slice) Sort() { sort.Sort(p) }
|
|
@ -1,3 +0,0 @@
|
|||
Tendermint Go-DB Copyright (C) 2015 All in Bits, Inc
|
||||
|
||||
Released under the Apache2.0 license
|
|
@ -1 +0,0 @@
|
|||
TODO: syndtr/goleveldb should be replaced with actual LevelDB instance
|
|
@ -13,7 +13,10 @@ import (
|
|||
)
|
||||
|
||||
func cleanupDBDir(dir, name string) {
|
||||
os.RemoveAll(filepath.Join(dir, name) + ".db")
|
||||
err := os.RemoveAll(filepath.Join(dir, name) + ".db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
|
||||
|
@ -21,6 +24,7 @@ func testBackendGetSetDelete(t *testing.T, backend DBBackendType) {
|
|||
dirname, err := ioutil.TempDir("", fmt.Sprintf("test_backend_%s_", backend))
|
||||
require.Nil(t, err)
|
||||
db := NewDB("testdb", backend, dirname)
|
||||
defer cleanupDBDir(dirname, "testdb")
|
||||
|
||||
// A nonexistent key should return nil, even if the key is empty
|
||||
require.Nil(t, db.Get([]byte("")))
|
||||
|
@ -55,9 +59,10 @@ func TestBackendsGetSetDelete(t *testing.T) {
|
|||
|
||||
func withDB(t *testing.T, creator dbCreator, fn func(DB)) {
|
||||
name := fmt.Sprintf("test_%x", cmn.RandStr(12))
|
||||
db, err := creator(name, "")
|
||||
defer cleanupDBDir("", name)
|
||||
assert.Nil(t, err)
|
||||
dir := os.TempDir()
|
||||
db, err := creator(name, dir)
|
||||
require.Nil(t, err)
|
||||
defer cleanupDBDir(dir, name)
|
||||
fn(db)
|
||||
db.Close()
|
||||
}
|
||||
|
@ -161,8 +166,9 @@ func TestDBIterator(t *testing.T) {
|
|||
|
||||
func testDBIterator(t *testing.T, backend DBBackendType) {
|
||||
name := fmt.Sprintf("test_%x", cmn.RandStr(12))
|
||||
db := NewDB(name, backend, "")
|
||||
defer cleanupDBDir("", name)
|
||||
dir := os.TempDir()
|
||||
db := NewDB(name, backend, dir)
|
||||
defer cleanupDBDir(dir, name)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if i != 6 { // but skip 6.
|
||||
|
|
|
@ -5,9 +5,11 @@ package db
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
|
@ -32,7 +34,7 @@ func BenchmarkRandomReadsWrites2(b *testing.B) {
|
|||
// Write something
|
||||
{
|
||||
idx := (int64(cmn.RandInt()) % numItems)
|
||||
internal[idx] += 1
|
||||
internal[idx]++
|
||||
val := internal[idx]
|
||||
idxBytes := int642Bytes(int64(idx))
|
||||
valBytes := int642Bytes(int64(val))
|
||||
|
@ -88,8 +90,11 @@ func bytes2Int64(buf []byte) int64 {
|
|||
|
||||
func TestCLevelDBBackend(t *testing.T) {
|
||||
name := fmt.Sprintf("test_%x", cmn.RandStr(12))
|
||||
db := NewDB(name, LevelDBBackend, "")
|
||||
defer cleanupDBDir("", name)
|
||||
// Can't use "" (current directory) or "./" here because levigo.Open returns:
|
||||
// "Error initializing DB: IO error: test_XXX.db: Invalid argument"
|
||||
dir := os.TempDir()
|
||||
db := NewDB(name, LevelDBBackend, dir)
|
||||
defer cleanupDBDir(dir, name)
|
||||
|
||||
_, ok := db.(*CLevelDB)
|
||||
assert.True(t, ok)
|
||||
|
|
|
@ -60,11 +60,10 @@ func checkValuePanics(t *testing.T, itr Iterator) {
|
|||
assert.Panics(t, func() { itr.Key() }, "checkValuePanics expected panic but didn't")
|
||||
}
|
||||
|
||||
func newTempDB(t *testing.T, backend DBBackendType) (db DB) {
|
||||
func newTempDB(t *testing.T, backend DBBackendType) (db DB, dbDir string) {
|
||||
dirname, err := ioutil.TempDir("", "db_common_test")
|
||||
require.Nil(t, err)
|
||||
db = NewDB("testdb", backend, dirname)
|
||||
return db
|
||||
return NewDB("testdb", backend, dirname), dirname
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package db
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
// Main entry
|
||||
|
@ -27,8 +30,23 @@ func registerDBCreator(backend DBBackendType, creator dbCreator, force bool) {
|
|||
backends[backend] = creator
|
||||
}
|
||||
|
||||
// NewDB creates a new database of type backend with the given name.
|
||||
// NOTE: function panics if:
|
||||
// - backend is unknown (not registered)
|
||||
// - creator function, provided during registration, returns error
|
||||
func NewDB(name string, backend DBBackendType, dir string) DB {
|
||||
db, err := backends[backend](name, dir)
|
||||
dbCreator, ok := backends[backend]
|
||||
if !ok {
|
||||
keys := make([]string, len(backends))
|
||||
i := 0
|
||||
for k := range backends {
|
||||
keys[i] = string(k)
|
||||
i++
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown db_backend %s, expected either %s", backend, strings.Join(keys, " or ")))
|
||||
}
|
||||
|
||||
db, err := dbCreator(name, dir)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error initializing DB: %v", err))
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package db
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -10,7 +11,9 @@ import (
|
|||
func TestDBIteratorSingleKey(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
db.SetSync(bz("1"), bz("value_1"))
|
||||
itr := db.Iterator(nil, nil)
|
||||
|
||||
|
@ -28,7 +31,9 @@ func TestDBIteratorSingleKey(t *testing.T) {
|
|||
func TestDBIteratorTwoKeys(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
db.SetSync(bz("1"), bz("value_1"))
|
||||
db.SetSync(bz("2"), bz("value_1"))
|
||||
|
||||
|
@ -54,7 +59,8 @@ func TestDBIteratorTwoKeys(t *testing.T) {
|
|||
func TestDBIteratorMany(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
keys := make([][]byte, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
|
@ -78,7 +84,9 @@ func TestDBIteratorMany(t *testing.T) {
|
|||
func TestDBIteratorEmpty(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
itr := db.Iterator(nil, nil)
|
||||
|
||||
checkInvalid(t, itr)
|
||||
|
@ -89,7 +97,9 @@ func TestDBIteratorEmpty(t *testing.T) {
|
|||
func TestDBIteratorEmptyBeginAfter(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
itr := db.Iterator(bz("1"), nil)
|
||||
|
||||
checkInvalid(t, itr)
|
||||
|
@ -100,7 +110,9 @@ func TestDBIteratorEmptyBeginAfter(t *testing.T) {
|
|||
func TestDBIteratorNonemptyBeginAfter(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
db.SetSync(bz("1"), bz("value_1"))
|
||||
itr := db.Iterator(bz("2"), nil)
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
tmerrors "github.com/tendermint/tendermint/libs/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -205,6 +207,13 @@ func write(path string, d []byte) error {
|
|||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
fInfo, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fInfo.Mode() != keyPerm {
|
||||
return tmerrors.NewErrPermissionsChanged(f.Name(), keyPerm, fInfo.Mode())
|
||||
}
|
||||
_, err = f.Write(d)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
|
@ -17,6 +18,7 @@ func TestNewGoLevelDB(t *testing.T) {
|
|||
// Test write locks
|
||||
db, err := NewGoLevelDB(name, "")
|
||||
require.Nil(t, err)
|
||||
defer os.RemoveAll("./" + name + ".db")
|
||||
_, err = NewGoLevelDB(name, "")
|
||||
require.NotNil(t, err)
|
||||
db.Close() // Close the db to release the lock
|
||||
|
|
|
@ -2,6 +2,7 @@ package db
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -9,7 +10,8 @@ import (
|
|||
func TestPrefixIteratorNoMatchNil(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
itr := IteratePrefix(db, []byte("2"))
|
||||
|
||||
checkInvalid(t, itr)
|
||||
|
@ -21,7 +23,8 @@ func TestPrefixIteratorNoMatchNil(t *testing.T) {
|
|||
func TestPrefixIteratorNoMatch1(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
itr := IteratePrefix(db, []byte("2"))
|
||||
db.SetSync(bz("1"), bz("value_1"))
|
||||
|
||||
|
@ -34,7 +37,8 @@ func TestPrefixIteratorNoMatch1(t *testing.T) {
|
|||
func TestPrefixIteratorNoMatch2(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
db.SetSync(bz("3"), bz("value_3"))
|
||||
itr := IteratePrefix(db, []byte("4"))
|
||||
|
||||
|
@ -47,7 +51,8 @@ func TestPrefixIteratorNoMatch2(t *testing.T) {
|
|||
func TestPrefixIteratorMatch1(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
db.SetSync(bz("2"), bz("value_2"))
|
||||
itr := IteratePrefix(db, bz("2"))
|
||||
|
||||
|
@ -65,7 +70,8 @@ func TestPrefixIteratorMatch1(t *testing.T) {
|
|||
func TestPrefixIteratorMatches1N(t *testing.T) {
|
||||
for backend := range backends {
|
||||
t.Run(fmt.Sprintf("Prefix w/ backend %s", backend), func(t *testing.T) {
|
||||
db := newTempDB(t, backend)
|
||||
db, dir := newTempDB(t, backend)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// prefixed
|
||||
db.SetSync(bz("a/1"), bz("value_1"))
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Package errors contains errors that are thrown across packages.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ErrPermissionsChanged occurs if the file permission have changed since the file was created.
|
||||
type ErrPermissionsChanged struct {
|
||||
name string
|
||||
got, want os.FileMode
|
||||
}
|
||||
|
||||
func NewErrPermissionsChanged(name string, got, want os.FileMode) *ErrPermissionsChanged {
|
||||
return &ErrPermissionsChanged{name: name, got: got, want: want}
|
||||
}
|
||||
|
||||
func (e ErrPermissionsChanged) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"file: [%v]\nexpected file permissions: %v, got: %v",
|
||||
e.name,
|
||||
e.want,
|
||||
e.got,
|
||||
)
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"container/list"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
@ -12,17 +11,27 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
auto "github.com/tendermint/tendermint/libs/autofile"
|
||||
"github.com/tendermint/tendermint/libs/clist"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// PreCheckFunc is an optional filter executed before CheckTx and rejects
|
||||
// transaction if false is returned. An example would be to ensure that a
|
||||
// transaction doesn't exceeded the block size.
|
||||
type PreCheckFunc func(types.Tx) bool
|
||||
|
||||
// PostCheckFunc is an optional filter executed after CheckTx and rejects
|
||||
// transaction if false is returned. An example would be to ensure a
|
||||
// transaction doesn't require more gas than available for the block.
|
||||
type PostCheckFunc func(types.Tx, *abci.ResponseCheckTx) bool
|
||||
|
||||
/*
|
||||
|
||||
The mempool pushes new txs onto the proxyAppConn.
|
||||
|
@ -59,6 +68,27 @@ var (
|
|||
ErrMempoolIsFull = errors.New("Mempool is full")
|
||||
)
|
||||
|
||||
// PreCheckAminoMaxBytes checks that the size of the transaction plus the amino
|
||||
// overhead is smaller or equal to the expected maxBytes.
|
||||
func PreCheckAminoMaxBytes(maxBytes int64) PreCheckFunc {
|
||||
return func(tx types.Tx) bool {
|
||||
// We have to account for the amino overhead in the tx size as well
|
||||
aminoOverhead := amino.UvarintSize(uint64(len(tx)))
|
||||
return int64(len(tx)+aminoOverhead) <= maxBytes
|
||||
}
|
||||
}
|
||||
|
||||
// PostCheckMaxGas checks that the wanted gas is smaller or equal to the passed
|
||||
// maxGas. Returns true if maxGas is -1.
|
||||
func PostCheckMaxGas(maxGas int64) PostCheckFunc {
|
||||
return func(tx types.Tx, res *abci.ResponseCheckTx) bool {
|
||||
if maxGas == -1 {
|
||||
return true
|
||||
}
|
||||
return res.GasWanted <= maxGas
|
||||
}
|
||||
}
|
||||
|
||||
// TxID is the hex encoded hash of the bytes as a types.Tx.
|
||||
func TxID(tx []byte) string {
|
||||
return fmt.Sprintf("%X", types.Tx(tx).Hash())
|
||||
|
@ -81,8 +111,8 @@ type Mempool struct {
|
|||
recheckEnd *clist.CElement // re-checking stops here
|
||||
notifiedTxsAvailable bool
|
||||
txsAvailable chan struct{} // fires once for each height, when the mempool is not empty
|
||||
// Filter mempool to only accept txs for which filter(tx) returns true.
|
||||
filter func(types.Tx) bool
|
||||
preCheck PreCheckFunc
|
||||
postCheck PostCheckFunc
|
||||
|
||||
// Keep a cache of already-seen txs.
|
||||
// This reduces the pressure on the proxyApp.
|
||||
|
@ -142,10 +172,16 @@ func (mem *Mempool) SetLogger(l log.Logger) {
|
|||
mem.logger = l
|
||||
}
|
||||
|
||||
// WithFilter sets a filter for mempool to only accept txs for which f(tx)
|
||||
// returns true.
|
||||
func WithFilter(f func(types.Tx) bool) MempoolOption {
|
||||
return func(mem *Mempool) { mem.filter = f }
|
||||
// WithPreCheck sets a filter for the mempool to reject a tx if f(tx) returns
|
||||
// false. This is ran before CheckTx.
|
||||
func WithPreCheck(f PreCheckFunc) MempoolOption {
|
||||
return func(mem *Mempool) { mem.preCheck = f }
|
||||
}
|
||||
|
||||
// WithPostCheck sets a filter for the mempool to reject a tx if f(tx) returns
|
||||
// false. This is ran after CheckTx.
|
||||
func WithPostCheck(f PostCheckFunc) MempoolOption {
|
||||
return func(mem *Mempool) { mem.postCheck = f }
|
||||
}
|
||||
|
||||
// WithMetrics sets the metrics.
|
||||
|
@ -249,7 +285,7 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) {
|
|||
return ErrMempoolIsFull
|
||||
}
|
||||
|
||||
if mem.filter != nil && !mem.filter(tx) {
|
||||
if mem.preCheck != nil && !mem.preCheck(tx) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -299,12 +335,14 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) {
|
|||
switch r := res.Value.(type) {
|
||||
case *abci.Response_CheckTx:
|
||||
tx := req.GetCheckTx().Tx
|
||||
if r.CheckTx.Code == abci.CodeTypeOK {
|
||||
if (r.CheckTx.Code == abci.CodeTypeOK) &&
|
||||
mem.isPostCheckPass(tx, r.CheckTx) {
|
||||
mem.counter++
|
||||
memTx := &mempoolTx{
|
||||
counter: mem.counter,
|
||||
height: mem.height,
|
||||
tx: tx,
|
||||
counter: mem.counter,
|
||||
height: mem.height,
|
||||
gasWanted: r.CheckTx.GasWanted,
|
||||
tx: tx,
|
||||
}
|
||||
mem.txs.PushBack(memTx)
|
||||
mem.logger.Info("Added good transaction", "tx", TxID(tx), "res", r, "total", mem.Size())
|
||||
|
@ -326,10 +364,15 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) {
|
|||
case *abci.Response_CheckTx:
|
||||
memTx := mem.recheckCursor.Value.(*mempoolTx)
|
||||
if !bytes.Equal(req.GetCheckTx().Tx, memTx.tx) {
|
||||
cmn.PanicSanity(fmt.Sprintf("Unexpected tx response from proxy during recheck\n"+
|
||||
"Expected %X, got %X", r.CheckTx.Data, memTx.tx))
|
||||
cmn.PanicSanity(
|
||||
fmt.Sprintf(
|
||||
"Unexpected tx response from proxy during recheck\nExpected %X, got %X",
|
||||
r.CheckTx.Data,
|
||||
memTx.tx,
|
||||
),
|
||||
)
|
||||
}
|
||||
if r.CheckTx.Code == abci.CodeTypeOK {
|
||||
if (r.CheckTx.Code == abci.CodeTypeOK) && mem.isPostCheckPass(memTx.tx, r.CheckTx) {
|
||||
// Good, nothing to do.
|
||||
} else {
|
||||
// Tx became invalidated due to newly committed block.
|
||||
|
@ -380,12 +423,11 @@ func (mem *Mempool) notifyTxsAvailable() {
|
|||
}
|
||||
}
|
||||
|
||||
// ReapMaxBytes reaps transactions from the mempool up to n bytes total.
|
||||
// If max is negative, there is no cap on the size of all returned
|
||||
// ReapMaxBytesMaxGas reaps transactions from the mempool up to maxBytes bytes total
|
||||
// with the condition that the total gasWanted must be less than maxGas.
|
||||
// If both maxes are negative, there is no cap on the size of all returned
|
||||
// transactions (~ all available transactions).
|
||||
func (mem *Mempool) ReapMaxBytes(max int) types.Txs {
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
|
||||
func (mem *Mempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs {
|
||||
mem.proxyMtx.Lock()
|
||||
defer mem.proxyMtx.Unlock()
|
||||
|
||||
|
@ -394,19 +436,25 @@ func (mem *Mempool) ReapMaxBytes(max int) types.Txs {
|
|||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
|
||||
var cur int
|
||||
var totalBytes int64
|
||||
var totalGas int64
|
||||
// TODO: we will get a performance boost if we have a good estimate of avg
|
||||
// size per tx, and set the initial capacity based off of that.
|
||||
// txs := make([]types.Tx, 0, cmn.MinInt(mem.txs.Len(), max/mem.avgTxSize))
|
||||
txs := make([]types.Tx, 0, mem.txs.Len())
|
||||
for e := mem.txs.Front(); e != nil; e = e.Next() {
|
||||
memTx := e.Value.(*mempoolTx)
|
||||
// amino.UvarintSize is not used here because it won't be possible to reuse buf
|
||||
aminoOverhead := binary.PutUvarint(buf[:], uint64(len(memTx.tx)))
|
||||
if max > 0 && cur+len(memTx.tx)+aminoOverhead > max {
|
||||
// Check total size requirement
|
||||
aminoOverhead := int64(amino.UvarintSize(uint64(len(memTx.tx))))
|
||||
if maxBytes > -1 && totalBytes+int64(len(memTx.tx))+aminoOverhead > maxBytes {
|
||||
return txs
|
||||
}
|
||||
cur += len(memTx.tx) + aminoOverhead
|
||||
totalBytes += int64(len(memTx.tx)) + aminoOverhead
|
||||
// Check total gas requirement
|
||||
if maxGas > -1 && totalGas+memTx.gasWanted > maxGas {
|
||||
return txs
|
||||
}
|
||||
totalGas += memTx.gasWanted
|
||||
txs = append(txs, memTx.tx)
|
||||
}
|
||||
return txs
|
||||
|
@ -439,7 +487,12 @@ func (mem *Mempool) ReapMaxTxs(max int) types.Txs {
|
|||
// Update informs the mempool that the given txs were committed and can be discarded.
|
||||
// NOTE: this should be called *after* block is committed by consensus.
|
||||
// NOTE: unsafe; Lock/Unlock must be managed by caller
|
||||
func (mem *Mempool) Update(height int64, txs types.Txs, filter func(types.Tx) bool) error {
|
||||
func (mem *Mempool) Update(
|
||||
height int64,
|
||||
txs types.Txs,
|
||||
preCheck PreCheckFunc,
|
||||
postCheck PostCheckFunc,
|
||||
) error {
|
||||
// First, create a lookup map of txns in new txs.
|
||||
txsMap := make(map[string]struct{}, len(txs))
|
||||
for _, tx := range txs {
|
||||
|
@ -450,8 +503,11 @@ func (mem *Mempool) Update(height int64, txs types.Txs, filter func(types.Tx) bo
|
|||
mem.height = height
|
||||
mem.notifiedTxsAvailable = false
|
||||
|
||||
if filter != nil {
|
||||
mem.filter = filter
|
||||
if preCheck != nil {
|
||||
mem.preCheck = preCheck
|
||||
}
|
||||
if postCheck != nil {
|
||||
mem.postCheck = postCheck
|
||||
}
|
||||
|
||||
// Remove transactions that are already in txs.
|
||||
|
@ -509,13 +565,18 @@ func (mem *Mempool) recheckTxs(goodTxs []types.Tx) {
|
|||
mem.proxyAppConn.FlushAsync()
|
||||
}
|
||||
|
||||
func (mem *Mempool) isPostCheckPass(tx types.Tx, r *abci.ResponseCheckTx) bool {
|
||||
return mem.postCheck == nil || mem.postCheck(tx, r)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// mempoolTx is a transaction that successfully ran
|
||||
type mempoolTx struct {
|
||||
counter int64 // a simple incrementing counter
|
||||
height int64 // height that this tx had been validated in
|
||||
tx types.Tx //
|
||||
counter int64 // a simple incrementing counter
|
||||
height int64 // height that this tx had been validated in
|
||||
gasWanted int64 // amount of gas this tx states it will require
|
||||
tx types.Tx //
|
||||
}
|
||||
|
||||
// Height returns the height for this transaction
|
||||
|
@ -567,7 +628,8 @@ func (cache *mapTxCache) Push(tx types.Tx) bool {
|
|||
|
||||
// Use the tx hash in the cache
|
||||
txHash := sha256.Sum256(tx)
|
||||
if _, exists := cache.map_[txHash]; exists {
|
||||
if moved, exists := cache.map_[txHash]; exists {
|
||||
cache.list.MoveToFront(moved)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue