lint markdown
This commit is contained in:
parent
5cfd8ebaa2
commit
ccaad06eb5
316
docs/abci-cli.md
316
docs/abci-cli.md
|
@ -10,41 +10,47 @@ Make sure you [have Go installed](https://golang.org/doc/install).
|
|||
|
||||
Next, install the `abci-cli` tool and example applications:
|
||||
|
||||
go get github.com/tendermint/tendermint
|
||||
```
|
||||
go get github.com/tendermint/tendermint
|
||||
```
|
||||
|
||||
to get vendored dependencies:
|
||||
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install_abci
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install_abci
|
||||
```
|
||||
|
||||
Now run `abci-cli` to see the list of commands:
|
||||
|
||||
Usage:
|
||||
abci-cli [command]
|
||||
```
|
||||
Usage:
|
||||
abci-cli [command]
|
||||
|
||||
Available Commands:
|
||||
batch Run a batch of abci commands against an application
|
||||
check_tx Validate a tx
|
||||
commit Commit the application state and return the Merkle root hash
|
||||
console Start an interactive abci console for multiple commands
|
||||
counter ABCI demo example
|
||||
deliver_tx Deliver a new tx to the application
|
||||
kvstore ABCI demo example
|
||||
echo Have the application echo a message
|
||||
help Help about any command
|
||||
info Get some info about the application
|
||||
query Query the application state
|
||||
set_option Set an options on the application
|
||||
Available Commands:
|
||||
batch Run a batch of abci commands against an application
|
||||
check_tx Validate a tx
|
||||
commit Commit the application state and return the Merkle root hash
|
||||
console Start an interactive abci console for multiple commands
|
||||
counter ABCI demo example
|
||||
deliver_tx Deliver a new tx to the application
|
||||
kvstore ABCI demo example
|
||||
echo Have the application echo a message
|
||||
help Help about any command
|
||||
info Get some info about the application
|
||||
query Query the application state
|
||||
set_option Set an options on the application
|
||||
|
||||
Flags:
|
||||
--abci string socket or grpc (default "socket")
|
||||
--address string address of application socket (default "tcp://127.0.0.1:26658")
|
||||
-h, --help help for abci-cli
|
||||
-v, --verbose print the command and results as if it were a console session
|
||||
Flags:
|
||||
--abci string socket or grpc (default "socket")
|
||||
--address string address of application socket (default "tcp://127.0.0.1:26658")
|
||||
-h, --help help for abci-cli
|
||||
-v, --verbose print the command and results as if it were a console session
|
||||
|
||||
Use "abci-cli [command] --help" for more information about a command.
|
||||
Use "abci-cli [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## KVStore - First Example
|
||||
|
||||
|
@ -63,59 +69,69 @@ Its code can be found
|
|||
[here](https://github.com/tendermint/tendermint/blob/develop/abci/cmd/abci-cli/abci-cli.go)
|
||||
and looks like:
|
||||
|
||||
func cmdKVStore(cmd *cobra.Command, args []string) error {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
// Create the application - in memory or persisted to disk
|
||||
var app types.Application
|
||||
if flagPersist == "" {
|
||||
app = kvstore.NewKVStoreApplication()
|
||||
} else {
|
||||
app = kvstore.NewPersistentKVStoreApplication(flagPersist)
|
||||
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
|
||||
}
|
||||
|
||||
// Start the listener
|
||||
srv, err := server.NewServer(flagAddrD, flagAbci, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.SetLogger(logger.With("module", "abci-server"))
|
||||
if err := srv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
srv.Stop()
|
||||
})
|
||||
return nil
|
||||
```
|
||||
func cmdKVStore(cmd *cobra.Command, args []string) error {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
// Create the application - in memory or persisted to disk
|
||||
var app types.Application
|
||||
if flagPersist == "" {
|
||||
app = kvstore.NewKVStoreApplication()
|
||||
} else {
|
||||
app = kvstore.NewPersistentKVStoreApplication(flagPersist)
|
||||
app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
|
||||
}
|
||||
|
||||
// Start the listener
|
||||
srv, err := server.NewServer(flagAddrD, flagAbci, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.SetLogger(logger.With("module", "abci-server"))
|
||||
if err := srv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
srv.Stop()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Start by running:
|
||||
|
||||
abci-cli kvstore
|
||||
```
|
||||
abci-cli kvstore
|
||||
```
|
||||
|
||||
And in another terminal, run
|
||||
|
||||
abci-cli echo hello
|
||||
abci-cli info
|
||||
```
|
||||
abci-cli echo hello
|
||||
abci-cli info
|
||||
```
|
||||
|
||||
You'll see something like:
|
||||
|
||||
-> data: hello
|
||||
-> data.hex: 68656C6C6F
|
||||
```
|
||||
-> data: hello
|
||||
-> data.hex: 68656C6C6F
|
||||
```
|
||||
|
||||
and:
|
||||
|
||||
-> data: {"size":0}
|
||||
-> data.hex: 7B2273697A65223A307D
|
||||
```
|
||||
-> data: {"size":0}
|
||||
-> data.hex: 7B2273697A65223A307D
|
||||
```
|
||||
|
||||
An ABCI application must provide two things:
|
||||
|
||||
- a socket server
|
||||
- a handler for ABCI messages
|
||||
- a socket server
|
||||
- a handler for ABCI messages
|
||||
|
||||
When we run the `abci-cli` tool we open a new connection to the
|
||||
application's socket server, send the given ABCI message, and wait for a
|
||||
|
@ -144,52 +160,54 @@ speaking ABCI messages to your application.
|
|||
|
||||
Try running these commands:
|
||||
|
||||
> echo hello
|
||||
-> code: OK
|
||||
-> data: hello
|
||||
-> data.hex: 0x68656C6C6F
|
||||
```
|
||||
> echo hello
|
||||
-> code: OK
|
||||
-> data: hello
|
||||
-> data.hex: 0x68656C6C6F
|
||||
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"size":0}
|
||||
-> data.hex: 0x7B2273697A65223A307D
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"size":0}
|
||||
-> data.hex: 0x7B2273697A65223A307D
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0000000000000000
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0000000000000000
|
||||
|
||||
> deliver_tx "abc"
|
||||
-> code: OK
|
||||
> deliver_tx "abc"
|
||||
-> code: OK
|
||||
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"size":1}
|
||||
-> data.hex: 0x7B2273697A65223A317D
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"size":1}
|
||||
-> data.hex: 0x7B2273697A65223A317D
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0200000000000000
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0200000000000000
|
||||
|
||||
> query "abc"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> value: abc
|
||||
-> value.hex: 616263
|
||||
> query "abc"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> value: abc
|
||||
-> value.hex: 616263
|
||||
|
||||
> deliver_tx "def=xyz"
|
||||
-> code: OK
|
||||
> deliver_tx "def=xyz"
|
||||
-> code: OK
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0400000000000000
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0400000000000000
|
||||
|
||||
> query "def"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> value: xyz
|
||||
-> value.hex: 78797A
|
||||
> query "def"
|
||||
-> code: OK
|
||||
-> log: exists
|
||||
-> height: 0
|
||||
-> value: xyz
|
||||
-> value.hex: 78797A
|
||||
```
|
||||
|
||||
Note that if we do `deliver_tx "abc"` it will store `(abc, abc)`, but if
|
||||
we do `deliver_tx "abc=efg"` it will store `(abc, efg)`.
|
||||
|
@ -206,29 +224,31 @@ Like the kvstore app, its code can be found
|
|||
[here](https://github.com/tendermint/tendermint/blob/master/abci/cmd/abci-cli/abci-cli.go)
|
||||
and looks like:
|
||||
|
||||
func cmdCounter(cmd *cobra.Command, args []string) error {
|
||||
|
||||
app := counter.NewCounterApplication(flagSerial)
|
||||
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
// Start the listener
|
||||
srv, err := server.NewServer(flagAddrC, flagAbci, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.SetLogger(logger.With("module", "abci-server"))
|
||||
if err := srv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
srv.Stop()
|
||||
})
|
||||
return nil
|
||||
```
|
||||
func cmdCounter(cmd *cobra.Command, args []string) error {
|
||||
|
||||
app := counter.NewCounterApplication(flagSerial)
|
||||
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
// Start the listener
|
||||
srv, err := server.NewServer(flagAddrC, flagAbci, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv.SetLogger(logger.With("module", "abci-server"))
|
||||
if err := srv.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
cmn.TrapSignal(func() {
|
||||
// Cleanup
|
||||
srv.Stop()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
The counter app doesn't use a Merkle tree, it just counts how many times
|
||||
we've sent a transaction, asked for a hash, or committed the state. The
|
||||
|
@ -256,38 +276,42 @@ whose integer is greater than the last committed one.
|
|||
Let's kill the console and the kvstore application, and start the
|
||||
counter app:
|
||||
|
||||
abci-cli counter
|
||||
```
|
||||
abci-cli counter
|
||||
```
|
||||
|
||||
In another window, start the `abci-cli console`:
|
||||
|
||||
> set_option serial on
|
||||
-> code: OK
|
||||
-> log: OK (SetOption doesn't return anything.)
|
||||
```
|
||||
> set_option serial on
|
||||
-> code: OK
|
||||
-> log: OK (SetOption doesn't return anything.)
|
||||
|
||||
> check_tx 0x00
|
||||
-> code: OK
|
||||
> check_tx 0x00
|
||||
-> code: OK
|
||||
|
||||
> check_tx 0xff
|
||||
-> code: OK
|
||||
> check_tx 0xff
|
||||
-> code: OK
|
||||
|
||||
> deliver_tx 0x00
|
||||
-> code: OK
|
||||
> deliver_tx 0x00
|
||||
-> code: OK
|
||||
|
||||
> check_tx 0x00
|
||||
-> code: BadNonce
|
||||
-> log: Invalid nonce. Expected >= 1, got 0
|
||||
> check_tx 0x00
|
||||
-> code: BadNonce
|
||||
-> log: Invalid nonce. Expected >= 1, got 0
|
||||
|
||||
> deliver_tx 0x01
|
||||
-> code: OK
|
||||
> deliver_tx 0x01
|
||||
-> code: OK
|
||||
|
||||
> deliver_tx 0x04
|
||||
-> code: BadNonce
|
||||
-> log: Invalid nonce. Expected 2, got 4
|
||||
> deliver_tx 0x04
|
||||
-> code: BadNonce
|
||||
-> log: Invalid nonce. Expected 2, got 4
|
||||
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"hashes":0,"txs":2}
|
||||
-> data.hex: 0x7B22686173686573223A302C22747873223A327D
|
||||
> info
|
||||
-> code: OK
|
||||
-> data: {"hashes":0,"txs":2}
|
||||
-> data.hex: 0x7B22686173686573223A302C22747873223A327D
|
||||
```
|
||||
|
||||
This is a very simple application, but between `counter` and `kvstore`,
|
||||
its easy to see how you can build out arbitrary application states on
|
||||
|
@ -304,7 +328,9 @@ example directory](https://github.com/tendermint/tendermint/tree/develop/abci/ex
|
|||
|
||||
To run the Node JS version, `cd` to `example/js` and run
|
||||
|
||||
node app.js
|
||||
```
|
||||
node app.js
|
||||
```
|
||||
|
||||
(you'll have to kill the other counter application process). In another
|
||||
window, run the console and those previous ABCI commands. You should get
|
||||
|
|
|
@ -36,9 +36,9 @@ Commit are included in the header of the next block.
|
|||
Tendermint opens three connections to the application to handle the
|
||||
different message types:
|
||||
|
||||
- `Consensus Connection - InitChain, BeginBlock, DeliverTx, EndBlock, Commit`
|
||||
- `Mempool Connection - CheckTx`
|
||||
- `Info Connection - Info, SetOption, Query`
|
||||
- `Consensus Connection - InitChain, BeginBlock, DeliverTx, EndBlock, Commit`
|
||||
- `Mempool Connection - CheckTx`
|
||||
- `Info Connection - Info, SetOption, Query`
|
||||
|
||||
The `Flush` message is used on every connection, and the `Echo` message
|
||||
is only used for debugging.
|
||||
|
@ -54,272 +54,272 @@ See below for more details on the message types and how they are used.
|
|||
|
||||
### Echo
|
||||
|
||||
- **Request**:
|
||||
- `Message (string)`: A string to echo back
|
||||
- **Response**:
|
||||
- `Message (string)`: The input string
|
||||
- **Usage**:
|
||||
- Echo a string to test an abci client/server implementation
|
||||
- **Request**:
|
||||
- `Message (string)`: A string to echo back
|
||||
- **Response**:
|
||||
- `Message (string)`: The input string
|
||||
- **Usage**:
|
||||
- Echo a string to test an abci client/server implementation
|
||||
|
||||
### Flush
|
||||
|
||||
- **Usage**:
|
||||
- Signals that messages queued on the client should be flushed to
|
||||
the server. It is called periodically by the client
|
||||
implementation to ensure asynchronous requests are actually
|
||||
sent, and is called immediately to make a synchronous request,
|
||||
which returns when the Flush response comes back.
|
||||
- **Usage**:
|
||||
- Signals that messages queued on the client should be flushed to
|
||||
the server. It is called periodically by the client
|
||||
implementation to ensure asynchronous requests are actually
|
||||
sent, and is called immediately to make a synchronous request,
|
||||
which returns when the Flush response comes back.
|
||||
|
||||
### Info
|
||||
|
||||
- **Request**:
|
||||
- `Version (string)`: The Tendermint version
|
||||
- **Response**:
|
||||
- `Data (string)`: Some arbitrary information
|
||||
- `Version (Version)`: Version information
|
||||
- `LastBlockHeight (int64)`: Latest block for which the app has
|
||||
called Commit
|
||||
- `LastBlockAppHash ([]byte)`: Latest result of Commit
|
||||
- **Usage**:
|
||||
- Return information about the application state.
|
||||
- Used to sync Tendermint with the application during a handshake
|
||||
that happens on startup.
|
||||
- Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to
|
||||
be updated during `Commit`, ensuring that `Commit` is never
|
||||
called twice for the same block height.
|
||||
- **Request**:
|
||||
- `Version (string)`: The Tendermint version
|
||||
- **Response**:
|
||||
- `Data (string)`: Some arbitrary information
|
||||
- `Version (Version)`: Version information
|
||||
- `LastBlockHeight (int64)`: Latest block for which the app has
|
||||
called Commit
|
||||
- `LastBlockAppHash ([]byte)`: Latest result of Commit
|
||||
- **Usage**:
|
||||
- Return information about the application state.
|
||||
- Used to sync Tendermint with the application during a handshake
|
||||
that happens on startup.
|
||||
- Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to
|
||||
be updated during `Commit`, ensuring that `Commit` is never
|
||||
called twice for the same block height.
|
||||
|
||||
### SetOption
|
||||
|
||||
- **Request**:
|
||||
- `Key (string)`: Key to set
|
||||
- `Value (string)`: Value to set for key
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- **Usage**:
|
||||
- Set non-consensus critical application specific options.
|
||||
- e.g. Key="min-fee", Value="100fermion" could set the minimum fee
|
||||
required for CheckTx (but not DeliverTx - that would be
|
||||
consensus critical).
|
||||
- **Request**:
|
||||
- `Key (string)`: Key to set
|
||||
- `Value (string)`: Value to set for key
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- **Usage**:
|
||||
- Set non-consensus critical application specific options.
|
||||
- e.g. Key="min-fee", Value="100fermion" could set the minimum fee
|
||||
required for CheckTx (but not DeliverTx - that would be
|
||||
consensus critical).
|
||||
|
||||
### InitChain
|
||||
|
||||
- **Request**:
|
||||
- `Validators ([]Validator)`: Initial genesis validators
|
||||
- `AppStateBytes ([]byte)`: Serialized initial application state
|
||||
- **Response**:
|
||||
- `ConsensusParams (ConsensusParams)`: Initial
|
||||
consensus-critical parameters.
|
||||
- `Validators ([]Validator)`: Initial validator set.
|
||||
- **Usage**:
|
||||
- Called once upon genesis.
|
||||
- **Request**:
|
||||
- `Validators ([]Validator)`: Initial genesis validators
|
||||
- `AppStateBytes ([]byte)`: Serialized initial application state
|
||||
- **Response**:
|
||||
- `ConsensusParams (ConsensusParams)`: Initial
|
||||
consensus-critical parameters.
|
||||
- `Validators ([]Validator)`: Initial validator set.
|
||||
- **Usage**:
|
||||
- Called once upon genesis.
|
||||
|
||||
### Query
|
||||
|
||||
- **Request**:
|
||||
- `Data ([]byte)`: Raw query bytes. Can be used with or in lieu
|
||||
of Path.
|
||||
- `Path (string)`: Path of request, like an HTTP GET path. Can be
|
||||
used with or in liue of Data.
|
||||
- Apps MUST interpret '/store' as a query by key on the
|
||||
underlying store. The key SHOULD be specified in the Data field.
|
||||
- Apps SHOULD allow queries over specific types like
|
||||
'/accounts/...' or '/votes/...'
|
||||
- `Height (int64)`: The block height for which you want the query
|
||||
(default=0 returns data for the latest committed block). Note
|
||||
that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- `Prove (bool)`: Return Merkle proof with response if possible
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `Index (int64)`: The index of the key in the tree.
|
||||
- `Key ([]byte)`: The key of the matching data.
|
||||
- `Value ([]byte)`: The value of the matching data.
|
||||
- `Proof ([]byte)`: Proof for the data, if requested.
|
||||
- `Height (int64)`: The block height from which data was derived.
|
||||
Note that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- **Usage**:
|
||||
- Query for data from the application at current or past height.
|
||||
- Optionally return Merkle proof.
|
||||
- **Request**:
|
||||
- `Data ([]byte)`: Raw query bytes. Can be used with or in lieu
|
||||
of Path.
|
||||
- `Path (string)`: Path of request, like an HTTP GET path. Can be
|
||||
used with or in liue of Data.
|
||||
- Apps MUST interpret '/store' as a query by key on the
|
||||
underlying store. The key SHOULD be specified in the Data field.
|
||||
- Apps SHOULD allow queries over specific types like
|
||||
'/accounts/...' or '/votes/...'
|
||||
- `Height (int64)`: The block height for which you want the query
|
||||
(default=0 returns data for the latest committed block). Note
|
||||
that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- `Prove (bool)`: Return Merkle proof with response if possible
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `Index (int64)`: The index of the key in the tree.
|
||||
- `Key ([]byte)`: The key of the matching data.
|
||||
- `Value ([]byte)`: The value of the matching data.
|
||||
- `Proof ([]byte)`: Proof for the data, if requested.
|
||||
- `Height (int64)`: The block height from which data was derived.
|
||||
Note that this is the height of the block containing the
|
||||
application's Merkle root hash, which represents the state as it
|
||||
was after committing the block at Height-1
|
||||
- **Usage**:
|
||||
- Query for data from the application at current or past height.
|
||||
- Optionally return Merkle proof.
|
||||
|
||||
### BeginBlock
|
||||
|
||||
- **Request**:
|
||||
- `Hash ([]byte)`: The block's hash. This can be derived from the
|
||||
block header.
|
||||
- `Header (struct{})`: The block header
|
||||
- `Validators ([]SigningValidator)`: List of validators in the current validator
|
||||
set and whether or not they signed a vote in the LastCommit
|
||||
- `ByzantineValidators ([]Evidence)`: List of evidence of
|
||||
validators that acted maliciously
|
||||
- **Response**:
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the beginning of a new block. Called prior to
|
||||
any DeliverTxs.
|
||||
- The header is expected to at least contain the Height.
|
||||
- The `Validators` and `ByzantineValidators` can be used to
|
||||
determine rewards and punishments for the validators.
|
||||
- **Request**:
|
||||
- `Hash ([]byte)`: The block's hash. This can be derived from the
|
||||
block header.
|
||||
- `Header (struct{})`: The block header
|
||||
- `Validators ([]SigningValidator)`: List of validators in the current validator
|
||||
set and whether or not they signed a vote in the LastCommit
|
||||
- `ByzantineValidators ([]Evidence)`: List of evidence of
|
||||
validators that acted maliciously
|
||||
- **Response**:
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the beginning of a new block. Called prior to
|
||||
any DeliverTxs.
|
||||
- The header is expected to at least contain the Height.
|
||||
- The `Validators` and `ByzantineValidators` can be used to
|
||||
determine rewards and punishments for the validators.
|
||||
|
||||
### CheckTx
|
||||
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas request for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction.
|
||||
- **Usage**: Validate a mempool transaction, prior to broadcasting
|
||||
or proposing. CheckTx should perform stateful but light-weight
|
||||
checks of the validity of the transaction (like checking signatures
|
||||
and account balances), but need not execute in full (like running a
|
||||
smart contract).
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas request for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction.
|
||||
- **Usage**: Validate a mempool transaction, prior to broadcasting
|
||||
or proposing. CheckTx should perform stateful but light-weight
|
||||
checks of the validity of the transaction (like checking signatures
|
||||
and account balances), but need not execute in full (like running a
|
||||
smart contract).
|
||||
|
||||
Tendermint runs CheckTx and DeliverTx concurrently with eachother,
|
||||
though on distinct ABCI connections - the mempool connection and the
|
||||
consensus connection, respectively.
|
||||
Tendermint runs CheckTx and DeliverTx concurrently with eachother,
|
||||
though on distinct ABCI connections - the mempool connection and the
|
||||
consensus connection, respectively.
|
||||
|
||||
The application should maintain a separate state to support CheckTx.
|
||||
This state can be reset to the latest committed state during
|
||||
`Commit`. Before calling Commit, Tendermint will lock and flush the mempool,
|
||||
ensuring that all existing CheckTx are responded to and no new ones can
|
||||
begin. After `Commit`, the mempool will rerun
|
||||
CheckTx for all remaining transactions, throwing out any that are no longer valid.
|
||||
Then the mempool will unlock and start sending CheckTx again.
|
||||
The application should maintain a separate state to support CheckTx.
|
||||
This state can be reset to the latest committed state during
|
||||
`Commit`. Before calling Commit, Tendermint will lock and flush the mempool,
|
||||
ensuring that all existing CheckTx are responded to and no new ones can
|
||||
begin. After `Commit`, the mempool will rerun
|
||||
CheckTx for all remaining transactions, throwing out any that are no longer valid.
|
||||
Then the mempool will unlock and start sending CheckTx again.
|
||||
|
||||
Keys and values in Tags must be UTF-8 encoded strings (e.g.
|
||||
"account.owner": "Bob", "balance": "100.0", "date": "2018-01-02")
|
||||
Keys and values in Tags must be UTF-8 encoded strings (e.g.
|
||||
"account.owner": "Bob", "balance": "100.0", "date": "2018-01-02")
|
||||
|
||||
### DeliverTx
|
||||
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes.
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas requested for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction.
|
||||
- **Usage**:
|
||||
- Deliver a transaction to be executed in full by the application.
|
||||
If the transaction is valid, returns CodeType.OK.
|
||||
- Keys and values in Tags must be UTF-8 encoded strings (e.g.
|
||||
"account.owner": "Bob", "balance": "100.0",
|
||||
"time": "2018-01-02T12:30:00Z")
|
||||
- **Request**:
|
||||
- `Tx ([]byte)`: The request transaction bytes.
|
||||
- **Response**:
|
||||
- `Code (uint32)`: Response code.
|
||||
- `Data ([]byte)`: Result bytes, if any.
|
||||
- `Log (string)`: The output of the application's logger. May
|
||||
be non-deterministic.
|
||||
- `Info (string)`: Additional information. May
|
||||
be non-deterministic.
|
||||
- `GasWanted (int64)`: Amount of gas requested for transaction.
|
||||
- `GasUsed (int64)`: Amount of gas consumed by transaction.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
transactions (eg. by account).
|
||||
- `Fee (cmn.KI64Pair)`: Fee paid for the transaction.
|
||||
- **Usage**:
|
||||
- Deliver a transaction to be executed in full by the application.
|
||||
If the transaction is valid, returns CodeType.OK.
|
||||
- Keys and values in Tags must be UTF-8 encoded strings (e.g.
|
||||
"account.owner": "Bob", "balance": "100.0",
|
||||
"time": "2018-01-02T12:30:00Z")
|
||||
|
||||
### EndBlock
|
||||
|
||||
- **Request**:
|
||||
- `Height (int64)`: Height of the block just executed.
|
||||
- **Response**:
|
||||
- `ValidatorUpdates ([]Validator)`: Changes to validator set (set
|
||||
voting power to 0 to remove).
|
||||
- `ConsensusParamUpdates (ConsensusParams)`: Changes to
|
||||
consensus-critical time, size, and other parameters.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the end of a block.
|
||||
- Called prior to each Commit, after all transactions.
|
||||
- Validator set and consensus params are updated with the result.
|
||||
- Validator pubkeys are expected to be go-wire encoded.
|
||||
- **Request**:
|
||||
- `Height (int64)`: Height of the block just executed.
|
||||
- **Response**:
|
||||
- `ValidatorUpdates ([]Validator)`: Changes to validator set (set
|
||||
voting power to 0 to remove).
|
||||
- `ConsensusParamUpdates (ConsensusParams)`: Changes to
|
||||
consensus-critical time, size, and other parameters.
|
||||
- `Tags ([]cmn.KVPair)`: Key-Value tags for filtering and indexing
|
||||
- **Usage**:
|
||||
- Signals the end of a block.
|
||||
- Called prior to each Commit, after all transactions.
|
||||
- Validator set and consensus params are updated with the result.
|
||||
- Validator pubkeys are expected to be go-wire encoded.
|
||||
|
||||
### Commit
|
||||
|
||||
- **Response**:
|
||||
- `Data ([]byte)`: The Merkle root hash
|
||||
- **Usage**:
|
||||
- Persist the application state.
|
||||
- Return a Merkle root hash of the application state.
|
||||
- It's critical that all application instances return the
|
||||
same hash. If not, they will not be able to agree on the next
|
||||
block, because the hash is included in the next block!
|
||||
- **Response**:
|
||||
- `Data ([]byte)`: The Merkle root hash
|
||||
- **Usage**:
|
||||
- Persist the application state.
|
||||
- Return a Merkle root hash of the application state.
|
||||
- It's critical that all application instances return the
|
||||
same hash. If not, they will not be able to agree on the next
|
||||
block, because the hash is included in the next block!
|
||||
|
||||
## Data Messages
|
||||
|
||||
### Header
|
||||
|
||||
- **Fields**:
|
||||
- `ChainID (string)`: ID of the blockchain
|
||||
- `Height (int64)`: Height of the block in the chain
|
||||
- `Time (int64)`: Unix time of the block
|
||||
- `NumTxs (int32)`: Number of transactions in the block
|
||||
- `TotalTxs (int64)`: Total number of transactions in the blockchain until
|
||||
now
|
||||
- `LastBlockHash ([]byte)`: Hash of the previous (parent) block
|
||||
- `ValidatorsHash ([]byte)`: Hash of the validator set for this block
|
||||
- `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the
|
||||
Merkle root of the application state after executing the previous block's
|
||||
transactions
|
||||
- `Proposer (Validator)`: Original proposer for the block
|
||||
- `ChainID (string)`: ID of the blockchain
|
||||
- `Height (int64)`: Height of the block in the chain
|
||||
- `Time (int64)`: Unix time of the block
|
||||
- `NumTxs (int32)`: Number of transactions in the block
|
||||
- `TotalTxs (int64)`: Total number of transactions in the blockchain until
|
||||
now
|
||||
- `LastBlockHash ([]byte)`: Hash of the previous (parent) block
|
||||
- `ValidatorsHash ([]byte)`: Hash of the validator set for this block
|
||||
- `AppHash ([]byte)`: Data returned by the last call to `Commit` - typically the
|
||||
Merkle root of the application state after executing the previous block's
|
||||
transactions
|
||||
- `Proposer (Validator)`: Original proposer for the block
|
||||
- **Usage**:
|
||||
- Provided in RequestBeginBlock
|
||||
- Provides important context about the current state of the blockchain -
|
||||
especially height and time.
|
||||
- Provides the proposer of the current block, for use in proposer-based
|
||||
reward mechanisms.
|
||||
- Provided in RequestBeginBlock
|
||||
- Provides important context about the current state of the blockchain -
|
||||
especially height and time.
|
||||
- Provides the proposer of the current block, for use in proposer-based
|
||||
reward mechanisms.
|
||||
|
||||
### Validator
|
||||
|
||||
- **Fields**:
|
||||
- `Address ([]byte)`: Address of the validator (hash of the public key)
|
||||
- `PubKey (PubKey)`: Public key of the validator
|
||||
- `Power (int64)`: Voting power of the validator
|
||||
- `Address ([]byte)`: Address of the validator (hash of the public key)
|
||||
- `PubKey (PubKey)`: Public key of the validator
|
||||
- `Power (int64)`: Voting power of the validator
|
||||
- **Usage**:
|
||||
- Provides all identifying information about the validator
|
||||
- Provides all identifying information about the validator
|
||||
|
||||
### SigningValidator
|
||||
|
||||
- **Fields**:
|
||||
- `Validator (Validator)`: A validator
|
||||
- `SignedLastBlock (bool)`: Indicated whether or not the validator signed
|
||||
the last block
|
||||
- `Validator (Validator)`: A validator
|
||||
- `SignedLastBlock (bool)`: Indicated whether or not the validator signed
|
||||
the last block
|
||||
- **Usage**:
|
||||
- Indicates whether a validator signed the last block, allowing for rewards
|
||||
based on validator availability
|
||||
- Indicates whether a validator signed the last block, allowing for rewards
|
||||
based on validator availability
|
||||
|
||||
### PubKey
|
||||
|
||||
- **Fields**:
|
||||
- `Type (string)`: Type of the public key. A simple string like `"ed25519"`.
|
||||
In the future, may indicate a serialization algorithm to parse the `Data`,
|
||||
for instance `"amino"`.
|
||||
- `Data ([]byte)`: Public key data. For a simple public key, it's just the
|
||||
raw bytes. If the `Type` indicates an encoding algorithm, this is the
|
||||
encoded public key.
|
||||
- `Type (string)`: Type of the public key. A simple string like `"ed25519"`.
|
||||
In the future, may indicate a serialization algorithm to parse the `Data`,
|
||||
for instance `"amino"`.
|
||||
- `Data ([]byte)`: Public key data. For a simple public key, it's just the
|
||||
raw bytes. If the `Type` indicates an encoding algorithm, this is the
|
||||
encoded public key.
|
||||
- **Usage**:
|
||||
- A generic and extensible typed public key
|
||||
- A generic and extensible typed public key
|
||||
|
||||
### Evidence
|
||||
|
||||
- **Fields**:
|
||||
- `Type (string)`: Type of the evidence. A hierarchical path like
|
||||
"duplicate/vote".
|
||||
- `Validator (Validator`: The offending validator
|
||||
- `Height (int64)`: Height when the offense was committed
|
||||
- `Time (int64)`: Unix time of the block at height `Height`
|
||||
- `TotalVotingPower (int64)`: Total voting power of the validator set at
|
||||
height `Height`
|
||||
- `Type (string)`: Type of the evidence. A hierarchical path like
|
||||
"duplicate/vote".
|
||||
- `Validator (Validator`: The offending validator
|
||||
- `Height (int64)`: Height when the offense was committed
|
||||
- `Time (int64)`: Unix time of the block at height `Height`
|
||||
- `TotalVotingPower (int64)`: Total voting power of the validator set at
|
||||
height `Height`
|
||||
|
|
|
@ -17,7 +17,7 @@ transaction is actually processed.
|
|||
|
||||
The ABCI application must be a deterministic result of the Tendermint
|
||||
consensus - any external influence on the application state that didn't
|
||||
come through Tendermint could cause a consensus failure. Thus *nothing*
|
||||
come through Tendermint could cause a consensus failure. Thus _nothing_
|
||||
should communicate with the application except Tendermint via ABCI.
|
||||
|
||||
If the application is written in Go, it can be compiled into the
|
||||
|
@ -43,6 +43,7 @@ all transactions, and possibly all queries, should still pass through
|
|||
Tendermint.
|
||||
|
||||
See the following for more extensive documentation:
|
||||
|
||||
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
|
||||
- [Tendermint RPC Docs](https://tendermint.github.io/slate/)
|
||||
- [Tendermint in Production](https://github.com/tendermint/tendermint/pull/1618)
|
||||
|
|
|
@ -16,28 +16,27 @@ committed in hash-linked blocks.
|
|||
|
||||
The ABCI design has a few distinct components:
|
||||
|
||||
- message protocol
|
||||
- pairs of request and response messages
|
||||
- consensus makes requests, application responds
|
||||
- defined using protobuf
|
||||
- server/client
|
||||
- consensus engine runs the client
|
||||
- application runs the server
|
||||
- two implementations:
|
||||
- async raw bytes
|
||||
- grpc
|
||||
- blockchain protocol
|
||||
- abci is connection oriented
|
||||
- Tendermint Core maintains three connections:
|
||||
- [mempool connection](#mempool-connection): for checking if
|
||||
transactions should be relayed before they are committed;
|
||||
only uses `CheckTx`
|
||||
- [consensus connection](#consensus-connection): for executing
|
||||
transactions that have been committed. Message sequence is
|
||||
-for every block
|
||||
-`BeginBlock, [DeliverTx, ...], EndBlock, Commit`
|
||||
- [query connection](#query-connection): for querying the
|
||||
application state; only uses Query and Info
|
||||
- message protocol
|
||||
- pairs of request and response messages
|
||||
- consensus makes requests, application responds
|
||||
- defined using protobuf
|
||||
- server/client
|
||||
- consensus engine runs the client
|
||||
- application runs the server
|
||||
- two implementations:
|
||||
- async raw bytes
|
||||
- grpc
|
||||
- blockchain protocol
|
||||
- abci is connection oriented
|
||||
- Tendermint Core maintains three connections:
|
||||
- [mempool connection](#mempool-connection): for checking if
|
||||
transactions should be relayed before they are committed;
|
||||
only uses `CheckTx`
|
||||
- [consensus connection](#consensus-connection): for executing
|
||||
transactions that have been committed. Message sequence is
|
||||
-for every block -`BeginBlock, [DeliverTx, ...], EndBlock, Commit`
|
||||
- [query connection](#query-connection): for querying the
|
||||
application state; only uses Query and Info
|
||||
|
||||
The mempool and consensus logic act as clients, and each maintains an
|
||||
open ABCI connection with the application, which hosts an ABCI server.
|
||||
|
@ -64,9 +63,9 @@ To use ABCI in your programming language of choice, there must be a ABCI
|
|||
server in that language. Tendermint supports two kinds of implementation
|
||||
of the server:
|
||||
|
||||
- Asynchronous, raw socket server (Tendermint Socket Protocol, also
|
||||
known as TSP or Teaspoon)
|
||||
- GRPC
|
||||
- Asynchronous, raw socket server (Tendermint Socket Protocol, also
|
||||
known as TSP or Teaspoon)
|
||||
- GRPC
|
||||
|
||||
Both can be tested using the `abci-cli` by setting the `--abci` flag
|
||||
appropriately (ie. to `socket` or `grpc`).
|
||||
|
@ -161,7 +160,7 @@ connection, to query the local state of the app.
|
|||
|
||||
### Mempool Connection
|
||||
|
||||
The mempool connection is used *only* for CheckTx requests. Transactions
|
||||
The mempool connection is used _only_ for CheckTx requests. Transactions
|
||||
are run using CheckTx in the same order they were received by the
|
||||
validator. If the CheckTx returns `OK`, the transaction is kept in
|
||||
memory and relayed to other peers in the same order it was received.
|
||||
|
@ -180,23 +179,27 @@ mempool state (this behaviour can be turned off with
|
|||
|
||||
In go:
|
||||
|
||||
func (app *KVStoreApplication) CheckTx(tx []byte) types.Result {
|
||||
return types.OK
|
||||
}
|
||||
```
|
||||
func (app *KVStoreApplication) CheckTx(tx []byte) types.Result {
|
||||
return types.OK
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
ResponseCheckTx requestCheckTx(RequestCheckTx req) {
|
||||
byte[] transaction = req.getTx().toByteArray();
|
||||
```
|
||||
ResponseCheckTx requestCheckTx(RequestCheckTx req) {
|
||||
byte[] transaction = req.getTx().toByteArray();
|
||||
|
||||
// validate transaction
|
||||
// validate transaction
|
||||
|
||||
if (notValid) {
|
||||
return ResponseCheckTx.newBuilder().setCode(CodeType.BadNonce).setLog("invalid tx").build();
|
||||
} else {
|
||||
return ResponseCheckTx.newBuilder().setCode(CodeType.OK).build();
|
||||
}
|
||||
if (notValid) {
|
||||
return ResponseCheckTx.newBuilder().setCode(CodeType.BadNonce).setLog("invalid tx").build();
|
||||
} else {
|
||||
return ResponseCheckTx.newBuilder().setCode(CodeType.OK).build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Replay Protection
|
||||
|
||||
|
@ -242,43 +245,47 @@ merkle root of the data returned by the DeliverTx requests, or both.
|
|||
|
||||
In go:
|
||||
|
||||
// tx is either "key=value" or just arbitrary bytes
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||
parts := strings.Split(string(tx), "=")
|
||||
if len(parts) == 2 {
|
||||
app.state.Set([]byte(parts[0]), []byte(parts[1]))
|
||||
} else {
|
||||
app.state.Set(tx, tx)
|
||||
}
|
||||
return types.OK
|
||||
}
|
||||
```
|
||||
// tx is either "key=value" or just arbitrary bytes
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||
parts := strings.Split(string(tx), "=")
|
||||
if len(parts) == 2 {
|
||||
app.state.Set([]byte(parts[0]), []byte(parts[1]))
|
||||
} else {
|
||||
app.state.Set(tx, tx)
|
||||
}
|
||||
return types.OK
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
/**
|
||||
* Using Protobuf types from the protoc compiler, we always start with a byte[]
|
||||
*/
|
||||
ResponseDeliverTx deliverTx(RequestDeliverTx request) {
|
||||
byte[] transaction = request.getTx().toByteArray();
|
||||
```
|
||||
/**
|
||||
* Using Protobuf types from the protoc compiler, we always start with a byte[]
|
||||
*/
|
||||
ResponseDeliverTx deliverTx(RequestDeliverTx request) {
|
||||
byte[] transaction = request.getTx().toByteArray();
|
||||
|
||||
// validate your transaction
|
||||
|
||||
if (notValid) {
|
||||
return ResponseDeliverTx.newBuilder().setCode(CodeType.BadNonce).setLog("transaction was invalid").build();
|
||||
} else {
|
||||
ResponseDeliverTx.newBuilder().setCode(CodeType.OK).build();
|
||||
}
|
||||
// validate your transaction
|
||||
|
||||
if (notValid) {
|
||||
return ResponseDeliverTx.newBuilder().setCode(CodeType.BadNonce).setLog("transaction was invalid").build();
|
||||
} else {
|
||||
ResponseDeliverTx.newBuilder().setCode(CodeType.OK).build();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Commit
|
||||
|
||||
Once all processing of the block is complete, Tendermint sends the
|
||||
Commit request and blocks waiting for a response. While the mempool may
|
||||
run concurrently with block processing (the BeginBlock, DeliverTxs, and
|
||||
EndBlock), it is locked for the Commit request so that its state can be
|
||||
safely reset during Commit. This means the app *MUST NOT* do any
|
||||
blocking communication with the mempool (ie. broadcast\_tx) during
|
||||
safely reset during Commit. This means the app _MUST NOT_ do any
|
||||
blocking communication with the mempool (ie. broadcast_tx) during
|
||||
Commit, or there will be deadlock. Note also that all remaining
|
||||
transactions in the mempool are replayed on the mempool connection
|
||||
(CheckTx) following a commit.
|
||||
|
@ -294,21 +301,25 @@ job of the [Handshake](#handshake).
|
|||
|
||||
In go:
|
||||
|
||||
func (app *KVStoreApplication) Commit() types.Result {
|
||||
hash := app.state.Hash()
|
||||
return types.NewResultOK(hash, "")
|
||||
}
|
||||
```
|
||||
func (app *KVStoreApplication) Commit() types.Result {
|
||||
hash := app.state.Hash()
|
||||
return types.NewResultOK(hash, "")
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
ResponseCommit requestCommit(RequestCommit requestCommit) {
|
||||
```
|
||||
ResponseCommit requestCommit(RequestCommit requestCommit) {
|
||||
|
||||
// update the internal app-state
|
||||
byte[] newAppState = calculateAppState();
|
||||
// update the internal app-state
|
||||
byte[] newAppState = calculateAppState();
|
||||
|
||||
// and return it to the node
|
||||
return ResponseCommit.newBuilder().setCode(CodeType.OK).setData(ByteString.copyFrom(newAppState)).build();
|
||||
}
|
||||
// and return it to the node
|
||||
return ResponseCommit.newBuilder().setCode(CodeType.OK).setData(ByteString.copyFrom(newAppState)).build();
|
||||
}
|
||||
```
|
||||
|
||||
### BeginBlock
|
||||
|
||||
|
@ -322,31 +333,35 @@ pick up from when it restarts. See information on the Handshake, below.
|
|||
|
||||
In go:
|
||||
|
||||
// Track the block hash and header information
|
||||
func (app *PersistentKVStoreApplication) BeginBlock(params types.RequestBeginBlock) {
|
||||
// update latest block info
|
||||
app.blockHeader = params.Header
|
||||
```
|
||||
// Track the block hash and header information
|
||||
func (app *PersistentKVStoreApplication) BeginBlock(params types.RequestBeginBlock) {
|
||||
// update latest block info
|
||||
app.blockHeader = params.Header
|
||||
|
||||
// reset valset changes
|
||||
app.changes = make([]*types.Validator, 0)
|
||||
}
|
||||
// reset valset changes
|
||||
app.changes = make([]*types.Validator, 0)
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
/*
|
||||
* all types come from protobuf definition
|
||||
*/
|
||||
ResponseBeginBlock requestBeginBlock(RequestBeginBlock req) {
|
||||
```
|
||||
/*
|
||||
* all types come from protobuf definition
|
||||
*/
|
||||
ResponseBeginBlock requestBeginBlock(RequestBeginBlock req) {
|
||||
|
||||
Header header = req.getHeader();
|
||||
byte[] prevAppHash = header.getAppHash().toByteArray();
|
||||
long prevHeight = header.getHeight();
|
||||
long numTxs = header.getNumTxs();
|
||||
Header header = req.getHeader();
|
||||
byte[] prevAppHash = header.getAppHash().toByteArray();
|
||||
long prevHeight = header.getHeight();
|
||||
long numTxs = header.getNumTxs();
|
||||
|
||||
// run your pre-block logic. Maybe prepare a state snapshot, message components, etc
|
||||
// run your pre-block logic. Maybe prepare a state snapshot, message components, etc
|
||||
|
||||
return ResponseBeginBlock.newBuilder().build();
|
||||
}
|
||||
return ResponseBeginBlock.newBuilder().build();
|
||||
}
|
||||
```
|
||||
|
||||
### EndBlock
|
||||
|
||||
|
@ -364,25 +379,29 @@ for details on how it tracks validators.
|
|||
|
||||
In go:
|
||||
|
||||
// Update the validator set
|
||||
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
|
||||
}
|
||||
```
|
||||
// Update the validator set
|
||||
func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock {
|
||||
return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates}
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
/*
|
||||
* Assume that one validator changes. The new validator has a power of 10
|
||||
*/
|
||||
ResponseEndBlock requestEndBlock(RequestEndBlock req) {
|
||||
final long currentHeight = req.getHeight();
|
||||
final byte[] validatorPubKey = getValPubKey();
|
||||
```
|
||||
/*
|
||||
* Assume that one validator changes. The new validator has a power of 10
|
||||
*/
|
||||
ResponseEndBlock requestEndBlock(RequestEndBlock req) {
|
||||
final long currentHeight = req.getHeight();
|
||||
final byte[] validatorPubKey = getValPubKey();
|
||||
|
||||
ResponseEndBlock.Builder builder = ResponseEndBlock.newBuilder();
|
||||
builder.addDiffs(1, Types.Validator.newBuilder().setPower(10L).setPubKey(ByteString.copyFrom(validatorPubKey)).build());
|
||||
ResponseEndBlock.Builder builder = ResponseEndBlock.newBuilder();
|
||||
builder.addDiffs(1, Types.Validator.newBuilder().setPower(10L).setPubKey(ByteString.copyFrom(validatorPubKey)).build());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Query Connection
|
||||
|
||||
|
@ -398,67 +417,71 @@ connecting, according to IP address or node ID. For instance,
|
|||
returning non-OK ABCI response to either of the following queries will
|
||||
cause Tendermint to not connect to the corresponding peer:
|
||||
|
||||
- `p2p/filter/addr/<ip addr>`, where `<ip addr>` is an IP address.
|
||||
- `p2p/filter/id/<id>`, where `<is>` is the hex-encoded node ID (the hash of
|
||||
the node's p2p pubkey).
|
||||
- `p2p/filter/addr/<ip addr>`, where `<ip addr>` is an IP address.
|
||||
- `p2p/filter/id/<id>`, where `<is>` is the hex-encoded node ID (the hash of
|
||||
the node's p2p pubkey).
|
||||
|
||||
Note: these query formats are subject to change!
|
||||
|
||||
In go:
|
||||
|
||||
func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
||||
if reqQuery.Prove {
|
||||
value, proof, exists := app.state.Proof(reqQuery.Data)
|
||||
resQuery.Index = -1 // TODO make Proof return index
|
||||
resQuery.Key = reqQuery.Data
|
||||
resQuery.Value = value
|
||||
resQuery.Proof = proof
|
||||
if exists {
|
||||
resQuery.Log = "exists"
|
||||
} else {
|
||||
resQuery.Log = "does not exist"
|
||||
}
|
||||
return
|
||||
} else {
|
||||
index, value, exists := app.state.Get(reqQuery.Data)
|
||||
resQuery.Index = int64(index)
|
||||
resQuery.Value = value
|
||||
if exists {
|
||||
resQuery.Log = "exists"
|
||||
} else {
|
||||
resQuery.Log = "does not exist"
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
||||
func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
||||
if reqQuery.Prove {
|
||||
value, proof, exists := app.state.Proof(reqQuery.Data)
|
||||
resQuery.Index = -1 // TODO make Proof return index
|
||||
resQuery.Key = reqQuery.Data
|
||||
resQuery.Value = value
|
||||
resQuery.Proof = proof
|
||||
if exists {
|
||||
resQuery.Log = "exists"
|
||||
} else {
|
||||
resQuery.Log = "does not exist"
|
||||
}
|
||||
return
|
||||
} else {
|
||||
index, value, exists := app.state.Get(reqQuery.Data)
|
||||
resQuery.Index = int64(index)
|
||||
resQuery.Value = value
|
||||
if exists {
|
||||
resQuery.Log = "exists"
|
||||
} else {
|
||||
resQuery.Log = "does not exist"
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
ResponseQuery requestQuery(RequestQuery req) {
|
||||
final boolean isProveQuery = req.getProve();
|
||||
final ResponseQuery.Builder responseBuilder = ResponseQuery.newBuilder();
|
||||
```
|
||||
ResponseQuery requestQuery(RequestQuery req) {
|
||||
final boolean isProveQuery = req.getProve();
|
||||
final ResponseQuery.Builder responseBuilder = ResponseQuery.newBuilder();
|
||||
|
||||
if (isProveQuery) {
|
||||
com.app.example.ProofResult proofResult = generateProof(req.getData().toByteArray());
|
||||
final byte[] proofAsByteArray = proofResult.getAsByteArray();
|
||||
if (isProveQuery) {
|
||||
com.app.example.ProofResult proofResult = generateProof(req.getData().toByteArray());
|
||||
final byte[] proofAsByteArray = proofResult.getAsByteArray();
|
||||
|
||||
responseBuilder.setProof(ByteString.copyFrom(proofAsByteArray));
|
||||
responseBuilder.setKey(req.getData());
|
||||
responseBuilder.setValue(ByteString.copyFrom(proofResult.getData()));
|
||||
responseBuilder.setLog(result.getLogValue());
|
||||
} else {
|
||||
byte[] queryData = req.getData().toByteArray();
|
||||
responseBuilder.setProof(ByteString.copyFrom(proofAsByteArray));
|
||||
responseBuilder.setKey(req.getData());
|
||||
responseBuilder.setValue(ByteString.copyFrom(proofResult.getData()));
|
||||
responseBuilder.setLog(result.getLogValue());
|
||||
} else {
|
||||
byte[] queryData = req.getData().toByteArray();
|
||||
|
||||
final com.app.example.QueryResult result = generateQueryResult(queryData);
|
||||
final com.app.example.QueryResult result = generateQueryResult(queryData);
|
||||
|
||||
responseBuilder.setIndex(result.getIndex());
|
||||
responseBuilder.setValue(ByteString.copyFrom(result.getValue()));
|
||||
responseBuilder.setLog(result.getLogValue());
|
||||
}
|
||||
|
||||
return responseBuilder.build();
|
||||
responseBuilder.setIndex(result.getIndex());
|
||||
responseBuilder.setValue(ByteString.copyFrom(result.getValue()));
|
||||
responseBuilder.setLog(result.getLogValue());
|
||||
}
|
||||
|
||||
return responseBuilder.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Handshake
|
||||
|
||||
When the app or tendermint restarts, they need to sync to a common
|
||||
|
@ -477,17 +500,21 @@ all blocks.
|
|||
|
||||
In go:
|
||||
|
||||
func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
||||
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())}
|
||||
}
|
||||
```
|
||||
func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
||||
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())}
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
ResponseInfo requestInfo(RequestInfo req) {
|
||||
final byte[] lastAppHash = getLastAppHash();
|
||||
final long lastHeight = getLastHeight();
|
||||
return ResponseInfo.newBuilder().setLastBlockAppHash(ByteString.copyFrom(lastAppHash)).setLastBlockHeight(lastHeight).build();
|
||||
}
|
||||
```
|
||||
ResponseInfo requestInfo(RequestInfo req) {
|
||||
final byte[] lastAppHash = getLastAppHash();
|
||||
final long lastHeight = getLastHeight();
|
||||
return ResponseInfo.newBuilder().setLastBlockAppHash(ByteString.copyFrom(lastAppHash)).setLastBlockHeight(lastHeight).build();
|
||||
}
|
||||
```
|
||||
|
||||
### Genesis
|
||||
|
||||
|
@ -497,31 +524,35 @@ consensus params.
|
|||
|
||||
In go:
|
||||
|
||||
// Save the validators in the merkle tree
|
||||
func (app *PersistentKVStoreApplication) InitChain(params types.RequestInitChain) {
|
||||
for _, v := range params.Validators {
|
||||
r := app.updateValidator(v)
|
||||
if r.IsErr() {
|
||||
app.logger.Error("Error updating validators", "r", r)
|
||||
}
|
||||
}
|
||||
```
|
||||
// Save the validators in the merkle tree
|
||||
func (app *PersistentKVStoreApplication) InitChain(params types.RequestInitChain) {
|
||||
for _, v := range params.Validators {
|
||||
r := app.updateValidator(v)
|
||||
if r.IsErr() {
|
||||
app.logger.Error("Error updating validators", "r", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Java:
|
||||
|
||||
/*
|
||||
* all types come from protobuf definition
|
||||
*/
|
||||
ResponseInitChain requestInitChain(RequestInitChain req) {
|
||||
final int validatorsCount = req.getValidatorsCount();
|
||||
final List<Types.Validator> validatorsList = req.getValidatorsList();
|
||||
```
|
||||
/*
|
||||
* all types come from protobuf definition
|
||||
*/
|
||||
ResponseInitChain requestInitChain(RequestInitChain req) {
|
||||
final int validatorsCount = req.getValidatorsCount();
|
||||
final List<Types.Validator> validatorsList = req.getValidatorsList();
|
||||
|
||||
validatorsList.forEach((validator) -> {
|
||||
long power = validator.getPower();
|
||||
byte[] validatorPubKey = validator.getPubKey().toByteArray();
|
||||
validatorsList.forEach((validator) -> {
|
||||
long power = validator.getPower();
|
||||
byte[] validatorPubKey = validator.getPubKey().toByteArray();
|
||||
|
||||
// do somehing for validator setup in app
|
||||
});
|
||||
// do somehing for validator setup in app
|
||||
});
|
||||
|
||||
return ResponseInitChain.newBuilder().build();
|
||||
}
|
||||
return ResponseInitChain.newBuilder().build();
|
||||
}
|
||||
```
|
||||
|
|
|
@ -16,30 +16,29 @@ to.
|
|||
|
||||
Here are the steps to setting up a testnet manually:
|
||||
|
||||
1) Provision nodes on your cloud provider of choice
|
||||
2) Install Tendermint and the application of interest on all nodes
|
||||
3) Generate a private key and a node key for each validator using
|
||||
1. Provision nodes on your cloud provider of choice
|
||||
2. Install Tendermint and the application of interest on all nodes
|
||||
3. Generate a private key and a node key for each validator using
|
||||
`tendermint init`
|
||||
4) Compile a list of public keys for each validator into a
|
||||
4. Compile a list of public keys for each validator into a
|
||||
`genesis.json` file and replace the existing file with it.
|
||||
5) Run
|
||||
`tendermint node --proxy_app=kvstore --p2p.persistent_peers=< peer
|
||||
addresses >` on each node, where `< peer addresses >` is a comma separated
|
||||
5. Run
|
||||
`tendermint node --proxy_app=kvstore --p2p.persistent_peers=< peer addresses >` on each node, where `< peer addresses >` is a comma separated
|
||||
list of the ID@IP:PORT combination for each node. The default port for
|
||||
Tendermint is `26656`. The ID of a node can be obtained by running
|
||||
`tendermint show_node_id` command. Thus, if the IP addresses of your nodes
|
||||
were `192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4`, the command
|
||||
would look like:
|
||||
|
||||
|
||||
tendermint node --proxy_app=kvstore --p2p.persistent_peers=96663a3dd0d7b9d17d4c8211b191af259621c693@192.168.0.1:26656, 429fcf25974313b95673f58d77eacdd434402665@192.168.0.2:26656, 0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@192.168.0.3:26656, f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@192.168.0.4:26656
|
||||
```
|
||||
tendermint node --proxy_app=kvstore --p2p.persistent_peers=96663a3dd0d7b9d17d4c8211b191af259621c693@192.168.0.1:26656, 429fcf25974313b95673f58d77eacdd434402665@192.168.0.2:26656, 0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@192.168.0.3:26656, f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@192.168.0.4:26656
|
||||
```
|
||||
|
||||
After a few seconds, all the nodes should connect to each other and
|
||||
start making blocks! For more information, see the Tendermint Networks
|
||||
section of [the guide to using Tendermint](./using-tendermint.md).
|
||||
|
||||
But wait! Steps 3, 4 and 5 are quite manual. Instead, use the `tendermint
|
||||
testnet` command. By default, running `tendermint testnet` will create all the
|
||||
But wait! Steps 3, 4 and 5 are quite manual. Instead, use the `tendermint testnet` command. By default, running `tendermint testnet` will create all the
|
||||
required files, but it won't populate the list of persistent peers. It will do
|
||||
it however if you provide the `--populate-persistent-peers` flag and optional
|
||||
`--starting-ip-address` flag. Run `tendermint testnet --help` for more details
|
||||
|
@ -63,7 +62,9 @@ The easiest and fastest way to get a testnet up in less than 5 minutes.
|
|||
|
||||
With `docker` and `docker-compose` installed, run the command:
|
||||
|
||||
make localnet-start
|
||||
```
|
||||
make localnet-start
|
||||
```
|
||||
|
||||
from the root of the tendermint repository. This will spin up a 4-node
|
||||
local testnet. Review the target in the Makefile to debug any problems.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
The growing list of applications built using various pieces of the
|
||||
Tendermint stack can be found at:
|
||||
|
||||
- https://tendermint.com/ecosystem
|
||||
- https://tendermint.com/ecosystem
|
||||
|
||||
We thank the community for their contributions thus far and welcome the
|
||||
addition of new projects. A pull request can be submitted to [this
|
||||
|
|
|
@ -24,6 +24,7 @@ The script is also used to facilitate cluster deployment below.
|
|||
### Manual Install
|
||||
|
||||
Requires:
|
||||
|
||||
- `go` minimum version 1.10
|
||||
- `$GOPATH` environment variable must be set
|
||||
- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH)
|
||||
|
|
|
@ -25,11 +25,13 @@ more info.
|
|||
|
||||
Then run
|
||||
|
||||
go get github.com/tendermint/tendermint
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install_abci
|
||||
```
|
||||
go get github.com/tendermint/tendermint
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install_abci
|
||||
```
|
||||
|
||||
Now you should have the `abci-cli` installed; you'll see a couple of
|
||||
commands (`counter` and `kvstore`) that are example applications written
|
||||
|
@ -47,13 +49,17 @@ full transaction bytes are stored as the key and the value.
|
|||
|
||||
Let's start a kvstore application.
|
||||
|
||||
abci-cli kvstore
|
||||
```
|
||||
abci-cli kvstore
|
||||
```
|
||||
|
||||
In another terminal, we can start Tendermint. If you have never run
|
||||
Tendermint before, use:
|
||||
|
||||
tendermint init
|
||||
tendermint node
|
||||
```
|
||||
tendermint init
|
||||
tendermint node
|
||||
```
|
||||
|
||||
If you have used Tendermint, you may want to reset the data for a new
|
||||
blockchain by running `tendermint unsafe_reset_all`. Then you can run
|
||||
|
@ -63,14 +69,18 @@ details, see [the guide on using Tendermint](./using-tendermint.md).
|
|||
You should see Tendermint making blocks! We can get the status of our
|
||||
Tendermint node as follows:
|
||||
|
||||
curl -s localhost:26657/status
|
||||
```
|
||||
curl -s localhost:26657/status
|
||||
```
|
||||
|
||||
The `-s` just silences `curl`. For nicer output, pipe the result into a
|
||||
tool like [jq](https://stedolan.github.io/jq/) or `json_pp`.
|
||||
|
||||
Now let's send some transactions to the kvstore.
|
||||
|
||||
curl -s 'localhost:26657/broadcast_tx_commit?tx="abcd"'
|
||||
```
|
||||
curl -s 'localhost:26657/broadcast_tx_commit?tx="abcd"'
|
||||
```
|
||||
|
||||
Note the single quote (`'`) around the url, which ensures that the
|
||||
double quotes (`"`) are not escaped by bash. This command sent a
|
||||
|
@ -78,50 +88,56 @@ transaction with bytes `abcd`, so `abcd` will be stored as both the key
|
|||
and the value in the Merkle tree. The response should look something
|
||||
like:
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"deliver_tx": {
|
||||
"tags": [
|
||||
{
|
||||
"key": "YXBwLmNyZWF0b3I=",
|
||||
"value": "amFl"
|
||||
},
|
||||
"deliver_tx": {
|
||||
"tags": [
|
||||
{
|
||||
"key": "YXBwLmNyZWF0b3I=",
|
||||
"value": "amFl"
|
||||
},
|
||||
{
|
||||
"key": "YXBwLmtleQ==",
|
||||
"value": "YWJjZA=="
|
||||
}
|
||||
],
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "9DF66553F98DE3C26E3C3317A3E4CED54F714E39",
|
||||
"height": 14
|
||||
}
|
||||
}
|
||||
{
|
||||
"key": "YXBwLmtleQ==",
|
||||
"value": "YWJjZA=="
|
||||
}
|
||||
],
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "9DF66553F98DE3C26E3C3317A3E4CED54F714E39",
|
||||
"height": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can confirm that our transaction worked and the value got stored by
|
||||
querying the app:
|
||||
|
||||
curl -s 'localhost:26657/abci_query?data="abcd"'
|
||||
```
|
||||
curl -s 'localhost:26657/abci_query?data="abcd"'
|
||||
```
|
||||
|
||||
The result should look like:
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"response": {
|
||||
"log": "exists",
|
||||
"index": "-1",
|
||||
"key": "YWJjZA==",
|
||||
"value": "YWJjZA=="
|
||||
}
|
||||
}
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"response": {
|
||||
"log": "exists",
|
||||
"index": "-1",
|
||||
"key": "YWJjZA==",
|
||||
"value": "YWJjZA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note the `value` in the result (`YWJjZA==`); this is the base64-encoding
|
||||
of the ASCII of `abcd`. You can verify this in a python 2 shell by
|
||||
|
@ -132,12 +148,16 @@ human-readable](https://github.com/tendermint/tendermint/issues/1794).
|
|||
|
||||
Now let's try setting a different key and value:
|
||||
|
||||
curl -s 'localhost:26657/broadcast_tx_commit?tx="name=satoshi"'
|
||||
```
|
||||
curl -s 'localhost:26657/broadcast_tx_commit?tx="name=satoshi"'
|
||||
```
|
||||
|
||||
Now if we query for `name`, we should get `satoshi`, or `c2F0b3NoaQ==`
|
||||
in base64:
|
||||
|
||||
curl -s 'localhost:26657/abci_query?data="name"'
|
||||
```
|
||||
curl -s 'localhost:26657/abci_query?data="name"'
|
||||
```
|
||||
|
||||
Try some other transactions and queries to make sure everything is
|
||||
working!
|
||||
|
@ -171,57 +191,67 @@ Let's kill the previous instance of `tendermint` and the `kvstore`
|
|||
application, and start the counter app. We can enable `serial=on` with a
|
||||
flag:
|
||||
|
||||
abci-cli counter --serial
|
||||
```
|
||||
abci-cli counter --serial
|
||||
```
|
||||
|
||||
In another window, reset then start Tendermint:
|
||||
|
||||
tendermint unsafe_reset_all
|
||||
tendermint node
|
||||
```
|
||||
tendermint unsafe_reset_all
|
||||
tendermint node
|
||||
```
|
||||
|
||||
Once again, you can see the blocks streaming by. Let's send some
|
||||
transactions. Since we have set `serial=on`, the first transaction must
|
||||
be the number `0`:
|
||||
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x00
|
||||
```
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x00
|
||||
```
|
||||
|
||||
Note the empty (hence successful) response. The next transaction must be
|
||||
the number `1`. If instead, we try to send a `5`, we get an error:
|
||||
|
||||
> curl localhost:26657/broadcast_tx_commit?tx=0x05
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 2,
|
||||
"log": "Invalid nonce. Expected 1, got 5",
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "33B93DFF98749B0D6996A70F64071347060DC19C",
|
||||
"height": 34
|
||||
}
|
||||
}
|
||||
```
|
||||
> curl localhost:26657/broadcast_tx_commit?tx=0x05
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"deliver_tx": {
|
||||
"code": 2,
|
||||
"log": "Invalid nonce. Expected 1, got 5",
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "33B93DFF98749B0D6996A70F64071347060DC19C",
|
||||
"height": 34
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
But if we send a `1`, it works again:
|
||||
|
||||
> curl localhost:26657/broadcast_tx_commit?tx=0x01
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"deliver_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "F17854A977F6FA7EEA1BD758E296710B86F72F3D",
|
||||
"height": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
> curl localhost:26657/broadcast_tx_commit?tx=0x01
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "",
|
||||
"result": {
|
||||
"check_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"deliver_tx": {
|
||||
"fee": {}
|
||||
},
|
||||
"hash": "F17854A977F6FA7EEA1BD758E296710B86F72F3D",
|
||||
"height": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more details on the `broadcast_tx` API, see [the guide on using
|
||||
Tendermint](./using-tendermint.md).
|
||||
|
@ -236,26 +266,34 @@ You'll also need to fetch the relevant repository, from
|
|||
[here](https://github.com/tendermint/js-abci) then install it. As go
|
||||
devs, we keep all our code under the `$GOPATH`, so run:
|
||||
|
||||
go get github.com/tendermint/js-abci &> /dev/null
|
||||
cd $GOPATH/src/github.com/tendermint/js-abci/example
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
go get github.com/tendermint/js-abci &> /dev/null
|
||||
cd $GOPATH/src/github.com/tendermint/js-abci/example
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
Kill the previous `counter` and `tendermint` processes. Now run the app:
|
||||
|
||||
node example/counter.js
|
||||
```
|
||||
node example/counter.js
|
||||
```
|
||||
|
||||
In another window, reset and start `tendermint`:
|
||||
|
||||
tendermint unsafe_reset_all
|
||||
tendermint node
|
||||
```
|
||||
tendermint unsafe_reset_all
|
||||
tendermint node
|
||||
```
|
||||
|
||||
Once again, you should see blocks streaming by - but now, our
|
||||
application is written in javascript! Try sending some transactions, and
|
||||
like before - the results should be the same:
|
||||
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x00 # ok
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x05 # invalid nonce
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x01 # ok
|
||||
```
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x00 # ok
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x05 # invalid nonce
|
||||
curl localhost:26657/broadcast_tx_commit?tx=0x01 # ok
|
||||
```
|
||||
|
||||
Neat, eh?
|
||||
|
|
|
@ -5,49 +5,59 @@
|
|||
We first create three connections (mempool, consensus and query) to the
|
||||
application (running `kvstore` locally in this case).
|
||||
|
||||
I[10-04|13:54:27.364] Starting multiAppConn module=proxy impl=multiAppConn
|
||||
I[10-04|13:54:27.366] Starting localClient module=abci-client connection=query impl=localClient
|
||||
I[10-04|13:54:27.366] Starting localClient module=abci-client connection=mempool impl=localClient
|
||||
I[10-04|13:54:27.367] Starting localClient module=abci-client connection=consensus impl=localClient
|
||||
```
|
||||
I[10-04|13:54:27.364] Starting multiAppConn module=proxy impl=multiAppConn
|
||||
I[10-04|13:54:27.366] Starting localClient module=abci-client connection=query impl=localClient
|
||||
I[10-04|13:54:27.366] Starting localClient module=abci-client connection=mempool impl=localClient
|
||||
I[10-04|13:54:27.367] Starting localClient module=abci-client connection=consensus impl=localClient
|
||||
```
|
||||
|
||||
Then Tendermint Core and the application perform a handshake.
|
||||
|
||||
I[10-04|13:54:27.367] ABCI Handshake module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:27.368] ABCI Replay Blocks module=consensus appHeight=90 storeHeight=90 stateHeight=90
|
||||
I[10-04|13:54:27.368] Completed ABCI Handshake - Tendermint and App are synced module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
```
|
||||
I[10-04|13:54:27.367] ABCI Handshake module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:27.368] ABCI Replay Blocks module=consensus appHeight=90 storeHeight=90 stateHeight=90
|
||||
I[10-04|13:54:27.368] Completed ABCI Handshake - Tendermint and App are synced module=consensus appHeight=90 appHash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
```
|
||||
|
||||
After that, we start a few more things like the event switch, reactors,
|
||||
and perform UPNP discover in order to detect the IP address.
|
||||
|
||||
I[10-04|13:54:27.374] Starting EventSwitch module=types impl=EventSwitch
|
||||
I[10-04|13:54:27.375] This node is a validator module=consensus
|
||||
I[10-04|13:54:27.379] Starting Node module=main impl=Node
|
||||
I[10-04|13:54:27.381] Local listener module=p2p ip=:: port=26656
|
||||
I[10-04|13:54:27.382] Getting UPNP external address module=p2p
|
||||
I[10-04|13:54:30.386] Could not perform UPNP discover module=p2p err="write udp4 0.0.0.0:38238->239.255.255.250:1900: i/o timeout"
|
||||
I[10-04|13:54:30.386] Starting DefaultListener module=p2p impl=Listener(@10.0.2.15:26656)
|
||||
I[10-04|13:54:30.387] Starting P2P Switch module=p2p impl="P2P Switch"
|
||||
I[10-04|13:54:30.387] Starting MempoolReactor module=mempool impl=MempoolReactor
|
||||
I[10-04|13:54:30.387] Starting BlockchainReactor module=blockchain impl=BlockchainReactor
|
||||
I[10-04|13:54:30.387] Starting ConsensusReactor module=consensus impl=ConsensusReactor
|
||||
I[10-04|13:54:30.387] ConsensusReactor module=consensus fastSync=false
|
||||
I[10-04|13:54:30.387] Starting ConsensusState module=consensus impl=ConsensusState
|
||||
I[10-04|13:54:30.387] Starting WAL module=consensus wal=/home/vagrant/.tendermint/data/cs.wal/wal impl=WAL
|
||||
I[10-04|13:54:30.388] Starting TimeoutTicker module=consensus impl=TimeoutTicker
|
||||
```
|
||||
I[10-04|13:54:27.374] Starting EventSwitch module=types impl=EventSwitch
|
||||
I[10-04|13:54:27.375] This node is a validator module=consensus
|
||||
I[10-04|13:54:27.379] Starting Node module=main impl=Node
|
||||
I[10-04|13:54:27.381] Local listener module=p2p ip=:: port=26656
|
||||
I[10-04|13:54:27.382] Getting UPNP external address module=p2p
|
||||
I[10-04|13:54:30.386] Could not perform UPNP discover module=p2p err="write udp4 0.0.0.0:38238->239.255.255.250:1900: i/o timeout"
|
||||
I[10-04|13:54:30.386] Starting DefaultListener module=p2p impl=Listener(@10.0.2.15:26656)
|
||||
I[10-04|13:54:30.387] Starting P2P Switch module=p2p impl="P2P Switch"
|
||||
I[10-04|13:54:30.387] Starting MempoolReactor module=mempool impl=MempoolReactor
|
||||
I[10-04|13:54:30.387] Starting BlockchainReactor module=blockchain impl=BlockchainReactor
|
||||
I[10-04|13:54:30.387] Starting ConsensusReactor module=consensus impl=ConsensusReactor
|
||||
I[10-04|13:54:30.387] ConsensusReactor module=consensus fastSync=false
|
||||
I[10-04|13:54:30.387] Starting ConsensusState module=consensus impl=ConsensusState
|
||||
I[10-04|13:54:30.387] Starting WAL module=consensus wal=/home/vagrant/.tendermint/data/cs.wal/wal impl=WAL
|
||||
I[10-04|13:54:30.388] Starting TimeoutTicker module=consensus impl=TimeoutTicker
|
||||
```
|
||||
|
||||
Notice the second row where Tendermint Core reports that "This node is a
|
||||
validator". It also could be just an observer (regular node).
|
||||
|
||||
Next we replay all the messages from the WAL.
|
||||
|
||||
I[10-04|13:54:30.390] Catchup by replaying consensus messages module=consensus height=91
|
||||
I[10-04|13:54:30.390] Replay: New Step module=consensus height=91 round=0 step=RoundStepNewHeight
|
||||
I[10-04|13:54:30.390] Replay: Done module=consensus
|
||||
```
|
||||
I[10-04|13:54:30.390] Catchup by replaying consensus messages module=consensus height=91
|
||||
I[10-04|13:54:30.390] Replay: New Step module=consensus height=91 round=0 step=RoundStepNewHeight
|
||||
I[10-04|13:54:30.390] Replay: Done module=consensus
|
||||
```
|
||||
|
||||
"Started node" message signals that everything is ready for work.
|
||||
|
||||
I[10-04|13:54:30.391] Starting RPC HTTP server on tcp socket 0.0.0.0:26657 module=rpc-server
|
||||
I[10-04|13:54:30.392] Started node module=main nodeInfo="NodeInfo{id: DF22D7C92C91082324A1312F092AA1DA197FA598DBBFB6526E, moniker: anonymous, network: test-chain-3MNw2N [remote , listen 10.0.2.15:26656], version: 0.11.0-10f361fc ([wire_version=0.6.2 p2p_version=0.5.0 consensus_version=v1/0.2.2 rpc_version=0.7.0/3 tx_index=on rpc_addr=tcp://0.0.0.0:26657])}"
|
||||
```
|
||||
I[10-04|13:54:30.391] Starting RPC HTTP server on tcp socket 0.0.0.0:26657 module=rpc-server
|
||||
I[10-04|13:54:30.392] Started node module=main nodeInfo="NodeInfo{id: DF22D7C92C91082324A1312F092AA1DA197FA598DBBFB6526E, moniker: anonymous, network: test-chain-3MNw2N [remote , listen 10.0.2.15:26656], version: 0.11.0-10f361fc ([wire_version=0.6.2 p2p_version=0.5.0 consensus_version=v1/0.2.2 rpc_version=0.7.0/3 tx_index=on rpc_addr=tcp://0.0.0.0:26657])}"
|
||||
```
|
||||
|
||||
Next follows a standard block creation cycle, where we enter a new
|
||||
round, propose a block, receive more than 2/3 of prevotes, then
|
||||
|
@ -56,75 +66,77 @@ please refer to [Consensus
|
|||
Overview](./introduction.md#consensus-overview) or [Byzantine Consensus
|
||||
Algorithm](./spec/consensus).
|
||||
|
||||
I[10-04|13:54:30.393] enterNewRound(91/0). Current: 91/0/RoundStepNewHeight module=consensus
|
||||
I[10-04|13:54:30.393] enterPropose(91/0). Current: 91/0/RoundStepNewRound module=consensus
|
||||
I[10-04|13:54:30.393] enterPropose: Our turn to propose module=consensus proposer=125B0E3C5512F5C2B0E1109E31885C4511570C42 privValidator="PrivValidator{125B0E3C5512F5C2B0E1109E31885C4511570C42 LH:90, LR:0, LS:3}"
|
||||
I[10-04|13:54:30.394] Signed proposal module=consensus height=91 round=0 proposal="Proposal{91/0 1:21B79872514F (-1,:0:000000000000) {/10EDEDD7C84E.../}}"
|
||||
I[10-04|13:54:30.397] Received complete proposal block module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
I[10-04|13:54:30.397] enterPrevote(91/0). Current: 91/0/RoundStepPropose module=consensus
|
||||
I[10-04|13:54:30.397] enterPrevote: ProposalBlock is valid module=consensus height=91 round=0
|
||||
I[10-04|13:54:30.398] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" err=null
|
||||
I[10-04|13:54:30.401] Added to prevote module=consensus vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" prevotes="VoteSet{H:91 R:0 T:1 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}"
|
||||
I[10-04|13:54:30.401] enterPrecommit(91/0). Current: 91/0/RoundStepPrevote module=consensus
|
||||
I[10-04|13:54:30.401] enterPrecommit: +2/3 prevoted proposal block. Locking module=consensus hash=F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
I[10-04|13:54:30.402] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" err=null
|
||||
I[10-04|13:54:30.404] Added to precommit module=consensus vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" precommits="VoteSet{H:91 R:0 T:2 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}"
|
||||
I[10-04|13:54:30.404] enterCommit(91/0). Current: 91/0/RoundStepPrecommit module=consensus
|
||||
I[10-04|13:54:30.405] Finalizing commit of block with 0 txs module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E root=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:30.405] Block{
|
||||
Header{
|
||||
ChainID: test-chain-3MNw2N
|
||||
Height: 91
|
||||
Time: 2017-10-04 13:54:30.393 +0000 UTC
|
||||
NumTxs: 0
|
||||
LastBlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544
|
||||
LastCommit: 56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D
|
||||
Data:
|
||||
Validators: CE25FBFF2E10C0D51AA1A07C064A96931BC8B297
|
||||
App: E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
}#F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
Data{
|
||||
```
|
||||
I[10-04|13:54:30.393] enterNewRound(91/0). Current: 91/0/RoundStepNewHeight module=consensus
|
||||
I[10-04|13:54:30.393] enterPropose(91/0). Current: 91/0/RoundStepNewRound module=consensus
|
||||
I[10-04|13:54:30.393] enterPropose: Our turn to propose module=consensus proposer=125B0E3C5512F5C2B0E1109E31885C4511570C42 privValidator="PrivValidator{125B0E3C5512F5C2B0E1109E31885C4511570C42 LH:90, LR:0, LS:3}"
|
||||
I[10-04|13:54:30.394] Signed proposal module=consensus height=91 round=0 proposal="Proposal{91/0 1:21B79872514F (-1,:0:000000000000) {/10EDEDD7C84E.../}}"
|
||||
I[10-04|13:54:30.397] Received complete proposal block module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
I[10-04|13:54:30.397] enterPrevote(91/0). Current: 91/0/RoundStepPropose module=consensus
|
||||
I[10-04|13:54:30.397] enterPrevote: ProposalBlock is valid module=consensus height=91 round=0
|
||||
I[10-04|13:54:30.398] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" err=null
|
||||
I[10-04|13:54:30.401] Added to prevote module=consensus vote="Vote{0:125B0E3C5512 91/00/1(Prevote) F671D562C7B9 {/89047FFC21D8.../}}" prevotes="VoteSet{H:91 R:0 T:1 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}"
|
||||
I[10-04|13:54:30.401] enterPrecommit(91/0). Current: 91/0/RoundStepPrevote module=consensus
|
||||
I[10-04|13:54:30.401] enterPrecommit: +2/3 prevoted proposal block. Locking module=consensus hash=F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
I[10-04|13:54:30.402] Signed and pushed vote module=consensus height=91 round=0 vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" err=null
|
||||
I[10-04|13:54:30.404] Added to precommit module=consensus vote="Vote{0:125B0E3C5512 91/00/2(Precommit) F671D562C7B9 {/80533478E41A.../}}" precommits="VoteSet{H:91 R:0 T:2 +2/3:F671D562C7B9242900A286E1882EE64E5556FE9E:1:21B79872514F BA{1:X} map[]}"
|
||||
I[10-04|13:54:30.404] enterCommit(91/0). Current: 91/0/RoundStepPrecommit module=consensus
|
||||
I[10-04|13:54:30.405] Finalizing commit of block with 0 txs module=consensus height=91 hash=F671D562C7B9242900A286E1882EE64E5556FE9E root=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:30.405] Block{
|
||||
Header{
|
||||
ChainID: test-chain-3MNw2N
|
||||
Height: 91
|
||||
Time: 2017-10-04 13:54:30.393 +0000 UTC
|
||||
NumTxs: 0
|
||||
LastBlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544
|
||||
LastCommit: 56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D
|
||||
Data:
|
||||
Validators: CE25FBFF2E10C0D51AA1A07C064A96931BC8B297
|
||||
App: E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
}#F671D562C7B9242900A286E1882EE64E5556FE9E
|
||||
Data{
|
||||
|
||||
}#
|
||||
Commit{
|
||||
BlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544
|
||||
Precommits: Vote{0:125B0E3C5512 90/00/2(Precommit) F15AB8BEF9A6 {/FE98E2B956F0.../}}
|
||||
}#56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D
|
||||
}#F671D562C7B9242900A286E1882EE64E5556FE9E module=consensus
|
||||
I[10-04|13:54:30.408] Executed block module=state height=91 validTxs=0 invalidTxs=0
|
||||
I[10-04|13:54:30.410] Committed state module=state height=91 txs=0 hash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:30.410] Recheck txs module=mempool numtxs=0 height=91
|
||||
}#
|
||||
Commit{
|
||||
BlockID: F15AB8BEF9A6AAB07E457A6E16BC410546AA4DC6:1:D505DA273544
|
||||
Precommits: Vote{0:125B0E3C5512 90/00/2(Precommit) F15AB8BEF9A6 {/FE98E2B956F0.../}}
|
||||
}#56FEF2EFDB8B37E9C6E6D635749DF3169D5F005D
|
||||
}#F671D562C7B9242900A286E1882EE64E5556FE9E module=consensus
|
||||
I[10-04|13:54:30.408] Executed block module=state height=91 validTxs=0 invalidTxs=0
|
||||
I[10-04|13:54:30.410] Committed state module=state height=91 txs=0 hash=E0FBAFBF6FCED8B9786DDFEB1A0D4FA2501BADAD
|
||||
I[10-04|13:54:30.410] Recheck txs module=mempool numtxs=0 height=91
|
||||
```
|
||||
|
||||
## List of modules
|
||||
|
||||
Here is the list of modules you may encounter in Tendermint's log and a
|
||||
little overview what they do.
|
||||
|
||||
- `abci-client` As mentioned in [Application Development Guide](./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).
|
||||
- `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
|
||||
implementation of the consensus algorithm. Includes two
|
||||
"submodules": `wal` (write-ahead logging) for ensuring data
|
||||
integrity and `replay` to replay blocks and messages on recovery
|
||||
from a crash.
|
||||
- `events` Simple event notification system. The list of events can be
|
||||
found
|
||||
[here](https://github.com/tendermint/tendermint/blob/master/types/events.go).
|
||||
You can subscribe to them by calling `subscribe` RPC method. Refer
|
||||
to [RPC docs](./specification/rpc.md) for additional information.
|
||||
- `mempool` Mempool module handles all incoming transactions, whenever
|
||||
they are coming from peers or the application.
|
||||
- `p2p` Provides an abstraction around peer-to-peer communication. For
|
||||
more details, please check out the
|
||||
[README](https://github.com/tendermint/tendermint/blob/master/p2p/README.md).
|
||||
- `rpc` [Tendermint's RPC](./specification/rpc.md).
|
||||
- `rpc-server` RPC server. For implementation details, please read the
|
||||
[README](https://github.com/tendermint/tendermint/blob/master/rpc/lib/README.md).
|
||||
- `state` Represents the latest state and execution submodule, which
|
||||
executes blocks against the application.
|
||||
- `types` A collection of the publicly exposed types and methods to
|
||||
work with them.
|
||||
- `abci-client` As mentioned in [Application Development Guide](./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).
|
||||
- `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
|
||||
implementation of the consensus algorithm. Includes two
|
||||
"submodules": `wal` (write-ahead logging) for ensuring data
|
||||
integrity and `replay` to replay blocks and messages on recovery
|
||||
from a crash.
|
||||
- `events` Simple event notification system. The list of events can be
|
||||
found
|
||||
[here](https://github.com/tendermint/tendermint/blob/master/types/events.go).
|
||||
You can subscribe to them by calling `subscribe` RPC method. Refer
|
||||
to [RPC docs](./specification/rpc.md) for additional information.
|
||||
- `mempool` Mempool module handles all incoming transactions, whenever
|
||||
they are coming from peers or the application.
|
||||
- `p2p` Provides an abstraction around peer-to-peer communication. For
|
||||
more details, please check out the
|
||||
[README](https://github.com/tendermint/tendermint/blob/master/p2p/README.md).
|
||||
- `rpc` [Tendermint's RPC](./specification/rpc.md).
|
||||
- `rpc-server` RPC server. For implementation details, please read the
|
||||
[README](https://github.com/tendermint/tendermint/blob/master/rpc/lib/README.md).
|
||||
- `state` Represents the latest state and execution submodule, which
|
||||
executes blocks against the application.
|
||||
- `types` A collection of the publicly exposed types and methods to
|
||||
work with them.
|
||||
|
|
|
@ -5,27 +5,29 @@ to their results.
|
|||
|
||||
Let's take a look at the `[tx_index]` config section:
|
||||
|
||||
##### transactions indexer configuration options #####
|
||||
[tx_index]
|
||||
```
|
||||
##### transactions indexer configuration options #####
|
||||
[tx_index]
|
||||
|
||||
# What indexer to use for transactions
|
||||
#
|
||||
# Options:
|
||||
# 1) "null" (default)
|
||||
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||
indexer = "kv"
|
||||
# What indexer to use for transactions
|
||||
#
|
||||
# Options:
|
||||
# 1) "null" (default)
|
||||
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
|
||||
indexer = "kv"
|
||||
|
||||
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||
#
|
||||
# It's recommended to index only a subset of tags due to possible memory
|
||||
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||
# transactions.
|
||||
index_tags = ""
|
||||
# Comma-separated list of tags to index (by default the only tag is tx hash)
|
||||
#
|
||||
# It's recommended to index only a subset of tags due to possible memory
|
||||
# bloat. This is, of course, depends on the indexer's DB and the volume of
|
||||
# transactions.
|
||||
index_tags = ""
|
||||
|
||||
# When set to true, tells indexer to index all tags. Note this may be not
|
||||
# desirable (see the comment above). IndexTags has a precedence over
|
||||
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||
index_all_tags = false
|
||||
# When set to true, tells indexer to index all tags. Note this may be not
|
||||
# desirable (see the comment above). IndexTags has a precedence over
|
||||
# IndexAllTags (i.e. when given both, IndexTags will be indexed).
|
||||
index_all_tags = false
|
||||
```
|
||||
|
||||
By default, Tendermint will index all transactions by their respective
|
||||
hashes using an embedded simple indexer. Note, we are planning to add
|
||||
|
@ -39,15 +41,17 @@ pairs of UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance":
|
|||
|
||||
Example:
|
||||
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||
...
|
||||
tags := []cmn.KVPair{
|
||||
{[]byte("account.name"), []byte("igor")},
|
||||
{[]byte("account.address"), []byte("0xdeadbeef")},
|
||||
{[]byte("tx.amount"), []byte("7")},
|
||||
}
|
||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags}
|
||||
```
|
||||
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||
...
|
||||
tags := []cmn.KVPair{
|
||||
{[]byte("account.name"), []byte("igor")},
|
||||
{[]byte("account.address"), []byte("0xdeadbeef")},
|
||||
{[]byte("tx.amount"), []byte("7")},
|
||||
}
|
||||
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags}
|
||||
}
|
||||
```
|
||||
|
||||
If you want Tendermint to only index transactions by "account.name" tag,
|
||||
in the config set `tx_index.index_tags="account.name"`. If you to index
|
||||
|
@ -55,9 +59,9 @@ all tags, set `index_all_tags=true`
|
|||
|
||||
Note, there are a few predefined tags:
|
||||
|
||||
- `tm.event` (event type)
|
||||
- `tx.hash` (transaction's hash)
|
||||
- `tx.height` (height of the block transaction was committed in)
|
||||
- `tm.event` (event type)
|
||||
- `tx.hash` (transaction's hash)
|
||||
- `tx.height` (height of the block transaction was committed in)
|
||||
|
||||
Tendermint will throw a warning if you try to use any of the above keys.
|
||||
|
||||
|
@ -66,7 +70,9 @@ Tendermint will throw a warning if you try to use any of the above keys.
|
|||
You can query the transaction results by calling `/tx_search` RPC
|
||||
endpoint:
|
||||
|
||||
curl "localhost:26657/tx_search?query=\"account.name='igor'\"&prove=true"
|
||||
```
|
||||
curl "localhost:26657/tx_search?query=\"account.name='igor'\"&prove=true"
|
||||
```
|
||||
|
||||
Check out [API docs](https://tendermint.github.io/slate/?shell#txsearch)
|
||||
for more information on query syntax and other options.
|
||||
|
@ -76,14 +82,16 @@ for more information on query syntax and other options.
|
|||
Clients can subscribe to transactions with the given tags via Websocket
|
||||
by providing a query to `/subscribe` RPC endpoint.
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subscribe",
|
||||
"id": "0",
|
||||
"params": {
|
||||
"query": "account.name='igor'"
|
||||
}
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subscribe",
|
||||
"id": "0",
|
||||
"params": {
|
||||
"query": "account.name='igor'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Check out [API docs](https://tendermint.github.io/slate/#subscribe) for
|
||||
more information on query syntax and other options.
|
||||
|
|
|
@ -57,7 +57,7 @@ cd $GOPATH/src/github.com/tendermint/tendermint
|
|||
make install
|
||||
```
|
||||
|
||||
To upgrade, run
|
||||
To upgrade, run
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/tendermint
|
||||
|
|
|
@ -61,13 +61,14 @@ providing basic services to distributed systems, such as dynamic
|
|||
configuration, service discovery, locking, leader-election, and so on.
|
||||
|
||||
Tendermint is in essence similar software, but with two key differences:
|
||||
|
||||
- It is Byzantine Fault Tolerant, meaning it can only tolerate up to a
|
||||
1/3 of failures, but those failures can include arbitrary behaviour -
|
||||
including hacking and malicious attacks. - It does not specify a
|
||||
particular application, like a fancy key-value store. Instead, it
|
||||
focuses on arbitrary state machine replication, so developers can build
|
||||
the application logic that's right for them, from key-value store to
|
||||
cryptocurrency to e-voting platform and beyond.
|
||||
1/3 of failures, but those failures can include arbitrary behaviour -
|
||||
including hacking and malicious attacks. - It does not specify a
|
||||
particular application, like a fancy key-value store. Instead, it
|
||||
focuses on arbitrary state machine replication, so developers can build
|
||||
the application logic that's right for them, from key-value store to
|
||||
cryptocurrency to e-voting platform and beyond.
|
||||
|
||||
The layout of this Tendermint website content is also ripped directly
|
||||
and without shame from [consul.io](https://www.consul.io/) and the other
|
||||
|
@ -167,16 +168,16 @@ maintains a fully audited Unspent Transaction Output (UTXO) database. If
|
|||
one wanted to create a Bitcoin-like system on top of ABCI, Tendermint
|
||||
Core would be responsible for
|
||||
|
||||
- Sharing blocks and transactions between nodes
|
||||
- Establishing a canonical/immutable order of transactions
|
||||
(the blockchain)
|
||||
- Sharing blocks and transactions between nodes
|
||||
- Establishing a canonical/immutable order of transactions
|
||||
(the blockchain)
|
||||
|
||||
The application will be responsible for
|
||||
|
||||
- Maintaining the UTXO database
|
||||
- Validating cryptographic signatures of transactions
|
||||
- Preventing transactions from spending non-existent transactions
|
||||
- Allowing clients to query the UTXO database.
|
||||
- Maintaining the UTXO database
|
||||
- Validating cryptographic signatures of transactions
|
||||
- Preventing transactions from spending non-existent transactions
|
||||
- Allowing clients to query the UTXO database.
|
||||
|
||||
Tendermint is able to decompose the blockchain design by offering a very
|
||||
simple API (ie. the ABCI) between the application process and consensus
|
||||
|
@ -242,14 +243,14 @@ Java, C++, Python, or Go. Game programmers and blockchain developers are
|
|||
already familiar with creating deterministic programs by avoiding
|
||||
sources of non-determinism such as:
|
||||
|
||||
- random number generators (without deterministic seeding)
|
||||
- race conditions on threads (or avoiding threads altogether)
|
||||
- the system clock
|
||||
- uninitialized memory (in unsafe programming languages like C
|
||||
or C++)
|
||||
- [floating point
|
||||
arithmetic](http://gafferongames.com/networking-for-game-programmers/floating-point-determinism/)
|
||||
- language features that are random (e.g. map iteration in Go)
|
||||
- random number generators (without deterministic seeding)
|
||||
- race conditions on threads (or avoiding threads altogether)
|
||||
- the system clock
|
||||
- uninitialized memory (in unsafe programming languages like C
|
||||
or C++)
|
||||
- [floating point
|
||||
arithmetic](http://gafferongames.com/networking-for-game-programmers/floating-point-determinism/)
|
||||
- language features that are random (e.g. map iteration in Go)
|
||||
|
||||
While programmers can avoid non-determinism by being careful, it is also
|
||||
possible to create a special linter or static analyzer for each language
|
||||
|
@ -298,8 +299,8 @@ introduces a few **locking** rules which modulate which paths can be
|
|||
followed in the flow diagram. Once a validator precommits a block, it is
|
||||
locked on that block. Then,
|
||||
|
||||
1) it must prevote for the block it is locked on
|
||||
2) it can only unlock, and precommit for a new block, if there is a
|
||||
1. it must prevote for the block it is locked on
|
||||
2. it can only unlock, and precommit for a new block, if there is a
|
||||
polka for that block in a later round
|
||||
|
||||
## Stake
|
||||
|
|
|
@ -49,21 +49,25 @@ second TODO is to query the /status RPC endpoint. It provides the
|
|||
necessary info: whenever the node is syncing or not, what height it is
|
||||
on, etc.
|
||||
|
||||
$ curl http(s)://{ip}:{rpcPort}/status
|
||||
```
|
||||
curl http(s)://{ip}:{rpcPort}/status
|
||||
```
|
||||
|
||||
`dump_consensus_state` will give you a detailed overview of the
|
||||
consensus state (proposer, lastest validators, peers states). From it,
|
||||
you should be able to figure out why, for example, the network had
|
||||
halted.
|
||||
|
||||
$ curl http(s)://{ip}:{rpcPort}/dump_consensus_state
|
||||
```
|
||||
curl http(s)://{ip}:{rpcPort}/dump_consensus_state
|
||||
```
|
||||
|
||||
There is a reduced version of this endpoint - `consensus_state`, which
|
||||
returns just the votes seen at the current height.
|
||||
|
||||
- [Github Issues](https://github.com/tendermint/tendermint/issues)
|
||||
- [StackOverflow
|
||||
questions](https://stackoverflow.com/questions/tagged/tendermint)
|
||||
- [Github Issues](https://github.com/tendermint/tendermint/issues)
|
||||
- [StackOverflow
|
||||
questions](https://stackoverflow.com/questions/tagged/tendermint)
|
||||
|
||||
## Monitoring Tendermint
|
||||
|
||||
|
@ -107,18 +111,18 @@ programs](https://golang.org/pkg/os/signal/#hdr-Default_behavior_of_signals_in_G
|
|||
While actual specs vary depending on the load and validators count,
|
||||
minimal requirements are:
|
||||
|
||||
- 1GB RAM
|
||||
- 25GB of disk space
|
||||
- 1.4 GHz CPU
|
||||
- 1GB RAM
|
||||
- 25GB of disk space
|
||||
- 1.4 GHz CPU
|
||||
|
||||
SSD disks are preferable for applications with high transaction
|
||||
throughput.
|
||||
|
||||
Recommended:
|
||||
|
||||
- 2GB RAM
|
||||
- 100GB SSD
|
||||
- x64 2.0 GHz 2v CPU
|
||||
- 2GB RAM
|
||||
- 100GB SSD
|
||||
- x64 2.0 GHz 2v CPU
|
||||
|
||||
While for now, Tendermint stores all the history and it may require
|
||||
significant disk space over time, we are planning to implement state
|
||||
|
@ -145,21 +149,23 @@ Cosmos network.
|
|||
|
||||
## Configuration parameters
|
||||
|
||||
- `p2p.flush_throttle_timeout` `p2p.max_packet_msg_payload_size`
|
||||
`p2p.send_rate` `p2p.recv_rate`
|
||||
- `p2p.flush_throttle_timeout` `p2p.max_packet_msg_payload_size`
|
||||
`p2p.send_rate` `p2p.recv_rate`
|
||||
|
||||
If you are going to use Tendermint in a private domain and you have a
|
||||
private high-speed network among your peers, it makes sense to lower
|
||||
flush throttle timeout and increase other params.
|
||||
|
||||
[p2p]
|
||||
```
|
||||
[p2p]
|
||||
|
||||
send_rate=20000000 # 2MB/s
|
||||
recv_rate=20000000 # 2MB/s
|
||||
flush_throttle_timeout=10
|
||||
max_packet_msg_payload_size=10240 # 10KB
|
||||
send_rate=20000000 # 2MB/s
|
||||
recv_rate=20000000 # 2MB/s
|
||||
flush_throttle_timeout=10
|
||||
max_packet_msg_payload_size=10240 # 10KB
|
||||
```
|
||||
|
||||
- `mempool.recheck`
|
||||
- `mempool.recheck`
|
||||
|
||||
After every block, Tendermint rechecks every transaction left in the
|
||||
mempool to see if transactions committed in that block affected the
|
||||
|
@ -167,13 +173,13 @@ application state, so some of the transactions left may become invalid.
|
|||
If that does not apply to your application, you can disable it by
|
||||
setting `mempool.recheck=false`.
|
||||
|
||||
- `mempool.broadcast`
|
||||
- `mempool.broadcast`
|
||||
|
||||
Setting this to false will stop the mempool from relaying transactions
|
||||
to other peers until they are included in a block. It means only the
|
||||
peer you send the tx to will see it until it is included in a block.
|
||||
|
||||
- `consensus.skip_timeout_commit`
|
||||
- `consensus.skip_timeout_commit`
|
||||
|
||||
We want `skip_timeout_commit=false` when there is economics on the line
|
||||
because proposers should wait to hear for more votes. But if you don't
|
||||
|
@ -182,22 +188,22 @@ be kept false by default for public deployments (e.g. [Cosmos
|
|||
Hub](https://cosmos.network/intro/hub)) while for enterprise
|
||||
applications, setting it to true is not a problem.
|
||||
|
||||
- `consensus.peer_gossip_sleep_duration`
|
||||
- `consensus.peer_gossip_sleep_duration`
|
||||
|
||||
You can try to reduce the time your node sleeps before checking if
|
||||
theres something to send its peers.
|
||||
|
||||
- `consensus.timeout_commit`
|
||||
- `consensus.timeout_commit`
|
||||
|
||||
You can also try lowering `timeout_commit` (time we sleep before
|
||||
proposing the next block).
|
||||
|
||||
- `consensus.max_block_size_txs`
|
||||
- `consensus.max_block_size_txs`
|
||||
|
||||
By default, the maximum number of transactions per a block is 10_000.
|
||||
Feel free to change it to suit your needs.
|
||||
|
||||
- `p2p.addr_book_strict`
|
||||
- `p2p.addr_book_strict`
|
||||
|
||||
By default, Tendermint checks whenever a peer's address is routable before
|
||||
saving it to the address book. The address is considered as routable if the IP
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
Tendermint Core can be configured via a TOML file in
|
||||
`$TMHOME/config/config.toml`. Some of these parameters can be overridden by
|
||||
command-line flags. For most users, the options in the `##### main
|
||||
base configuration options #####` are intended to be modified while
|
||||
command-line flags. For most users, the options in the `##### main base configuration options #####` are intended to be modified while
|
||||
config options further below are intended for advance power users.
|
||||
|
||||
## Options
|
||||
|
|
|
@ -9,14 +9,16 @@ for third-party applications (for analysys) or inspecting state.
|
|||
You can subscribe to any of the events above by calling `subscribe` RPC
|
||||
method via Websocket.
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subscribe",
|
||||
"id": "0",
|
||||
"params": {
|
||||
"query": "tm.event='NewBlock'"
|
||||
}
|
||||
```
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "subscribe",
|
||||
"id": "0",
|
||||
"params": {
|
||||
"query": "tm.event='NewBlock'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Check out [API docs](https://tendermint.github.io/slate/#subscribe) for
|
||||
more information on query syntax and other options.
|
||||
|
|
|
@ -12,17 +12,17 @@ script](https://github.com/tendermint/tendermint/blob/develop/networks/remote/in
|
|||
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.
|
||||
|
||||
- Install [Terraform](https://www.terraform.io/downloads.html) and
|
||||
[Ansible](http://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html)
|
||||
on a Linux machine.
|
||||
- Create a [DigitalOcean API
|
||||
token](https://cloud.digitalocean.com/settings/api/tokens) with read
|
||||
and write capability.
|
||||
- Install the python dopy package (`pip install dopy`)
|
||||
- Create SSH keys (`ssh-keygen`)
|
||||
- Set environment variables:
|
||||
- Install [Terraform](https://www.terraform.io/downloads.html) and
|
||||
[Ansible](http://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html)
|
||||
on a Linux machine.
|
||||
- Create a [DigitalOcean API
|
||||
token](https://cloud.digitalocean.com/settings/api/tokens) with read
|
||||
and write capability.
|
||||
- Install the python dopy package (`pip install dopy`)
|
||||
- Create SSH keys (`ssh-keygen`)
|
||||
- Set environment variables:
|
||||
|
||||
```
|
||||
```
|
||||
export DO_API_TOKEN="abcdef01234567890abcdef01234567890"
|
||||
export SSH_KEY_FILE="$HOME/.ssh/id_rsa.pub"
|
||||
```
|
||||
|
@ -44,6 +44,7 @@ then:
|
|||
terraform init
|
||||
terraform apply -var DO_API_TOKEN="$DO_API_TOKEN" -var SSH_KEY_FILE="$SSH_KEY_FILE"
|
||||
```
|
||||
|
||||
and you will get a list of IP addresses that belong to your droplets.
|
||||
|
||||
With the droplets created and running, let's setup Ansible.
|
||||
|
@ -154,7 +155,7 @@ page](https://app.logz.io/#/dashboard/data-sources/Filebeat), then:
|
|||
```
|
||||
yum install systemd-devel || echo "This will only work on RHEL-based systems."
|
||||
apt-get install libsystemd-dev || echo "This will only work on Debian-based systems."
|
||||
|
||||
|
||||
go get github.com/mheese/journalbeat
|
||||
ansible-playbook -i inventory/digital_ocean.py -l sentrynet logzio.yml -e LOGZIO_TOKEN=ABCDEFGHIJKLMNOPQRSTUVWXYZ012345
|
||||
```
|
||||
|
|
|
@ -16,7 +16,9 @@ this by setting the `TMHOME` environment variable.
|
|||
|
||||
Initialize the root directory by running:
|
||||
|
||||
tendermint init
|
||||
```
|
||||
tendermint init
|
||||
```
|
||||
|
||||
This will create a new private key (`priv_validator.json`), and a
|
||||
genesis file (`genesis.json`) containing the associated public key, in
|
||||
|
@ -25,24 +27,30 @@ with one validator.
|
|||
|
||||
For more elaborate initialization, see the tesnet command:
|
||||
|
||||
tendermint testnet --help
|
||||
```
|
||||
tendermint testnet --help
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
To run a Tendermint node, use
|
||||
|
||||
tendermint node
|
||||
```
|
||||
tendermint node
|
||||
```
|
||||
|
||||
By default, Tendermint will try to connect to an ABCI application on
|
||||
[127.0.0.1:26658](127.0.0.1:26658). If you have the `kvstore` ABCI app
|
||||
installed, run it in another window. If you don't, kill Tendermint and
|
||||
run an in-process version of the `kvstore` app:
|
||||
|
||||
tendermint node --proxy_app=kvstore
|
||||
```
|
||||
tendermint node --proxy_app=kvstore
|
||||
```
|
||||
|
||||
After a few seconds you should see blocks start streaming in. Note that
|
||||
blocks are produced regularly, even if there are no transactions. See
|
||||
*No Empty Blocks*, below, to modify this setting.
|
||||
_No Empty Blocks_, below, to modify this setting.
|
||||
|
||||
Tendermint supports in-process versions of the `counter`, `kvstore` and
|
||||
`nil` apps that ship as examples with `abci-cli`. It's easy to compile
|
||||
|
@ -51,22 +59,30 @@ app is not written in Go, simply run it in another process, and use the
|
|||
`--proxy_app` flag to specify the address of the socket it is listening
|
||||
on, for instance:
|
||||
|
||||
tendermint node --proxy_app=/var/run/abci.sock
|
||||
```
|
||||
tendermint node --proxy_app=/var/run/abci.sock
|
||||
```
|
||||
|
||||
## Transactions
|
||||
|
||||
To send a transaction, use `curl` to make requests to the Tendermint RPC
|
||||
server, for example:
|
||||
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=\"abcd\"
|
||||
```
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=\"abcd\"
|
||||
```
|
||||
|
||||
We can see the chain's status at the `/status` end-point:
|
||||
|
||||
curl http://localhost:26657/status | json_pp
|
||||
```
|
||||
curl http://localhost:26657/status | json_pp
|
||||
```
|
||||
|
||||
and the `latest_app_hash` in particular:
|
||||
|
||||
curl http://localhost:26657/status | json_pp | grep latest_app_hash
|
||||
```
|
||||
curl http://localhost:26657/status | json_pp | grep latest_app_hash
|
||||
```
|
||||
|
||||
Visit http://localhost:26657> in your browser to see the list of other
|
||||
endpoints. Some take no arguments (like `/status`), while others specify
|
||||
|
@ -81,30 +97,40 @@ With `GET`:
|
|||
|
||||
To send a UTF8 string byte array, quote the value of the tx pramater:
|
||||
|
||||
curl 'http://localhost:26657/broadcast_tx_commit?tx="hello"'
|
||||
```
|
||||
curl 'http://localhost:26657/broadcast_tx_commit?tx="hello"'
|
||||
```
|
||||
|
||||
which sends a 5 byte transaction: "h e l l o" \[68 65 6c 6c 6f\].
|
||||
|
||||
Note the URL must be wrapped with single quoes, else bash will ignore
|
||||
the double quotes. To avoid the single quotes, escape the double quotes:
|
||||
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=\"hello\"
|
||||
```
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=\"hello\"
|
||||
```
|
||||
|
||||
Using a special character:
|
||||
|
||||
curl 'http://localhost:26657/broadcast_tx_commit?tx="€5"'
|
||||
```
|
||||
curl 'http://localhost:26657/broadcast_tx_commit?tx="€5"'
|
||||
```
|
||||
|
||||
sends a 4 byte transaction: "€5" (UTF8) \[e2 82 ac 35\].
|
||||
|
||||
To send as raw hex, omit quotes AND prefix the hex string with `0x`:
|
||||
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=0x01020304
|
||||
```
|
||||
curl http://localhost:26657/broadcast_tx_commit?tx=0x01020304
|
||||
```
|
||||
|
||||
which sends a 4 byte transaction: \[01 02 03 04\].
|
||||
|
||||
With `POST` (using `json`), the raw hex must be `base64` encoded:
|
||||
|
||||
curl --data-binary '{"jsonrpc":"2.0","id":"anything","method":"broadcast_tx_commit","params": {"tx": "AQIDBA=="}}' -H 'content-type:text/plain;' http://localhost:26657
|
||||
```
|
||||
curl --data-binary '{"jsonrpc":"2.0","id":"anything","method":"broadcast_tx_commit","params": {"tx": "AQIDBA=="}}' -H 'content-type:text/plain;' http://localhost:26657
|
||||
```
|
||||
|
||||
which sends the same 4 byte transaction: \[01 02 03 04\].
|
||||
|
||||
|
@ -118,7 +144,9 @@ afford to lose all blockchain data!
|
|||
To reset a blockchain, stop the node, remove the `~/.tendermint/data`
|
||||
directory and run
|
||||
|
||||
tendermint unsafe_reset_priv_validator
|
||||
```
|
||||
tendermint unsafe_reset_priv_validator
|
||||
```
|
||||
|
||||
This final step is necessary to reset the `priv_validator.json`, which
|
||||
otherwise prevents you from making conflicting votes in the consensus
|
||||
|
@ -150,21 +178,27 @@ To configure Tendermint to not produce empty blocks unless there are
|
|||
transactions or the app hash changes, run Tendermint with this
|
||||
additional flag:
|
||||
|
||||
tendermint node --consensus.create_empty_blocks=false
|
||||
```
|
||||
tendermint node --consensus.create_empty_blocks=false
|
||||
```
|
||||
|
||||
or set the configuration via the `config.toml` file:
|
||||
|
||||
[consensus]
|
||||
create_empty_blocks = false
|
||||
```
|
||||
[consensus]
|
||||
create_empty_blocks = false
|
||||
```
|
||||
|
||||
Remember: because the default is to *create empty blocks*, avoiding
|
||||
Remember: because the default is to _create empty blocks_, avoiding
|
||||
empty blocks requires the config option to be set to `false`.
|
||||
|
||||
The block interval setting allows for a delay (in seconds) between the
|
||||
creation of each new empty block. It is set via the `config.toml`:
|
||||
|
||||
[consensus]
|
||||
create_empty_blocks_interval = 5
|
||||
```
|
||||
[consensus]
|
||||
create_empty_blocks_interval = 5
|
||||
```
|
||||
|
||||
With this setting, empty blocks will be produced every 5s if no block
|
||||
has been produced otherwise, regardless of the value of
|
||||
|
@ -181,9 +215,11 @@ eventually included in a block.
|
|||
Since there are multiple phases to processing a transaction, we offer
|
||||
multiple endpoints to broadcast a transaction:
|
||||
|
||||
/broadcast_tx_async
|
||||
/broadcast_tx_sync
|
||||
/broadcast_tx_commit
|
||||
```
|
||||
/broadcast_tx_async
|
||||
/broadcast_tx_sync
|
||||
/broadcast_tx_commit
|
||||
```
|
||||
|
||||
These correspond to no-processing, processing through the mempool, and
|
||||
processing through a block, respectively. That is, `broadcast_tx_async`,
|
||||
|
@ -208,38 +244,42 @@ When `tendermint init` is run, both a `genesis.json` and
|
|||
`priv_validator.json` are created in `~/.tendermint/config`. The
|
||||
`genesis.json` might look like:
|
||||
|
||||
```
|
||||
{
|
||||
"validators" : [
|
||||
{
|
||||
"validators" : [
|
||||
{
|
||||
"pub_key" : {
|
||||
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
}
|
||||
],
|
||||
"app_hash" : "",
|
||||
"chain_id" : "test-chain-rDlYSN",
|
||||
"genesis_time" : "0001-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
And the `priv_validator.json`:
|
||||
|
||||
{
|
||||
"last_step" : 0,
|
||||
"last_round" : "0",
|
||||
"address" : "B788DEDE4F50AD8BC9462DE76741CCAFF87D51E2",
|
||||
"pub_key" : {
|
||||
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"last_height" : "0",
|
||||
"priv_key" : {
|
||||
"value" : "JPivl82x+LfVkp8i3ztoTjY6c6GJ4pBxQexErOCyhwqHeGT5ATxzpAtPJKnxNx/NyUnD8Ebv3OIYH+kgD4N88Q==",
|
||||
"type" : "tendermint/PrivKeyEd25519"
|
||||
}
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
}
|
||||
],
|
||||
"app_hash" : "",
|
||||
"chain_id" : "test-chain-rDlYSN",
|
||||
"genesis_time" : "0001-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
And the `priv_validator.json`:
|
||||
|
||||
```
|
||||
{
|
||||
"last_step" : 0,
|
||||
"last_round" : "0",
|
||||
"address" : "B788DEDE4F50AD8BC9462DE76741CCAFF87D51E2",
|
||||
"pub_key" : {
|
||||
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"last_height" : "0",
|
||||
"priv_key" : {
|
||||
"value" : "JPivl82x+LfVkp8i3ztoTjY6c6GJ4pBxQexErOCyhwqHeGT5ATxzpAtPJKnxNx/NyUnD8Ebv3OIYH+kgD4N88Q==",
|
||||
"type" : "tendermint/PrivKeyEd25519"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `priv_validator.json` actually contains a private key, and should
|
||||
thus be kept absolutely secret; for now we work with the plain text.
|
||||
|
@ -272,6 +312,7 @@ with the consensus protocol.
|
|||
### Peers
|
||||
|
||||
#### Seed
|
||||
|
||||
A seed node is a node who relays the addresses of other peers which they know
|
||||
of. These nodes constantly crawl the network to try to get more peers. The
|
||||
addresses which the seed node relays get saved into a local address book. Once
|
||||
|
@ -282,6 +323,7 @@ only need them on the first start. The seed node will immediately disconnect
|
|||
from you after sending you some addresses.
|
||||
|
||||
#### Persistent Peer
|
||||
|
||||
Persistent peers are people you want to be constantly connected with. If you
|
||||
disconnect you will try to connect directly back to them as opposed to using
|
||||
another address from the address book. On restarts you will always try to
|
||||
|
@ -302,12 +344,16 @@ persistent connections with.
|
|||
|
||||
For example,
|
||||
|
||||
tendermint node --p2p.seeds "f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@1.2.3.4:26656,0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@5.6.7.8:26656"
|
||||
```
|
||||
tendermint node --p2p.seeds "f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@1.2.3.4:26656,0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@5.6.7.8:26656"
|
||||
```
|
||||
|
||||
Alternatively, you can use the `/dial_seeds` endpoint of the RPC to
|
||||
specify seeds for a running node to connect to:
|
||||
|
||||
curl 'localhost:26657/dial_seeds?seeds=\["f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@1.2.3.4:26656","0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@5.6.7.8:26656"\]'
|
||||
```
|
||||
curl 'localhost:26657/dial_seeds?seeds=\["f9baeaa15fedf5e1ef7448dd60f46c01f1a9e9c4@1.2.3.4:26656","0491d373a8e0fcf1023aaf18c51d6a1d0d4f31bd@5.6.7.8:26656"\]'
|
||||
```
|
||||
|
||||
Note, with PeX enabled, you
|
||||
should not need seeds after the first start.
|
||||
|
@ -318,8 +364,11 @@ maintain a persistent connection with each, you can use the
|
|||
`config.toml` or the `/dial_peers` RPC endpoint to do it without
|
||||
stopping Tendermint core instance.
|
||||
|
||||
tendermint node --p2p.persistent_peers "429fcf25974313b95673f58d77eacdd434402665@10.11.12.13:26656,96663a3dd0d7b9d17d4c8211b191af259621c693@10.11.12.14:26656"
|
||||
curl 'localhost:26657/dial_peers?persistent=true&peers=\["429fcf25974313b95673f58d77eacdd434402665@10.11.12.13:26656","96663a3dd0d7b9d17d4c8211b191af259621c693@10.11.12.14:26656"\]'
|
||||
```
|
||||
tendermint node --p2p.persistent_peers "429fcf25974313b95673f58d77eacdd434402665@10.11.12.13:26656,96663a3dd0d7b9d17d4c8211b191af259621c693@10.11.12.14:26656"
|
||||
|
||||
curl 'localhost:26657/dial_peers?persistent=true&peers=\["429fcf25974313b95673f58d77eacdd434402665@10.11.12.13:26656","96663a3dd0d7b9d17d4c8211b191af259621c693@10.11.12.14:26656"\]'
|
||||
```
|
||||
|
||||
### Adding a Non-Validator
|
||||
|
||||
|
@ -338,51 +387,57 @@ before starting the network. For instance, we could make a new
|
|||
|
||||
We can generate a new `priv_validator.json` with the command:
|
||||
|
||||
tendermint gen_validator
|
||||
```
|
||||
tendermint gen_validator
|
||||
```
|
||||
|
||||
Now we can update our genesis file. For instance, if the new
|
||||
`priv_validator.json` looks like:
|
||||
|
||||
```
|
||||
{
|
||||
"address" : "5AF49D2A2D4F5AD4C7C8C4CC2FB020131E9C4902",
|
||||
"pub_key" : {
|
||||
"value" : "l9X9+fjkeBzDfPGbUM7AMIRE6uJN78zN5+lk5OYotek=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"priv_key" : {
|
||||
"value" : "EDJY9W6zlAw+su6ITgTKg2nTZcHAH1NMTW5iwlgmNDuX1f35+OR4HMN88ZtQzsAwhETq4k3vzM3n6WTk5ii16Q==",
|
||||
"type" : "tendermint/PrivKeyEd25519"
|
||||
},
|
||||
"last_step" : 0,
|
||||
"last_round" : "0",
|
||||
"last_height" : "0"
|
||||
}
|
||||
```
|
||||
|
||||
then the new `genesis.json` will be:
|
||||
|
||||
```
|
||||
{
|
||||
"validators" : [
|
||||
{
|
||||
"pub_key" : {
|
||||
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
},
|
||||
{
|
||||
"address" : "5AF49D2A2D4F5AD4C7C8C4CC2FB020131E9C4902",
|
||||
"pub_key" : {
|
||||
"value" : "l9X9+fjkeBzDfPGbUM7AMIRE6uJN78zN5+lk5OYotek=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"priv_key" : {
|
||||
"value" : "EDJY9W6zlAw+su6ITgTKg2nTZcHAH1NMTW5iwlgmNDuX1f35+OR4HMN88ZtQzsAwhETq4k3vzM3n6WTk5ii16Q==",
|
||||
"type" : "tendermint/PrivKeyEd25519"
|
||||
},
|
||||
"last_step" : 0,
|
||||
"last_round" : "0",
|
||||
"last_height" : "0"
|
||||
}
|
||||
|
||||
then the new `genesis.json` will be:
|
||||
|
||||
{
|
||||
"validators" : [
|
||||
{
|
||||
"pub_key" : {
|
||||
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
},
|
||||
{
|
||||
"pub_key" : {
|
||||
"value" : "l9X9+fjkeBzDfPGbUM7AMIRE6uJN78zN5+lk5OYotek=",
|
||||
"type" : "tendermint/PubKeyEd25519"
|
||||
},
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
}
|
||||
],
|
||||
"app_hash" : "",
|
||||
"chain_id" : "test-chain-rDlYSN",
|
||||
"genesis_time" : "0001-01-01T00:00:00Z"
|
||||
"power" : 10,
|
||||
"name" : ""
|
||||
}
|
||||
],
|
||||
"app_hash" : "",
|
||||
"chain_id" : "test-chain-rDlYSN",
|
||||
"genesis_time" : "0001-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Update the `genesis.json` in `~/.tendermint/config`. Copy the genesis
|
||||
file and the new `priv_validator.json` to the `~/.tendermint/config` on
|
||||
|
|
Loading…
Reference in New Issue