mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-12 21:01:21 +00:00
Merge branch 'master' into ancaz/blockchain_reactor_reorg
This commit is contained in:
@ -6,14 +6,12 @@ The documentation for Tendermint Core is hosted at:
|
||||
- https://tendermint-staging.interblock.io/docs/
|
||||
|
||||
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.
|
||||
[master](https://github.com/tendermint/tendermint/tree/master/docs) respectively.
|
||||
|
||||
## How It Works
|
||||
|
||||
There is a CircleCI job listening for changes in the `/docs` directory, on both
|
||||
the `master` and `develop` branches. Any updates to files in this directory
|
||||
the `master` branch. Any updates to files in this directory
|
||||
on those branches will automatically trigger a website deployment. Under the hood,
|
||||
the private website repository has a `make build-docs` target consumed by a CircleCI job in that repo.
|
||||
|
||||
@ -35,7 +33,7 @@ of the sidebar.
|
||||
**NOTE:** Strongly consider the existing links - both within this directory
|
||||
and to the website docs - when moving or deleting files.
|
||||
|
||||
Links to directories *MUST* end in a `/`.
|
||||
Links to directories _MUST_ end in a `/`.
|
||||
|
||||
Relative links should be used nearly everywhere, having discovered and weighed the following:
|
||||
|
||||
@ -101,4 +99,4 @@ We are using [Algolia](https://www.algolia.com) to power full-text search. This
|
||||
## 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).
|
||||
much as possible with its [counterpart in the Cosmos SDK repo](https://github.com/cosmos/cosmos-sdk/blob/master/docs/DOCS_README.md).
|
||||
|
@ -62,7 +62,7 @@ as `abci-cli` above. The kvstore just stores transactions in a merkle
|
||||
tree.
|
||||
|
||||
Its code can be found
|
||||
[here](https://github.com/tendermint/tendermint/blob/develop/abci/cmd/abci-cli/abci-cli.go)
|
||||
[here](https://github.com/tendermint/tendermint/blob/master/abci/cmd/abci-cli/abci-cli.go)
|
||||
and looks like:
|
||||
|
||||
```
|
||||
@ -137,7 +137,7 @@ response.
|
||||
|
||||
The server may be generic for a particular language, and we provide a
|
||||
[reference implementation in
|
||||
Golang](https://github.com/tendermint/tendermint/tree/develop/abci/server). See the
|
||||
Golang](https://github.com/tendermint/tendermint/tree/master/abci/server). See the
|
||||
[list of other ABCI implementations](./ecosystem.md) for servers in
|
||||
other languages.
|
||||
|
||||
@ -324,7 +324,7 @@ But the ultimate flexibility comes from being able to write the
|
||||
application easily in any language.
|
||||
|
||||
We have implemented the counter in a number of languages [see the
|
||||
example directory](https://github.com/tendermint/tendermint/tree/develop/abci/example).
|
||||
example directory](https://github.com/tendermint/tendermint/tree/master/abci/example).
|
||||
|
||||
To run the Node.js version, fist download & install [the Javascript ABCI server](https://github.com/tendermint/js-abci):
|
||||
|
||||
|
@ -48,9 +48,9 @@ open ABCI connection with the application, which hosts an ABCI server.
|
||||
Shown are the request and response types sent on each connection.
|
||||
|
||||
Most of the examples below are from [kvstore
|
||||
application](https://github.com/tendermint/tendermint/blob/develop/abci/example/kvstore/kvstore.go),
|
||||
application](https://github.com/tendermint/tendermint/blob/master/abci/example/kvstore/kvstore.go),
|
||||
which is a part of the abci repo. [persistent_kvstore
|
||||
application](https://github.com/tendermint/tendermint/blob/develop/abci/example/kvstore/persistent_kvstore.go)
|
||||
application](https://github.com/tendermint/tendermint/blob/master/abci/example/kvstore/persistent_kvstore.go)
|
||||
is used to show `BeginBlock`, `EndBlock` and `InitChain` example
|
||||
implementations.
|
||||
|
||||
|
@ -20,3 +20,41 @@ it stands today.
|
||||
If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match.
|
||||
|
||||
Note the context/background should be written in the present tense.
|
||||
|
||||
### Table of Contents:
|
||||
|
||||
- [ADR-001-Logging](./adr-001-logging.md)
|
||||
- [ADR-002-Event-Subscription](./adr-002-event-subscription.md)
|
||||
- [ADR-003-ABCI-APP-RPC](./adr-003-abci-app-rpc.md)
|
||||
- [ADR-004-Historical-Validators](./adr-004-historical-validators.md)
|
||||
- [ADR-005-Consensus-Params](./adr-005-consensus-params.md)
|
||||
- [ADR-006-Trust-Metric](./adr-006-trust-metric.md)
|
||||
- [ADR-007-Trust-Metric-Usage](./adr-007-trust-metric-usage.md)
|
||||
- [ADR-008-Priv-Validator](./adr-008-priv-validator.md)
|
||||
- [ADR-009-ABCI-Design](./adr-009-abci-design.md)
|
||||
- [ADR-010-Crypto-Changes](./adr-010-crypto-changes.md)
|
||||
- [ADR-011-Monitoring](./adr-011-monitoring.md)
|
||||
- [ADR-012-Peer-Transport](./adr-012-peer-transport.md)
|
||||
- [ADR-013-Symmetric-Crypto](./adr-013-symmetric-crypto.md)
|
||||
- [ADR-014-Secp-Malleability](./adr-014-secp-malleability.md)
|
||||
- [ADR-015-Crypto-Encoding](./adr-015-crypto-encoding.md)
|
||||
- [ADR-016-Protocol-Versions](./adr-016-protocol-versions.md)
|
||||
- [ADR-017-Chain-Versions](./adr-017-chain-versions.md)
|
||||
- [ADR-018-ABCI-Validators](./adr-018-abci-validators.md)
|
||||
- [ADR-019-Multisigs](./adr-019-multisigs.md)
|
||||
- [ADR-020-Block-Size](./adr-020-block-size.md)
|
||||
- [ADR-021-ABCI-Events](./adr-021-abci-events.md)
|
||||
- [ADR-022-ABCI-Errors](./adr-022-abci-errors.md)
|
||||
- [ADR-023-ABCI-Propose-tx](./adr-023-ABCI-propose-tx.md)
|
||||
- [ADR-024-Sign-Bytes](./adr-024-sign-bytes.md)
|
||||
- [ADR-025-Commit](./adr-025-commit.md)
|
||||
- [ADR-026-General-Merkle-Proof](./adr-026-general-merkle-proof.md)
|
||||
- [ADR-029-Check-Tx-Consensus](./adr-029-check-tx-consensus.md)
|
||||
- [ADR-030-Consensus-Refactor](./adr-030-consensus-refactor.md)
|
||||
- [ADR-033-Pubsub](./adr-033-pubsub.md)
|
||||
- [ADR-034-Priv-Validator-File-Structure](./adr-034-priv-validator-file-structure.md)
|
||||
- [ADR-035-Documentation](./adr-035-documentation.md)
|
||||
- [ADR-037-Deliver-Block](./adr-037-deliver-block.md)
|
||||
- [ADR-039-Peer-Behaviour](./adr-039-peer-behaviour.md)
|
||||
- [ADR-041-Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md)
|
||||
- [ADR-043-Blockchain-RiRi-Org](./adr-043-blockchain-riri-org.md)
|
||||
|
@ -2,10 +2,7 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
016-08-2018: Follow up from review:
|
||||
- Revert changes to commit round
|
||||
- Remind about justification for removing pubkey
|
||||
- Update pros/cons
|
||||
016-08-2018: Follow up from review: - Revert changes to commit round - Remind about justification for removing pubkey - Update pros/cons
|
||||
05-08-2018: Initial draft
|
||||
|
||||
## Context
|
||||
@ -35,11 +32,11 @@ message ValidatorUpdate {
|
||||
}
|
||||
```
|
||||
|
||||
As noted in ADR-009[https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-009-ABCI-design.md],
|
||||
As noted in ADR-009[https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-009-ABCI-design.md],
|
||||
the `Validator` does not contain a pubkey because quantum public keys are
|
||||
quite large and it would be wasteful to send them all over ABCI with every block.
|
||||
Thus, applications that want to take advantage of the information in BeginBlock
|
||||
are *required* to store pubkeys in state (or use much less efficient lazy means
|
||||
are _required_ to store pubkeys in state (or use much less efficient lazy means
|
||||
of verifying BeginBlock data).
|
||||
|
||||
### RequestBeginBlock
|
||||
|
391
docs/architecture/adr-043-blockchain-riri-org.md
Normal file
391
docs/architecture/adr-043-blockchain-riri-org.md
Normal file
@ -0,0 +1,391 @@
|
||||
# ADR 043: Blockhchain Reactor Riri-Org
|
||||
|
||||
## Changelog
|
||||
* 18-06-2019: Initial draft
|
||||
* 08-07-2019: Reviewed
|
||||
|
||||
## Context
|
||||
|
||||
The blockchain reactor is responsible for two high level processes:sending/receiving blocks from peers and FastSync-ing blocks to catch upnode who is far behind. The goal of [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md) was to refactor these two processes by separating business logic currently wrapped up in go-channels into pure `handle*` functions. While the ADR specified what the final form of the reactor might look like it lacked guidance on intermediary steps to get there.
|
||||
The following diagram illustrates the state of the [blockchain-reorg](https://github.com/tendermint/tendermint/pull/35610) reactor which will be referred to as `v1`.
|
||||
|
||||

|
||||
|
||||
While `v1` of the blockchain reactor has shown significant improvements in terms of simplifying the concurrency model, the current PR has run into few roadblocks.
|
||||
|
||||
* The current PR large and difficult to review.
|
||||
* Block gossiping and fast sync processes are highly coupled to the shared `Pool` data structure.
|
||||
* Peer communication is spread over multiple components creating complex dependency graph which must be mocked out during testing.
|
||||
* Timeouts modeled as stateful tickers introduce non-determinism in tests
|
||||
|
||||
This ADR is meant to specify the missing components and control necessary to achieve [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md).
|
||||
|
||||
## Decision
|
||||
|
||||
Partition the responsibilities of the blockchain reactor into a set of components which communicate exclusively with events. Events will contain timestamps allowing each component to track time as internal state. The internal state will be mutated by a set of `handle*` which will produce event(s). The integration between components will happen in the reactor and reactor tests will then become integration tests between components. This design will be known as `v2`.
|
||||
|
||||

|
||||
|
||||
### Reactor changes in detail
|
||||
|
||||
The reactor will include a demultiplexing routine which will send each message to each sub routine for independent processing. Each sub routine will then select the messages it's interested in and call the handle specific function specified in [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md). The demuxRoutine acts as "pacemaker" setting the time in which events are expected to be handled.
|
||||
|
||||
|
||||
```go
|
||||
func demuxRoutine(msgs, scheduleMsgs, processorMsgs, ioMsgs) {
|
||||
timer := time.NewTicker(interval)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
now := evTimeCheck{time.Now()}
|
||||
schedulerMsgs <- now
|
||||
processorMsgs <- now
|
||||
ioMsgs <- now
|
||||
case msg:= <- msgs:
|
||||
msg.time = time.Now()
|
||||
// These channels should produce backpressure before
|
||||
// being full to avoid starving each other
|
||||
schedulerMsgs <- msg
|
||||
processorMsgs <- msg
|
||||
ioMesgs <- msg
|
||||
if msg == stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processRoutine(input chan Message, output chan Message) {
|
||||
processor := NewProcessor(..)
|
||||
for {
|
||||
msg := <- input
|
||||
switch msg := msg.(type) {
|
||||
case bcBlockRequestMessage:
|
||||
output <- processor.handleBlockRequest(msg))
|
||||
...
|
||||
case stop:
|
||||
processor.stop()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleRoutine(input chan Message, output chan Message) {
|
||||
schelduer = NewScheduler(...)
|
||||
for {
|
||||
msg := <-msgs
|
||||
switch msg := input.(type) {
|
||||
case bcBlockResponseMessage:
|
||||
output <- scheduler.handleBlockResponse(msg)
|
||||
...
|
||||
case stop:
|
||||
schedule.stop()
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle management
|
||||
|
||||
A set of routines for individual processes allow processes to run in parallel with clear lifecycle management. `Start`, `Stop`, and `AddPeer` hooks currently present in the reactor will delegate to the sub-routines allowing them to manage internal state independent without further coupling to the reactor.
|
||||
|
||||
```go
|
||||
func (r *BlockChainReactor) Start() {
|
||||
r.msgs := make(chan Message, maxInFlight)
|
||||
schedulerMsgs := make(chan Message)
|
||||
processorMsgs := make(chan Message)
|
||||
ioMsgs := make(chan Message)
|
||||
|
||||
go processorRoutine(processorMsgs, r.msgs)
|
||||
go scheduleRoutine(schedulerMsgs, r.msgs)
|
||||
go ioRoutine(ioMsgs, r.msgs)
|
||||
...
|
||||
}
|
||||
|
||||
func (bcR *BlockchainReactor) Receive(...) {
|
||||
...
|
||||
r.msgs <- msg
|
||||
...
|
||||
}
|
||||
|
||||
func (r *BlockchainReactor) Stop() {
|
||||
...
|
||||
r.msgs <- stop
|
||||
...
|
||||
}
|
||||
|
||||
...
|
||||
func (r *BlockchainReactor) Stop() {
|
||||
...
|
||||
r.msgs <- stop
|
||||
...
|
||||
}
|
||||
...
|
||||
|
||||
func (r *BlockchainReactor) AddPeer(peer p2p.Peer) {
|
||||
...
|
||||
r.msgs <- bcAddPeerEv{peer.ID}
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## IO handling
|
||||
An io handling routine within the reactor will isolate peer communication. Message going through the ioRoutine will usually be one way, using `p2p` APIs. In the case in which the `p2p` API such as `trySend` return errors, the ioRoutine can funnel those message back to the demuxRoutine for distribution to the other routines. For instance errors from the ioRoutine can be consumed by the scheduler to inform better peer selection implementations.
|
||||
|
||||
```go
|
||||
func (r *BlockchainReacor) ioRoutine(ioMesgs chan Message, outMsgs chan Message) {
|
||||
...
|
||||
for {
|
||||
msg := <-ioMsgs
|
||||
switch msg := msg.(type) {
|
||||
case scBlockRequestMessage:
|
||||
queued := r.sendBlockRequestToPeer(...)
|
||||
if queued {
|
||||
outMsgs <- ioSendQueued{...}
|
||||
}
|
||||
case scStatusRequestMessage
|
||||
r.sendStatusRequestToPeer(...)
|
||||
case bcPeerError
|
||||
r.Swtich.StopPeerForError(msg.src)
|
||||
...
|
||||
...
|
||||
case bcFinished
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
### Processor Internals
|
||||
|
||||
The processor is responsible for ordering, verifying and executing blocks. The Processor will maintain an internal cursor `height` refering to the last processed block. As a set of blocks arrive unordered, the Processor will check if it has `height+1` necessary to process the next block. The processor also maintains the map `blockPeers` of peers to height, to keep track of which peer provided the block at `height`. `blockPeers` can be used in`handleRemovePeer(...)` to reschedule all unprocessed blocks provided by a peer who has errored.
|
||||
|
||||
```go
|
||||
type Processor struct {
|
||||
height int64 // the height cursor
|
||||
state ...
|
||||
blocks [height]*Block // keep a set of blocks in memory until they are processed
|
||||
blockPeers [height]PeerID // keep track of which heights came from which peerID
|
||||
lastTouch timestamp
|
||||
}
|
||||
|
||||
func (proc *Processor) handleBlockResponse(peerID, block) {
|
||||
if block.height <= height || block[block.height] {
|
||||
} else if blocks[block.height] {
|
||||
return errDuplicateBlock{}
|
||||
} else {
|
||||
blocks[block.height] = block
|
||||
}
|
||||
|
||||
if blocks[height] && blocks[height+1] {
|
||||
... = state.Validators.VerifyCommit(...)
|
||||
... = store.SaveBlock(...)
|
||||
state, err = blockExec.ApplyBlock(...)
|
||||
...
|
||||
if err == nil {
|
||||
delete blocks[height]
|
||||
height++
|
||||
lastTouch = msg.time
|
||||
return pcBlockProcessed{height-1}
|
||||
} else {
|
||||
... // Delete all unprocessed block from the peer
|
||||
return pcBlockProcessError{peerID, height}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (proc *Processor) handleRemovePeer(peerID) {
|
||||
events = []
|
||||
// Delete all unprocessed blocks from peerID
|
||||
for i = height; i < len(blocks); i++ {
|
||||
if blockPeers[i] == peerID {
|
||||
events = append(events, pcBlockReschedule{height})
|
||||
|
||||
delete block[height]
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func handleTimeCheckEv(time) {
|
||||
if time - lastTouch > timeout {
|
||||
// Timeout the processor
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Schedule
|
||||
|
||||
The Schedule maintains the internal state used for scheduling blockRequestMessages based on some scheduling algorithm. The schedule needs to maintain state on:
|
||||
|
||||
* The state `blockState` of every block seem up to height of maxHeight
|
||||
* The set of peers and their peer state `peerState`
|
||||
* which peers have which blocks
|
||||
* which blocks have been requested from which peers
|
||||
|
||||
```go
|
||||
type blockState int
|
||||
|
||||
const (
|
||||
blockStateNew = iota
|
||||
blockStatePending,
|
||||
blockStateReceived,
|
||||
blockStateProcessed
|
||||
)
|
||||
|
||||
type schedule {
|
||||
// a list of blocks in which blockState
|
||||
blockStates map[height]blockState
|
||||
|
||||
// a map of which blocks are available from which peers
|
||||
blockPeers map[height]map[p2p.ID]scPeer
|
||||
|
||||
// a map of peerID to schedule specific peer struct `scPeer`
|
||||
peers map[p2p.ID]scPeer
|
||||
|
||||
// a map of heights to the peer we are waiting for a response from
|
||||
pending map[height]scPeer
|
||||
|
||||
targetPending int // the number of blocks we want in blockStatePending
|
||||
targetReceived int // the number of blocks we want in blockStateReceived
|
||||
|
||||
peerTimeout int
|
||||
peerMinSpeed int
|
||||
}
|
||||
|
||||
func (sc *schedule) numBlockInState(state blockState) uint32 {
|
||||
num := 0
|
||||
for i := sc.minHeight(); i <= sc.maxHeight(); i++ {
|
||||
if sc.blockState[i] == state {
|
||||
num++
|
||||
}
|
||||
}
|
||||
return num
|
||||
}
|
||||
|
||||
|
||||
func (sc *schedule) popSchedule(maxRequest int) []scBlockRequestMessage {
|
||||
// We only want to schedule requests such that we have less than sc.targetPending and sc.targetReceived
|
||||
// This ensures we don't saturate the network or flood the processor with unprocessed blocks
|
||||
todo := min(sc.targetPending - sc.numBlockInState(blockStatePending), sc.numBlockInState(blockStateReceived))
|
||||
events := []scBlockRequestMessage{}
|
||||
for i := sc.minHeight(); i < sc.maxMaxHeight(); i++ {
|
||||
if todo == 0 {
|
||||
break
|
||||
}
|
||||
if blockStates[i] == blockStateNew {
|
||||
peer = sc.selectPeer(blockPeers[i])
|
||||
sc.blockStates[i] = blockStatePending
|
||||
sc.pending[i] = peer
|
||||
events = append(events, scBlockRequestMessage{peerID: peer.peerID, height: i})
|
||||
todo--
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
...
|
||||
|
||||
type scPeer struct {
|
||||
peerID p2p.ID
|
||||
numOustandingRequest int
|
||||
lastTouched time.Time
|
||||
monitor flow.Monitor
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# Scheduler
|
||||
The scheduler is configured to maintain a target `n` of in flight
|
||||
messages and will use feedback from `_blockResponseMessage`,
|
||||
`_statusResponseMessage` and `_peerError` produce an optimal assignment
|
||||
of scBlockRequestMessage at each `timeCheckEv`.
|
||||
|
||||
```
|
||||
|
||||
func handleStatusResponse(peerID, height, time) {
|
||||
schedule.touchPeer(peerID, time)
|
||||
schedule.setPeerHeight(peerID, height)
|
||||
}
|
||||
|
||||
func handleBlockResponseMessage(peerID, height, block, time) {
|
||||
schedule.touchPeer(peerID, time)
|
||||
schedule.markReceived(peerID, height, size(block))
|
||||
}
|
||||
|
||||
func handleNoBlockResponseMessage(peerID, height, time) {
|
||||
schedule.touchPeer(peerID, time)
|
||||
// reschedule that block, punish peer...
|
||||
...
|
||||
}
|
||||
|
||||
func handlePeerError(peerID) {
|
||||
// Remove the peer, reschedule the requests
|
||||
...
|
||||
}
|
||||
|
||||
func handleTimeCheckEv(time) {
|
||||
// clean peer list
|
||||
|
||||
events = []
|
||||
for peerID := range schedule.peersNotTouchedSince(time) {
|
||||
pending = schedule.pendingFrom(peerID)
|
||||
schedule.setPeerState(peerID, timedout)
|
||||
schedule.resetBlocks(pending)
|
||||
events = append(events, peerTimeout{peerID})
|
||||
}
|
||||
|
||||
events = append(events, schedule.popSchedule())
|
||||
|
||||
return events
|
||||
}
|
||||
```
|
||||
|
||||
## Peer
|
||||
The Peer Stores per peer state based on messages received by the scheduler.
|
||||
|
||||
```go
|
||||
type Peer struct {
|
||||
lastTouched timestamp
|
||||
lastDownloaded timestamp
|
||||
pending map[height]struct{}
|
||||
height height // max height for the peer
|
||||
state {
|
||||
pending, // we know the peer but not the height
|
||||
active, // we know the height
|
||||
timeout // the peer has timed out
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Work in progress
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
* Test become deterministic
|
||||
* Simulation becomes a-termporal: no need wait for a wall-time timeout
|
||||
* Peer Selection can be independently tested/simulated
|
||||
* Develop a general approach to refactoring reactors
|
||||
|
||||
### Negative
|
||||
|
||||
### Neutral
|
||||
|
||||
### Implementation Path
|
||||
|
||||
* Implement the scheduler, test the scheduler, review the rescheduler
|
||||
* Implement the processor, test the processor, review the processor
|
||||
* Implement the demuxer, write integration test, review integration tests
|
||||
|
||||
## References
|
||||
|
||||
|
||||
* [ADR-40](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-040-blockchain-reactor-refactor.md): The original blockchain reactor re-org proposal
|
||||
* [Blockchain re-org](https://github.com/tendermint/tendermint/pull/3561): The current blockchain reactor re-org implementation (v1)
|
BIN
docs/architecture/img/blockchain-reactor-v1.png
Normal file
BIN
docs/architecture/img/blockchain-reactor-v1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
BIN
docs/architecture/img/blockchain-reactor-v2.png
Normal file
BIN
docs/architecture/img/blockchain-reactor-v2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
630
docs/guides/go-built-in.md
Normal file
630
docs/guides/go-built-in.md
Normal file
@ -0,0 +1,630 @@
|
||||
# 1 Guide Assumptions
|
||||
|
||||
This guide is designed for beginners who want to get started with a Tendermint
|
||||
Core application from scratch. It does not assume that you have any prior
|
||||
experience with Tendermint Core.
|
||||
|
||||
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.
|
||||
|
||||
Although Tendermint Core is written in the Golang programming language, prior
|
||||
knowledge of it is not required for this guide. You can learn it as we go due
|
||||
to it's simplicity. However, you may want to go through [Learn X in Y minutes
|
||||
Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize
|
||||
yourself with the syntax.
|
||||
|
||||
By following along with this guide, you'll create a Tendermint Core project
|
||||
called kvstore, a (very) simple distributed BFT key-value store.
|
||||
|
||||
# 1 Creating a built-in application in Go
|
||||
|
||||
Running your application inside the same process as Tendermint Core will give
|
||||
you the best possible performance.
|
||||
|
||||
For other languages, your application have to communicate with Tendermint Core
|
||||
through a TCP, Unix domain socket or gRPC.
|
||||
|
||||
## 1.1 Installing Go
|
||||
|
||||
Please refer to [the official guide for installing
|
||||
Go](https://golang.org/doc/install).
|
||||
|
||||
Verify that you have the latest version of Go installed:
|
||||
|
||||
```sh
|
||||
$ go version
|
||||
go version go1.12.7 darwin/amd64
|
||||
```
|
||||
|
||||
Make sure you have `$GOPATH` environment variable set:
|
||||
|
||||
```sh
|
||||
$ echo $GOPATH
|
||||
/Users/melekes/go
|
||||
```
|
||||
|
||||
## 1.2 Creating a new Go project
|
||||
|
||||
We'll start by creating a new Go project.
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/me/kvstore
|
||||
$ cd $GOPATH/src/github.com/me/kvstore
|
||||
```
|
||||
|
||||
Inside the example directory create a `main.go` file with the following content:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, Tendermint Core")
|
||||
}
|
||||
```
|
||||
|
||||
When run, this should print "Hello, Tendermint Core" to the standard output.
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
Hello, Tendermint Core
|
||||
```
|
||||
|
||||
## 1.3 Writing a Tendermint Core application
|
||||
|
||||
Tendermint Core communicates with the application through the Application
|
||||
BlockChain Interface (ABCI). All message types are defined in the [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
This allows Tendermint Core to run applications written in any programming
|
||||
language.
|
||||
|
||||
Create a file called `app.go` with the following content:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
abcitypes "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
type KVStoreApplication struct {}
|
||||
|
||||
var _ abcitypes.Application = (*KVStoreApplication)(nil)
|
||||
|
||||
func NewKVStoreApplication() *KVStoreApplication {
|
||||
return &KVStoreApplication{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {
|
||||
return abcitypes.ResponseInfo{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) SetOption(req abcitypes.RequestSetOption) abcitypes.ResponseSetOption {
|
||||
return abcitypes.ResponseSetOption{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
|
||||
return abcitypes.ResponseDeliverTx{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
|
||||
return abcitypes.ResponseCheckTx{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Commit() abcitypes.ResponseCommit {
|
||||
return abcitypes.ResponseCommit{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
|
||||
return abcitypes.ResponseQuery{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
|
||||
return abcitypes.ResponseInitChain{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
|
||||
return abcitypes.ResponseBeginBlock{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
|
||||
return abcitypes.ResponseEndBlock{}
|
||||
}
|
||||
```
|
||||
|
||||
Now I will go through each method explaining when it's called and adding
|
||||
required business logic.
|
||||
|
||||
### 1.3.1 CheckTx
|
||||
|
||||
When a new transaction is added to the Tendermint Core, it will ask the
|
||||
application to check it (validate the format, signatures, etc.).
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {
|
||||
// check format
|
||||
parts := bytes.Split(tx, []byte("="))
|
||||
if len(parts) != 2 {
|
||||
return 1
|
||||
}
|
||||
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
// check if the same key=value already exists
|
||||
err := app.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(key)
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
return item.Value(func(val []byte) error {
|
||||
if bytes.Equal(val, value) {
|
||||
code = 2
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
|
||||
code := app.isValid(req.Tx)
|
||||
return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1}
|
||||
}
|
||||
```
|
||||
|
||||
Don't worry if this does not compile yet.
|
||||
|
||||
If the transaction does not have a form of `{bytes}={bytes}`, we return `1`
|
||||
code. When the same key=value already exist (same key and value), we return `2`
|
||||
code. For others, we return a zero code indicating that they are valid.
|
||||
|
||||
Note that anything with non-zero code will be considered invalid (`-1`, `100`,
|
||||
etc.) by Tendermint Core.
|
||||
|
||||
Valid transactions will eventually be committed given they are not too big and
|
||||
have enough gas. To learn more about gas, check out ["the
|
||||
specification"](https://tendermint.com/docs/spec/abci/apps.html#gas).
|
||||
|
||||
For the underlying key-value store we'll use
|
||||
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
|
||||
persistent and fast key-value (KV) database.
|
||||
|
||||
```go
|
||||
import "github.com/dgraph-io/badger"
|
||||
|
||||
type KVStoreApplication struct {
|
||||
db *badger.DB
|
||||
currentBatch *badger.Txn
|
||||
}
|
||||
|
||||
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
|
||||
return &KVStoreApplication{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
|
||||
|
||||
When Tendermint Core has decided on the block, it's transfered to the
|
||||
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
|
||||
`EndBlock` in the end. DeliverTx are being transfered asynchronously, but the
|
||||
responses are expected to come in order.
|
||||
|
||||
```
|
||||
func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
|
||||
app.currentBatch = app.db.NewTransaction(true)
|
||||
return abcitypes.ResponseBeginBlock{}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Here we create a batch, which will store block's transactions.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
|
||||
code := app.isValid(req.Tx)
|
||||
if code != 0 {
|
||||
return abcitypes.ResponseDeliverTx{Code: code}
|
||||
}
|
||||
|
||||
parts := bytes.Split(req.Tx, []byte("="))
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
err := app.currentBatch.Set(key, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return abcitypes.ResponseDeliverTx{Code: 0}
|
||||
}
|
||||
```
|
||||
|
||||
If the transaction is badly formatted or the same key=value already exist, we
|
||||
again return the non-zero code. Otherwise, we add it to the current batch.
|
||||
|
||||
In the current design, a block can include incorrect transactions (those who
|
||||
passed CheckTx, but failed DeliverTx or transactions included by the proposer
|
||||
directly). This is done for performance reasons.
|
||||
|
||||
Note we can't commit transactions inside the `DeliverTx` because in such case
|
||||
`Query`, which may be called in parallel, will return inconsistent data (i.e.
|
||||
it will report that some value already exist even when the actual block was not
|
||||
yet committed).
|
||||
|
||||
`Commit` instructs the application to persist the new state.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
|
||||
app.currentBatch.Commit()
|
||||
return abcitypes.ResponseCommit{Data: []byte{}}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3.3 Query
|
||||
|
||||
Now, when the client wants to know whenever a particular key/value exist, it
|
||||
will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call
|
||||
the application's `Query` method.
|
||||
|
||||
Applications are free to provide their own APIs. But by using Tendermint Core
|
||||
as a proxy, clients (including [light client
|
||||
package](https://godoc.org/github.com/tendermint/tendermint/lite)) can leverage
|
||||
the unified API across different applications. Plus they won't have to call the
|
||||
otherwise separate Tendermint Core API for additional proofs.
|
||||
|
||||
Note we don't include a proof here.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {
|
||||
resQuery.Key = reqQuery.Data
|
||||
err := app.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(reqQuery.Data)
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if err == badger.ErrKeyNotFound {
|
||||
resQuery.Log = "does not exist"
|
||||
} else {
|
||||
return item.Value(func(val []byte) error {
|
||||
resQuery.Log = "exists"
|
||||
resQuery.Value = val
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
The complete specification can be found
|
||||
[here](https://tendermint.com/docs/spec/abci/).
|
||||
|
||||
## 1.4 Starting an application and a Tendermint Core instance in the same process
|
||||
|
||||
Put the following code into the "main.go" file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/dgraph-io/badger"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
tmflags "github.com/tendermint/tendermint/libs/cli/flags"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
)
|
||||
|
||||
var configFile string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&configFile, "config", "$HOME/.tendermint/config/config.toml", "Path to config.toml")
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
app := NewKVStoreApplication(db)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
node, err := newTendermint(app, configFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
node.Start()
|
||||
defer func() {
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
}()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
<-c
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func newTendermint(app abci.Application, configFile string) (*nm.Node, error) {
|
||||
// read config
|
||||
config := cfg.DefaultConfig()
|
||||
config.RootDir = filepath.Dir(filepath.Dir(configFile))
|
||||
viper.SetConfigFile(configFile)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, errors.Wrap(err, "viper failed to read config file")
|
||||
}
|
||||
if err := viper.Unmarshal(config); err != nil {
|
||||
return nil, errors.Wrap(err, "viper failed to unmarshal config")
|
||||
}
|
||||
if err := config.ValidateBasic(); err != nil {
|
||||
return nil, errors.Wrap(err, "config is invalid")
|
||||
}
|
||||
|
||||
// create logger
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
var err error
|
||||
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse log level")
|
||||
}
|
||||
|
||||
// read private validator
|
||||
pv := privval.LoadFilePV(
|
||||
config.PrivValidatorKeyFile(),
|
||||
config.PrivValidatorStateFile(),
|
||||
)
|
||||
|
||||
// read node key
|
||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load node's key")
|
||||
}
|
||||
|
||||
// create node
|
||||
node, err := nm.NewNode(
|
||||
config,
|
||||
pv,
|
||||
nodeKey,
|
||||
proxy.NewLocalClientCreator(app),
|
||||
nm.DefaultGenesisDocProviderFunc(config),
|
||||
nm.DefaultDBProvider,
|
||||
nm.DefaultMetricsProvider(config.Instrumentation),
|
||||
logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create new Tendermint node")
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
```
|
||||
|
||||
This is a huge blob of code, so let's break it down into pieces.
|
||||
|
||||
First, we initialize the Badger database and create an app instance:
|
||||
|
||||
```go
|
||||
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
app := NewKVStoreApplication(db)
|
||||
```
|
||||
|
||||
Then we use it to create a Tendermint Core `Node` instance:
|
||||
|
||||
```go
|
||||
flag.Parse()
|
||||
|
||||
node, err := newTendermint(app, configFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
// create node
|
||||
node, err := nm.NewNode(
|
||||
config,
|
||||
pv,
|
||||
nodeKey,
|
||||
proxy.NewLocalClientCreator(app),
|
||||
nm.DefaultGenesisDocProviderFunc(config),
|
||||
nm.DefaultDBProvider,
|
||||
nm.DefaultMetricsProvider(config.Instrumentation),
|
||||
logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create new Tendermint node")
|
||||
}
|
||||
```
|
||||
|
||||
`NewNode` requires a few things including a configuration file, a private
|
||||
validator, a node key and a few others in order to construct the full node.
|
||||
|
||||
Note we use `proxy.NewLocalClientCreator` here to create a local client instead
|
||||
of one communicating through a socket or gRPC.
|
||||
|
||||
[viper](https://github.com/spf13/viper) is being used for reading the config,
|
||||
which we will generate later using the `tendermint init` command.
|
||||
|
||||
```go
|
||||
config := cfg.DefaultConfig()
|
||||
config.RootDir = filepath.Dir(filepath.Dir(configFile))
|
||||
viper.SetConfigFile(configFile)
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, errors.Wrap(err, "viper failed to read config file")
|
||||
}
|
||||
if err := viper.Unmarshal(config); err != nil {
|
||||
return nil, errors.Wrap(err, "viper failed to unmarshal config")
|
||||
}
|
||||
if err := config.ValidateBasic(); err != nil {
|
||||
return nil, errors.Wrap(err, "config is invalid")
|
||||
}
|
||||
```
|
||||
|
||||
We use `FilePV`, which is a private validator (i.e. thing which signs consensus
|
||||
messages). Normally, you would use `SignerRemote` to connect to an external
|
||||
[HSM](https://kb.certus.one/hsm.html).
|
||||
|
||||
```go
|
||||
pv := privval.LoadFilePV(
|
||||
config.PrivValidatorKeyFile(),
|
||||
config.PrivValidatorStateFile(),
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
`nodeKey` is needed to identify the node in a p2p network.
|
||||
|
||||
```go
|
||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to load node's key")
|
||||
}
|
||||
```
|
||||
|
||||
As for the logger, we use the build-in library, which provides a nice
|
||||
abstraction over [go-kit's
|
||||
logger](https://github.com/go-kit/kit/tree/master/log).
|
||||
|
||||
```go
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
var err error
|
||||
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse log level")
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we start the node and add some signal handling to gracefully stop it
|
||||
upon receiving SIGTERM or Ctrl-C.
|
||||
|
||||
```go
|
||||
node.Start()
|
||||
defer func() {
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
}()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
<-c
|
||||
os.Exit(0)
|
||||
```
|
||||
|
||||
## 1.5 Getting Up and Running
|
||||
|
||||
We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
|
||||
dependency management.
|
||||
|
||||
```sh
|
||||
$ export GO111MODULE=on
|
||||
$ go mod init github.com/me/example
|
||||
$ go build
|
||||
```
|
||||
|
||||
This should build the binary.
|
||||
|
||||
To create a default configuration, nodeKey and private validator files, let's
|
||||
execute `tendermint init`. But before we do that, we will need to install
|
||||
Tendermint Core.
|
||||
|
||||
```sh
|
||||
$ rm -rf /tmp/example
|
||||
$ cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
$ make install
|
||||
$ TMHOME="/tmp/example" tendermint init
|
||||
|
||||
I[2019-07-16|18:40:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json
|
||||
I[2019-07-16|18:40:36.481] Generated node key module=main path=/tmp/example/config/node_key.json
|
||||
I[2019-07-16|18:40:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
|
||||
```
|
||||
|
||||
We are ready to start our application:
|
||||
|
||||
```sh
|
||||
$ ./example -config "/tmp/example/config/config.toml"
|
||||
|
||||
badger 2019/07/16 18:42:25 INFO: All 0 tables opened in 0s
|
||||
badger 2019/07/16 18:42:25 INFO: Replaying file id: 0 at offset: 0
|
||||
badger 2019/07/16 18:42:25 INFO: Replay took: 695.227s
|
||||
E[2019-07-16|18:42:25.818] Couldn't connect to any seeds module=p2p
|
||||
I[2019-07-16|18:42:26.853] Executed block module=state height=1 validTxs=0 invalidTxs=0
|
||||
I[2019-07-16|18:42:26.865] Committed state module=state height=1 txs=0 appHash=
|
||||
```
|
||||
|
||||
Now open another tab in your terminal and try sending a transaction:
|
||||
|
||||
```sh
|
||||
$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"gasWanted": "1"
|
||||
},
|
||||
"deliver_tx": {},
|
||||
"hash": "1B3C5A1093DB952C331B1749A21DCCBB0F6C7F4E0055CD04D16346472FC60EC6",
|
||||
"height": "128"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response should contain the height where this transaction was committed.
|
||||
|
||||
Now let's check if the given key now exists and its value:
|
||||
|
||||
```
|
||||
$ curl -s 'localhost:26657/abci_query?data="tendermint"'
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"response": {
|
||||
"log": "exists",
|
||||
"key": "dGVuZGVybWludA==",
|
||||
"value": "cm9ja3M="
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of
|
||||
"tendermint" and "rocks" accordingly.
|
514
docs/guides/go.md
Normal file
514
docs/guides/go.md
Normal file
@ -0,0 +1,514 @@
|
||||
# 1 Guide Assumptions
|
||||
|
||||
This guide is designed for beginners who want to get started with a Tendermint
|
||||
Core application from scratch. It does not assume that you have any prior
|
||||
experience with Tendermint Core.
|
||||
|
||||
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.
|
||||
|
||||
Although Tendermint Core is written in the Golang programming language, prior
|
||||
knowledge of it is not required for this guide. You can learn it as we go due
|
||||
to it's simplicity. However, you may want to go through [Learn X in Y minutes
|
||||
Where X=Go](https://learnxinyminutes.com/docs/go/) first to familiarize
|
||||
yourself with the syntax.
|
||||
|
||||
By following along with this guide, you'll create a Tendermint Core project
|
||||
called kvstore, a (very) simple distributed BFT key-value store.
|
||||
|
||||
# 1 Creating an application in Go
|
||||
|
||||
To get maximum performance it is better to run your application alongside
|
||||
Tendermint Core. [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is written
|
||||
this way. Please refer to [Writing a built-in Tendermint Core application in
|
||||
Go](./go-built-in.md) guide for details.
|
||||
|
||||
Having a separate application might give you better security guarantees as two
|
||||
processes would be communicating via established binary protocol. Tendermint
|
||||
Core will not have access to application's state.
|
||||
|
||||
## 1.1 Installing Go
|
||||
|
||||
Please refer to [the official guide for installing
|
||||
Go](https://golang.org/doc/install).
|
||||
|
||||
Verify that you have the latest version of Go installed:
|
||||
|
||||
```sh
|
||||
$ go version
|
||||
go version go1.12.7 darwin/amd64
|
||||
```
|
||||
|
||||
Make sure you have `$GOPATH` environment variable set:
|
||||
|
||||
```sh
|
||||
$ echo $GOPATH
|
||||
/Users/melekes/go
|
||||
```
|
||||
|
||||
## 1.2 Creating a new Go project
|
||||
|
||||
We'll start by creating a new Go project.
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/me/kvstore
|
||||
$ cd $GOPATH/src/github.com/me/kvstore
|
||||
```
|
||||
|
||||
Inside the example directory create a `main.go` file with the following content:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, Tendermint Core")
|
||||
}
|
||||
```
|
||||
|
||||
When run, this should print "Hello, Tendermint Core" to the standard output.
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
Hello, Tendermint Core
|
||||
```
|
||||
|
||||
## 1.3 Writing a Tendermint Core application
|
||||
|
||||
Tendermint Core communicates with the application through the Application
|
||||
BlockChain Interface (ABCI). All message types are defined in the [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
This allows Tendermint Core to run applications written in any programming
|
||||
language.
|
||||
|
||||
Create a file called `app.go` with the following content:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
abcitypes "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
type KVStoreApplication struct {}
|
||||
|
||||
var _ abcitypes.Application = (*KVStoreApplication)(nil)
|
||||
|
||||
func NewKVStoreApplication() *KVStoreApplication {
|
||||
return &KVStoreApplication{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {
|
||||
return abcitypes.ResponseInfo{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) SetOption(req abcitypes.RequestSetOption) abcitypes.ResponseSetOption {
|
||||
return abcitypes.ResponseSetOption{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
|
||||
return abcitypes.ResponseDeliverTx{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
|
||||
return abcitypes.ResponseCheckTx{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Commit() abcitypes.ResponseCommit {
|
||||
return abcitypes.ResponseCommit{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) Query(req abcitypes.RequestQuery) abcitypes.ResponseQuery {
|
||||
return abcitypes.ResponseQuery{Code: 0}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {
|
||||
return abcitypes.ResponseInitChain{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
|
||||
return abcitypes.ResponseBeginBlock{}
|
||||
}
|
||||
|
||||
func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
|
||||
return abcitypes.ResponseEndBlock{}
|
||||
}
|
||||
```
|
||||
|
||||
Now I will go through each method explaining when it's called and adding
|
||||
required business logic.
|
||||
|
||||
### 1.3.1 CheckTx
|
||||
|
||||
When a new transaction is added to the Tendermint Core, it will ask the
|
||||
application to check it (validate the format, signatures, etc.).
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {
|
||||
// check format
|
||||
parts := bytes.Split(tx, []byte("="))
|
||||
if len(parts) != 2 {
|
||||
return 1
|
||||
}
|
||||
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
// check if the same key=value already exists
|
||||
err := app.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(key)
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
return item.Value(func(val []byte) error {
|
||||
if bytes.Equal(val, value) {
|
||||
code = 2
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
|
||||
code := app.isValid(req.Tx)
|
||||
return abcitypes.ResponseCheckTx{Code: code, GasWanted: 1}
|
||||
}
|
||||
```
|
||||
|
||||
Don't worry if this does not compile yet.
|
||||
|
||||
If the transaction does not have a form of `{bytes}={bytes}`, we return `1`
|
||||
code. When the same key=value already exist (same key and value), we return `2`
|
||||
code. For others, we return a zero code indicating that they are valid.
|
||||
|
||||
Note that anything with non-zero code will be considered invalid (`-1`, `100`,
|
||||
etc.) by Tendermint Core.
|
||||
|
||||
Valid transactions will eventually be committed given they are not too big and
|
||||
have enough gas. To learn more about gas, check out ["the
|
||||
specification"](https://tendermint.com/docs/spec/abci/apps.html#gas).
|
||||
|
||||
For the underlying key-value store we'll use
|
||||
[badger](https://github.com/dgraph-io/badger), which is an embeddable,
|
||||
persistent and fast key-value (KV) database.
|
||||
|
||||
```go
|
||||
import "github.com/dgraph-io/badger"
|
||||
|
||||
type KVStoreApplication struct {
|
||||
db *badger.DB
|
||||
currentBatch *badger.Txn
|
||||
}
|
||||
|
||||
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
|
||||
return &KVStoreApplication{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3.2 BeginBlock -> DeliverTx -> EndBlock -> Commit
|
||||
|
||||
When Tendermint Core has decided on the block, it's transfered to the
|
||||
application in 3 parts: `BeginBlock`, one `DeliverTx` per transaction and
|
||||
`EndBlock` in the end. DeliverTx are being transfered asynchronously, but the
|
||||
responses are expected to come in order.
|
||||
|
||||
```
|
||||
func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
|
||||
app.currentBatch = app.db.NewTransaction(true)
|
||||
return abcitypes.ResponseBeginBlock{}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Here we create a batch, which will store block's transactions.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
|
||||
code := app.isValid(req.Tx)
|
||||
if code != 0 {
|
||||
return abcitypes.ResponseDeliverTx{Code: code}
|
||||
}
|
||||
|
||||
parts := bytes.Split(req.Tx, []byte("="))
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
err := app.currentBatch.Set(key, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return abcitypes.ResponseDeliverTx{Code: 0}
|
||||
}
|
||||
```
|
||||
|
||||
If the transaction is badly formatted or the same key=value already exist, we
|
||||
again return the non-zero code. Otherwise, we add it to the current batch.
|
||||
|
||||
In the current design, a block can include incorrect transactions (those who
|
||||
passed CheckTx, but failed DeliverTx or transactions included by the proposer
|
||||
directly). This is done for performance reasons.
|
||||
|
||||
Note we can't commit transactions inside the `DeliverTx` because in such case
|
||||
`Query`, which may be called in parallel, will return inconsistent data (i.e.
|
||||
it will report that some value already exist even when the actual block was not
|
||||
yet committed).
|
||||
|
||||
`Commit` instructs the application to persist the new state.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
|
||||
app.currentBatch.Commit()
|
||||
return abcitypes.ResponseCommit{Data: []byte{}}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3.3 Query
|
||||
|
||||
Now, when the client wants to know whenever a particular key/value exist, it
|
||||
will call Tendermint Core RPC `/abci_query` endpoint, which in turn will call
|
||||
the application's `Query` method.
|
||||
|
||||
Applications are free to provide their own APIs. But by using Tendermint Core
|
||||
as a proxy, clients (including [light client
|
||||
package](https://godoc.org/github.com/tendermint/tendermint/lite)) can leverage
|
||||
the unified API across different applications. Plus they won't have to call the
|
||||
otherwise separate Tendermint Core API for additional proofs.
|
||||
|
||||
Note we don't include a proof here.
|
||||
|
||||
```go
|
||||
func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {
|
||||
resQuery.Key = reqQuery.Data
|
||||
err := app.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(reqQuery.Data)
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if err == badger.ErrKeyNotFound {
|
||||
resQuery.Log = "does not exist"
|
||||
} else {
|
||||
return item.Value(func(val []byte) error {
|
||||
resQuery.Log = "exists"
|
||||
resQuery.Value = val
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
The complete specification can be found
|
||||
[here](https://tendermint.com/docs/spec/abci/).
|
||||
|
||||
## 1.4 Starting an application and a Tendermint Core instances
|
||||
|
||||
Put the following code into the "main.go" file:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/dgraph-io/badger"
|
||||
|
||||
abciserver "github.com/tendermint/tendermint/abci/server"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
var socketAddr string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&socketAddr, "socket-addr", "unix://example.sock", "Unix domain socket address")
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
app := NewKVStoreApplication(db)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
server := abciserver.NewSocketServer(socketAddr, app)
|
||||
server.SetLogger(logger)
|
||||
if err := server.Start(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
<-c
|
||||
os.Exit(0)
|
||||
}
|
||||
```
|
||||
|
||||
This is a huge blob of code, so let's break it down into pieces.
|
||||
|
||||
First, we initialize the Badger database and create an app instance:
|
||||
|
||||
```go
|
||||
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to open badger db: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
app := NewKVStoreApplication(db)
|
||||
```
|
||||
|
||||
Then we start the ABCI server and add some signal handling to gracefully stop
|
||||
it upon receiving SIGTERM or Ctrl-C. Tendermint Core will act as a client,
|
||||
which connects to our server and send us transactions and other messages.
|
||||
|
||||
```go
|
||||
server := abciserver.NewSocketServer(socketAddr, app)
|
||||
server.SetLogger(logger)
|
||||
if err := server.Start(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error starting socket server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
<-c
|
||||
os.Exit(0)
|
||||
```
|
||||
|
||||
## 1.5 Getting Up and Running
|
||||
|
||||
We are going to use [Go modules](https://github.com/golang/go/wiki/Modules) for
|
||||
dependency management.
|
||||
|
||||
```sh
|
||||
$ export GO111MODULE=on
|
||||
$ go mod init github.com/me/example
|
||||
$ go build
|
||||
```
|
||||
|
||||
This should build the binary.
|
||||
|
||||
To create a default configuration, nodeKey and private validator files, let's
|
||||
execute `tendermint init`. But before we do that, we will need to install
|
||||
Tendermint Core.
|
||||
|
||||
```sh
|
||||
$ rm -rf /tmp/example
|
||||
$ cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
$ make install
|
||||
$ TMHOME="/tmp/example" tendermint init
|
||||
|
||||
I[2019-07-16|18:20:36.480] Generated private validator module=main keyFile=/tmp/example/config/priv_validator_key.json stateFile=/tmp/example2/data/priv_validator_state.json
|
||||
I[2019-07-16|18:20:36.481] Generated node key module=main path=/tmp/example/config/node_key.json
|
||||
I[2019-07-16|18:20:36.482] Generated genesis file module=main path=/tmp/example/config/genesis.json
|
||||
```
|
||||
|
||||
Feel free to explore the generated files, which can be found at
|
||||
`/tmp/example/config` directory. Documentation on the config can be found
|
||||
[here](https://tendermint.com/docs/tendermint-core/configuration.html).
|
||||
|
||||
We are ready to start our application:
|
||||
|
||||
```sh
|
||||
$ rm example.sock
|
||||
$ ./example
|
||||
|
||||
badger 2019/07/16 18:25:11 INFO: All 0 tables opened in 0s
|
||||
badger 2019/07/16 18:25:11 INFO: Replaying file id: 0 at offset: 0
|
||||
badger 2019/07/16 18:25:11 INFO: Replay took: 300.4s
|
||||
I[2019-07-16|18:25:11.523] Starting ABCIServer impl=ABCIServ
|
||||
```
|
||||
|
||||
Then we need to start Tendermint Core and point it to our application. Staying
|
||||
within the application directory execute:
|
||||
|
||||
```sh
|
||||
$ TMHOME="/tmp/example" tendermint node --proxy_app=unix://example.sock
|
||||
|
||||
I[2019-07-16|18:26:20.362] Version info module=main software=0.32.1 block=10 p2p=7
|
||||
I[2019-07-16|18:26:20.383] Starting Node module=main impl=Node
|
||||
E[2019-07-16|18:26:20.392] Couldn't connect to any seeds module=p2p
|
||||
I[2019-07-16|18:26:20.394] Started node module=main nodeInfo="{ProtocolVersion:{P2P:7 Block:10 App:0} ID_:8dab80770ae8e295d4ce905d86af78c4ff634b79 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-nIO96P Version:0.32.1 Channels:4020212223303800 Moniker:app48.fun-box.ru Other:{TxIndex:on RPCAddress:tcp://127.0.0.1:26657}}"
|
||||
I[2019-07-16|18:26:21.440] Executed block module=state height=1 validTxs=0 invalidTxs=0
|
||||
I[2019-07-16|18:26:21.446] Committed state module=state height=1 txs=0 appHash=
|
||||
```
|
||||
|
||||
This should start the full node and connect to our ABCI application.
|
||||
|
||||
```
|
||||
I[2019-07-16|18:25:11.525] Waiting for new connection...
|
||||
I[2019-07-16|18:26:20.329] Accepted a new connection
|
||||
I[2019-07-16|18:26:20.329] Waiting for new connection...
|
||||
I[2019-07-16|18:26:20.330] Accepted a new connection
|
||||
I[2019-07-16|18:26:20.330] Waiting for new connection...
|
||||
I[2019-07-16|18:26:20.330] Accepted a new connection
|
||||
```
|
||||
|
||||
Now open another tab in your terminal and try sending a transaction:
|
||||
|
||||
```sh
|
||||
$ curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"gasWanted": "1"
|
||||
},
|
||||
"deliver_tx": {},
|
||||
"hash": "CDD3C6DFA0A08CAEDF546F9938A2EEC232209C24AA0E4201194E0AFB78A2C2BB",
|
||||
"height": "33"
|
||||
}
|
||||
```
|
||||
|
||||
Response should contain the height where this transaction was committed.
|
||||
|
||||
Now let's check if the given key now exists and its value:
|
||||
|
||||
```
|
||||
$ curl -s 'localhost:26657/abci_query?data="tendermint"'
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"response": {
|
||||
"log": "exists",
|
||||
"key": "dGVuZGVybWludA==",
|
||||
"value": "cm9ja3My"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
"dGVuZGVybWludA==" and "cm9ja3M=" are the base64-encoding of the ASCII of
|
||||
"tendermint" and "rocks" accordingly.
|
@ -1,9 +1,9 @@
|
||||
# Install Tendermint
|
||||
|
||||
The fastest and easiest way to install the `tendermint` binary
|
||||
is to run [this script](https://github.com/tendermint/tendermint/blob/develop/scripts/install/install_tendermint_ubuntu.sh) on
|
||||
is to run [this script](https://github.com/tendermint/tendermint/blob/master/scripts/install/install_tendermint_ubuntu.sh) on
|
||||
a fresh Ubuntu instance,
|
||||
or [this script](https://github.com/tendermint/tendermint/blob/develop/scripts/install/install_tendermint_bsd.sh)
|
||||
or [this script](https://github.com/tendermint/tendermint/blob/master/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 you are okay with the network connections being made).
|
||||
|
||||
|
@ -122,7 +122,7 @@ consensus engine, and provides a particular application state.
|
||||
## ABCI Overview
|
||||
|
||||
The [Application BlockChain Interface
|
||||
(ABCI)](https://github.com/tendermint/tendermint/tree/develop/abci)
|
||||
(ABCI)](https://github.com/tendermint/tendermint/tree/master/abci)
|
||||
allows for Byzantine Fault Tolerant replication of applications
|
||||
written in any programming language.
|
||||
|
||||
@ -190,7 +190,7 @@ core to the application. The application replies with corresponding
|
||||
response messages.
|
||||
|
||||
The messages are specified here: [ABCI Message
|
||||
Types](https://github.com/tendermint/tendermint/blob/develop/abci/README.md#message-types).
|
||||
Types](https://github.com/tendermint/tendermint/blob/master/abci/README.md#message-types).
|
||||
|
||||
The **DeliverTx** message is the work horse of the application. Each
|
||||
transaction in the blockchain is delivered with this message. The
|
||||
|
@ -116,7 +116,7 @@ consensus engine, and provides a particular application state.
|
||||
## ABCI Overview
|
||||
|
||||
The [Application BlockChain Interface
|
||||
(ABCI)](https://github.com/tendermint/tendermint/tree/develop/abci)
|
||||
(ABCI)](https://github.com/tendermint/tendermint/tree/master/abci)
|
||||
allows for Byzantine Fault Tolerant replication of applications
|
||||
written in any programming language.
|
||||
|
||||
@ -184,7 +184,7 @@ core to the application. The application replies with corresponding
|
||||
response messages.
|
||||
|
||||
The messages are specified here: [ABCI Message
|
||||
Types](https://github.com/tendermint/tendermint/blob/develop/abci/README.md#message-types).
|
||||
Types](https://github.com/tendermint/tendermint/blob/master/abci/README.md#message-types).
|
||||
|
||||
The **DeliverTx** message is the work horse of the application. Each
|
||||
transaction in the blockchain is delivered with this message. The
|
||||
|
@ -78,9 +78,9 @@ cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
rm -rf ./build/node*
|
||||
```
|
||||
|
||||
## Configuring abci containers
|
||||
## Configuring abci containers
|
||||
|
||||
To use your own abci applications with 4-node setup edit the [docker-compose.yaml](https://github.com/tendermint/tendermint/blob/develop/docker-compose.yml) file and add image to your abci application.
|
||||
To use your own abci applications with 4-node setup edit the [docker-compose.yaml](https://github.com/tendermint/tendermint/blob/master/docker-compose.yml) file and add image to your abci application.
|
||||
|
||||
```
|
||||
abci0:
|
||||
@ -129,7 +129,7 @@ To use your own abci applications with 4-node setup edit the [docker-compose.yam
|
||||
|
||||
```
|
||||
|
||||
Override the [command](https://github.com/tendermint/tendermint/blob/master/networks/local/localnode/Dockerfile#L12) in each node to connect to it's abci.
|
||||
Override the [command](https://github.com/tendermint/tendermint/blob/master/networks/local/localnode/Dockerfile#L12) in each node to connect to it's abci.
|
||||
|
||||
```
|
||||
node0:
|
||||
|
@ -8,7 +8,7 @@ testnets on those servers.
|
||||
## Install
|
||||
|
||||
NOTE: see the [integration bash
|
||||
script](https://github.com/tendermint/tendermint/blob/develop/networks/remote/integration.sh)
|
||||
script](https://github.com/tendermint/tendermint/blob/master/networks/remote/integration.sh)
|
||||
that can be run on a fresh DO droplet and will automatically spin up a 4
|
||||
node testnet. The script more or less does everything described below.
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
ABCI is the interface between Tendermint (a state-machine replication engine)
|
||||
and your application (the actual state machine). It consists of a set of
|
||||
*methods*, where each method has a corresponding `Request` and `Response`
|
||||
_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).
|
||||
All message types are defined in a [protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto).
|
||||
This allows Tendermint to run applications written in any programming language.
|
||||
|
||||
This specification is split as follows:
|
||||
|
@ -3,9 +3,9 @@
|
||||
## Overview
|
||||
|
||||
The ABCI message types are defined in a [protobuf
|
||||
file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto).
|
||||
file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto).
|
||||
|
||||
ABCI methods are split across 3 separate ABCI *connections*:
|
||||
ABCI methods are split across 3 separate ABCI _connections_:
|
||||
|
||||
- `Consensus Connection`: `InitChain, BeginBlock, DeliverTx, EndBlock, Commit`
|
||||
- `Mempool Connection`: `CheckTx`
|
||||
@ -85,7 +85,7 @@ Example:
|
||||
cmn.KVPair{Key: []byte("amount"), Value: []byte("...")},
|
||||
cmn.KVPair{Key: []byte("reason"), Value: []byte("...")},
|
||||
},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
},
|
||||
}
|
||||
@ -115,19 +115,19 @@ non-determinism must be fixed and the nodes restarted.
|
||||
Sources of non-determinism in applications may include:
|
||||
|
||||
- Hardware failures
|
||||
- Cosmic rays, overheating, etc.
|
||||
- Cosmic rays, overheating, etc.
|
||||
- Node-dependent state
|
||||
- Random numbers
|
||||
- Time
|
||||
- Random numbers
|
||||
- Time
|
||||
- Underspecification
|
||||
- Library version changes
|
||||
- Race conditions
|
||||
- Floating point numbers
|
||||
- JSON serialization
|
||||
- Iterating through hash-tables/maps/dictionaries
|
||||
- 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)
|
||||
- Filesystem
|
||||
- Network calls (eg. some external REST API service)
|
||||
|
||||
See [#56](https://github.com/tendermint/abci/issues/56) for original discussion.
|
||||
|
||||
@ -240,9 +240,9 @@ Commit are included in the header of the next block.
|
||||
- `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.
|
||||
underlying store. The key SHOULD be specified in the Data field.
|
||||
- Apps SHOULD allow queries over specific types like
|
||||
'/accounts/...' or '/votes/...'
|
||||
'/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
|
||||
@ -269,7 +269,7 @@ Commit are included in the header of the next block.
|
||||
- Query for data from the application at current or past height.
|
||||
- Optionally return Merkle proof.
|
||||
- Merkle proof includes self-describing `type` field to support many types
|
||||
of Merkle trees and encoding formats.
|
||||
of Merkle trees and encoding formats.
|
||||
|
||||
### BeginBlock
|
||||
|
||||
@ -297,11 +297,9 @@ Commit are included in the header of the next block.
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes
|
||||
- `Type (CheckTxType)`: What type of `CheckTx` request is this? At present,
|
||||
there are two possible values: `CheckTx_Unchecked` (the default, which says
|
||||
that a full check is required), and `CheckTx_Checked` (when the mempool is
|
||||
there are two possible values: `CheckTx_New` (the default, which says
|
||||
that a full check is required), and `CheckTx_Recheck` (when the mempool is
|
||||
initiating a normal recheck of a transaction).
|
||||
- `AdditionalData ([]byte)`: Reserved for future use. See
|
||||
[here](https://github.com/tendermint/tendermint/issues/2127#issuecomment-456661420).
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
@ -486,7 +484,7 @@ Commit are included in the header of the next block.
|
||||
- `Votes ([]VoteInfo)`: List of validators addresses in the last validator set
|
||||
with their voting power and whether or not they signed a vote.
|
||||
|
||||
### ConsensusParams
|
||||
### ConsensusParams
|
||||
|
||||
- **Fields**:
|
||||
- `Block (BlockParams)`: Parameters limiting the size of a block and time between consecutive blocks.
|
||||
@ -500,17 +498,17 @@ Commit are included in the header of the next block.
|
||||
- `MaxBytes (int64)`: Max size of a block, in bytes.
|
||||
- `MaxGas (int64)`: Max sum of `GasWanted` in a proposed block.
|
||||
- NOTE: blocks that violate this may be committed if there are Byzantine proposers.
|
||||
It's the application's responsibility to handle this when processing a
|
||||
block!
|
||||
It's the application's responsibility to handle this when processing a
|
||||
block!
|
||||
|
||||
### EvidenceParams
|
||||
|
||||
- **Fields**:
|
||||
- `MaxAge (int64)`: Max age of evidence, in blocks. Evidence older than this
|
||||
is considered stale and ignored.
|
||||
- This should correspond with an app's "unbonding period" or other
|
||||
similar mechanism for handling Nothing-At-Stake attacks.
|
||||
- NOTE: this should change to time (instead of blocks)!
|
||||
- This should correspond with an app's "unbonding period" or other
|
||||
similar mechanism for handling Nothing-At-Stake attacks.
|
||||
- NOTE: this should change to time (instead of blocks)!
|
||||
|
||||
### ValidatorParams
|
||||
|
||||
@ -532,4 +530,3 @@ Commit are included in the header of the next block.
|
||||
- `Type (string)`: Type of Merkle proof and how it's encoded.
|
||||
- `Key ([]byte)`: Key in the Merkle tree that this proof is for.
|
||||
- `Data ([]byte)`: Encoded Merkle proof for the key.
|
||||
|
||||
|
@ -65,7 +65,10 @@ 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`.
|
||||
the configuration option `mempool.recheck=false`. As of Tendermint v0.32.1,
|
||||
an additional `Type` parameter is made available to the CheckTx function that
|
||||
indicates whether an incoming transaction is new (`CheckTxType_New`), or a
|
||||
recheck (`CheckTxType_Recheck`).
|
||||
|
||||
Finally, the mempool will unlock and new transactions can be processed through CheckTx again.
|
||||
|
||||
|
@ -9,7 +9,7 @@ Applications](./apps.md).
|
||||
## 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).
|
||||
[protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto).
|
||||
|
||||
Some messages have no fields, while others may include byte-arrays, strings, integers,
|
||||
or custom protobuf types.
|
||||
@ -33,9 +33,9 @@ The latter two 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),
|
||||
[Go](https://github.com/tendermint/tendermint/tree/master/abci/server),
|
||||
[JavaScript](https://github.com/tendermint/js-abci),
|
||||
[Python](https://github.com/tendermint/tendermint/tree/develop/abci/example/python3/abci),
|
||||
[Python](https://github.com/tendermint/tendermint/tree/master/abci/example/python3/abci),
|
||||
[C++](https://github.com/mdyring/cpp-tmsp), and
|
||||
[Java](https://github.com/jTendermint/jabci).
|
||||
|
||||
@ -44,14 +44,13 @@ See examples, in various stages of maintenance, in
|
||||
The simplest implementation uses function calls within Golang.
|
||||
This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary.
|
||||
|
||||
|
||||
### 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)
|
||||
file](https://github.com/tendermint/tendermint/blob/master/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/).
|
||||
@ -107,4 +106,4 @@ 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).
|
||||
[here](https://github.com/tendermint/tendermint/tree/master/abci/client).
|
||||
|
@ -59,20 +59,20 @@ familiar with amino encoding.
|
||||
You can simply use below table and concatenate Prefix || Length (of raw bytes) || raw bytes
|
||||
( while || stands for byte concatenation here).
|
||||
|
||||
| Type | Name | Prefix | Length | Notes |
|
||||
| ------------------ | ----------------------------- | ---------- | -------- | ----- |
|
||||
| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | |
|
||||
| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | |
|
||||
| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | |
|
||||
| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | |
|
||||
| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | |
|
||||
| Type | Name | Prefix | Length | Notes |
|
||||
| ----------------------- | ---------------------------------- | ---------- | -------- | ----- |
|
||||
| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | |
|
||||
| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | |
|
||||
| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | |
|
||||
| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | |
|
||||
| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | |
|
||||
|
||||
### Example
|
||||
|
||||
For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey
|
||||
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
would be encoded as
|
||||
`EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
would be encoded as
|
||||
`EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
|
||||
### Key Types
|
||||
|
||||
@ -170,11 +170,11 @@ We use the RFC 6962 specification of a merkle tree, with sha256 as the hash func
|
||||
Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure.
|
||||
The differences between RFC 6962 and the simplest form a merkle tree are that:
|
||||
|
||||
1) leaf nodes and inner nodes have different hashes.
|
||||
1. leaf nodes and inner nodes have different hashes.
|
||||
This is for "second pre-image resistance", to prevent the proof to an inner node being valid as the proof of a leaf.
|
||||
The leaf nodes are `SHA256(0x00 || leaf_data)`, and inner nodes are `SHA256(0x01 || left_hash || right_hash)`.
|
||||
|
||||
2) When the number of items isn't a power of two, the left half of the tree is as big as it could be.
|
||||
2. When the number of items isn't a power of two, the left half of the tree is as big as it could be.
|
||||
(The largest power of two less than the number of items) This allows new leaves to be added with less
|
||||
recomputation. For example:
|
||||
|
||||
@ -290,7 +290,7 @@ func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byt
|
||||
|
||||
### 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/sdk/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/master/docs/clients/lite/specification.md)
|
||||
|
||||
## JSON
|
||||
|
||||
|
@ -73,11 +73,11 @@ parameters over each successive round.
|
||||
|(When +2/3 Precommits for block found) |
|
||||
v |
|
||||
+--------------------------------------------------------------------+
|
||||
| Commit |
|
||||
| |
|
||||
| * Set CommitTime = now; |
|
||||
| * Wait for block, then stage/save/commit block; |
|
||||
+--------------------------------------------------------------------+
|
||||
| Commit |
|
||||
| |
|
||||
| * Set CommitTime = now; |
|
||||
| * Wait for block, then stage/save/commit block; |
|
||||
+--------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
# Background Gossip
|
||||
@ -120,7 +120,7 @@ A proposal is signed and published by the designated proposer at each
|
||||
round. The proposer is chosen by a deterministic and non-choking round
|
||||
robin selection algorithm that selects proposers in proportion to their
|
||||
voting power (see
|
||||
[implementation](https://github.com/tendermint/tendermint/blob/develop/types/validator_set.go)).
|
||||
[implementation](https://github.com/tendermint/tendermint/blob/master/types/validator_set.go)).
|
||||
|
||||
A proposal at `(H,R)` is composed of a block and an optional latest
|
||||
`PoLC-Round < R` which is included iff the proposer knows of one. This
|
||||
@ -131,13 +131,15 @@ liveness property.
|
||||
|
||||
### Propose Step (height:H,round:R)
|
||||
|
||||
Upon entering `Propose`: - The designated proposer proposes a block at
|
||||
`(H,R)`.
|
||||
Upon entering `Propose`:
|
||||
- The designated proposer proposes a block at `(H,R)`.
|
||||
|
||||
The `Propose` step ends: - After `timeoutProposeR` after entering
|
||||
`Propose`. --> goto `Prevote(H,R)` - After receiving proposal block
|
||||
and all prevotes at `PoLC-Round`. --> goto `Prevote(H,R)` - After
|
||||
[common exit conditions](#common-exit-conditions)
|
||||
The `Propose` step ends:
|
||||
- After `timeoutProposeR` after entering `Propose`. --> goto
|
||||
`Prevote(H,R)`
|
||||
- After receiving proposal block and all prevotes at `PoLC-Round`. -->
|
||||
goto `Prevote(H,R)`
|
||||
- After [common exit conditions](#common-exit-conditions)
|
||||
|
||||
### Prevote Step (height:H,round:R)
|
||||
|
||||
@ -152,10 +154,12 @@ Upon entering `Prevote`, each validator broadcasts its prevote vote.
|
||||
- Else, if the proposal is invalid or wasn't received on time, it
|
||||
prevotes `<nil>`.
|
||||
|
||||
The `Prevote` step ends: - After +2/3 prevotes for a particular block or
|
||||
`<nil>`. -->; goto `Precommit(H,R)` - After `timeoutPrevote` after
|
||||
receiving any +2/3 prevotes. --> goto `Precommit(H,R)` - After
|
||||
[common exit conditions](#common-exit-conditions)
|
||||
The `Prevote` step ends:
|
||||
- After +2/3 prevotes for a particular block or `<nil>`. -->; goto
|
||||
`Precommit(H,R)`
|
||||
- After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto
|
||||
`Precommit(H,R)`
|
||||
- After [common exit conditions](#common-exit-conditions)
|
||||
|
||||
### Precommit Step (height:H,round:R)
|
||||
|
||||
@ -163,17 +167,19 @@ Upon entering `Precommit`, each validator broadcasts its precommit vote.
|
||||
|
||||
- If the validator has a PoLC at `(H,R)` for a particular block `B`, it
|
||||
(re)locks (or changes lock to) and precommits `B` and sets
|
||||
`LastLockRound = R`. - Else, if the validator has a PoLC at `(H,R)` for
|
||||
`<nil>`, it unlocks and precommits `<nil>`. - Else, it keeps the lock
|
||||
unchanged and precommits `<nil>`.
|
||||
`LastLockRound = R`.
|
||||
- Else, if the validator has a PoLC at `(H,R)` for `<nil>`, it unlocks
|
||||
and precommits `<nil>`.
|
||||
- Else, it keeps the lock unchanged and precommits `<nil>`.
|
||||
|
||||
A precommit for `<nil>` means "I didn’t see a PoLC for this round, but I
|
||||
did get +2/3 prevotes and waited a bit".
|
||||
|
||||
The Precommit step ends: - After +2/3 precommits for `<nil>`. -->
|
||||
goto `Propose(H,R+1)` - After `timeoutPrecommit` after receiving any
|
||||
+2/3 precommits. --> goto `Propose(H,R+1)` - After [common exit
|
||||
conditions](#common-exit-conditions)
|
||||
The Precommit step ends:
|
||||
- After +2/3 precommits for `<nil>`. --> goto `Propose(H,R+1)`
|
||||
- After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto
|
||||
`Propose(H,R+1)`
|
||||
- After [common exit conditions](#common-exit-conditions)
|
||||
|
||||
### Common exit conditions
|
||||
|
||||
|
@ -7,7 +7,7 @@ See [this issue](https://github.com/tendermint/tendermint/issues/1503)
|
||||
Mempool maintains a cache of the last 10000 transactions to prevent
|
||||
replaying old transactions (plus transactions coming from other
|
||||
validators, who are continually exchanging transactions). Read [Replay
|
||||
Protection](../../../../app-development.md#replay-protection)
|
||||
Protection](../../../app-dev/app-development.md#replay-protection)
|
||||
for details.
|
||||
|
||||
Sending incorrectly encoded data or data exceeding `maxMsgSize` will result
|
||||
|
@ -28,5 +28,5 @@ WAL. Then it will go to precommit, and that time it will work because the
|
||||
private validator contains the `LastSignBytes` and then we’ll replay the
|
||||
precommit from the WAL.
|
||||
|
||||
Make sure to read about [WAL corruption](../../../tendermint-core/running-in-production.md#wal-corruption)
|
||||
Make sure to read about [WAL corruption](../../tendermint-core/running-in-production.md#wal-corruption)
|
||||
and recovery strategies.
|
||||
|
@ -138,6 +138,12 @@ max_subscriptions_per_client = 5
|
||||
# See https://github.com/tendermint/tendermint/issues/3435
|
||||
timeout_broadcast_tx_commit = "10s"
|
||||
|
||||
# Maximum size of request body, in bytes
|
||||
max_body_bytes = {{ .RPC.MaxBodyBytes }}
|
||||
|
||||
# Maximum size of request header, in bytes
|
||||
max_header_bytes = {{ .RPC.MaxHeaderBytes }}
|
||||
|
||||
# The path to a file containing certificate that is used to create the HTTPS server.
|
||||
# Migth be either absolute path or path related to tendermint's config directory.
|
||||
# If the certificate is signed by a certificate authority,
|
||||
@ -323,8 +329,7 @@ namespace = "tendermint"
|
||||
|
||||
If `create_empty_blocks` is set to `true` in your config, blocks will be
|
||||
created ~ every second (with default consensus parameters). You can regulate
|
||||
the delay between blocks by changing the `timeout_commit`. E.g. `timeout_commit
|
||||
= "10s"` should result in ~ 10 second blocks.
|
||||
the delay between blocks by changing the `timeout_commit`. E.g. `timeout_commit = "10s"` should result in ~ 10 second blocks.
|
||||
|
||||
**create_empty_blocks = false**
|
||||
|
||||
@ -350,7 +355,7 @@ Tendermint will only create blocks if there are transactions, or after waiting
|
||||
## Consensus timeouts explained
|
||||
|
||||
There's a variety of information about timeouts in [Running in
|
||||
production](./running-in-production.html)
|
||||
production](./running-in-production.md)
|
||||
|
||||
You can also find more detailed technical explanation in the spec: [The latest
|
||||
gossip on BFT consensus](https://arxiv.org/abs/1807.04938).
|
||||
|
@ -115,7 +115,7 @@ little overview what they do.
|
||||
- `abci-client` As mentioned in [Application Development Guide](../app-dev/app-development.md), Tendermint acts as an ABCI
|
||||
client with respect to the application and maintains 3 connections:
|
||||
mempool, consensus and query. The code used by Tendermint Core can
|
||||
be found [here](https://github.com/tendermint/tendermint/tree/develop/abci/client).
|
||||
be found [here](https://github.com/tendermint/tendermint/tree/master/abci/client).
|
||||
- `blockchain` Provides storage, pool (a group of peers), and reactor
|
||||
for both storing and exchanging blocks between peers.
|
||||
- `consensus` The heart of Tendermint core, which is the
|
||||
|
@ -4,4 +4,4 @@ The RPC documentation is hosted here:
|
||||
|
||||
- [https://tendermint.com/rpc/](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).
|
||||
To update the documentation, edit the relevant `godoc` comments in the [rpc/core directory](https://github.com/tendermint/tendermint/tree/master/rpc/core).
|
||||
|
@ -20,7 +20,7 @@ Initialize the root directory by running:
|
||||
tendermint init
|
||||
```
|
||||
|
||||
This will create a new private key (`priv_validator.json`), and a
|
||||
This will create a new private key (`priv_validator_key.json`), and a
|
||||
genesis file (`genesis.json`) containing the associated public key, in
|
||||
`$TMHOME/config`. This is all that's necessary to run a local testnet
|
||||
with one validator.
|
||||
@ -43,6 +43,11 @@ definition](https://github.com/tendermint/tendermint/blob/master/types/genesis.g
|
||||
- `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 symbols.
|
||||
- `consensus_params`
|
||||
- `block`
|
||||
- `time_iota_ms`: Minimum time increment between consecutive blocks (in
|
||||
milliseconds). If the block header timestamp is ahead of the system clock,
|
||||
decrease this value.
|
||||
- `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.
|
||||
@ -63,9 +68,10 @@ definition](https://github.com/tendermint/tendermint/blob/master/types/genesis.g
|
||||
"genesis_time": "2018-11-13T18:11:50.277637Z",
|
||||
"chain_id": "test-chain-s4ui7D",
|
||||
"consensus_params": {
|
||||
"block_size": {
|
||||
"block": {
|
||||
"max_bytes": "22020096",
|
||||
"max_gas": "-1"
|
||||
"max_gas": "-1",
|
||||
"time_iota_ms": "1000"
|
||||
},
|
||||
"evidence": {
|
||||
"max_age": "100000"
|
||||
@ -308,7 +314,7 @@ write-ahead-log](../tendermint-core/running-in-production.md#mempool-wal)
|
||||
## Tendermint Networks
|
||||
|
||||
When `tendermint init` is run, both a `genesis.json` and
|
||||
`priv_validator.json` are created in `~/.tendermint/config`. The
|
||||
`priv_validator_key.json` are created in `~/.tendermint/config`. The
|
||||
`genesis.json` might look like:
|
||||
|
||||
```
|
||||
@ -329,7 +335,7 @@ When `tendermint init` is run, both a `genesis.json` and
|
||||
}
|
||||
```
|
||||
|
||||
And the `priv_validator.json`:
|
||||
And the `priv_validator_key.json`:
|
||||
|
||||
```
|
||||
{
|
||||
@ -348,20 +354,20 @@ And the `priv_validator.json`:
|
||||
}
|
||||
```
|
||||
|
||||
The `priv_validator.json` actually contains a private key, and should
|
||||
The `priv_validator_key.json` actually contains a private key, and should
|
||||
thus be kept absolutely secret; for now we work with the plain text.
|
||||
Note the `last_` fields, which are used to prevent us from signing
|
||||
conflicting messages.
|
||||
|
||||
Note also that the `pub_key` (the public key) in the
|
||||
`priv_validator.json` is also present in the `genesis.json`.
|
||||
`priv_validator_key.json` is also present in the `genesis.json`.
|
||||
|
||||
The genesis file contains the list of public keys which may participate
|
||||
in the consensus, and their corresponding voting power. Greater than 2/3
|
||||
of the voting power must be active (i.e. the corresponding private keys
|
||||
must be producing signatures) for the consensus to make progress. In our
|
||||
case, the genesis file contains the public key of our
|
||||
`priv_validator.json`, so a Tendermint node started with the default
|
||||
`priv_validator_key.json`, so a Tendermint node started with the default
|
||||
root directory will be able to make progress. Voting power uses an int64
|
||||
but must be positive, thus the range is: 0 through 9223372036854775807.
|
||||
Because of how the current proposer selection algorithm works, we do not
|
||||
@ -447,16 +453,16 @@ not connected to the other peer.
|
||||
|
||||
The easiest way to add new validators is to do it in the `genesis.json`,
|
||||
before starting the network. For instance, we could make a new
|
||||
`priv_validator.json`, and copy it's `pub_key` into the above genesis.
|
||||
`priv_validator_key.json`, and copy it's `pub_key` into the above genesis.
|
||||
|
||||
We can generate a new `priv_validator.json` with the command:
|
||||
We can generate a new `priv_validator_key.json` with the command:
|
||||
|
||||
```
|
||||
tendermint gen_validator
|
||||
```
|
||||
|
||||
Now we can update our genesis file. For instance, if the new
|
||||
`priv_validator.json` looks like:
|
||||
`priv_validator_key.json` looks like:
|
||||
|
||||
```
|
||||
{
|
||||
@ -504,7 +510,7 @@ then the new `genesis.json` will be:
|
||||
```
|
||||
|
||||
Update the `genesis.json` in `~/.tendermint/config`. Copy the genesis
|
||||
file and the new `priv_validator.json` to the `~/.tendermint/config` on
|
||||
file and the new `priv_validator_key.json` to the `~/.tendermint/config` on
|
||||
a new machine.
|
||||
|
||||
Now run `tendermint node` on both machines, and use either
|
||||
|
Reference in New Issue
Block a user