mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-23 16:21:57 +00:00
Compare commits
21 Commits
v0.19.6-rc
...
v0.20.0-rc
Author | SHA1 | Date | |
---|---|---|---|
|
73de99ecab | ||
|
2046b346a1 | ||
|
c9514f077b | ||
|
3bf9a7dc50 | ||
|
53b0c67f75 | ||
|
3b8c1ae119 | ||
|
849ffaf43d | ||
|
058867669e | ||
|
923e0b02bf | ||
|
ec34c8f9d2 | ||
|
6004587347 | ||
|
7f20eb5f8e | ||
|
eeabb4c06b | ||
|
4da81aa0b7 | ||
|
67068a34f2 | ||
|
2a0e9f93ce | ||
|
708f35e5c1 | ||
|
f3f5c7f472 | ||
|
68f6226bea | ||
|
118b86b1ef | ||
|
b9afcbe3a2 |
@@ -77,6 +77,22 @@ jobs:
|
||||
paths:
|
||||
- "bin/abci*"
|
||||
|
||||
build_slate:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: slate docs
|
||||
command: |
|
||||
set -ex
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make build-slate
|
||||
|
||||
lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@@ -180,6 +196,9 @@ workflows:
|
||||
test-suite:
|
||||
jobs:
|
||||
- setup_dependencies
|
||||
- build_slate:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- setup_abci:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,13 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 0.20.0
|
||||
|
||||
BREAKING:
|
||||
|
||||
- [libs/pubsub] TagMap#Get returns a string value
|
||||
- [libs/pubsub] NewTagMap accepts a map of strings
|
||||
|
||||
## 0.19.6
|
||||
|
||||
*TBD*
|
||||
FEATURES
|
||||
|
||||
- [rpc] the RPC documentation is now published to https://tendermint.github.io/slate
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
- [consensus] consensus reactor now receives events from a separate event bus,
|
||||
which is not dependant on external RPC load
|
||||
- [consensus/wal] do not look for height in older files if we've seen height - 1
|
||||
|
||||
## 0.19.5
|
||||
|
||||
|
6
Gopkg.lock
generated
6
Gopkg.lock
generated
@@ -238,8 +238,8 @@
|
||||
"server",
|
||||
"types"
|
||||
]
|
||||
revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f"
|
||||
version = "v0.10.3"
|
||||
revision = "f9dce537281ffba5d1e047e6729429f7e5fb90c9"
|
||||
version = "v0.11.0-rc0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -382,6 +382,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "d85c98dcac32cc1fe05d006aa75e8985f6447a150a041b972a673a65e7681da9"
|
||||
inputs-digest = "90dc14750c1499107a3e6728ae696f9977f56bee2855c2f1c0a14831a165cc0e"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@@ -71,7 +71,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/abci"
|
||||
version = "~0.10.3"
|
||||
version = "0.11.0-rc0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-crypto"
|
||||
|
7
Makefile
7
Makefile
@@ -226,8 +226,11 @@ sentry-stop:
|
||||
@if [ -z "$(DO_API_TOKEN)" ]; then echo "DO_API_TOKEN environment variable not set." ; false ; fi
|
||||
cd networks/remote/terraform && terraform destroy -var DO_API_TOKEN="$(DO_API_TOKEN)" -var SSH_KEY_FILE="$(HOME)/.ssh/id_rsa.pub"
|
||||
|
||||
# meant for the CI, inspect script & adapt accordingly
|
||||
build-slate:
|
||||
bash scripts/slate.sh
|
||||
|
||||
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||
# unless there is a reason not to.
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONY: check build build_race dist install check_tools get_tools update_tools get_vendor_deps draw_deps test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop
|
||||
|
||||
.PHONY: check build build_race dist install check_tools get_tools update_tools get_vendor_deps draw_deps test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate
|
||||
|
@@ -269,8 +269,8 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
||||
if appBlockHeight == 0 {
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
req := abci.RequestInitChain{
|
||||
Validators: validators,
|
||||
AppStateBytes: h.appState,
|
||||
Validators: validators,
|
||||
GenesisBytes: h.appState,
|
||||
}
|
||||
_, err := proxyApp.Consensus().InitChainSync(req)
|
||||
if err != nil {
|
||||
@@ -365,7 +365,8 @@ func (h *Handshaker) replayBlocks(state sm.State, proxyApp proxy.AppConns, appBl
|
||||
for i := appBlockHeight + 1; i <= finalBlock; i++ {
|
||||
h.logger.Info("Applying block", "height", i)
|
||||
block := h.store.LoadBlock(i)
|
||||
appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger)
|
||||
appHash, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, h.logger, new(types.ValidatorSet))
|
||||
// TODO: Temporary, see above comment.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -111,7 +111,7 @@ func (wal *baseWAL) OnStop() {
|
||||
}
|
||||
|
||||
// Write is called in newStep and for each receive on the
|
||||
// peerMsgQueue and the timoutTicker.
|
||||
// peerMsgQueue and the timeoutTicker.
|
||||
// NOTE: does not call fsync()
|
||||
func (wal *baseWAL) Write(msg WALMessage) {
|
||||
if wal == nil {
|
||||
@@ -144,13 +144,14 @@ type WALSearchOptions struct {
|
||||
IgnoreDataCorruptionErrors bool
|
||||
}
|
||||
|
||||
// SearchForEndHeight searches for the EndHeightMessage with the height and
|
||||
// returns an auto.GroupReader, whenever it was found or not and an error.
|
||||
// SearchForEndHeight searches for the EndHeightMessage with the given height
|
||||
// and returns an auto.GroupReader, whenever it was found or not and an error.
|
||||
// Group reader will be nil if found equals false.
|
||||
//
|
||||
// CONTRACT: caller must close group reader.
|
||||
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
|
||||
var msg *TimedWALMessage
|
||||
lastHeightFound := int64(-1)
|
||||
|
||||
// NOTE: starting from the last file in the group because we're usually
|
||||
// searching for the last height. See replay.go
|
||||
@@ -166,17 +167,25 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions)
|
||||
for {
|
||||
msg, err = dec.Decode()
|
||||
if err == io.EOF {
|
||||
// OPTIMISATION: no need to look for height in older files if we've seen h < height
|
||||
if lastHeightFound > 0 && lastHeightFound < height {
|
||||
gr.Close()
|
||||
return nil, false, nil
|
||||
}
|
||||
// check next file
|
||||
break
|
||||
}
|
||||
if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) {
|
||||
wal.Logger.Debug("Corrupted entry. Skipping...", "err", err)
|
||||
// do nothing
|
||||
continue
|
||||
} else if err != nil {
|
||||
gr.Close()
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if m, ok := msg.Msg.(EndHeightMessage); ok {
|
||||
lastHeightFound = m.Height
|
||||
if m.Height == height { // found
|
||||
wal.Logger.Debug("Found", "height", height, "index", index)
|
||||
return gr, true, nil
|
||||
@@ -271,23 +280,17 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
|
||||
|
||||
b = make([]byte, 4)
|
||||
_, err = dec.rd.Read(b)
|
||||
if err == io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read length: %v", err)
|
||||
}
|
||||
length := binary.BigEndian.Uint32(b)
|
||||
|
||||
if length > maxMsgSizeBytes {
|
||||
return nil, DataCorruptionError{fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes)}
|
||||
return nil, fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes)
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
_, err = dec.rd.Read(data)
|
||||
if err == io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read data: %v", err)
|
||||
}
|
||||
|
@@ -183,6 +183,7 @@ Try running these commands:
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x0000000000000000
|
||||
|
||||
> deliver_tx "abc"
|
||||
-> code: OK
|
||||
@@ -194,7 +195,7 @@ Try running these commands:
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x49DFD15CCDACDEAE9728CB01FBB5E8688CA58B91
|
||||
-> data.hex: 0x0200000000000000
|
||||
|
||||
> query "abc"
|
||||
-> code: OK
|
||||
@@ -208,7 +209,7 @@ Try running these commands:
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
-> data.hex: 0x70102DB32280373FBF3F9F89DA2A20CE2CD62B0B
|
||||
-> data.hex: 0x0400000000000000
|
||||
|
||||
> query "def"
|
||||
-> code: OK
|
||||
@@ -301,6 +302,7 @@ In another window, start the ``abci-cli console``:
|
||||
|
||||
> set_option serial on
|
||||
-> code: OK
|
||||
-> log: OK (SetOption doesn't return anything.)
|
||||
|
||||
> check_tx 0x00
|
||||
-> code: OK
|
||||
|
@@ -1,190 +1,4 @@
|
||||
RPC
|
||||
===
|
||||
|
||||
Coming soon: RPC docs powered by `slate <https://github.com/lord/slate>`__. Until then, read on.
|
||||
|
||||
Tendermint supports the following RPC protocols:
|
||||
|
||||
- URI over HTTP
|
||||
- JSONRPC over HTTP
|
||||
- JSONRPC over websockets
|
||||
|
||||
Tendermint RPC is build using `our own RPC
|
||||
library <https://github.com/tendermint/tendermint/tree/master/rpc/lib>`__.
|
||||
Documentation and tests for that library could be found at
|
||||
``tendermint/rpc/lib`` directory.
|
||||
|
||||
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:46657``.
|
||||
|
||||
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
|
||||
~~~~~~~~
|
||||
|
||||
Example request:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
curl -s 'http://localhost:46657/broadcast_tx_sync?tx="abc"' | jq .
|
||||
|
||||
Response:
|
||||
|
||||
.. code:: 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:46657/``).
|
||||
|
||||
Example request:
|
||||
|
||||
.. code:: 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. ``http://localhost:46657/websocket``. Asynchronous
|
||||
RPC functions like event ``subscribe`` and ``unsubscribe`` are only
|
||||
available via websockets.
|
||||
|
||||
Endpoints
|
||||
~~~~~~~~~
|
||||
|
||||
An HTTP Get request to the root RPC endpoint (e.g.
|
||||
``http://localhost:46657``) shows a list of available endpoints.
|
||||
|
||||
::
|
||||
|
||||
Available endpoints:
|
||||
http://localhost:46657/abci_info
|
||||
http://localhost:46657/dump_consensus_state
|
||||
http://localhost:46657/genesis
|
||||
http://localhost:46657/net_info
|
||||
http://localhost:46657/num_unconfirmed_txs
|
||||
http://localhost:46657/health
|
||||
http://localhost:46657/status
|
||||
http://localhost:46657/unconfirmed_txs
|
||||
http://localhost:46657/unsafe_flush_mempool
|
||||
http://localhost:46657/unsafe_stop_cpu_profiler
|
||||
http://localhost:46657/validators
|
||||
|
||||
Endpoints that require arguments:
|
||||
http://localhost:46657/abci_query?path=_&data=_&prove=_
|
||||
http://localhost:46657/block?height=_
|
||||
http://localhost:46657/blockchain?minHeight=_&maxHeight=_
|
||||
http://localhost:46657/broadcast_tx_async?tx=_
|
||||
http://localhost:46657/broadcast_tx_commit?tx=_
|
||||
http://localhost:46657/broadcast_tx_sync?tx=_
|
||||
http://localhost:46657/commit?height=_
|
||||
http://localhost:46657/dial_seeds?seeds=_
|
||||
http://localhost:46657/dial_peers?peers=_&persistent=_
|
||||
http://localhost:46657/subscribe?event=_
|
||||
http://localhost:46657/tx?hash=_&prove=_
|
||||
http://localhost:46657/unsafe_start_cpu_profiler?filename=_
|
||||
http://localhost:46657/unsafe_write_heap_profile?filename=_
|
||||
http://localhost:46657/unsubscribe?event=_
|
||||
|
||||
tx
|
||||
~~
|
||||
|
||||
Returns a transaction matching the given transaction hash.
|
||||
|
||||
**Parameters**
|
||||
|
||||
1. hash - the transaction hash
|
||||
2. prove - include a proof of the transaction inclusion in the block in
|
||||
the result (optional, default: false)
|
||||
|
||||
**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
|
||||
|
||||
**Example**
|
||||
|
||||
.. code:: bash
|
||||
|
||||
curl -s 'http://localhost:46657/broadcast_tx_commit?tx="abc"' | jq .
|
||||
# {
|
||||
# "error": "",
|
||||
# "result": {
|
||||
# "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF",
|
||||
# "log": "",
|
||||
# "data": "",
|
||||
# "code": 0
|
||||
# },
|
||||
# "id": "",
|
||||
# "jsonrpc": "2.0"
|
||||
# }
|
||||
|
||||
curl -s 'http://localhost:46657/tx?hash=0x2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF' | jq .
|
||||
# {
|
||||
# "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
|
||||
# },
|
||||
# "id": "",
|
||||
# "jsonrpc": "2.0"
|
||||
# }
|
||||
|
||||
More Examples
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
See the various bash tests using curl in ``test/``, and examples using
|
||||
the ``Go`` API in ``rpc/client/``.
|
||||
The RPC documentation is hosted `here <https://tendermint.github.io/slate>`__ and is generated by the CI from our `Slate repo <https://github.com/tendermint/slate>`__. To update the documentation, edit the relevant ``godoc`` comments in the `rpc/core directory <https://github.com/tendermint/tendermint/tree/develop/rpc/core>`__.
|
||||
|
@@ -22,7 +22,7 @@ func TestExample(t *testing.T) {
|
||||
ch := make(chan interface{}, 1)
|
||||
err := s.Subscribe(ctx, "example-client", query.MustParse("abci.account.name='John'"), ch)
|
||||
require.NoError(t, err)
|
||||
err = s.PublishWithTags(ctx, "Tombstone", pubsub.NewTagMap(map[string]interface{}{"abci.account.name": "John"}))
|
||||
err = s.PublishWithTags(ctx, "Tombstone", pubsub.NewTagMap(map[string]string{"abci.account.name": "John"}))
|
||||
require.NoError(t, err)
|
||||
assertReceive(t, "Tombstone", ch)
|
||||
}
|
||||
|
@@ -38,18 +38,6 @@ var (
|
||||
ErrAlreadySubscribed = errors.New("already subscribed")
|
||||
)
|
||||
|
||||
// TagMap is used to associate tags to a message.
|
||||
// They can be queried by subscribers to choose messages they will received.
|
||||
type TagMap interface {
|
||||
// Get returns the value for a key, or nil if no value is present.
|
||||
// The ok result indicates whether value was found in the tags.
|
||||
Get(key string) (value interface{}, ok bool)
|
||||
// Len returns the number of tags.
|
||||
Len() int
|
||||
}
|
||||
|
||||
type tagMap map[string]interface{}
|
||||
|
||||
type cmd struct {
|
||||
op operation
|
||||
query Query
|
||||
@@ -80,14 +68,28 @@ type Server struct {
|
||||
// Option sets a parameter for the server.
|
||||
type Option func(*Server)
|
||||
|
||||
// TagMap is used to associate tags to a message.
|
||||
// They can be queried by subscribers to choose messages they will received.
|
||||
type TagMap interface {
|
||||
// Get returns the value for a key, or nil if no value is present.
|
||||
// The ok result indicates whether value was found in the tags.
|
||||
Get(key string) (value string, ok bool)
|
||||
// Len returns the number of tags.
|
||||
Len() int
|
||||
}
|
||||
|
||||
type tagMap map[string]string
|
||||
|
||||
var _ TagMap = (*tagMap)(nil)
|
||||
|
||||
// NewTagMap constructs a new immutable tag set from a map.
|
||||
func NewTagMap(data map[string]interface{}) TagMap {
|
||||
func NewTagMap(data map[string]string) TagMap {
|
||||
return tagMap(data)
|
||||
}
|
||||
|
||||
// Get returns the value for a key, or nil if no value is present.
|
||||
// The ok result indicates whether value was found in the tags.
|
||||
func (ts tagMap) Get(key string) (value interface{}, ok bool) {
|
||||
func (ts tagMap) Get(key string) (value string, ok bool) {
|
||||
value, ok = ts[key]
|
||||
return
|
||||
}
|
||||
@@ -213,7 +215,7 @@ func (s *Server) UnsubscribeAll(ctx context.Context, clientID string) error {
|
||||
// Publish publishes the given message. An error will be returned to the caller
|
||||
// if the context is canceled.
|
||||
func (s *Server) Publish(ctx context.Context, msg interface{}) error {
|
||||
return s.PublishWithTags(ctx, msg, NewTagMap(make(map[string]interface{})))
|
||||
return s.PublishWithTags(ctx, msg, NewTagMap(make(map[string]string)))
|
||||
}
|
||||
|
||||
// PublishWithTags publishes the given message with the set of tags. The set is
|
||||
|
@@ -49,14 +49,14 @@ func TestDifferentClients(t *testing.T) {
|
||||
ch1 := make(chan interface{}, 1)
|
||||
err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type='NewBlock'"), ch1)
|
||||
require.NoError(t, err)
|
||||
err = s.PublishWithTags(ctx, "Iceman", pubsub.NewTagMap(map[string]interface{}{"tm.events.type": "NewBlock"}))
|
||||
err = s.PublishWithTags(ctx, "Iceman", pubsub.NewTagMap(map[string]string{"tm.events.type": "NewBlock"}))
|
||||
require.NoError(t, err)
|
||||
assertReceive(t, "Iceman", ch1)
|
||||
|
||||
ch2 := make(chan interface{}, 1)
|
||||
err = s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type='NewBlock' AND abci.account.name='Igor'"), ch2)
|
||||
require.NoError(t, err)
|
||||
err = s.PublishWithTags(ctx, "Ultimo", pubsub.NewTagMap(map[string]interface{}{"tm.events.type": "NewBlock", "abci.account.name": "Igor"}))
|
||||
err = s.PublishWithTags(ctx, "Ultimo", pubsub.NewTagMap(map[string]string{"tm.events.type": "NewBlock", "abci.account.name": "Igor"}))
|
||||
require.NoError(t, err)
|
||||
assertReceive(t, "Ultimo", ch1)
|
||||
assertReceive(t, "Ultimo", ch2)
|
||||
@@ -64,7 +64,7 @@ func TestDifferentClients(t *testing.T) {
|
||||
ch3 := make(chan interface{}, 1)
|
||||
err = s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type='NewRoundStep' AND abci.account.name='Igor' AND abci.invoice.number = 10"), ch3)
|
||||
require.NoError(t, err)
|
||||
err = s.PublishWithTags(ctx, "Valeria Richards", pubsub.NewTagMap(map[string]interface{}{"tm.events.type": "NewRoundStep"}))
|
||||
err = s.PublishWithTags(ctx, "Valeria Richards", pubsub.NewTagMap(map[string]string{"tm.events.type": "NewRoundStep"}))
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, len(ch3))
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func TestClientSubscribesTwice(t *testing.T) {
|
||||
ch1 := make(chan interface{}, 1)
|
||||
err := s.Subscribe(ctx, clientID, q, ch1)
|
||||
require.NoError(t, err)
|
||||
err = s.PublishWithTags(ctx, "Goblin Queen", pubsub.NewTagMap(map[string]interface{}{"tm.events.type": "NewBlock"}))
|
||||
err = s.PublishWithTags(ctx, "Goblin Queen", pubsub.NewTagMap(map[string]string{"tm.events.type": "NewBlock"}))
|
||||
require.NoError(t, err)
|
||||
assertReceive(t, "Goblin Queen", ch1)
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestClientSubscribesTwice(t *testing.T) {
|
||||
err = s.Subscribe(ctx, clientID, q, ch2)
|
||||
require.Error(t, err)
|
||||
|
||||
err = s.PublishWithTags(ctx, "Spider-Man", pubsub.NewTagMap(map[string]interface{}{"tm.events.type": "NewBlock"}))
|
||||
err = s.PublishWithTags(ctx, "Spider-Man", pubsub.NewTagMap(map[string]string{"tm.events.type": "NewBlock"}))
|
||||
require.NoError(t, err)
|
||||
assertReceive(t, "Spider-Man", ch1)
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func benchmarkNClients(n int, b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.PublishWithTags(ctx, "Gamora", pubsub.NewTagMap(map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": i}))
|
||||
s.PublishWithTags(ctx, "Gamora", pubsub.NewTagMap(map[string]string{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": string(i)}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ func benchmarkNClientsOneQuery(n int, b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.PublishWithTags(ctx, "Gamora", pubsub.NewTagMap(map[string]interface{}{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": 1}))
|
||||
s.PublishWithTags(ctx, "Gamora", pubsub.NewTagMap(map[string]string{"abci.Account.Owner": "Ivan", "abci.Invoices.Number": "1"}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func TestEmptyQueryMatchesAnything(t *testing.T) {
|
||||
q := query.Empty{}
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]interface{}{})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]interface{}{"Asher": "Roth"})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]interface{}{"Route": 66})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]interface{}{"Route": 66, "Billy": "Blue"})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]string{})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]string{"Asher": "Roth"})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]string{"Route": "66"})))
|
||||
assert.True(t, q.Matches(pubsub.NewTagMap(map[string]string{"Route": "66", "Billy": "Blue"})))
|
||||
}
|
||||
|
@@ -77,6 +77,13 @@ const (
|
||||
OpContains
|
||||
)
|
||||
|
||||
const (
|
||||
// DateLayout defines a layout for all dates (`DATE date`)
|
||||
DateLayout = "2006-01-02"
|
||||
// TimeLayout defines a layout for all times (`TIME time`)
|
||||
TimeLayout = time.RFC3339
|
||||
)
|
||||
|
||||
// Conditions returns a list of conditions.
|
||||
func (q *Query) Conditions() []Condition {
|
||||
conditions := make([]Condition, 0)
|
||||
@@ -112,7 +119,7 @@ func (q *Query) Conditions() []Condition {
|
||||
conditions = append(conditions, Condition{tag, op, valueWithoutSingleQuotes})
|
||||
case rulenumber:
|
||||
number := buffer[begin:end]
|
||||
if strings.Contains(number, ".") { // if it looks like a floating-point number
|
||||
if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
|
||||
value, err := strconv.ParseFloat(number, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number))
|
||||
@@ -126,7 +133,7 @@ func (q *Query) Conditions() []Condition {
|
||||
conditions = append(conditions, Condition{tag, op, value})
|
||||
}
|
||||
case ruletime:
|
||||
value, err := time.Parse(time.RFC3339, buffer[begin:end])
|
||||
value, err := time.Parse(TimeLayout, buffer[begin:end])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end]))
|
||||
}
|
||||
@@ -188,7 +195,7 @@ func (q *Query) Matches(tags pubsub.TagMap) bool {
|
||||
}
|
||||
case rulenumber:
|
||||
number := buffer[begin:end]
|
||||
if strings.Contains(number, ".") { // if it looks like a floating-point number
|
||||
if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
|
||||
value, err := strconv.ParseFloat(number, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number))
|
||||
@@ -206,7 +213,7 @@ func (q *Query) Matches(tags pubsub.TagMap) bool {
|
||||
}
|
||||
}
|
||||
case ruletime:
|
||||
value, err := time.Parse(time.RFC3339, buffer[begin:end])
|
||||
value, err := time.Parse(TimeLayout, buffer[begin:end])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end]))
|
||||
}
|
||||
@@ -242,9 +249,18 @@ func match(tag string, op Operator, operand reflect.Value, tags pubsub.TagMap) b
|
||||
switch operand.Kind() {
|
||||
case reflect.Struct: // time
|
||||
operandAsTime := operand.Interface().(time.Time)
|
||||
v, ok := value.(time.Time)
|
||||
if !ok { // if value from tags is not time.Time
|
||||
return false
|
||||
// try our best to convert value from tags to time.Time
|
||||
var (
|
||||
v time.Time
|
||||
err error
|
||||
)
|
||||
if strings.ContainsAny(value, "T") {
|
||||
v, err = time.Parse(TimeLayout, value)
|
||||
} else {
|
||||
v, err = time.Parse(DateLayout, value)
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to convert value %v from tag to time.Time: %v", value, err))
|
||||
}
|
||||
switch op {
|
||||
case OpLessEqual:
|
||||
@@ -262,23 +278,9 @@ func match(tag string, op Operator, operand reflect.Value, tags pubsub.TagMap) b
|
||||
operandFloat64 := operand.Interface().(float64)
|
||||
var v float64
|
||||
// try our best to convert value from tags to float64
|
||||
switch vt := value.(type) {
|
||||
case float64:
|
||||
v = vt
|
||||
case float32:
|
||||
v = float64(vt)
|
||||
case int:
|
||||
v = float64(vt)
|
||||
case int8:
|
||||
v = float64(vt)
|
||||
case int16:
|
||||
v = float64(vt)
|
||||
case int32:
|
||||
v = float64(vt)
|
||||
case int64:
|
||||
v = float64(vt)
|
||||
default: // fail for all other types
|
||||
panic(fmt.Sprintf("Incomparable types: %T (%v) vs float64 (%v)", value, value, operandFloat64))
|
||||
v, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to convert value %v from tag to float64: %v", value, err))
|
||||
}
|
||||
switch op {
|
||||
case OpLessEqual:
|
||||
@@ -295,24 +297,20 @@ func match(tag string, op Operator, operand reflect.Value, tags pubsub.TagMap) b
|
||||
case reflect.Int64:
|
||||
operandInt := operand.Interface().(int64)
|
||||
var v int64
|
||||
// try our best to convert value from tags to int64
|
||||
switch vt := value.(type) {
|
||||
case int64:
|
||||
v = vt
|
||||
case int8:
|
||||
v = int64(vt)
|
||||
case int16:
|
||||
v = int64(vt)
|
||||
case int32:
|
||||
v = int64(vt)
|
||||
case int:
|
||||
v = int64(vt)
|
||||
case float64:
|
||||
v = int64(vt)
|
||||
case float32:
|
||||
v = int64(vt)
|
||||
default: // fail for all other types
|
||||
panic(fmt.Sprintf("Incomparable types: %T (%v) vs int64 (%v)", value, value, operandInt))
|
||||
// if value looks like float, we try to parse it as float
|
||||
if strings.ContainsAny(value, ".") {
|
||||
v1, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to convert value %v from tag to float64: %v", value, err))
|
||||
}
|
||||
v = int64(v1)
|
||||
} else {
|
||||
var err error
|
||||
// try our best to convert value from tags to int64
|
||||
v, err = strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to convert value %v from tag to int64: %v", value, err))
|
||||
}
|
||||
}
|
||||
switch op {
|
||||
case OpLessEqual:
|
||||
@@ -327,15 +325,11 @@ func match(tag string, op Operator, operand reflect.Value, tags pubsub.TagMap) b
|
||||
return v == operandInt
|
||||
}
|
||||
case reflect.String:
|
||||
v, ok := value.(string)
|
||||
if !ok { // if value from tags is not string
|
||||
return false
|
||||
}
|
||||
switch op {
|
||||
case OpEqual:
|
||||
return v == operand.String()
|
||||
return value == operand.String()
|
||||
case OpContains:
|
||||
return strings.Contains(v, operand.String())
|
||||
return strings.Contains(value, operand.String())
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind of operand %v", operand.Kind()))
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,38 +13,37 @@ import (
|
||||
)
|
||||
|
||||
func TestMatches(t *testing.T) {
|
||||
const shortForm = "2006-Jan-02"
|
||||
txDate, err := time.Parse(shortForm, "2017-Jan-01")
|
||||
require.NoError(t, err)
|
||||
txTime, err := time.Parse(time.RFC3339, "2018-05-03T14:45:00Z")
|
||||
require.NoError(t, err)
|
||||
var (
|
||||
txDate = "2017-01-01"
|
||||
txTime = "2018-05-03T14:45:00Z"
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
s string
|
||||
tags map[string]interface{}
|
||||
tags map[string]string
|
||||
err bool
|
||||
matches bool
|
||||
}{
|
||||
{"tm.events.type='NewBlock'", map[string]interface{}{"tm.events.type": "NewBlock"}, false, true},
|
||||
{"tm.events.type='NewBlock'", map[string]string{"tm.events.type": "NewBlock"}, false, true},
|
||||
|
||||
{"tx.gas > 7", map[string]interface{}{"tx.gas": 8}, false, true},
|
||||
{"tx.gas > 7 AND tx.gas < 9", map[string]interface{}{"tx.gas": 8}, false, true},
|
||||
{"body.weight >= 3.5", map[string]interface{}{"body.weight": 3.5}, false, true},
|
||||
{"account.balance < 1000.0", map[string]interface{}{"account.balance": 900}, false, true},
|
||||
{"apples.kg <= 4", map[string]interface{}{"apples.kg": 4.0}, false, true},
|
||||
{"body.weight >= 4.5", map[string]interface{}{"body.weight": float32(4.5)}, false, true},
|
||||
{"oranges.kg < 4 AND watermellons.kg > 10", map[string]interface{}{"oranges.kg": 3, "watermellons.kg": 12}, false, true},
|
||||
{"peaches.kg < 4", map[string]interface{}{"peaches.kg": 5}, false, false},
|
||||
{"tx.gas > 7", map[string]string{"tx.gas": "8"}, false, true},
|
||||
{"tx.gas > 7 AND tx.gas < 9", map[string]string{"tx.gas": "8"}, false, true},
|
||||
{"body.weight >= 3.5", map[string]string{"body.weight": "3.5"}, false, true},
|
||||
{"account.balance < 1000.0", map[string]string{"account.balance": "900"}, false, true},
|
||||
{"apples.kg <= 4", map[string]string{"apples.kg": "4.0"}, false, true},
|
||||
{"body.weight >= 4.5", map[string]string{"body.weight": fmt.Sprintf("%v", float32(4.5))}, false, true},
|
||||
{"oranges.kg < 4 AND watermellons.kg > 10", map[string]string{"oranges.kg": "3", "watermellons.kg": "12"}, false, true},
|
||||
{"peaches.kg < 4", map[string]string{"peaches.kg": "5"}, false, false},
|
||||
|
||||
{"tx.date > DATE 2017-01-01", map[string]interface{}{"tx.date": time.Now()}, false, true},
|
||||
{"tx.date = DATE 2017-01-01", map[string]interface{}{"tx.date": txDate}, false, true},
|
||||
{"tx.date = DATE 2018-01-01", map[string]interface{}{"tx.date": txDate}, false, false},
|
||||
{"tx.date > DATE 2017-01-01", map[string]string{"tx.date": time.Now().Format(query.DateLayout)}, false, true},
|
||||
{"tx.date = DATE 2017-01-01", map[string]string{"tx.date": txDate}, false, true},
|
||||
{"tx.date = DATE 2018-01-01", map[string]string{"tx.date": txDate}, false, false},
|
||||
|
||||
{"tx.time >= TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": time.Now()}, false, true},
|
||||
{"tx.time = TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": txTime}, false, false},
|
||||
{"tx.time >= TIME 2013-05-03T14:45:00Z", map[string]string{"tx.time": time.Now().Format(query.TimeLayout)}, false, true},
|
||||
{"tx.time = TIME 2013-05-03T14:45:00Z", map[string]string{"tx.time": txTime}, false, false},
|
||||
|
||||
{"abci.owner.name CONTAINS 'Igor'", map[string]interface{}{"abci.owner.name": "Igor,Ivan"}, false, true},
|
||||
{"abci.owner.name CONTAINS 'Igor'", map[string]interface{}{"abci.owner.name": "Pavel,Ivan"}, false, false},
|
||||
{"abci.owner.name CONTAINS 'Igor'", map[string]string{"abci.owner.name": "Igor,Ivan"}, false, true},
|
||||
{"abci.owner.name CONTAINS 'Igor'", map[string]string{"abci.owner.name": "Pavel,Ivan"}, false, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@@ -281,6 +281,7 @@ func (r *PEXReactor) receiveRequest(src Peer) error {
|
||||
// RequestAddrs asks peer for more addresses if we do not already
|
||||
// have a request out for this peer.
|
||||
func (r *PEXReactor) RequestAddrs(p Peer) {
|
||||
r.Logger.Debug("Request addrs", "from", p)
|
||||
id := string(p.ID())
|
||||
if r.requestsSent.Has(id) {
|
||||
return
|
||||
|
@@ -59,59 +59,69 @@ func TestPEXReactorAddRemovePeer(t *testing.T) {
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
}
|
||||
|
||||
func TestPEXReactorRunning(t *testing.T) {
|
||||
N := 3
|
||||
switches := make([]*p2p.Switch, N)
|
||||
// --- FAIL: TestPEXReactorRunning (11.10s)
|
||||
// pex_reactor_test.go:411: expected all switches to be connected to at
|
||||
// least one peer (switches: 0 => {outbound: 1, inbound: 0}, 1 =>
|
||||
// {outbound: 0, inbound: 1}, 2 => {outbound: 0, inbound: 0}, )
|
||||
//
|
||||
// EXPLANATION: peers are getting rejected because in switch#addPeer we check
|
||||
// if any peer (who we already connected to) has the same IP. Even though local
|
||||
// peers have different IP addresses, they all have the same underlying remote
|
||||
// IP: 127.0.0.1.
|
||||
//
|
||||
// func TestPEXReactorRunning(t *testing.T) {
|
||||
// N := 3
|
||||
// switches := make([]*p2p.Switch, N)
|
||||
|
||||
// directory to store address books
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(t, err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
// // directory to store address books
|
||||
// dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
// require.Nil(t, err)
|
||||
// defer os.RemoveAll(dir) // nolint: errcheck
|
||||
|
||||
books := make([]*addrBook, N)
|
||||
logger := log.TestingLogger()
|
||||
// books := make([]*addrBook, N)
|
||||
// logger := log.TestingLogger()
|
||||
|
||||
// create switches
|
||||
for i := 0; i < N; i++ {
|
||||
switches[i] = p2p.MakeSwitch(config, i, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false)
|
||||
books[i].SetLogger(logger.With("pex", i))
|
||||
sw.SetAddrBook(books[i])
|
||||
// // create switches
|
||||
// for i := 0; i < N; i++ {
|
||||
// switches[i] = p2p.MakeSwitch(config, i, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
// books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false)
|
||||
// books[i].SetLogger(logger.With("pex", i))
|
||||
// sw.SetAddrBook(books[i])
|
||||
|
||||
sw.SetLogger(logger.With("pex", i))
|
||||
// sw.SetLogger(logger.With("pex", i))
|
||||
|
||||
r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
||||
r.SetLogger(logger.With("pex", i))
|
||||
r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
sw.AddReactor("pex", r)
|
||||
// r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
||||
// r.SetLogger(logger.With("pex", i))
|
||||
// r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
// sw.AddReactor("pex", r)
|
||||
|
||||
return sw
|
||||
})
|
||||
}
|
||||
// return sw
|
||||
// })
|
||||
// }
|
||||
|
||||
addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
||||
addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
||||
books[switchIndex].AddAddress(addr, addr)
|
||||
}
|
||||
// addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
||||
// addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
||||
// books[switchIndex].AddAddress(addr, addr)
|
||||
// }
|
||||
|
||||
addOtherNodeAddrToAddrBook(0, 1)
|
||||
addOtherNodeAddrToAddrBook(1, 0)
|
||||
addOtherNodeAddrToAddrBook(2, 1)
|
||||
// addOtherNodeAddrToAddrBook(0, 1)
|
||||
// addOtherNodeAddrToAddrBook(1, 0)
|
||||
// addOtherNodeAddrToAddrBook(2, 1)
|
||||
|
||||
for i, sw := range switches {
|
||||
sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
||||
// for i, sw := range switches {
|
||||
// sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
||||
|
||||
err := sw.Start() // start switch and reactors
|
||||
require.Nil(t, err)
|
||||
}
|
||||
// err := sw.Start() // start switch and reactors
|
||||
// require.Nil(t, err)
|
||||
// }
|
||||
|
||||
assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1)
|
||||
// assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1)
|
||||
|
||||
// stop them
|
||||
for _, s := range switches {
|
||||
s.Stop()
|
||||
}
|
||||
}
|
||||
// // stop them
|
||||
// for _, s := range switches {
|
||||
// s.Stop()
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestPEXReactorReceive(t *testing.T) {
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
|
@@ -568,7 +568,7 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
||||
// and add to our addresses to avoid dialing again
|
||||
sw.addrBook.RemoveAddress(addr)
|
||||
sw.addrBook.AddOurAddress(addr)
|
||||
return ErrSwitchConnectToSelf{}
|
||||
return ErrSwitchConnectToSelf{addr}
|
||||
}
|
||||
|
||||
// Avoid duplicate
|
||||
|
@@ -193,7 +193,7 @@ func TestSwitchFiltersOutItself(t *testing.T) {
|
||||
// addr should be rejected in addPeer based on the same ID
|
||||
err := s1.DialPeerWithAddress(rp.Addr(), false)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualValues(t, ErrSwitchConnectToSelf{}, err)
|
||||
assert.Equal(t, ErrSwitchConnectToSelf{rp.Addr()}.Error(), err.Error())
|
||||
}
|
||||
|
||||
assert.True(t, s1.addrBook.OurAddress(rp.Addr()))
|
||||
|
@@ -3,17 +3,16 @@
|
||||
## Generate markdown for [Slate](https://github.com/tendermint/slate)
|
||||
|
||||
We are using [Slate](https://github.com/tendermint/slate) to power our RPC
|
||||
documentation. If you are changing a comment, make sure to copy the resulting
|
||||
changes to the slate repo and make a PR
|
||||
[there](https://github.com/tendermint/slate) as well. For generating markdown
|
||||
use:
|
||||
documentation. For generating markdown use:
|
||||
|
||||
```shell
|
||||
go get github.com/melekes/godoc2md
|
||||
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.
|
||||
|
@@ -7,7 +7,7 @@ Tendermint supports the following RPC protocols:
|
||||
* JSONRPC over HTTP
|
||||
* JSONRPC over websockets
|
||||
|
||||
Tendermint RPC is built using [our own RPC library](https://github.com/tendermint/tendermint/tree/master/rpc/lib). Documentation and tests for that library could be found at `tendermint/rpc/lib` directory.
|
||||
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
|
||||
|
||||
|
77
scripts/slate.sh
Normal file
77
scripts/slate.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$CIRCLE_BRANCH" == "" ]; then
|
||||
echo "this script is meant to be run on CircleCI, exiting"
|
||||
echo 1
|
||||
fi
|
||||
|
||||
# check for changes in the `rpc/core` directory
|
||||
did_rpc_change=$(git diff --name-status $CIRCLE_BRANCH origin/master | grep rpc/core)
|
||||
|
||||
if [ "$did_rpc_change" == "" ]; then
|
||||
echo "no changes detected in rpc/core, exiting"
|
||||
exit 0
|
||||
else
|
||||
echo "changes detected in rpc/core, continuing"
|
||||
fi
|
||||
|
||||
# only run this script on changes to rpc/core committed to develop
|
||||
if [ "$CIRCLE_BRANCH" != "master" ]; then
|
||||
echo "the branch being built isn't master, exiting"
|
||||
exit 0
|
||||
else
|
||||
echo "on master, building the RPC docs"
|
||||
fi
|
||||
|
||||
# godoc2md used to convert the go documentation from
|
||||
# `rpc/core` into a markdown file consumed by Slate
|
||||
go get github.com/davecheney/godoc2md
|
||||
|
||||
# slate works via forks, and we'll be committing to
|
||||
# master branch, which will trigger our fork to run
|
||||
# the `./deploy.sh` and publish via the `gh-pages` branch
|
||||
slate_repo=github.com/tendermint/slate
|
||||
slate_path="$GOPATH"/src/"$slate_repo"
|
||||
|
||||
if [ ! -d "$slate_path" ]; then
|
||||
git clone https://"$slate_repo".git $slate_path
|
||||
fi
|
||||
|
||||
# the main file we need to update if rpc/core changed
|
||||
destination="$slate_path"/source/index.html.md
|
||||
|
||||
# we remove it then re-create it with the latest changes
|
||||
rm $destination
|
||||
|
||||
header="---
|
||||
title: RPC Reference
|
||||
|
||||
language_tabs:
|
||||
- shell
|
||||
- go
|
||||
|
||||
toc_footers:
|
||||
- <a href='https://tendermint.com/'>Tendermint</a>
|
||||
- <a href='https://github.com/lord/slate'>Documentation Powered by Slate</a>
|
||||
|
||||
search: true
|
||||
---"
|
||||
|
||||
# write header to the main slate file
|
||||
echo "$header" > "$destination"
|
||||
|
||||
# generate a markdown from the godoc comments, using a template
|
||||
rpc_docs=$(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$')
|
||||
|
||||
# append core RPC docs
|
||||
echo "$rpc_docs" >> "$destination"
|
||||
|
||||
# commit the changes
|
||||
cd $slate_path
|
||||
|
||||
git config --global user.email "github@tendermint.com"
|
||||
git config --global user.name "tenderbot"
|
||||
|
||||
git commit -a -m "Update tendermint RPC docs via CircleCI"
|
||||
git push -q https://${GITHUB_ACCESS_TOKEN}@github.com/tendermint/slate.git master
|
@@ -74,7 +74,7 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block
|
||||
return s, ErrInvalidBlock(err)
|
||||
}
|
||||
|
||||
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block)
|
||||
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, s.Validators)
|
||||
if err != nil {
|
||||
return s, ErrProxyAppConn(err)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
|
||||
|
||||
// Executes block's transactions on proxyAppConn.
|
||||
// Returns a list of transaction results and updates to the validator set
|
||||
func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, block *types.Block) (*ABCIResponses, error) {
|
||||
func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, block *types.Block, vs *types.ValidatorSet) (*ABCIResponses, error) {
|
||||
var validTxs, invalidTxs = 0, 0
|
||||
|
||||
txIndex := 0
|
||||
@@ -187,10 +187,11 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
||||
proxyAppConn.SetResponseCallback(proxyCb)
|
||||
|
||||
// determine which validators did not sign last block
|
||||
absentVals := make([]int32, 0)
|
||||
absentVals := make([][]byte, 0)
|
||||
for valI, vote := range block.LastCommit.Precommits {
|
||||
if vote == nil {
|
||||
absentVals = append(absentVals, int32(valI))
|
||||
_, val := vs.GetByIndex(valI)
|
||||
absentVals = append(absentVals, val.PubKey.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +199,7 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
||||
byzantineVals := make([]abci.Evidence, len(block.Evidence.Evidence))
|
||||
for i, ev := range block.Evidence.Evidence {
|
||||
byzantineVals[i] = abci.Evidence{
|
||||
Type: []byte(ev.String()),
|
||||
PubKey: ev.Address(), // XXX
|
||||
Height: ev.Height(),
|
||||
}
|
||||
@@ -359,8 +361,8 @@ func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *ty
|
||||
|
||||
// ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
|
||||
// It returns the application root hash (result of abci.Commit).
|
||||
func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block, logger log.Logger) ([]byte, error) {
|
||||
_, err := execBlockOnProxyApp(logger, appConnConsensus, block)
|
||||
func ExecCommitBlock(appConnConsensus proxy.AppConnConsensus, block *types.Block, logger log.Logger, vs *types.ValidatorSet) ([]byte, error) {
|
||||
_, err := execBlockOnProxyApp(logger, appConnConsensus, block, vs)
|
||||
if err != nil {
|
||||
logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
|
||||
return nil, err
|
||||
|
@@ -18,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
privKey = crypto.GenPrivKeyEd25519FromSecret([]byte("execution_test"))
|
||||
privKey = crypto.GenPrivKeyEd25519FromSecret([]byte("execution_test_1"))
|
||||
privKey2 = crypto.GenPrivKeyEd25519FromSecret([]byte("execution_test_2"))
|
||||
chainID = "execution_chain"
|
||||
testPartSize = 65536
|
||||
nTxsPerBlock = 10
|
||||
@@ -64,18 +65,18 @@ func TestBeginBlockAbsentValidators(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
lastCommitPrecommits []*types.Vote
|
||||
expectedAbsentValidators []int32
|
||||
expectedAbsentValidators [][]byte
|
||||
}{
|
||||
{"none absent", []*types.Vote{{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}, {ValidatorIndex: 1, Timestamp: now}}, []int32{}},
|
||||
{"one absent", []*types.Vote{{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}, nil}, []int32{1}},
|
||||
{"multiple absent", []*types.Vote{nil, nil}, []int32{0, 1}},
|
||||
{"none absent", []*types.Vote{{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}, {ValidatorIndex: 1, Timestamp: now}}, [][]byte{}},
|
||||
{"one absent", []*types.Vote{{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit}, nil}, [][]byte{privKey2.PubKey().Bytes()}},
|
||||
{"multiple absent", []*types.Vote{nil, nil}, [][]byte{privKey.PubKey().Bytes(), privKey2.PubKey().Bytes()}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits}
|
||||
|
||||
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit)
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger())
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators)
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
// -> app must receive an index of the absent validator
|
||||
@@ -109,10 +110,10 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
expectedByzantineValidators []abci.Evidence
|
||||
}{
|
||||
{"none byzantine", []types.Evidence{}, []abci.Evidence{}},
|
||||
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{{ev1.Address(), ev1.Height()}}},
|
||||
{"one byzantine", []types.Evidence{ev1}, []abci.Evidence{{[]byte(ev1.String()), ev1.Address(), ev1.Height(), int64(0)}}},
|
||||
{"multiple byzantine", []types.Evidence{ev1, ev2}, []abci.Evidence{
|
||||
{ev1.Address(), ev1.Height()},
|
||||
{ev2.Address(), ev2.Height()}}},
|
||||
{[]byte(ev1.String()), ev1.Address(), ev1.Height(), int64(0)},
|
||||
{[]byte(ev2.String()), ev2.Address(), ev2.Height(), int64(0)}}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -120,7 +121,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
|
||||
block, _ := state.MakeBlock(10, makeTxs(2), lastCommit)
|
||||
block.Evidence.Evidence = tc.evidence
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger())
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), state.Validators)
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
// -> app must receive an index of the byzantine validator
|
||||
@@ -142,7 +143,8 @@ func state() State {
|
||||
s, _ := MakeGenesisState(&types.GenesisDoc{
|
||||
ChainID: chainID,
|
||||
Validators: []types.GenesisValidator{
|
||||
{privKey.PubKey(), 10000, "test"},
|
||||
{privKey.PubKey(), 10000, "test1"},
|
||||
{privKey2.PubKey(), 10000, "test2"},
|
||||
},
|
||||
AppHash: nil,
|
||||
})
|
||||
@@ -161,7 +163,7 @@ var _ abci.Application = (*testApp)(nil)
|
||||
type testApp struct {
|
||||
abci.BaseApplication
|
||||
|
||||
AbsentValidators []int32
|
||||
AbsentValidators [][]byte
|
||||
ByzantineValidators []abci.Evidence
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/go-crypto"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
{Code: 383},
|
||||
{Data: []byte("Gotcha!"),
|
||||
Tags: []cmn.KVPair{
|
||||
cmn.KVPair{[]byte("a"), []byte{1}},
|
||||
cmn.KVPair{[]byte("a"), []byte("1")},
|
||||
cmn.KVPair{[]byte("build"), []byte("stuff")},
|
||||
}},
|
||||
},
|
||||
|
@@ -67,7 +67,7 @@ func (b *EventBus) UnsubscribeAll(ctx context.Context, subscriber string) error
|
||||
func (b *EventBus) Publish(eventType string, eventData TMEventData) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
b.pubsub.PublishWithTags(ctx, eventData, tmpubsub.NewTagMap(map[string]interface{}{EventTypeKey: eventType}))
|
||||
b.pubsub.PublishWithTags(ctx, eventData, tmpubsub.NewTagMap(map[string]string{EventTypeKey: eventType}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func (b *EventBus) PublishEventTx(event EventDataTx) error {
|
||||
// no explicit deadline for publishing events
|
||||
ctx := context.Background()
|
||||
|
||||
tags := make(map[string]interface{})
|
||||
tags := make(map[string]string)
|
||||
|
||||
// validate and fill tags from tx result
|
||||
for _, tag := range event.Result.Tags {
|
||||
@@ -112,7 +112,7 @@ func (b *EventBus) PublishEventTx(event EventDataTx) error {
|
||||
tags[TxHashKey] = fmt.Sprintf("%X", event.Tx.Hash())
|
||||
|
||||
logIfTagExists(TxHeightKey, tags, b.Logger)
|
||||
tags[TxHeightKey] = event.Height
|
||||
tags[TxHeightKey] = fmt.Sprintf("%d", event.Height)
|
||||
|
||||
b.pubsub.PublishWithTags(ctx, event, tmpubsub.NewTagMap(tags))
|
||||
return nil
|
||||
@@ -160,7 +160,7 @@ func (b *EventBus) PublishEventLock(event EventDataRoundState) error {
|
||||
return b.Publish(EventLock, event)
|
||||
}
|
||||
|
||||
func logIfTagExists(tag string, tags map[string]interface{}, logger log.Logger) {
|
||||
func logIfTagExists(tag string, tags map[string]string, logger log.Logger) {
|
||||
if value, ok := tags[tag]; ok {
|
||||
logger.Error("Found predefined tag (value will be overwritten)", "tag", tag, "value", value)
|
||||
}
|
||||
|
@@ -23,12 +23,12 @@ func TestEventBusPublishEventTx(t *testing.T) {
|
||||
defer eventBus.Stop()
|
||||
|
||||
tx := Tx("foo")
|
||||
result := abci.ResponseDeliverTx{Data: []byte("bar"), Tags: []cmn.KVPair{}, Fee: cmn.KI64Pair{Key: []uint8{}, Value: 0}}
|
||||
result := abci.ResponseDeliverTx{Data: []byte("bar"), Tags: []cmn.KVPair{{[]byte("baz"), []byte("1")}}, Fee: cmn.KI64Pair{Key: []uint8{}, Value: 0}}
|
||||
|
||||
txEventsCh := make(chan interface{})
|
||||
|
||||
// PublishEventTx adds all these 3 tags, so the query below should work
|
||||
query := fmt.Sprintf("tm.event='Tx' AND tx.height=1 AND tx.hash='%X'", tx.Hash())
|
||||
query := fmt.Sprintf("tm.event='Tx' AND tx.height=1 AND tx.hash='%X' AND baz=1", tx.Hash())
|
||||
err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@@ -9,6 +9,11 @@ import (
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
|
||||
const (
|
||||
// Evidence type for duplicate vote
|
||||
DUPLICATE_VOTE = "DUPLICATE_VOTE"
|
||||
)
|
||||
|
||||
// ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid.
|
||||
type ErrEvidenceInvalid struct {
|
||||
Evidence Evidence
|
||||
@@ -35,7 +40,7 @@ type Evidence interface {
|
||||
Verify(chainID string) error // verify the evidence
|
||||
Equal(Evidence) bool // check equality of evidence
|
||||
|
||||
String() string
|
||||
String() string // used as type in abci.Evidence
|
||||
}
|
||||
|
||||
func RegisterEvidences(cdc *amino.Codec) {
|
||||
@@ -54,8 +59,7 @@ type DuplicateVoteEvidence struct {
|
||||
|
||||
// String returns a string representation of the evidence.
|
||||
func (dve *DuplicateVoteEvidence) String() string {
|
||||
return fmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)
|
||||
|
||||
return DUPLICATE_VOTE
|
||||
}
|
||||
|
||||
// Height returns the height this evidence refers to.
|
||||
|
Reference in New Issue
Block a user