mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-29 11:15:50 +00:00
detele everything
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
# Tendermint RPC
|
||||
|
||||
## Generate markdown for [Slate](https://github.com/tendermint/slate)
|
||||
|
||||
We are using [Slate](https://github.com/tendermint/slate) to power our RPC
|
||||
documentation. For generating markdown use:
|
||||
|
||||
```shell
|
||||
go get github.com/davecheney/godoc2md
|
||||
|
||||
godoc2md -template rpc/core/doc_template.txt github.com/tendermint/tendermint/rpc/core | grep -v -e "pipe.go" -e "routes.go" -e "dev.go" | sed 's$/src/target$https://github.com/tendermint/tendermint/tree/master/rpc/core$'
|
||||
```
|
||||
|
||||
For more information see the [CI script for building the Slate docs](/scripts/slate.sh)
|
||||
|
||||
## Pagination
|
||||
|
||||
Requests that return multiple items will be paginated to 30 items by default.
|
||||
You can specify further pages with the ?page parameter. You can also set a
|
||||
custom page size up to 100 with the ?per_page parameter.
|
||||
@@ -1,95 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// Query the application for some information.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/abci_query?path=""&data="abcd"&trusted=false'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.ABCIQuery("", "abcd", true)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "response": {
|
||||
// "log": "exists",
|
||||
// "height": 0,
|
||||
// "proof": "010114FED0DAD959F36091AD761C922ABA3CBF1D8349990101020103011406AA2262E2F448242DF2C2607C3CDC705313EE3B0001149D16177BC71E445476174622EA559715C293740C",
|
||||
// "value": "61626364",
|
||||
// "key": "61626364",
|
||||
// "index": -1,
|
||||
// "code": 0
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+------------------------------------------------|
|
||||
// | path | string | false | false | Path to the data ("/a/b/c") |
|
||||
// | data | []byte | false | true | Data |
|
||||
// | height | int64 | 0 | false | Height (0 means latest) |
|
||||
// | trusted | bool | false | false | Does not include a proof of the data inclusion |
|
||||
func ABCIQuery(path string, data cmn.HexBytes, height int64, trusted bool) (*ctypes.ResultABCIQuery, error) {
|
||||
resQuery, err := proxyAppQuery.QuerySync(abci.RequestQuery{
|
||||
Path: path,
|
||||
Data: data,
|
||||
Height: height,
|
||||
Prove: !trusted,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("ABCIQuery", "path", path, "data", data, "result", resQuery)
|
||||
return &ctypes.ResultABCIQuery{*resQuery}, nil
|
||||
}
|
||||
|
||||
// Get some info about the application.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/abci_info'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.ABCIInfo()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "response": {
|
||||
// "data": "{\"size\":3}"
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
resInfo, err := proxyAppQuery.InfoSync(abci.RequestInfo{version.Version})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultABCIInfo{*resInfo}, nil
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// Get block headers for minHeight <= height <= maxHeight.
|
||||
// Block headers are returned in descending order (highest first).
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/blockchain?minHeight=10&maxHeight=10'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.BlockchainInfo(10, 10)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "block_metas": [
|
||||
// {
|
||||
// "header": {
|
||||
// "app_hash": "",
|
||||
// "chain_id": "test-chain-6UTNIN",
|
||||
// "height": 10,
|
||||
// "time": "2017-05-29T15:05:53.877Z",
|
||||
// "num_txs": 0,
|
||||
// "last_block_id": {
|
||||
// "parts": {
|
||||
// "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
|
||||
// },
|
||||
// "last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
|
||||
// "data_hash": "",
|
||||
// "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
|
||||
// },
|
||||
// "block_id": {
|
||||
// "parts": {
|
||||
// "hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
// "last_height": 5493
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// <aside class="notice">Returns at most 20 items.</aside>
|
||||
func BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
|
||||
if minHeight == 0 {
|
||||
minHeight = 1
|
||||
}
|
||||
|
||||
if maxHeight == 0 {
|
||||
maxHeight = blockStore.Height()
|
||||
} else {
|
||||
maxHeight = cmn.MinInt64(blockStore.Height(), maxHeight)
|
||||
}
|
||||
|
||||
// maximum 20 block metas
|
||||
const limit int64 = 20
|
||||
minHeight = cmn.MaxInt64(minHeight, maxHeight-limit)
|
||||
|
||||
logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight)
|
||||
|
||||
if minHeight > maxHeight {
|
||||
return nil, fmt.Errorf("min height %d can't be greater than max height %d", minHeight, maxHeight)
|
||||
}
|
||||
|
||||
blockMetas := []*types.BlockMeta{}
|
||||
for height := maxHeight; height >= minHeight; height-- {
|
||||
blockMeta := blockStore.LoadBlockMeta(height)
|
||||
blockMetas = append(blockMetas, blockMeta)
|
||||
}
|
||||
|
||||
return &ctypes.ResultBlockchainInfo{blockStore.Height(), blockMetas}, nil
|
||||
}
|
||||
|
||||
// Get block at a given height.
|
||||
// If no height is provided, it will fetch the latest block.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/block?height=10'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.Block(10)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "block": {
|
||||
// "last_commit": {
|
||||
// "precommits": [
|
||||
// {
|
||||
// "signature": {
|
||||
// "data": "12C0D8893B8A38224488DC1DE6270DF76BB1A5E9DB1C68577706A6A97C6EC34FFD12339183D5CA8BC2F46148773823DE905B7F6F5862FD564038BB7AE03BF50D",
|
||||
// "type": "ed25519"
|
||||
// },
|
||||
// "block_id": {
|
||||
// "parts": {
|
||||
// "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
|
||||
// },
|
||||
// "type": 2,
|
||||
// "round": 0,
|
||||
// "height": 9,
|
||||
// "validator_index": 0,
|
||||
// "validator_address": "E89A51D60F68385E09E716D353373B11F8FACD62"
|
||||
// }
|
||||
// ],
|
||||
// "blockID": {
|
||||
// "parts": {
|
||||
// "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
|
||||
// }
|
||||
// },
|
||||
// "data": {
|
||||
// "txs": []
|
||||
// },
|
||||
// "header": {
|
||||
// "app_hash": "",
|
||||
// "chain_id": "test-chain-6UTNIN",
|
||||
// "height": 10,
|
||||
// "time": "2017-05-29T15:05:53.877Z",
|
||||
// "num_txs": 0,
|
||||
// "last_block_id": {
|
||||
// "parts": {
|
||||
// "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
|
||||
// },
|
||||
// "last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
|
||||
// "data_hash": "",
|
||||
// "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
|
||||
// }
|
||||
// },
|
||||
// "block_meta": {
|
||||
// "header": {
|
||||
// "app_hash": "",
|
||||
// "chain_id": "test-chain-6UTNIN",
|
||||
// "height": 10,
|
||||
// "time": "2017-05-29T15:05:53.877Z",
|
||||
// "num_txs": 0,
|
||||
// "last_block_id": {
|
||||
// "parts": {
|
||||
// "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
|
||||
// },
|
||||
// "last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
|
||||
// "data_hash": "",
|
||||
// "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
|
||||
// },
|
||||
// "block_id": {
|
||||
// "parts": {
|
||||
// "hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Block(heightPtr *int64) (*ctypes.ResultBlock, error) {
|
||||
storeHeight := blockStore.Height()
|
||||
height, err := getHeight(storeHeight, heightPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockMeta := blockStore.LoadBlockMeta(height)
|
||||
block := blockStore.LoadBlock(height)
|
||||
return &ctypes.ResultBlock{blockMeta, block}, nil
|
||||
}
|
||||
|
||||
// Get block commit at a given height.
|
||||
// If no height is provided, it will fetch the commit for the latest block.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/commit?height=11'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.Commit(11)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "canonical": true,
|
||||
// "commit": {
|
||||
// "precommits": [
|
||||
// {
|
||||
// "signature": {
|
||||
// "data": "00970429FEC652E9E21D106A90AE8C5413759A7488775CEF4A3F44DC46C7F9D941070E4FBE9ED54DF247FA3983359A0C3A238D61DE55C75C9116D72ABC9CF50F",
|
||||
// "type": "ed25519"
|
||||
// },
|
||||
// "block_id": {
|
||||
// "parts": {
|
||||
// "hash": "9E37CBF266BC044A779E09D81C456E653B89E006",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "CC6E861E31CA4334E9888381B4A9137D1458AB6A"
|
||||
// },
|
||||
// "type": 2,
|
||||
// "round": 0,
|
||||
// "height": 11,
|
||||
// "validator_index": 0,
|
||||
// "validator_address": "E89A51D60F68385E09E716D353373B11F8FACD62"
|
||||
// }
|
||||
// ],
|
||||
// "blockID": {
|
||||
// "parts": {
|
||||
// "hash": "9E37CBF266BC044A779E09D81C456E653B89E006",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "CC6E861E31CA4334E9888381B4A9137D1458AB6A"
|
||||
// }
|
||||
// },
|
||||
// "header": {
|
||||
// "app_hash": "",
|
||||
// "chain_id": "test-chain-6UTNIN",
|
||||
// "height": 11,
|
||||
// "time": "2017-05-29T15:05:54.893Z",
|
||||
// "num_txs": 0,
|
||||
// "last_block_id": {
|
||||
// "parts": {
|
||||
// "hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
|
||||
// "total": 1
|
||||
// },
|
||||
// "hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
|
||||
// },
|
||||
// "last_commit_hash": "3CE0C9727CE524BA9CB7C91E28F08E2B94001087",
|
||||
// "data_hash": "",
|
||||
// "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) {
|
||||
storeHeight := blockStore.Height()
|
||||
height, err := getHeight(storeHeight, heightPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := blockStore.LoadBlockMeta(height).Header
|
||||
|
||||
// If the next block has not been committed yet,
|
||||
// use a non-canonical commit
|
||||
if height == storeHeight {
|
||||
commit := blockStore.LoadSeenCommit(height)
|
||||
return ctypes.NewResultCommit(header, commit, false), nil
|
||||
}
|
||||
|
||||
// Return the canonical commit (comes from the block at height+1)
|
||||
commit := blockStore.LoadBlockCommit(height)
|
||||
return ctypes.NewResultCommit(header, commit, true), nil
|
||||
}
|
||||
|
||||
// BlockResults gets ABCIResults at a given height.
|
||||
// If no height is provided, it will fetch results for the latest block.
|
||||
//
|
||||
// Results are for the height of the block containing the txs.
|
||||
// Thus response.results[5] is the results of executing getBlock(h).Txs[5]
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/block_results?height=10'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.BlockResults(10)
|
||||
// ```
|
||||
//
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "height": 10,
|
||||
// "results": [
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": "CAFE00F00D"
|
||||
// },
|
||||
// {
|
||||
// "code": 102,
|
||||
// "data": ""
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ```
|
||||
func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) {
|
||||
storeHeight := blockStore.Height()
|
||||
height, err := getHeight(storeHeight, heightPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load the results
|
||||
results, err := sm.LoadABCIResponses(stateDB, height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &ctypes.ResultBlockResults{
|
||||
Height: height,
|
||||
Results: results,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getHeight(storeHeight int64, heightPtr *int64) (int64, error) {
|
||||
if heightPtr != nil {
|
||||
height := *heightPtr
|
||||
if height <= 0 {
|
||||
return 0, fmt.Errorf("Height must be greater than 0")
|
||||
}
|
||||
if height > storeHeight {
|
||||
return 0, fmt.Errorf("Height must be less than or equal to the current blockchain height")
|
||||
}
|
||||
return height, nil
|
||||
}
|
||||
return storeHeight, nil
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
cm "github.com/tendermint/tendermint/consensus"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Get the validator set at the given block height.
|
||||
// If no height is provided, it will fetch the current validator set.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/validators'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// state, err := client.Validators()
|
||||
// ```
|
||||
//
|
||||
// The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "validators": [
|
||||
// {
|
||||
// "accum": 0,
|
||||
// "voting_power": 10,
|
||||
// "pub_key": {
|
||||
// "data": "68DFDA7E50F82946E7E8546BED37944A422CD1B831E70DF66BA3B8430593944D",
|
||||
// "type": "ed25519"
|
||||
// },
|
||||
// "address": "E89A51D60F68385E09E716D353373B11F8FACD62"
|
||||
// }
|
||||
// ],
|
||||
// "block_height": 5241
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) {
|
||||
storeHeight := blockStore.Height()
|
||||
height, err := getHeight(storeHeight, heightPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validators, err := sm.LoadValidators(stateDB, height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultValidators{height, validators.Validators}, nil
|
||||
}
|
||||
|
||||
// DumpConsensusState dumps consensus state.
|
||||
// UNSTABLE
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/dump_consensus_state'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// state, err := client.DumpConsensusState()
|
||||
// ```
|
||||
//
|
||||
// The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": "",
|
||||
// "result": {
|
||||
// "round_state": {
|
||||
// "height": 7185,
|
||||
// "round": 0,
|
||||
// "step": 1,
|
||||
// "start_time": "2018-05-12T13:57:28.440293621-07:00",
|
||||
// "commit_time": "2018-05-12T13:57:27.440293621-07:00",
|
||||
// "validators": {
|
||||
// "validators": [
|
||||
// {
|
||||
// "address": "B5B3D40BE53982AD294EF99FF5A34C0C3E5A3244",
|
||||
// "pub_key": {
|
||||
// "type": "tendermint/PubKeyEd25519",
|
||||
// "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg="
|
||||
// },
|
||||
// "voting_power": 10,
|
||||
// "accum": 0
|
||||
// }
|
||||
// ],
|
||||
// "proposer": {
|
||||
// "address": "B5B3D40BE53982AD294EF99FF5A34C0C3E5A3244",
|
||||
// "pub_key": {
|
||||
// "type": "tendermint/PubKeyEd25519",
|
||||
// "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg="
|
||||
// },
|
||||
// "voting_power": 10,
|
||||
// "accum": 0
|
||||
// }
|
||||
// },
|
||||
// "proposal": null,
|
||||
// "proposal_block": null,
|
||||
// "proposal_block_parts": null,
|
||||
// "locked_round": 0,
|
||||
// "locked_block": null,
|
||||
// "locked_block_parts": null,
|
||||
// "valid_round": 0,
|
||||
// "valid_block": null,
|
||||
// "valid_block_parts": null,
|
||||
// "votes": [
|
||||
// {
|
||||
// "round": 0,
|
||||
// "prevotes": "_",
|
||||
// "precommits": "_"
|
||||
// }
|
||||
// ],
|
||||
// "commit_round": -1,
|
||||
// "last_commit": {
|
||||
// "votes": [
|
||||
// "Vote{0:B5B3D40BE539 7184/00/2(Precommit) 14F946FA7EF0 /702B1B1A602A.../ @ 2018-05-12T20:57:27.342Z}"
|
||||
// ],
|
||||
// "votes_bit_array": "x",
|
||||
// "peer_maj_23s": {}
|
||||
// },
|
||||
// "last_validators": {
|
||||
// "validators": [
|
||||
// {
|
||||
// "address": "B5B3D40BE53982AD294EF99FF5A34C0C3E5A3244",
|
||||
// "pub_key": {
|
||||
// "type": "tendermint/PubKeyEd25519",
|
||||
// "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg="
|
||||
// },
|
||||
// "voting_power": 10,
|
||||
// "accum": 0
|
||||
// }
|
||||
// ],
|
||||
// "proposer": {
|
||||
// "address": "B5B3D40BE53982AD294EF99FF5A34C0C3E5A3244",
|
||||
// "pub_key": {
|
||||
// "type": "tendermint/PubKeyEd25519",
|
||||
// "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg="
|
||||
// },
|
||||
// "voting_power": 10,
|
||||
// "accum": 0
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "peers": [
|
||||
// {
|
||||
// "node_address": "30ad1854af22506383c3f0e57fb3c7f90984c5e8@172.16.63.221:26656",
|
||||
// "peer_state": {
|
||||
// "round_state": {
|
||||
// "height": 7185,
|
||||
// "round": 0,
|
||||
// "step": 1,
|
||||
// "start_time": "2018-05-12T13:57:27.438039872-07:00",
|
||||
// "proposal": false,
|
||||
// "proposal_block_parts_header": {
|
||||
// "total": 0,
|
||||
// "hash": ""
|
||||
// },
|
||||
// "proposal_block_parts": null,
|
||||
// "proposal_pol_round": -1,
|
||||
// "proposal_pol": "_",
|
||||
// "prevotes": "_",
|
||||
// "precommits": "_",
|
||||
// "last_commit_round": 0,
|
||||
// "last_commit": "x",
|
||||
// "catchup_commit_round": -1,
|
||||
// "catchup_commit": "_"
|
||||
// },
|
||||
// "stats": {
|
||||
// "last_vote_height": 7184,
|
||||
// "votes": 255,
|
||||
// "last_block_part_height": 7184,
|
||||
// "block_parts": 255
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
// Get Peer consensus states.
|
||||
peers := p2pSwitch.Peers().List()
|
||||
peerStates := make([]ctypes.PeerStateInfo, len(peers))
|
||||
for i, peer := range peers {
|
||||
peerState := peer.Get(types.PeerStateKey).(*cm.PeerState)
|
||||
peerStateJSON, err := peerState.ToJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peerStates[i] = ctypes.PeerStateInfo{
|
||||
// Peer basic info.
|
||||
NodeAddress: p2p.IDAddressString(peer.ID(), peer.NodeInfo().ListenAddr),
|
||||
// Peer consensus state.
|
||||
PeerState: peerStateJSON,
|
||||
}
|
||||
}
|
||||
// Get self round state.
|
||||
roundState, err := consensusState.GetRoundStateJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultDumpConsensusState{roundState, peerStates}, nil
|
||||
}
|
||||
|
||||
// ConsensusState returns a concise summary of the consensus state.
|
||||
// UNSTABLE
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/consensus_state'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// state, err := client.ConsensusState()
|
||||
// ```
|
||||
//
|
||||
// The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
//{
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": "",
|
||||
// "result": {
|
||||
// "round_state": {
|
||||
// "height/round/step": "9336/0/1",
|
||||
// "start_time": "2018-05-14T10:25:45.72595357-04:00",
|
||||
// "proposal_block_hash": "",
|
||||
// "locked_block_hash": "",
|
||||
// "valid_block_hash": "",
|
||||
// "height_vote_set": [
|
||||
// {
|
||||
// "round": 0,
|
||||
// "prevotes": [
|
||||
// "nil-Vote"
|
||||
// ],
|
||||
// "prevotes_bit_array": "BA{1:_} 0/10 = 0.00",
|
||||
// "precommits": [
|
||||
// "nil-Vote"
|
||||
// ],
|
||||
// "precommits_bit_array": "BA{1:_} 0/10 = 0.00"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//```
|
||||
func ConsensusState() (*ctypes.ResultConsensusState, error) {
|
||||
// Get self round state.
|
||||
bz, err := consensusState.GetRoundStateSimpleJSON()
|
||||
return &ctypes.ResultConsensusState{bz}, err
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
func UnsafeFlushMempool() (*ctypes.ResultUnsafeFlushMempool, error) {
|
||||
mempool.Flush()
|
||||
return &ctypes.ResultUnsafeFlushMempool{}, nil
|
||||
}
|
||||
|
||||
var profFile *os.File
|
||||
|
||||
func UnsafeStartCPUProfiler(filename string) (*ctypes.ResultUnsafeProfile, error) {
|
||||
var err error
|
||||
profFile, err = os.Create(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pprof.StartCPUProfile(profFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultUnsafeProfile{}, nil
|
||||
}
|
||||
|
||||
func UnsafeStopCPUProfiler() (*ctypes.ResultUnsafeProfile, error) {
|
||||
pprof.StopCPUProfile()
|
||||
if err := profFile.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultUnsafeProfile{}, nil
|
||||
}
|
||||
|
||||
func UnsafeWriteHeapProfile(filename string) (*ctypes.ResultUnsafeProfile, error) {
|
||||
memProfFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pprof.WriteHeapProfile(memProfFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := memProfFile.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ctypes.ResultUnsafeProfile{}, nil
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
# Introduction
|
||||
|
||||
Tendermint supports the following RPC protocols:
|
||||
|
||||
* URI over HTTP
|
||||
* JSONRPC over HTTP
|
||||
* JSONRPC over websockets
|
||||
|
||||
Tendermint RPC is built using [our own RPC library](https://github.com/tendermint/tendermint/tree/master/rpc/lib) which contains its own set of documentation and tests.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the `laddr` config parameter under `[rpc]` table in the `$TMHOME/config/config.toml` file or the `--rpc.laddr` command-line flag to the desired protocol://host:port setting. Default: `tcp://0.0.0.0:26657`.
|
||||
|
||||
## Arguments
|
||||
|
||||
Arguments which expect strings or byte arrays may be passed as quoted strings, like `"abc"` or as `0x`-prefixed strings, like `0x616263`.
|
||||
|
||||
## URI/HTTP
|
||||
|
||||
```bash
|
||||
curl 'localhost:26657/broadcast_tx_sync?tx="abc"'
|
||||
```
|
||||
|
||||
> Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "",
|
||||
"result": {
|
||||
"hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF",
|
||||
"log": "",
|
||||
"data": "",
|
||||
"code": 0
|
||||
},
|
||||
"id": "",
|
||||
"jsonrpc": "2.0"
|
||||
}
|
||||
```
|
||||
|
||||
The first entry in the result-array (`96`) is the method this response correlates with. `96` refers to "ResultTypeBroadcastTx", see [responses.go](https://github.com/tendermint/tendermint/blob/master/rpc/core/types/responses.go) for a complete overview.
|
||||
|
||||
## JSONRPC/HTTP
|
||||
|
||||
JSONRPC requests can be POST'd to the root RPC endpoint via HTTP (e.g. `http://localhost:26657/`).
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "broadcast_tx_sync",
|
||||
"jsonrpc": "2.0",
|
||||
"params": [ "abc" ],
|
||||
"id": "dontcare"
|
||||
}
|
||||
```
|
||||
|
||||
## JSONRPC/websockets
|
||||
|
||||
JSONRPC requests can be made via websocket. The websocket endpoint is at `/websocket`, e.g. `localhost:26657/websocket`. Asynchronous RPC functions like event `subscribe` and `unsubscribe` are only available via websockets.
|
||||
|
||||
|
||||
## More Examples
|
||||
|
||||
See the various bash tests using curl in `test/`, and examples using the `Go` API in `rpc/client/`.
|
||||
|
||||
## Get the list
|
||||
|
||||
An HTTP Get request to the root RPC endpoint shows a list of available endpoints.
|
||||
|
||||
```bash
|
||||
curl 'localhost:26657'
|
||||
```
|
||||
|
||||
> Response:
|
||||
|
||||
```plain
|
||||
Available endpoints:
|
||||
/abci_info
|
||||
/dump_consensus_state
|
||||
/genesis
|
||||
/net_info
|
||||
/num_unconfirmed_txs
|
||||
/status
|
||||
/health
|
||||
/unconfirmed_txs
|
||||
/unsafe_flush_mempool
|
||||
/unsafe_stop_cpu_profiler
|
||||
/validators
|
||||
|
||||
Endpoints that require arguments:
|
||||
/abci_query?path=_&data=_&prove=_
|
||||
/block?height=_
|
||||
/blockchain?minHeight=_&maxHeight=_
|
||||
/broadcast_tx_async?tx=_
|
||||
/broadcast_tx_commit?tx=_
|
||||
/broadcast_tx_sync?tx=_
|
||||
/commit?height=_
|
||||
/dial_seeds?seeds=_
|
||||
/dial_persistent_peers?persistent_peers=_
|
||||
/subscribe?event=_
|
||||
/tx?hash=_&prove=_
|
||||
/unsafe_start_cpu_profiler?filename=_
|
||||
/unsafe_write_heap_profile?filename=_
|
||||
/unsubscribe?event=_
|
||||
```
|
||||
|
||||
# Endpoints
|
||||
*/
|
||||
package core
|
||||
@@ -1,8 +0,0 @@
|
||||
{{with .PDoc}}
|
||||
{{comment_md .Doc}}
|
||||
{{example_html $ ""}}
|
||||
|
||||
{{range .Funcs}}{{$name_html := html .Name}}## [{{$name_html}}]({{posLink_url $ .Decl}})
|
||||
{{comment_md .Doc}}{{end}}
|
||||
{{end}}
|
||||
---
|
||||
@@ -1,188 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Subscribe for events via WebSocket.
|
||||
//
|
||||
// To tell which events you want, you need to provide a query. query is a
|
||||
// string, which has a form: "condition AND condition ..." (no OR at the
|
||||
// moment). condition has a form: "key operation operand". key is a string with
|
||||
// a restricted set of possible symbols ( \t\n\r\\()"'=>< are not allowed).
|
||||
// operation can be "=", "<", "<=", ">", ">=", "CONTAINS". operand can be a
|
||||
// string (escaped with single quotes), number, date or time.
|
||||
//
|
||||
// Examples:
|
||||
// tm.event = 'NewBlock' # new blocks
|
||||
// tm.event = 'CompleteProposal' # node got a complete proposal
|
||||
// tm.event = 'Tx' AND tx.hash = 'XYZ' # single transaction
|
||||
// tm.event = 'Tx' AND tx.height = 5 # all txs of the fifth block
|
||||
// tx.height = 5 # all txs of the fifth block
|
||||
//
|
||||
// Tendermint provides a few predefined keys: tm.event, tx.hash and tx.height.
|
||||
// Note for transactions, you can define additional keys by providing tags with
|
||||
// DeliverTx response.
|
||||
//
|
||||
// DeliverTx{
|
||||
// Tags: []*KVPair{
|
||||
// "agent.name": "K",
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// tm.event = 'Tx' AND agent.name = 'K'
|
||||
// tm.event = 'Tx' AND account.created_at >= TIME 2013-05-03T14:45:00Z
|
||||
// tm.event = 'Tx' AND contract.sign_date = DATE 2017-01-01
|
||||
// tm.event = 'Tx' AND account.owner CONTAINS 'Igor'
|
||||
//
|
||||
// See list of all possible events here
|
||||
// https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants
|
||||
//
|
||||
// For complete query syntax, check out
|
||||
// https://godoc.org/github.com/tendermint/tendermint/libs/pubsub/query.
|
||||
//
|
||||
// ```go
|
||||
// import "github.com/tendermint/tendermint/libs/pubsub/query"
|
||||
// import "github.com/tendermint/tendermint/types"
|
||||
//
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
// defer cancel()
|
||||
// query := query.MustParse("tm.event = 'Tx' AND tx.height = 3")
|
||||
// txs := make(chan interface{})
|
||||
// err := client.Subscribe(ctx, "test-client", query, txs)
|
||||
//
|
||||
// go func() {
|
||||
// for e := range txs {
|
||||
// fmt.Println("got ", e.(types.EventDataTx))
|
||||
// }
|
||||
// }()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {},
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+-------------|
|
||||
// | query | string | "" | true | Query |
|
||||
//
|
||||
// <aside class="notice">WebSocket only</aside>
|
||||
func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscribe, error) {
|
||||
addr := wsCtx.GetRemoteAddr()
|
||||
logger.Info("Subscribe to query", "remote", addr, "query", query)
|
||||
|
||||
q, err := tmquery.New(query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse query")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), subscribeTimeout)
|
||||
defer cancel()
|
||||
ch := make(chan interface{})
|
||||
err = eventBusFor(wsCtx).Subscribe(ctx, addr, q, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for event := range ch {
|
||||
tmResult := &ctypes.ResultEvent{query, event.(tmtypes.TMEventData)}
|
||||
wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Codec(), wsCtx.Request.ID+"#event", tmResult))
|
||||
}
|
||||
}()
|
||||
|
||||
return &ctypes.ResultSubscribe{}, nil
|
||||
}
|
||||
|
||||
// Unsubscribe from events via WebSocket.
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// err := client.Unsubscribe("test-client", query)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {},
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+-------------|
|
||||
// | query | string | "" | true | Query |
|
||||
//
|
||||
// <aside class="notice">WebSocket only</aside>
|
||||
func Unsubscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultUnsubscribe, error) {
|
||||
addr := wsCtx.GetRemoteAddr()
|
||||
logger.Info("Unsubscribe from query", "remote", addr, "query", query)
|
||||
q, err := tmquery.New(query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse query")
|
||||
}
|
||||
err = eventBusFor(wsCtx).Unsubscribe(context.Background(), addr, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultUnsubscribe{}, nil
|
||||
}
|
||||
|
||||
// Unsubscribe from all events via WebSocket.
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// err := client.UnsubscribeAll("test-client")
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {},
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// <aside class="notice">WebSocket only</aside>
|
||||
func UnsubscribeAll(wsCtx rpctypes.WSRPCContext) (*ctypes.ResultUnsubscribe, error) {
|
||||
addr := wsCtx.GetRemoteAddr()
|
||||
logger.Info("Unsubscribe from all", "remote", addr)
|
||||
err := eventBusFor(wsCtx).UnsubscribeAll(context.Background(), addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultUnsubscribe{}, nil
|
||||
}
|
||||
|
||||
func eventBusFor(wsCtx rpctypes.WSRPCContext) tmtypes.EventBusSubscriber {
|
||||
es := wsCtx.GetEventSubscriber()
|
||||
if es == nil {
|
||||
es = eventBus
|
||||
}
|
||||
return es
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// Get node health. Returns empty result (200 OK) on success, no response - in
|
||||
// case of an error.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/health'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.Health()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {},
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Health() (*ctypes.ResultHealth, error) {
|
||||
return &ctypes.ResultHealth{}, nil
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// NOTE: tx should be signed, but this is only checked at the app level (not by Tendermint!)
|
||||
|
||||
// Returns right away, with no response
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/broadcast_tx_async?tx="123"'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.BroadcastTxAsync("123")
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52",
|
||||
// "log": "",
|
||||
// "data": "",
|
||||
// "code": 0
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+------+---------+----------+-----------------|
|
||||
// | tx | Tx | nil | true | The transaction |
|
||||
func BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
err := mempool.CheckTx(tx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
|
||||
}
|
||||
return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
|
||||
}
|
||||
|
||||
// Returns with the response from CheckTx.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/broadcast_tx_sync?tx="456"'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.BroadcastTxSync("456")
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": "",
|
||||
// "result": {
|
||||
// "code": 0,
|
||||
// "data": "",
|
||||
// "log": "",
|
||||
// "hash": "0D33F2F03A5234F38706E43004489E061AC40A2E"
|
||||
// },
|
||||
// "error": ""
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+------+---------+----------+-----------------|
|
||||
// | tx | Tx | nil | true | The transaction |
|
||||
func BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
resCh := make(chan *abci.Response, 1)
|
||||
err := mempool.CheckTx(tx, func(res *abci.Response) {
|
||||
resCh <- res
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
|
||||
}
|
||||
res := <-resCh
|
||||
r := res.GetCheckTx()
|
||||
return &ctypes.ResultBroadcastTx{
|
||||
Code: r.Code,
|
||||
Data: r.Data,
|
||||
Log: r.Log,
|
||||
Hash: tx.Hash(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CONTRACT: only returns error if mempool.BroadcastTx errs (ie. problem with the app)
|
||||
// or if we timeout waiting for tx to commit.
|
||||
// If CheckTx or DeliverTx fail, no error will be returned, but the returned result
|
||||
// will contain a non-OK ABCI code.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/broadcast_tx_commit?tx="789"'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.BroadcastTxCommit("789")
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "height": 26682,
|
||||
// "hash": "75CA0F856A4DA078FC4911580360E70CEFB2EBEE",
|
||||
// "deliver_tx": {
|
||||
// "log": "",
|
||||
// "data": "",
|
||||
// "code": 0
|
||||
// },
|
||||
// "check_tx": {
|
||||
// "log": "",
|
||||
// "data": "",
|
||||
// "code": 0
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+------+---------+----------+-----------------|
|
||||
// | tx | Tx | nil | true | The transaction |
|
||||
func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
// subscribe to tx being committed in block
|
||||
ctx, cancel := context.WithTimeout(context.Background(), subscribeTimeout)
|
||||
defer cancel()
|
||||
deliverTxResCh := make(chan interface{})
|
||||
q := types.EventQueryTxFor(tx)
|
||||
err := eventBus.Subscribe(ctx, "mempool", q, deliverTxResCh)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "failed to subscribe to tx")
|
||||
logger.Error("Error on broadcastTxCommit", "err", err)
|
||||
return nil, fmt.Errorf("Error on broadcastTxCommit: %v", err)
|
||||
}
|
||||
defer eventBus.Unsubscribe(context.Background(), "mempool", q)
|
||||
|
||||
// broadcast the tx and register checktx callback
|
||||
checkTxResCh := make(chan *abci.Response, 1)
|
||||
err = mempool.CheckTx(tx, func(res *abci.Response) {
|
||||
checkTxResCh <- res
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Error on broadcastTxCommit", "err", err)
|
||||
return nil, fmt.Errorf("Error on broadcastTxCommit: %v", err)
|
||||
}
|
||||
checkTxRes := <-checkTxResCh
|
||||
checkTxR := checkTxRes.GetCheckTx()
|
||||
if checkTxR.Code != abci.CodeTypeOK {
|
||||
// CheckTx failed!
|
||||
return &ctypes.ResultBroadcastTxCommit{
|
||||
CheckTx: *checkTxR,
|
||||
DeliverTx: abci.ResponseDeliverTx{},
|
||||
Hash: tx.Hash(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Wait for the tx to be included in a block,
|
||||
// timeout after something reasonable.
|
||||
// TODO: configurable?
|
||||
timer := time.NewTimer(60 * 2 * time.Second)
|
||||
select {
|
||||
case deliverTxResMsg := <-deliverTxResCh:
|
||||
deliverTxRes := deliverTxResMsg.(types.EventDataTx)
|
||||
// The tx was included in a block.
|
||||
deliverTxR := deliverTxRes.Result
|
||||
logger.Info("DeliverTx passed ", "tx", cmn.HexBytes(tx), "response", deliverTxR)
|
||||
return &ctypes.ResultBroadcastTxCommit{
|
||||
CheckTx: *checkTxR,
|
||||
DeliverTx: deliverTxR,
|
||||
Hash: tx.Hash(),
|
||||
Height: deliverTxRes.Height,
|
||||
}, nil
|
||||
case <-timer.C:
|
||||
logger.Error("failed to include tx")
|
||||
return &ctypes.ResultBroadcastTxCommit{
|
||||
CheckTx: *checkTxR,
|
||||
DeliverTx: abci.ResponseDeliverTx{},
|
||||
Hash: tx.Hash(),
|
||||
}, fmt.Errorf("Timed out waiting for transaction to be included in a block")
|
||||
}
|
||||
}
|
||||
|
||||
// Get unconfirmed transactions (maximum ?limit entries) including their number.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/unconfirmed_txs'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.UnconfirmedTxs()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "txs": [],
|
||||
// "n_txs": 0
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+------+---------+----------+--------------------------------------|
|
||||
// | limit | int | 30 | false | Maximum number of entries (max: 100) |
|
||||
// ```
|
||||
func UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
// reuse per_page validator
|
||||
limit = validatePerPage(limit)
|
||||
|
||||
txs := mempool.Reap(limit)
|
||||
return &ctypes.ResultUnconfirmedTxs{len(txs), txs}, nil
|
||||
}
|
||||
|
||||
// Get number of unconfirmed transactions.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/num_unconfirmed_txs'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.UnconfirmedTxs()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "txs": null,
|
||||
// "n_txs": 0
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) {
|
||||
return &ctypes.ResultUnconfirmedTxs{N: mempool.Size()}, nil
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// Get network info.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/net_info'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// info, err := client.NetInfo()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "n_peers": 0,
|
||||
// "peers": [],
|
||||
// "listeners": [
|
||||
// "Listener(@10.0.2.15:26656)"
|
||||
// ],
|
||||
// "listening": true
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
listening := p2pSwitch.IsListening()
|
||||
listeners := []string{}
|
||||
for _, listener := range p2pSwitch.Listeners() {
|
||||
listeners = append(listeners, listener.String())
|
||||
}
|
||||
peers := []ctypes.Peer{}
|
||||
for _, peer := range p2pSwitch.Peers().List() {
|
||||
peers = append(peers, ctypes.Peer{
|
||||
NodeInfo: peer.NodeInfo(),
|
||||
IsOutbound: peer.IsOutbound(),
|
||||
ConnectionStatus: peer.Status(),
|
||||
})
|
||||
}
|
||||
// TODO: Should we include PersistentPeers and Seeds in here?
|
||||
// PRO: useful info
|
||||
// CON: privacy
|
||||
return &ctypes.ResultNetInfo{
|
||||
Listening: listening,
|
||||
Listeners: listeners,
|
||||
NPeers: len(peers),
|
||||
Peers: peers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
|
||||
if len(seeds) == 0 {
|
||||
return &ctypes.ResultDialSeeds{}, errors.New("No seeds provided")
|
||||
}
|
||||
// starts go routines to dial each peer after random delays
|
||||
logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds)
|
||||
err := p2pSwitch.DialPeersAsync(addrBook, seeds, false)
|
||||
if err != nil {
|
||||
return &ctypes.ResultDialSeeds{}, err
|
||||
}
|
||||
return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil
|
||||
}
|
||||
|
||||
func UnsafeDialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) {
|
||||
if len(peers) == 0 {
|
||||
return &ctypes.ResultDialPeers{}, errors.New("No peers provided")
|
||||
}
|
||||
// starts go routines to dial each peer after random delays
|
||||
logger.Info("DialPeers", "addrBook", addrBook, "peers", peers, "persistent", persistent)
|
||||
err := p2pSwitch.DialPeersAsync(addrBook, peers, persistent)
|
||||
if err != nil {
|
||||
return &ctypes.ResultDialPeers{}, err
|
||||
}
|
||||
return &ctypes.ResultDialPeers{"Dialing peers in progress. See /net_info for details"}, nil
|
||||
}
|
||||
|
||||
// Get genesis file.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/genesis'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// genesis, err := client.Genesis()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "genesis": {
|
||||
// "app_hash": "",
|
||||
// "validators": [
|
||||
// {
|
||||
// "name": "",
|
||||
// "power": 10,
|
||||
// "pub_key": {
|
||||
// "data": "68DFDA7E50F82946E7E8546BED37944A422CD1B831E70DF66BA3B8430593944D",
|
||||
// "type": "ed25519"
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
// "chain_id": "test-chain-6UTNIN",
|
||||
// "genesis_time": "2017-05-29T15:05:41.671Z"
|
||||
// }
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
func Genesis() (*ctypes.ResultGenesis, error) {
|
||||
return &ctypes.ResultGenesis{genDoc}, nil
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/consensus"
|
||||
crypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/state/txindex"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// see README
|
||||
defaultPerPage = 30
|
||||
maxPerPage = 100
|
||||
)
|
||||
|
||||
var subscribeTimeout = 5 * time.Second
|
||||
|
||||
//----------------------------------------------
|
||||
// These interfaces are used by RPC and must be thread safe
|
||||
|
||||
type Consensus interface {
|
||||
GetState() sm.State
|
||||
GetValidators() (int64, []*types.Validator)
|
||||
GetRoundStateJSON() ([]byte, error)
|
||||
GetRoundStateSimpleJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
type P2P interface {
|
||||
Listeners() []p2p.Listener
|
||||
Peers() p2p.IPeerSet
|
||||
NumPeers() (outbound, inbound, dialig int)
|
||||
NodeInfo() p2p.NodeInfo
|
||||
IsListening() bool
|
||||
DialPeersAsync(p2p.AddrBook, []string, bool) error
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// These package level globals come with setters
|
||||
// that are expected to be called only once, on startup
|
||||
|
||||
var (
|
||||
// external, thread safe interfaces
|
||||
proxyAppQuery proxy.AppConnQuery
|
||||
|
||||
// interfaces defined in types and above
|
||||
stateDB dbm.DB
|
||||
blockStore sm.BlockStore
|
||||
mempool sm.Mempool
|
||||
evidencePool sm.EvidencePool
|
||||
consensusState Consensus
|
||||
p2pSwitch P2P
|
||||
|
||||
// objects
|
||||
pubKey crypto.PubKey
|
||||
genDoc *types.GenesisDoc // cache the genesis structure
|
||||
addrBook p2p.AddrBook
|
||||
txIndexer txindex.TxIndexer
|
||||
consensusReactor *consensus.ConsensusReactor
|
||||
eventBus *types.EventBus // thread safe
|
||||
|
||||
logger log.Logger
|
||||
)
|
||||
|
||||
func SetStateDB(db dbm.DB) {
|
||||
stateDB = db
|
||||
}
|
||||
|
||||
func SetBlockStore(bs sm.BlockStore) {
|
||||
blockStore = bs
|
||||
}
|
||||
|
||||
func SetMempool(mem sm.Mempool) {
|
||||
mempool = mem
|
||||
}
|
||||
|
||||
func SetEvidencePool(evpool sm.EvidencePool) {
|
||||
evidencePool = evpool
|
||||
}
|
||||
|
||||
func SetConsensusState(cs Consensus) {
|
||||
consensusState = cs
|
||||
}
|
||||
|
||||
func SetSwitch(sw P2P) {
|
||||
p2pSwitch = sw
|
||||
}
|
||||
|
||||
func SetPubKey(pk crypto.PubKey) {
|
||||
pubKey = pk
|
||||
}
|
||||
|
||||
func SetGenesisDoc(doc *types.GenesisDoc) {
|
||||
genDoc = doc
|
||||
}
|
||||
|
||||
func SetAddrBook(book p2p.AddrBook) {
|
||||
addrBook = book
|
||||
}
|
||||
|
||||
func SetProxyAppQuery(appConn proxy.AppConnQuery) {
|
||||
proxyAppQuery = appConn
|
||||
}
|
||||
|
||||
func SetTxIndexer(indexer txindex.TxIndexer) {
|
||||
txIndexer = indexer
|
||||
}
|
||||
|
||||
func SetConsensusReactor(conR *consensus.ConsensusReactor) {
|
||||
consensusReactor = conR
|
||||
}
|
||||
|
||||
func SetLogger(l log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
func SetEventBus(b *types.EventBus) {
|
||||
eventBus = b
|
||||
}
|
||||
|
||||
func validatePage(page, perPage, totalCount int) int {
|
||||
if perPage < 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
pages := ((totalCount - 1) / perPage) + 1
|
||||
if page < 1 {
|
||||
page = 1
|
||||
} else if page > pages {
|
||||
page = pages
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
func validatePerPage(perPage int) int {
|
||||
if perPage < 1 || perPage > maxPerPage {
|
||||
return defaultPerPage
|
||||
}
|
||||
return perPage
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPaginationPage(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
totalCount int
|
||||
perPage int
|
||||
page int
|
||||
newPage int
|
||||
}{
|
||||
{0, 0, 1, 1},
|
||||
|
||||
{0, 10, 0, 1},
|
||||
{0, 10, 1, 1},
|
||||
{0, 10, 2, 1},
|
||||
|
||||
{5, 10, -1, 1},
|
||||
{5, 10, 0, 1},
|
||||
{5, 10, 1, 1},
|
||||
{5, 10, 2, 1},
|
||||
{5, 10, 2, 1},
|
||||
|
||||
{5, 5, 1, 1},
|
||||
{5, 5, 2, 1},
|
||||
{5, 5, 3, 1},
|
||||
|
||||
{5, 3, 2, 2},
|
||||
{5, 3, 3, 2},
|
||||
|
||||
{5, 2, 2, 2},
|
||||
{5, 2, 3, 3},
|
||||
{5, 2, 4, 3},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
p := validatePage(c.page, c.perPage, c.totalCount)
|
||||
assert.Equal(t, c.newPage, p, fmt.Sprintf("%v", c))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPaginationPerPage(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
totalCount int
|
||||
perPage int
|
||||
newPerPage int
|
||||
}{
|
||||
{5, 0, defaultPerPage},
|
||||
{5, 1, 1},
|
||||
{5, 2, 2},
|
||||
{5, defaultPerPage, defaultPerPage},
|
||||
{5, maxPerPage - 1, maxPerPage - 1},
|
||||
{5, maxPerPage, maxPerPage},
|
||||
{5, maxPerPage + 1, defaultPerPage},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
p := validatePerPage(c.perPage)
|
||||
assert.Equal(t, c.newPerPage, p, fmt.Sprintf("%v", c))
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
rpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
// TODO: better system than "unsafe" prefix
|
||||
// NOTE: Amino is registered in rpc/core/types/wire.go.
|
||||
var Routes = map[string]*rpc.RPCFunc{
|
||||
// subscribe/unsubscribe are reserved for websocket events.
|
||||
"subscribe": rpc.NewWSRPCFunc(Subscribe, "query"),
|
||||
"unsubscribe": rpc.NewWSRPCFunc(Unsubscribe, "query"),
|
||||
"unsubscribe_all": rpc.NewWSRPCFunc(UnsubscribeAll, ""),
|
||||
|
||||
// info API
|
||||
"health": rpc.NewRPCFunc(Health, ""),
|
||||
"status": rpc.NewRPCFunc(Status, ""),
|
||||
"net_info": rpc.NewRPCFunc(NetInfo, ""),
|
||||
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
|
||||
"genesis": rpc.NewRPCFunc(Genesis, ""),
|
||||
"block": rpc.NewRPCFunc(Block, "height"),
|
||||
"block_results": rpc.NewRPCFunc(BlockResults, "height"),
|
||||
"commit": rpc.NewRPCFunc(Commit, "height"),
|
||||
"tx": rpc.NewRPCFunc(Tx, "hash,prove"),
|
||||
"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page"),
|
||||
"validators": rpc.NewRPCFunc(Validators, "height"),
|
||||
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""),
|
||||
"consensus_state": rpc.NewRPCFunc(ConsensusState, ""),
|
||||
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, "limit"),
|
||||
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""),
|
||||
|
||||
// broadcast API
|
||||
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"),
|
||||
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"),
|
||||
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"),
|
||||
|
||||
// abci API
|
||||
"abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,height,trusted"),
|
||||
"abci_info": rpc.NewRPCFunc(ABCIInfo, ""),
|
||||
}
|
||||
|
||||
func AddUnsafeRoutes() {
|
||||
// control API
|
||||
Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds")
|
||||
Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent")
|
||||
Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "")
|
||||
|
||||
// profiler API
|
||||
Routes["unsafe_start_cpu_profiler"] = rpc.NewRPCFunc(UnsafeStartCPUProfiler, "filename")
|
||||
Routes["unsafe_stop_cpu_profiler"] = rpc.NewRPCFunc(UnsafeStopCPUProfiler, "")
|
||||
Routes["unsafe_write_heap_profile"] = rpc.NewRPCFunc(UnsafeWriteHeapProfile, "filename")
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
sm "github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
// Get Tendermint status including node info, pubkey, latest block
|
||||
// hash, app hash, block height and time.
|
||||
//
|
||||
// ```shell
|
||||
// curl 'localhost:26657/status'
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// result, err := client.Status()
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
//{
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": "",
|
||||
// "result": {
|
||||
// "node_info": {
|
||||
// "id": "562dd7f579f0ecee8c94a11a3c1e378c1876f433",
|
||||
// "listen_addr": "192.168.1.2:26656",
|
||||
// "network": "test-chain-I6zScH",
|
||||
// "version": "0.19.0",
|
||||
// "channels": "4020212223303800",
|
||||
// "moniker": "Ethans-MacBook-Pro.local",
|
||||
// "other": [
|
||||
// "amino_version=0.9.8",
|
||||
// "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"
|
||||
// ]
|
||||
// },
|
||||
// "sync_info": {
|
||||
// "latest_block_hash": "2D4D7055BE685E3CB2410603C92AD37AE557AC59",
|
||||
// "latest_app_hash": "0000000000000000",
|
||||
// "latest_block_height": 231,
|
||||
// "latest_block_time": "2018-04-27T23:18:08.459766485-04:00",
|
||||
// "catching_up": false
|
||||
// },
|
||||
// "validator_info": {
|
||||
// "address": "5875562FF0FFDECC895C20E32FC14988952E99E7",
|
||||
// "pub_key": {
|
||||
// "type": "tendermint/PubKeyEd25519",
|
||||
// "value": "PpDJRUrLG2RgFqYYjawfn/AcAgacSXpLFrmfYYQnuzE="
|
||||
// },
|
||||
// "voting_power": 10
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
// ```
|
||||
func Status() (*ctypes.ResultStatus, error) {
|
||||
latestHeight := blockStore.Height()
|
||||
var (
|
||||
latestBlockMeta *types.BlockMeta
|
||||
latestBlockHash cmn.HexBytes
|
||||
latestAppHash cmn.HexBytes
|
||||
latestBlockTimeNano int64
|
||||
)
|
||||
if latestHeight != 0 {
|
||||
latestBlockMeta = blockStore.LoadBlockMeta(latestHeight)
|
||||
latestBlockHash = latestBlockMeta.BlockID.Hash
|
||||
latestAppHash = latestBlockMeta.Header.AppHash
|
||||
latestBlockTimeNano = latestBlockMeta.Header.Time.UnixNano()
|
||||
}
|
||||
|
||||
latestBlockTime := time.Unix(0, latestBlockTimeNano)
|
||||
|
||||
var votingPower int64
|
||||
if val := validatorAtHeight(latestHeight); val != nil {
|
||||
votingPower = val.VotingPower
|
||||
}
|
||||
|
||||
result := &ctypes.ResultStatus{
|
||||
NodeInfo: p2pSwitch.NodeInfo(),
|
||||
SyncInfo: ctypes.SyncInfo{
|
||||
LatestBlockHash: latestBlockHash,
|
||||
LatestAppHash: latestAppHash,
|
||||
LatestBlockHeight: latestHeight,
|
||||
LatestBlockTime: latestBlockTime,
|
||||
CatchingUp: consensusReactor.FastSync(),
|
||||
},
|
||||
ValidatorInfo: ctypes.ValidatorInfo{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
VotingPower: votingPower,
|
||||
},
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func validatorAtHeight(h int64) *types.Validator {
|
||||
lastBlockHeight, vals := consensusState.GetValidators()
|
||||
|
||||
privValAddress := pubKey.Address()
|
||||
|
||||
// if we're still at height h, search in the current validator set
|
||||
if lastBlockHeight == h {
|
||||
for _, val := range vals {
|
||||
if bytes.Equal(val.Address, privValAddress) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we've moved to the next height, retrieve the validator set from DB
|
||||
if lastBlockHeight > h {
|
||||
vals, err := sm.LoadValidators(stateDB, h)
|
||||
if err != nil {
|
||||
// should not happen
|
||||
return nil
|
||||
}
|
||||
_, val := vals.GetByAddress(privValAddress)
|
||||
return val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
-219
@@ -1,219 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/state/txindex/null"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Tx allows you to query the transaction results. `nil` could mean the
|
||||
// transaction is in the mempool, invalidated, or was not sent in the first
|
||||
// place.
|
||||
//
|
||||
// ```shell
|
||||
// curl "localhost:26657/tx?hash=0x2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// tx, err := client.Tx([]byte("2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"), true)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "error": "",
|
||||
// "result": {
|
||||
// "proof": {
|
||||
// "Proof": {
|
||||
// "aunts": []
|
||||
// },
|
||||
// "Data": "YWJjZA==",
|
||||
// "RootHash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF",
|
||||
// "Total": 1,
|
||||
// "Index": 0
|
||||
// },
|
||||
// "tx": "YWJjZA==",
|
||||
// "tx_result": {
|
||||
// "log": "",
|
||||
// "data": "",
|
||||
// "code": 0
|
||||
// },
|
||||
// "index": 0,
|
||||
// "height": 52,
|
||||
// "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"
|
||||
// },
|
||||
// "id": "",
|
||||
// "jsonrpc": "2.0"
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// Returns a transaction matching the given transaction hash.
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+-----------------------------------------------------------|
|
||||
// | hash | []byte | nil | true | The transaction hash |
|
||||
// | prove | bool | false | false | Include a proof of the transaction inclusion in the block |
|
||||
//
|
||||
// ### Returns
|
||||
//
|
||||
// - `proof`: the `types.TxProof` object
|
||||
// - `tx`: `[]byte` - the transaction
|
||||
// - `tx_result`: the `abci.Result` object
|
||||
// - `index`: `int` - index of the transaction
|
||||
// - `height`: `int` - height of the block where this transaction was in
|
||||
// - `hash`: `[]byte` - hash of the transaction
|
||||
func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
|
||||
|
||||
// if index is disabled, return error
|
||||
if _, ok := txIndexer.(*null.TxIndex); ok {
|
||||
return nil, fmt.Errorf("Transaction indexing is disabled")
|
||||
}
|
||||
|
||||
r, err := txIndexer.Get(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("Tx (%X) not found", hash)
|
||||
}
|
||||
|
||||
height := r.Height
|
||||
index := r.Index
|
||||
|
||||
var proof types.TxProof
|
||||
if prove {
|
||||
block := blockStore.LoadBlock(height)
|
||||
proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines
|
||||
}
|
||||
|
||||
return &ctypes.ResultTx{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Index: uint32(index),
|
||||
TxResult: r.Result,
|
||||
Tx: r.Tx,
|
||||
Proof: proof,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TxSearch allows you to query for multiple transactions results. It returns a
|
||||
// list of transactions (maximum ?per_page entries) and the total count.
|
||||
//
|
||||
// ```shell
|
||||
// curl "localhost:26657/tx_search?query=\"account.owner='Ivan'\"&prove=true"
|
||||
// ```
|
||||
//
|
||||
// ```go
|
||||
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
|
||||
// q, err := tmquery.New("account.owner='Ivan'")
|
||||
// tx, err := client.TxSearch(q, true)
|
||||
// ```
|
||||
//
|
||||
// > The above command returns JSON structured like this:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": "",
|
||||
// "result": {
|
||||
// "txs": [
|
||||
// {
|
||||
// "proof": {
|
||||
// "Proof": {
|
||||
// "aunts": [
|
||||
// "J3LHbizt806uKnABNLwG4l7gXCA=",
|
||||
// "iblMO/M1TnNtlAefJyNCeVhjAb0=",
|
||||
// "iVk3ryurVaEEhdeS0ohAJZ3wtB8=",
|
||||
// "5hqMkTeGqpct51ohX0lZLIdsn7Q=",
|
||||
// "afhsNxFnLlZgFDoyPpdQSe0bR8g="
|
||||
// ]
|
||||
// },
|
||||
// "Data": "mvZHHa7HhZ4aRT0xMDA=",
|
||||
// "RootHash": "F6541223AA46E428CB1070E9840D2C3DF3B6D776",
|
||||
// "Total": 32,
|
||||
// "Index": 31
|
||||
// },
|
||||
// "tx": "mvZHHa7HhZ4aRT0xMDA=",
|
||||
// "tx_result": {},
|
||||
// "index": 31,
|
||||
// "height": 12,
|
||||
// "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"
|
||||
// }
|
||||
// ],
|
||||
// "total_count": 1
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// ### Query Parameters
|
||||
//
|
||||
// | Parameter | Type | Default | Required | Description |
|
||||
// |-----------+--------+---------+----------+-----------------------------------------------------------|
|
||||
// | query | string | "" | true | Query |
|
||||
// | prove | bool | false | false | Include proofs of the transactions inclusion in the block |
|
||||
// | page | int | 1 | false | Page number (1-based) |
|
||||
// | per_page | int | 30 | false | Number of entries per page (max: 100) |
|
||||
//
|
||||
// ### Returns
|
||||
//
|
||||
// - `proof`: the `types.TxProof` object
|
||||
// - `tx`: `[]byte` - the transaction
|
||||
// - `tx_result`: the `abci.Result` object
|
||||
// - `index`: `int` - index of the transaction
|
||||
// - `height`: `int` - height of the block where this transaction was in
|
||||
// - `hash`: `[]byte` - hash of the transaction
|
||||
func TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
|
||||
// if index is disabled, return error
|
||||
if _, ok := txIndexer.(*null.TxIndex); ok {
|
||||
return nil, fmt.Errorf("Transaction indexing is disabled")
|
||||
}
|
||||
|
||||
q, err := tmquery.New(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results, err := txIndexer.Search(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount := len(results)
|
||||
perPage = validatePerPage(perPage)
|
||||
page = validatePage(page, perPage, totalCount)
|
||||
skipCount := (page - 1) * perPage
|
||||
|
||||
apiResults := make([]*ctypes.ResultTx, cmn.MinInt(perPage, totalCount-skipCount))
|
||||
var proof types.TxProof
|
||||
for i := 0; i < len(apiResults); i++ {
|
||||
r := results[skipCount+i]
|
||||
height := r.Height
|
||||
index := r.Index
|
||||
|
||||
if prove {
|
||||
block := blockStore.LoadBlock(height)
|
||||
proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines
|
||||
}
|
||||
|
||||
apiResults[i] = &ctypes.ResultTx{
|
||||
Hash: r.Tx.Hash(),
|
||||
Height: height,
|
||||
Index: index,
|
||||
TxResult: r.Result,
|
||||
Tx: r.Tx,
|
||||
Proof: proof,
|
||||
}
|
||||
}
|
||||
|
||||
return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package core_types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/state"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// List of blocks
|
||||
type ResultBlockchainInfo struct {
|
||||
LastHeight int64 `json:"last_height"`
|
||||
BlockMetas []*types.BlockMeta `json:"block_metas"`
|
||||
}
|
||||
|
||||
// Genesis file
|
||||
type ResultGenesis struct {
|
||||
Genesis *types.GenesisDoc `json:"genesis"`
|
||||
}
|
||||
|
||||
// Single block (with meta)
|
||||
type ResultBlock struct {
|
||||
BlockMeta *types.BlockMeta `json:"block_meta"`
|
||||
Block *types.Block `json:"block"`
|
||||
}
|
||||
|
||||
// Commit and Header
|
||||
type ResultCommit struct {
|
||||
// SignedHeader is header and commit, embedded so we only have
|
||||
// one level in the json output
|
||||
types.SignedHeader
|
||||
CanonicalCommit bool `json:"canonical"`
|
||||
}
|
||||
|
||||
// ABCI results from a block
|
||||
type ResultBlockResults struct {
|
||||
Height int64 `json:"height"`
|
||||
Results *state.ABCIResponses `json:"results"`
|
||||
}
|
||||
|
||||
// NewResultCommit is a helper to initialize the ResultCommit with
|
||||
// the embedded struct
|
||||
func NewResultCommit(header *types.Header, commit *types.Commit,
|
||||
canonical bool) *ResultCommit {
|
||||
|
||||
return &ResultCommit{
|
||||
SignedHeader: types.SignedHeader{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
},
|
||||
CanonicalCommit: canonical,
|
||||
}
|
||||
}
|
||||
|
||||
// Info about the node's syncing state
|
||||
type SyncInfo struct {
|
||||
LatestBlockHash cmn.HexBytes `json:"latest_block_hash"`
|
||||
LatestAppHash cmn.HexBytes `json:"latest_app_hash"`
|
||||
LatestBlockHeight int64 `json:"latest_block_height"`
|
||||
LatestBlockTime time.Time `json:"latest_block_time"`
|
||||
CatchingUp bool `json:"catching_up"`
|
||||
}
|
||||
|
||||
// Info about the node's validator
|
||||
type ValidatorInfo struct {
|
||||
Address cmn.HexBytes `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
VotingPower int64 `json:"voting_power"`
|
||||
}
|
||||
|
||||
// Node Status
|
||||
type ResultStatus struct {
|
||||
NodeInfo p2p.NodeInfo `json:"node_info"`
|
||||
SyncInfo SyncInfo `json:"sync_info"`
|
||||
ValidatorInfo ValidatorInfo `json:"validator_info"`
|
||||
}
|
||||
|
||||
// Is TxIndexing enabled
|
||||
func (s *ResultStatus) TxIndexEnabled() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
for _, s := range s.NodeInfo.Other {
|
||||
info := strings.Split(s, "=")
|
||||
if len(info) == 2 && info[0] == "tx_index" {
|
||||
return info[1] == "on"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Info about peer connections
|
||||
type ResultNetInfo struct {
|
||||
Listening bool `json:"listening"`
|
||||
Listeners []string `json:"listeners"`
|
||||
NPeers int `json:"n_peers"`
|
||||
Peers []Peer `json:"peers"`
|
||||
}
|
||||
|
||||
// Log from dialing seeds
|
||||
type ResultDialSeeds struct {
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
// Log from dialing peers
|
||||
type ResultDialPeers struct {
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
// A peer
|
||||
type Peer struct {
|
||||
p2p.NodeInfo `json:"node_info"`
|
||||
IsOutbound bool `json:"is_outbound"`
|
||||
ConnectionStatus p2p.ConnectionStatus `json:"connection_status"`
|
||||
}
|
||||
|
||||
// Validators for a height
|
||||
type ResultValidators struct {
|
||||
BlockHeight int64 `json:"block_height"`
|
||||
Validators []*types.Validator `json:"validators"`
|
||||
}
|
||||
|
||||
// Info about the consensus state.
|
||||
// UNSTABLE
|
||||
type ResultDumpConsensusState struct {
|
||||
RoundState json.RawMessage `json:"round_state"`
|
||||
Peers []PeerStateInfo `json:"peers"`
|
||||
}
|
||||
|
||||
// UNSTABLE
|
||||
type PeerStateInfo struct {
|
||||
NodeAddress string `json:"node_address"`
|
||||
PeerState json.RawMessage `json:"peer_state"`
|
||||
}
|
||||
|
||||
// UNSTABLE
|
||||
type ResultConsensusState struct {
|
||||
RoundState json.RawMessage `json:"round_state"`
|
||||
}
|
||||
|
||||
// CheckTx result
|
||||
type ResultBroadcastTx struct {
|
||||
Code uint32 `json:"code"`
|
||||
Data cmn.HexBytes `json:"data"`
|
||||
Log string `json:"log"`
|
||||
|
||||
Hash cmn.HexBytes `json:"hash"`
|
||||
}
|
||||
|
||||
// CheckTx and DeliverTx results
|
||||
type ResultBroadcastTxCommit struct {
|
||||
CheckTx abci.ResponseCheckTx `json:"check_tx"`
|
||||
DeliverTx abci.ResponseDeliverTx `json:"deliver_tx"`
|
||||
Hash cmn.HexBytes `json:"hash"`
|
||||
Height int64 `json:"height"`
|
||||
}
|
||||
|
||||
// Result of querying for a tx
|
||||
type ResultTx struct {
|
||||
Hash cmn.HexBytes `json:"hash"`
|
||||
Height int64 `json:"height"`
|
||||
Index uint32 `json:"index"`
|
||||
TxResult abci.ResponseDeliverTx `json:"tx_result"`
|
||||
Tx types.Tx `json:"tx"`
|
||||
Proof types.TxProof `json:"proof,omitempty"`
|
||||
}
|
||||
|
||||
// Result of searching for txs
|
||||
type ResultTxSearch struct {
|
||||
Txs []*ResultTx `json:"txs"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
// List of mempool txs
|
||||
type ResultUnconfirmedTxs struct {
|
||||
N int `json:"n_txs"`
|
||||
Txs []types.Tx `json:"txs"`
|
||||
}
|
||||
|
||||
// Info abci msg
|
||||
type ResultABCIInfo struct {
|
||||
Response abci.ResponseInfo `json:"response"`
|
||||
}
|
||||
|
||||
// Query abci msg
|
||||
type ResultABCIQuery struct {
|
||||
Response abci.ResponseQuery `json:"response"`
|
||||
}
|
||||
|
||||
// empty results
|
||||
type (
|
||||
ResultUnsafeFlushMempool struct{}
|
||||
ResultUnsafeProfile struct{}
|
||||
ResultSubscribe struct{}
|
||||
ResultUnsubscribe struct{}
|
||||
ResultHealth struct{}
|
||||
)
|
||||
|
||||
// Event data from a subscription
|
||||
type ResultEvent struct {
|
||||
Query string `json:"query"`
|
||||
Data types.TMEventData `json:"data"`
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package core_types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
)
|
||||
|
||||
func TestStatusIndexer(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var status *ResultStatus
|
||||
assert.False(status.TxIndexEnabled())
|
||||
|
||||
status = &ResultStatus{}
|
||||
assert.False(status.TxIndexEnabled())
|
||||
|
||||
status.NodeInfo = p2p.NodeInfo{}
|
||||
assert.False(status.TxIndexEnabled())
|
||||
|
||||
cases := []struct {
|
||||
expected bool
|
||||
other []string
|
||||
}{
|
||||
{false, nil},
|
||||
{false, []string{}},
|
||||
{false, []string{"a=b"}},
|
||||
{false, []string{"tx_indexiskv", "some=dood"}},
|
||||
{true, []string{"tx_index=on", "tx_index=other"}},
|
||||
{true, []string{"^(*^(", "tx_index=on", "a=n=b=d="}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
status.NodeInfo.Other = tc.other
|
||||
assert.Equal(tc.expected, status.TxIndexEnabled())
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package core_types
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func RegisterAmino(cdc *amino.Codec) {
|
||||
types.RegisterEventDatas(cdc)
|
||||
types.RegisterEvidences(cdc)
|
||||
crypto.RegisterAmino(cdc)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package core
|
||||
|
||||
// a single integer is sufficient here
|
||||
|
||||
const Version = "3" // rpc routes for profiling, setting config
|
||||
Reference in New Issue
Block a user