From 60827f75623b92eff132dc0eff5b49d2025c591e Mon Sep 17 00:00:00 2001 From: Hans Schoenburg Date: Wed, 19 Jun 2019 20:35:53 +0200 Subject: [PATCH 01/27] docs: fix some language issues and deprecated link (#3733) --- docs/.vuepress/config.js | 2 +- docs/app-dev/app-development.md | 6 +++--- docs/spec/abci/abci.md | 2 +- docs/spec/blockchain/encoding.md | 2 +- docs/spec/blockchain/state.md | 2 +- docs/tendermint-core/using-tendermint.md | 4 +++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 0b54d201..9adfc595 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -44,7 +44,7 @@ module.exports = { "/app-dev/app-development", "/app-dev/subscribing-to-events-via-websocket", "/app-dev/indexing-transactions", - "/app-dev/abci-spec", + "/spec/abci/abci", "/app-dev/ecosystem" ] }, diff --git a/docs/app-dev/app-development.md b/docs/app-dev/app-development.md index d157ce37..1b4e2680 100644 --- a/docs/app-dev/app-development.md +++ b/docs/app-dev/app-development.md @@ -133,8 +133,8 @@ the mempool. If Tendermint is just started or the clients sent more than 100k transactions, old transactions may be sent to the application. So it is important CheckTx implements some logic to handle them. -There are cases where a transaction will (or may) become valid in some -future state, in which case you probably want to disable Tendermint's +If there are cases in your application where a transaction may become invalid in some +future state, you probably want to disable Tendermint's cache. You can do that by setting `[mempool] cache_size = 0` in the config. @@ -205,7 +205,7 @@ Once all processing of the block is complete, Tendermint sends the Commit request and blocks waiting for a response. While the mempool may run concurrently with block processing (the BeginBlock, DeliverTxs, and EndBlock), it is locked for the Commit request so that its state can be -safely reset during Commit. This means the app _MUST NOT_ do any +safely updated during Commit. This means the app _MUST NOT_ do any blocking communication with the mempool (ie. broadcast_tx) during Commit, or there will be deadlock. Note also that all remaining transactions in the mempool are replayed on the mempool connection diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index 6dc00b5b..b7b2e09f 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -141,7 +141,7 @@ on them. All other fields in the `Response*` must be strictly deterministic. ## Block Execution The first time a new blockchain is started, Tendermint calls -`InitChain`. From then on, the follow sequence of methods is executed for each +`InitChain`. From then on, the following sequence of methods is executed for each block: `BeginBlock, [DeliverTx], EndBlock, Commit` diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index bde580a1..14d0e786 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -218,7 +218,7 @@ func MerkleRoot(items [][]byte) []byte{ case 0: return nil case 1: - return leafHash(leafs[0]) + return leafHash(items[0]) default: k := getSplitPoint(len(items)) left := MerkleRoot(items[:k]) diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index 3ab65e12..15fc3776 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -59,7 +59,7 @@ type Validator struct { When hashing the Validator struct, the address is not included, because it is redundant with the pubkey. -The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always by sorted by validator address, +The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always be sorted by validator address, so that there is a canonical order for computing the MerkleRoot. We also define a `TotalVotingPower` function, to return the total voting power: diff --git a/docs/tendermint-core/using-tendermint.md b/docs/tendermint-core/using-tendermint.md index 2ca8c9e9..05d481b2 100644 --- a/docs/tendermint-core/using-tendermint.md +++ b/docs/tendermint-core/using-tendermint.md @@ -202,8 +202,10 @@ Note that raw hex cannot be used in `POST` transactions. ## Reset -**WARNING: UNSAFE** Only do this in development and only if you can +::: warning +**UNSAFE** Only do this in development and only if you can afford to lose all blockchain data! +::: To reset a blockchain, stop the node and run: From 1b5110e91ffa81eb4387e88d57473ebaaed57ec0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 21 Jun 2019 09:30:32 +0400 Subject: [PATCH 02/27] node: fix a bug where `nil` is recorded as node's address (#3740) * node: fix a bug where `nil` is recorded as node's address Solution AddOurAddress when we know it sw.NetAddress is nil in createAddrBookAndSetOnSwitch it's set by n.transport.Listen function, which is called during start Fixes #3716 * use addr instead of n.sw.NetAddress * add both ExternalAddress and ListenAddress as our addresses --- CHANGELOG_PENDING.md | 1 + node/node.go | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b1b09e20..63cd05ae 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -37,3 +37,4 @@ ### BUG FIXES: - [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) - [libs/db] \#3718 Fixed the BoltDB backend's Get and Iterator implementation (@Yawning) +- [node] \#3716 Fix a bug where `nil` is recorded as node's address diff --git a/node/node.go b/node/node.go index 6f2cad1e..85fef5ee 100644 --- a/node/node.go +++ b/node/node.go @@ -441,17 +441,30 @@ func createSwitch(config *cfg.Config, } func createAddrBookAndSetOnSwitch(config *cfg.Config, sw *p2p.Switch, - p2pLogger log.Logger) pex.AddrBook { + p2pLogger log.Logger, nodeKey *p2p.NodeKey) (pex.AddrBook, error) { addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile())) // Add ourselves to addrbook to prevent dialing ourselves - addrBook.AddOurAddress(sw.NetAddress()) + if config.P2P.ExternalAddress != "" { + addr, err := p2p.NewNetAddressString(p2p.IDAddressString(nodeKey.ID(), config.P2P.ExternalAddress)) + if err != nil { + return nil, errors.Wrap(err, "p2p.external_address is incorrect") + } + addrBook.AddOurAddress(addr) + } + if config.P2P.ListenAddress != "" { + addr, err := p2p.NewNetAddressString(p2p.IDAddressString(nodeKey.ID(), config.P2P.ListenAddress)) + if err != nil { + return nil, errors.Wrap(err, "p2p.laddr is incorrect") + } + addrBook.AddOurAddress(addr) + } sw.SetAddrBook(addrBook) - return addrBook + return addrBook, nil } func createPEXReactorAndAddToSwitch(addrBook pex.AddrBook, config *cfg.Config, @@ -594,7 +607,10 @@ func NewNode(config *cfg.Config, return nil, errors.Wrap(err, "could not add peers from persistent_peers field") } - addrBook := createAddrBookAndSetOnSwitch(config, sw, p2pLogger) + addrBook, err := createAddrBookAndSetOnSwitch(config, sw, p2pLogger, nodeKey) + if err != nil { + return nil, errors.Wrap(err, "could not create addrbook") + } // Optionally, start the pex reactor // From 9d5ba576eeb11a979bd7aab166edaa22e256fc62 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 21 Jun 2019 01:56:27 -0400 Subject: [PATCH 03/27] abci: Refactor ABCI CheckTx and DeliverTx signatures (#3735) * Refactor signature of Application.CheckTx * Refactor signature of Application.DeliverTx * Refactor example variable names for clarity and consistency * Rename method variables for consistency * Rename method variables for consistency * add a changelog entry * update docs --- CHANGELOG_PENDING.md | 1 + abci/client/local_client.go | 8 +- abci/example/counter/counter.go | 16 +-- abci/example/kvstore/kvstore.go | 8 +- abci/example/kvstore/kvstore_test.go | 7 +- abci/example/kvstore/persistent_kvstore.go | 12 +- abci/server/socket_server.go | 4 +- abci/types/application.go | 12 +- blockchain/reactor_test.go | 4 +- consensus/mempool_test.go | 10 +- consensus/replay.go | 2 +- docs/app-dev/app-development.md | 139 +++++++++++---------- docs/app-dev/indexing-transactions.md | 2 +- mempool/clist_mempool_test.go | 2 +- rpc/client/mock/abci.go | 12 +- state/execution_test.go | 4 +- 16 files changed, 127 insertions(+), 116 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 63cd05ae..ed835aeb 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -23,6 +23,7 @@ If you have `db_backend` set to `leveldb` in your config file, please change it to `goleveldb` or `cleveldb`. - [p2p] \#3521 Remove NewNetAddressStringWithOptionalID + - [abci] \#3193 Use RequestDeliverTx and RequestCheckTx in the ABCI interface * Blockchain Protocol diff --git a/abci/client/local_client.go b/abci/client/local_client.go index d0e50c33..4a3e6a5e 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -85,7 +85,7 @@ func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.DeliverTx(tx) + res := app.Application.DeliverTx(types.RequestDeliverTx{Tx: tx}) return app.callback( types.ToRequestDeliverTx(tx), types.ToResponseDeliverTx(res), @@ -96,7 +96,7 @@ func (app *localClient) CheckTxAsync(tx []byte) *ReqRes { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.CheckTx(tx) + res := app.Application.CheckTx(types.RequestCheckTx{Tx: tx}) return app.callback( types.ToRequestCheckTx(tx), types.ToResponseCheckTx(res), @@ -188,7 +188,7 @@ func (app *localClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, erro app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.DeliverTx(tx) + res := app.Application.DeliverTx(types.RequestDeliverTx{Tx: tx}) return &res, nil } @@ -196,7 +196,7 @@ func (app *localClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.CheckTx(tx) + res := app.Application.CheckTx(types.RequestCheckTx{Tx: tx}) return &res, nil } diff --git a/abci/example/counter/counter.go b/abci/example/counter/counter.go index a77e7821..2cea1e55 100644 --- a/abci/example/counter/counter.go +++ b/abci/example/counter/counter.go @@ -42,15 +42,15 @@ func (app *CounterApplication) SetOption(req types.RequestSetOption) types.Respo return types.ResponseSetOption{} } -func (app *CounterApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { +func (app *CounterApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { if app.serial { - if len(tx) > 8 { + if len(req.Tx) > 8 { return types.ResponseDeliverTx{ Code: code.CodeTypeEncodingError, - Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))} + Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))} } tx8 := make([]byte, 8) - copy(tx8[len(tx8)-len(tx):], tx) + copy(tx8[len(tx8)-len(req.Tx):], req.Tx) txValue := binary.BigEndian.Uint64(tx8) if txValue != uint64(app.txCount) { return types.ResponseDeliverTx{ @@ -62,15 +62,15 @@ func (app *CounterApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { return types.ResponseDeliverTx{Code: code.CodeTypeOK} } -func (app *CounterApplication) CheckTx(tx []byte) types.ResponseCheckTx { +func (app *CounterApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { if app.serial { - if len(tx) > 8 { + if len(req.Tx) > 8 { return types.ResponseCheckTx{ Code: code.CodeTypeEncodingError, - Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))} + Log: fmt.Sprintf("Max tx size is 8 bytes, got %d", len(req.Tx))} } tx8 := make([]byte, 8) - copy(tx8[len(tx8)-len(tx):], tx) + copy(tx8[len(tx8)-len(req.Tx):], req.Tx) txValue := binary.BigEndian.Uint64(tx8) if txValue < uint64(app.txCount) { return types.ResponseCheckTx{ diff --git a/abci/example/kvstore/kvstore.go b/abci/example/kvstore/kvstore.go index 0c28813f..82d404c7 100644 --- a/abci/example/kvstore/kvstore.go +++ b/abci/example/kvstore/kvstore.go @@ -76,13 +76,13 @@ func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.Respon } // tx is either "key=value" or just arbitrary bytes -func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { +func (app *KVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { var key, value []byte - parts := bytes.Split(tx, []byte("=")) + parts := bytes.Split(req.Tx, []byte("=")) if len(parts) == 2 { key, value = parts[0], parts[1] } else { - key, value = tx, tx + key, value = req.Tx, req.Tx } app.state.db.Set(prefixKey(key), value) @@ -101,7 +101,7 @@ func (app *KVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events} } -func (app *KVStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx { +func (app *KVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} } diff --git a/abci/example/kvstore/kvstore_test.go b/abci/example/kvstore/kvstore_test.go index a18fb8d3..074baa49 100644 --- a/abci/example/kvstore/kvstore_test.go +++ b/abci/example/kvstore/kvstore_test.go @@ -19,10 +19,11 @@ import ( ) func testKVStore(t *testing.T, app types.Application, tx []byte, key, value string) { - ar := app.DeliverTx(tx) + req := types.RequestDeliverTx{Tx: tx} + ar := app.DeliverTx(req) require.False(t, ar.IsErr(), ar) // repeating tx doesn't raise error - ar = app.DeliverTx(tx) + ar = app.DeliverTx(req) require.False(t, ar.IsErr(), ar) // make sure query is fine @@ -179,7 +180,7 @@ func makeApplyBlock(t *testing.T, kvstore types.Application, heightInt int, diff kvstore.BeginBlock(types.RequestBeginBlock{Hash: hash, Header: header}) for _, tx := range txs { - if r := kvstore.DeliverTx(tx); r.IsErr() { + if r := kvstore.DeliverTx(types.RequestDeliverTx{Tx: tx}); r.IsErr() { t.Fatal(r) } } diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index f969eebf..b7484a4a 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -61,21 +61,21 @@ func (app *PersistentKVStoreApplication) SetOption(req types.RequestSetOption) t } // tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes -func (app *PersistentKVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { +func (app *PersistentKVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { // if it starts with "val:", update the validator set // format is "val:pubkey/power" - if isValidatorTx(tx) { + if isValidatorTx(req.Tx) { // update validators in the merkle tree // and in app.ValUpdates - return app.execValidatorTx(tx) + return app.execValidatorTx(req.Tx) } // otherwise, update the key-value store - return app.app.DeliverTx(tx) + return app.app.DeliverTx(req) } -func (app *PersistentKVStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx { - return app.app.CheckTx(tx) +func (app *PersistentKVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { + return app.app.CheckTx(req) } // Commit will panic if InitChain was not called diff --git a/abci/server/socket_server.go b/abci/server/socket_server.go index 4b92f04c..96cb844b 100644 --- a/abci/server/socket_server.go +++ b/abci/server/socket_server.go @@ -178,10 +178,10 @@ func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types res := s.app.SetOption(*r.SetOption) responses <- types.ToResponseSetOption(res) case *types.Request_DeliverTx: - res := s.app.DeliverTx(r.DeliverTx.Tx) + res := s.app.DeliverTx(*r.DeliverTx) responses <- types.ToResponseDeliverTx(res) case *types.Request_CheckTx: - res := s.app.CheckTx(r.CheckTx.Tx) + res := s.app.CheckTx(*r.CheckTx) responses <- types.ToResponseCheckTx(res) case *types.Request_Commit: res := s.app.Commit() diff --git a/abci/types/application.go b/abci/types/application.go index 88f8d001..90518851 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -15,12 +15,12 @@ type Application interface { Query(RequestQuery) ResponseQuery // Query for state // Mempool Connection - CheckTx(tx []byte) ResponseCheckTx // Validate a tx for the mempool + CheckTx(RequestCheckTx) ResponseCheckTx // Validate a tx for the mempool // Consensus Connection InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain with validators and other info from TendermintCore BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block - DeliverTx(tx []byte) ResponseDeliverTx // Deliver a tx for full processing + DeliverTx(RequestDeliverTx) ResponseDeliverTx // Deliver a tx for full processing EndBlock(RequestEndBlock) ResponseEndBlock // Signals the end of a block, returns changes to the validator set Commit() ResponseCommit // Commit the state and return the application Merkle root hash } @@ -45,11 +45,11 @@ func (BaseApplication) SetOption(req RequestSetOption) ResponseSetOption { return ResponseSetOption{} } -func (BaseApplication) DeliverTx(tx []byte) ResponseDeliverTx { +func (BaseApplication) DeliverTx(req RequestDeliverTx) ResponseDeliverTx { return ResponseDeliverTx{Code: CodeTypeOK} } -func (BaseApplication) CheckTx(tx []byte) ResponseCheckTx { +func (BaseApplication) CheckTx(req RequestCheckTx) ResponseCheckTx { return ResponseCheckTx{Code: CodeTypeOK} } @@ -103,12 +103,12 @@ func (app *GRPCApplication) SetOption(ctx context.Context, req *RequestSetOption } func (app *GRPCApplication) DeliverTx(ctx context.Context, req *RequestDeliverTx) (*ResponseDeliverTx, error) { - res := app.app.DeliverTx(req.Tx) + res := app.app.DeliverTx(*req) return &res, nil } func (app *GRPCApplication) CheckTx(ctx context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { - res := app.app.CheckTx(req.Tx) + res := app.app.CheckTx(*req) return &res, nil } diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 1a4a87d0..cb359866 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -291,11 +291,11 @@ func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { return abci.ResponseEndBlock{} } -func (app *testApp) DeliverTx(tx []byte) abci.ResponseDeliverTx { +func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { return abci.ResponseDeliverTx{Events: []abci.Event{}} } -func (app *testApp) CheckTx(tx []byte) abci.ResponseCheckTx { +func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { return abci.ResponseCheckTx{} } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index af15a1fe..d9feef9b 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -141,7 +141,7 @@ func TestMempoolRmBadTx(t *testing.T) { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(0)) - resDeliver := app.DeliverTx(txBytes) + resDeliver := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) assert.False(t, resDeliver.IsErr(), fmt.Sprintf("expected no error. got %v", resDeliver)) resCommit := app.Commit() @@ -209,8 +209,8 @@ func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo { return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)} } -func (app *CounterApplication) DeliverTx(tx []byte) abci.ResponseDeliverTx { - txValue := txAsUint64(tx) +func (app *CounterApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + txValue := txAsUint64(req.Tx) if txValue != uint64(app.txCount) { return abci.ResponseDeliverTx{ Code: code.CodeTypeBadNonce, @@ -220,8 +220,8 @@ func (app *CounterApplication) DeliverTx(tx []byte) abci.ResponseDeliverTx { return abci.ResponseDeliverTx{Code: code.CodeTypeOK} } -func (app *CounterApplication) CheckTx(tx []byte) abci.ResponseCheckTx { - txValue := txAsUint64(tx) +func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + txValue := txAsUint64(req.Tx) if txValue != uint64(app.mempoolTxCount) { return abci.ResponseCheckTx{ Code: code.CodeTypeBadNonce, diff --git a/consensus/replay.go b/consensus/replay.go index 794f870d..2c4377ff 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -515,7 +515,7 @@ type mockProxyApp struct { abciResponses *sm.ABCIResponses } -func (mock *mockProxyApp) DeliverTx(tx []byte) abci.ResponseDeliverTx { +func (mock *mockProxyApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { r := mock.abciResponses.DeliverTx[mock.txCount] mock.txCount++ if r == nil { //it could be nil because of amino unMarshall, it will cause an empty ResponseDeliverTx to become nil diff --git a/docs/app-dev/app-development.md b/docs/app-dev/app-development.md index 1b4e2680..c9983bea 100644 --- a/docs/app-dev/app-development.md +++ b/docs/app-dev/app-development.md @@ -101,8 +101,8 @@ mempool state (this behaviour can be turned off with In go: ``` -func (app *KVStoreApplication) CheckTx(tx []byte) types.Result { - return types.OK +func (app *KVStoreApplication) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { + return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} } ``` @@ -168,14 +168,29 @@ In go: ``` // tx is either "key=value" or just arbitrary bytes -func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result { - parts := strings.Split(string(tx), "=") - if len(parts) == 2 { - app.state.Set([]byte(parts[0]), []byte(parts[1])) - } else { - app.state.Set(tx, tx) - } - return types.OK +func (app *KVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { + var key, value []byte + parts := bytes.Split(req.Tx, []byte("=")) + if len(parts) == 2 { + key, value = parts[0], parts[1] + } else { + key, value = req.Tx, req.Tx + } + + app.state.db.Set(prefixKey(key), value) + app.state.Size += 1 + + events := []types.Event{ + { + Type: "app", + Attributes: []cmn.KVPair{ + {Key: []byte("creator"), Value: []byte("Cosmoshi Netowoko")}, + {Key: []byte("key"), Value: key}, + }, + }, + } + + return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events} } ``` @@ -223,9 +238,14 @@ job of the [Handshake](#handshake). In go: ``` -func (app *KVStoreApplication) Commit() types.Result { - hash := app.state.Hash() - return types.NewResultOK(hash, "") +func (app *KVStoreApplication) Commit() types.ResponseCommit { + // Using a memdb - just return the big endian size of the db + appHash := make([]byte, 8) + binary.PutVarint(appHash, app.state.Size) + app.state.AppHash = appHash + app.state.Height += 1 + saveState(app.state) + return types.ResponseCommit{Data: appHash} } ``` @@ -256,12 +276,10 @@ In go: ``` // Track the block hash and header information -func (app *PersistentKVStoreApplication) BeginBlock(params types.RequestBeginBlock) { - // update latest block info - app.blockHeader = params.Header - - // reset valset changes - app.changes = make([]*types.Validator, 0) +func (app *PersistentKVStoreApplication) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { + // reset valset changes + app.ValUpdates = make([]types.ValidatorUpdate, 0) + return types.ResponseBeginBlock{} } ``` @@ -303,7 +321,7 @@ In go: ``` // Update the validator set func (app *PersistentKVStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { - return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates} + return types.ResponseEndBlock{ValidatorUpdates: app.ValUpdates} } ``` @@ -347,43 +365,29 @@ Note: these query formats are subject to change! In go: ``` - func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { - if reqQuery.Prove { - value, proof, exists := app.state.GetWithProof(reqQuery.Data) - resQuery.Index = -1 // TODO make Proof return index - resQuery.Key = reqQuery.Data - resQuery.Value = value - resQuery.Proof = proof - if exists { - resQuery.Log = "exists" - } else { - resQuery.Log = "does not exist" - } - return - } else { - index, value, exists := app.state.Get(reqQuery.Data) - resQuery.Index = int64(index) - resQuery.Value = value - if exists { - resQuery.Log = "exists" - } else { - resQuery.Log = "does not exist" - } - return - } - } - return - } else { - index, value, exists := app.state.Get(reqQuery.Data) - resQuery.Index = int64(index) - resQuery.Value = value - if exists { - resQuery.Log = "exists" - } else { - resQuery.Log = "does not exist" - } - return - } +func (app *KVStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { + if reqQuery.Prove { + value := app.state.db.Get(prefixKey(reqQuery.Data)) + resQuery.Index = -1 // TODO make Proof return index + resQuery.Key = reqQuery.Data + resQuery.Value = value + if value != nil { + resQuery.Log = "exists" + } else { + resQuery.Log = "does not exist" + } + return + } else { + resQuery.Key = reqQuery.Data + value := app.state.db.Get(prefixKey(reqQuery.Data)) + resQuery.Value = value + if value != nil { + resQuery.Log = "exists" + } else { + resQuery.Log = "does not exist" + } + return + } } ``` @@ -439,7 +443,11 @@ In go: ``` func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { - return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size())} + return types.ResponseInfo{ + Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), + Version: version.ABCIVersion, + AppVersion: ProtocolVersion.Uint64(), + } } ``` @@ -463,13 +471,14 @@ In go: ``` // Save the validators in the merkle tree -func (app *PersistentKVStoreApplication) InitChain(params types.RequestInitChain) { - for _, v := range params.Validators { - r := app.updateValidator(v) - if r.IsErr() { - app.logger.Error("Error updating validators", "r", r) - } - } +func (app *PersistentKVStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain { + for _, v := range req.Validators { + r := app.updateValidator(v) + if r.IsErr() { + app.logger.Error("Error updating validators", "r", r) + } + } + return types.ResponseInitChain{} } ``` diff --git a/docs/app-dev/indexing-transactions.md b/docs/app-dev/indexing-transactions.md index de8336a4..ffe8b989 100644 --- a/docs/app-dev/indexing-transactions.md +++ b/docs/app-dev/indexing-transactions.md @@ -47,7 +47,7 @@ pairs of UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance": Example: ``` -func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result { +func (app *KVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.Result { ... tags := []cmn.KVPair{ {[]byte("account.name"), []byte("igor")}, diff --git a/mempool/clist_mempool_test.go b/mempool/clist_mempool_test.go index bf2c61dd..db6e800b 100644 --- a/mempool/clist_mempool_test.go +++ b/mempool/clist_mempool_test.go @@ -99,7 +99,7 @@ func TestReapMaxBytesMaxGas(t *testing.T) { checkTxs(t, mempool, 1, UnknownPeerID) tx0 := mempool.TxsFront().Value.(*mempoolTx) // assert that kv store has gas wanted = 1. - require.Equal(t, app.CheckTx(tx0.tx).GasWanted, int64(1), "KVStore had a gas value neq to 1") + require.Equal(t, app.CheckTx(abci.RequestCheckTx{Tx: tx0.tx}).GasWanted, int64(1), "KVStore had a gas value neq to 1") require.Equal(t, tx0.gasWanted, int64(1), "transactions gas was set incorrectly") // ensure each tx is 20 bytes long require.Equal(t, len(tx0.tx), 20, "Tx is longer than 20 bytes") diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index 2ab62a42..f40755fe 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -45,29 +45,29 @@ func (a ABCIApp) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts clien // TODO: Make it wait for a commit and set res.Height appropriately. func (a ABCIApp) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { res := ctypes.ResultBroadcastTxCommit{} - res.CheckTx = a.App.CheckTx(tx) + res.CheckTx = a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) if res.CheckTx.IsErr() { return &res, nil } - res.DeliverTx = a.App.DeliverTx(tx) + res.DeliverTx = a.App.DeliverTx(abci.RequestDeliverTx{Tx: tx}) res.Height = -1 // TODO return &res, nil } func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(tx) + c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.DeliverTx(tx) }() // nolint: errcheck + go func() { a.App.DeliverTx(abci.RequestDeliverTx{Tx: tx}) }() // nolint: errcheck } return &ctypes.ResultBroadcastTx{Code: c.Code, Data: c.Data, Log: c.Log, Hash: tx.Hash()}, nil } func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(tx) + c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) // and this gets written in a background thread... if !c.IsErr() { - go func() { a.App.DeliverTx(tx) }() // nolint: errcheck + go func() { a.App.DeliverTx(abci.RequestDeliverTx{Tx: tx}) }() // nolint: errcheck } return &ctypes.ResultBroadcastTx{Code: c.Code, Data: c.Data, Log: c.Log, Hash: tx.Hash()}, nil } diff --git a/state/execution_test.go b/state/execution_test.go index 80b442e3..269f489a 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -453,11 +453,11 @@ func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { return abci.ResponseEndBlock{ValidatorUpdates: app.ValidatorUpdates} } -func (app *testApp) DeliverTx(tx []byte) abci.ResponseDeliverTx { +func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { return abci.ResponseDeliverTx{Events: []abci.Event{}} } -func (app *testApp) CheckTx(tx []byte) abci.ResponseCheckTx { +func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { return abci.ResponseCheckTx{} } From 866b343c0c864c6ae4369c88506d17572513ce24 Mon Sep 17 00:00:00 2001 From: Marko Date: Fri, 21 Jun 2019 07:58:32 +0200 Subject: [PATCH 04/27] Changes to files that had linting issue (#3731) - Govet issues fixed - 1 gosec issue solved using nolint Signed-off-by: Marko Baricevic --- .golangci.yml | 1 - benchmarks/codec_test.go | 2 +- blockchain/reactor_test.go | 2 +- consensus/byzantine_test.go | 4 ++-- consensus/common_test.go | 4 ++-- consensus/reactor.go | 4 ---- consensus/state_test.go | 4 ++-- consensus/types/height_vote_set_test.go | 2 +- evidence/pool_test.go | 2 +- go.sum | 1 + node/node_test.go | 8 +++---- p2p/upnp/upnp.go | 2 +- privval/file_test.go | 14 ++++++------ state/execution_test.go | 20 ++++++++--------- state/state_test.go | 30 ++++++++++++------------- state/tx_filter_test.go | 2 +- state/txindex/indexer_service_test.go | 4 ++-- types/block.go | 2 +- types/protobuf_test.go | 2 +- 19 files changed, 53 insertions(+), 57 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a051e1a4..6adbbd9d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,7 +20,6 @@ linters: - gochecknoinits - scopelint - stylecheck - # linters-settings: # govet: # check-shadowing: true diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index eff5c734..64c0e72c 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -14,7 +14,7 @@ import ( func testNodeInfo(id p2p.ID) p2p.DefaultNodeInfo { return p2p.DefaultNodeInfo{ - ProtocolVersion: p2p.ProtocolVersion{1, 2, 3}, + ProtocolVersion: p2p.ProtocolVersion{P2P: 1, Block: 2, App: 3}, ID_: id, Moniker: "SOMENAME", Network: "SOMENAME", diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index cb359866..b5137bb2 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -111,7 +111,7 @@ func newBlockchainReactor(logger log.Logger, genDoc *types.GenesisDoc, privVals thisBlock := makeBlock(blockHeight, state, lastCommit) thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) - blockID := types.BlockID{thisBlock.Hash(), thisParts.Header()} + blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()} state, err = blockExec.ApplyBlock(state, blockID, thisBlock) if err != nil { diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index c2eb114d..1c52e79a 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -177,7 +177,7 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int, cs *Cons // Create a new proposal block from state/txs from the mempool. block1, blockParts1 := cs.createProposalBlock() - polRound, propBlockID := cs.ValidRound, types.BlockID{block1.Hash(), blockParts1.Header()} + polRound, propBlockID := cs.ValidRound, types.BlockID{Hash: block1.Hash(), PartsHeader: blockParts1.Header()} proposal1 := types.NewProposal(height, round, polRound, propBlockID) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal1); err != nil { t.Error(err) @@ -185,7 +185,7 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int, cs *Cons // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() - polRound, propBlockID = cs.ValidRound, types.BlockID{block2.Hash(), blockParts2.Header()} + polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartsHeader: blockParts2.Header()} proposal2 := types.NewProposal(height, round, polRound, propBlockID) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal2); err != nil { t.Error(err) diff --git a/consensus/common_test.go b/consensus/common_test.go index a4ad79c9..29db524e 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -86,7 +86,7 @@ func (vs *validatorStub) signVote(voteType types.SignedMsgType, hash []byte, hea Round: vs.Round, Timestamp: tmtime.Now(), Type: voteType, - BlockID: types.BlockID{hash, header}, + BlockID: types.BlockID{Hash: hash, PartsHeader: header}, } err := vs.PrivValidator.SignVote(config.ChainID(), vote) return vote, err @@ -159,7 +159,7 @@ func decideProposal(cs1 *ConsensusState, vs *validatorStub, height int64, round } // Make proposal - polRound, propBlockID := validRound, types.BlockID{block.Hash(), blockParts.Header()} + polRound, propBlockID := validRound, types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()} proposal = types.NewProposal(height, round, polRound, propBlockID) if err := vs.SignProposal(chainID, proposal); err != nil { panic(err) diff --git a/consensus/reactor.go b/consensus/reactor.go index 36e948f6..f690a407 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -351,10 +351,6 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) default: conR.Logger.Error(fmt.Sprintf("Unknown chId %X", chID)) } - - if err != nil { - conR.Logger.Error("Error in Receive()", "err", err) - } } // SetEventBus sets event bus. diff --git a/consensus/state_test.go b/consensus/state_test.go index 87e351dc..93ef0d4c 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -192,7 +192,7 @@ func TestStateBadProposal(t *testing.T) { stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet(partSize) - blockID := types.BlockID{propBlock.Hash(), propBlockParts.Header()} + blockID := types.BlockID{Hash: propBlock.Hash(), PartsHeader: propBlockParts.Header()} proposal := types.NewProposal(vs2.Height, round, -1, blockID) if err := vs2.SignProposal(config.ChainID(), proposal); err != nil { t.Fatal("failed to sign bad proposal", err) @@ -811,7 +811,7 @@ func TestStateLockPOLSafety2(t *testing.T) { _, propBlock0 := decideProposal(cs1, vss[0], height, round) propBlockHash0 := propBlock0.Hash() propBlockParts0 := propBlock0.MakePartSet(partSize) - propBlockID0 := types.BlockID{propBlockHash0, propBlockParts0.Header()} + propBlockID0 := types.BlockID{Hash: propBlockHash0, PartsHeader: propBlockParts0.Header()} // the others sign a polka but we don't see it prevotes := signVotes(types.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) diff --git a/consensus/types/height_vote_set_test.go b/consensus/types/height_vote_set_test.go index 42b5333a..f45492aa 100644 --- a/consensus/types/height_vote_set_test.go +++ b/consensus/types/height_vote_set_test.go @@ -62,7 +62,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivVali Round: round, Timestamp: tmtime.Now(), Type: types.PrecommitType, - BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}}, + BlockID: types.BlockID{Hash: []byte("fakehash"), PartsHeader: types.PartSetHeader{}}, } chainID := config.ChainID() err := privVal.SignVote(chainID, vote) diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 30b20011..13bc4556 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -60,7 +60,7 @@ func TestEvidencePool(t *testing.T) { pool := NewEvidencePool(stateDB, evidenceDB) goodEvidence := types.NewMockGoodEvidence(height, 0, valAddr) - badEvidence := types.MockBadEvidence{goodEvidence} + badEvidence := types.MockBadEvidence{MockGoodEvidence: goodEvidence} // bad evidence err := pool.AddEvidence(badEvidence) diff --git a/go.sum b/go.sum index 10e54c0f..fee691de 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0= github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fortytw2/leaktest v1.2.0 h1:cj6GCiwJDH7l3tMHLjZDo0QqPtrXJiWSI9JgpeQKw+Q= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= diff --git a/node/node_test.go b/node/node_test.go index 6971ddd3..ce4e82c2 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -285,10 +285,10 @@ func state(nVals int, height int64) (sm.State, dbm.DB) { secret := []byte(fmt.Sprintf("test%d", i)) pk := ed25519.GenPrivKeyFromSecret(secret) vals[i] = types.GenesisValidator{ - pk.PubKey().Address(), - pk.PubKey(), - 1000, - fmt.Sprintf("test%d", i), + Address: pk.PubKey().Address(), + PubKey: pk.PubKey(), + Power: 1000, + Name: fmt.Sprintf("test%d", i), } } s, _ := sm.MakeGenesisState(&types.GenesisDoc{ diff --git a/p2p/upnp/upnp.go b/p2p/upnp/upnp.go index d53974fc..89f35c5d 100644 --- a/p2p/upnp/upnp.go +++ b/p2p/upnp/upnp.go @@ -197,7 +197,7 @@ func localIPv4() (net.IP, error) { } func getServiceURL(rootURL string) (url, urnDomain string, err error) { - r, err := http.Get(rootURL) + r, err := http.Get(rootURL) // nolint: gosec if err != nil { return } diff --git a/privval/file_test.go b/privval/file_test.go index 06d75a80..98de6948 100644 --- a/privval/file_test.go +++ b/privval/file_test.go @@ -50,7 +50,7 @@ func TestResetValidator(t *testing.T) { // test vote height, round := int64(10), 1 voteType := byte(types.PrevoteType) - blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} + blockID := types.BlockID{Hash: []byte{1, 2, 3}, PartsHeader: types.PartSetHeader{}} vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) err = privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error signing vote") @@ -162,8 +162,8 @@ func TestSignVote(t *testing.T) { privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) - block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} - block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} + block1 := types.BlockID{Hash: []byte{1, 2, 3}, PartsHeader: types.PartSetHeader{}} + block2 := types.BlockID{Hash: []byte{3, 2, 1}, PartsHeader: types.PartSetHeader{}} height, round := int64(10), 1 voteType := byte(types.PrevoteType) @@ -207,8 +207,8 @@ func TestSignProposal(t *testing.T) { privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) - block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} - block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{10, []byte{3, 2, 1}}} + block1 := types.BlockID{Hash: []byte{1, 2, 3}, PartsHeader: types.PartSetHeader{Total: 5, Hash: []byte{1, 2, 3}}} + block2 := types.BlockID{Hash: []byte{3, 2, 1}, PartsHeader: types.PartSetHeader{Total: 10, Hash: []byte{3, 2, 1}}} height, round := int64(10), 1 // sign a proposal for first time @@ -249,7 +249,7 @@ func TestDifferByTimestamp(t *testing.T) { privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) - block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} + block1 := types.BlockID{Hash: []byte{1, 2, 3}, PartsHeader: types.PartSetHeader{Total: 5, Hash: []byte{1, 2, 3}}} height, round := int64(10), 1 chainID := "mychainid" @@ -277,7 +277,7 @@ func TestDifferByTimestamp(t *testing.T) { // test vote { voteType := byte(types.PrevoteType) - blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} + blockID := types.BlockID{Hash: []byte{1, 2, 3}, PartsHeader: types.PartSetHeader{}} vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) err := privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error signing vote") diff --git a/state/execution_test.go b/state/execution_test.go index 269f489a..ac7b9cc3 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -40,7 +40,7 @@ func TestApplyBlock(t *testing.T) { mock.Mempool{}, MockEvidencePool{}) block := makeBlock(state, 1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} //nolint:ineffassign state, err = blockExec.ApplyBlock(state, blockID, block) @@ -62,7 +62,7 @@ func TestBeginBlockValidators(t *testing.T) { prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} - prevBlockID := types.BlockID{prevHash, prevParts} + prevBlockID := types.BlockID{Hash: prevHash, PartsHeader: prevParts} now := tmtime.Now() commitSig0 := (&types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType}).CommitSig() @@ -115,7 +115,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} - prevBlockID := types.BlockID{prevHash, prevParts} + prevBlockID := types.BlockID{Hash: prevHash, PartsHeader: prevParts} height1, idx1, val1 := int64(8), 0, state.Validators.Validators[0].Address height2, idx2, val2 := int64(3), 1, state.Validators.Validators[1].Address @@ -159,7 +159,7 @@ func TestValidateValidatorUpdates(t *testing.T) { secpKey := secp256k1.GenPrivKey().PubKey() - defaultValidatorParams := types.ValidatorParams{[]string{types.ABCIPubKeyTypeEd25519}} + defaultValidatorParams := types.ValidatorParams{PubKeyTypes: []string{types.ABCIPubKeyTypeEd25519}} testCases := []struct { name string @@ -321,7 +321,7 @@ func TestEndBlockValidatorUpdates(t *testing.T) { require.NoError(t, err) block := makeBlock(state, 1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} pubkey := ed25519.GenPrivKey().PubKey() app.ValidatorUpdates = []abci.ValidatorUpdate{ @@ -369,7 +369,7 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, MockEvidencePool{}) block := makeBlock(state, 1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} // Remove the only validator app.ValidatorUpdates = []abci.ValidatorUpdate{ @@ -398,10 +398,10 @@ func state(nVals, height int) (State, dbm.DB) { secret := []byte(fmt.Sprintf("test%d", i)) pk := ed25519.GenPrivKeyFromSecret(secret) vals[i] = types.GenesisValidator{ - pk.PubKey().Address(), - pk.PubKey(), - 1000, - fmt.Sprintf("test%d", i), + Address: pk.PubKey().Address(), + PubKey: pk.PubKey(), + Power: 1000, + Name: fmt.Sprintf("test%d", i), } } s, _ := MakeGenesisState(&types.GenesisDoc{ diff --git a/state/state_test.go b/state/state_test.go index c7600cc3..1ff09ccd 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -129,7 +129,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { {Code: 32, Data: []byte("Hello"), Log: "Huh?"}, }, types.ABCIResults{ - {32, []byte("Hello")}, + {Code: 32, Data: []byte("Hello")}, }}, 2: { []*abci.ResponseDeliverTx{ @@ -143,8 +143,8 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { }, }, types.ABCIResults{ - {383, nil}, - {0, []byte("Gotcha!")}, + {Code: 383, Data: nil}, + {Code: 0, Data: []byte("Gotcha!")}, }}, 3: { nil, @@ -404,7 +404,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { assert.EqualValues(t, 0, val1.ProposerPriority) block := makeBlock(state, state.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} abciResponses := &ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } @@ -514,7 +514,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address) block := makeBlock(state, state.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} // no updates: abciResponses := &ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, @@ -667,7 +667,7 @@ func TestLargeGenesisValidator(t *testing.T) { require.NoError(t, err) block := makeBlock(oldState, oldState.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) @@ -693,7 +693,7 @@ func TestLargeGenesisValidator(t *testing.T) { EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) @@ -707,7 +707,7 @@ func TestLargeGenesisValidator(t *testing.T) { require.NoError(t, err) block := makeBlock(lastState, lastState.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} updatedStateInner, err := updateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) @@ -738,7 +738,7 @@ func TestLargeGenesisValidator(t *testing.T) { EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) } @@ -750,7 +750,7 @@ func TestLargeGenesisValidator(t *testing.T) { EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, } block = makeBlock(oldState, oldState.LastBlockHeight+1) - blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) updatedState, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) @@ -770,7 +770,7 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) block = makeBlock(curState, curState.LastBlockHeight+1) - blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} curState, err = updateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { @@ -794,7 +794,7 @@ func TestLargeGenesisValidator(t *testing.T) { require.NoError(t, err) block := makeBlock(updatedState, updatedState.LastBlockHeight+1) - blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} updatedState, err = updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) @@ -1033,7 +1033,7 @@ func makeHeaderPartsResponsesValPubKeyChange(state State, height int64, } } - return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses } func makeHeaderPartsResponsesValPowerChange(state State, height int64, @@ -1054,7 +1054,7 @@ func makeHeaderPartsResponsesValPowerChange(state State, height int64, } } - return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses } func makeHeaderPartsResponsesParams(state State, height int64, @@ -1064,7 +1064,7 @@ func makeHeaderPartsResponsesParams(state State, height int64, abciResponses := &ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, } - return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses } type paramsChangeTestCase struct { diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index ffb41c17..e48ad2c3 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -51,7 +51,7 @@ func randomGenesisDoc() *types.GenesisDoc { return &types.GenesisDoc{ GenesisTime: tmtime.Now(), ChainID: "abc", - Validators: []types.GenesisValidator{{pubkey.Address(), pubkey, 10, "myval"}}, + Validators: []types.GenesisValidator{{Address: pubkey.Address(), PubKey: pubkey, Power: 10, Name: "myval"}}, ConsensusParams: types.DefaultConsensusParams(), } } diff --git a/state/txindex/indexer_service_test.go b/state/txindex/indexer_service_test.go index 982d7b8c..079f9cec 100644 --- a/state/txindex/indexer_service_test.go +++ b/state/txindex/indexer_service_test.go @@ -43,14 +43,14 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { Tx: types.Tx("foo"), Result: abci.ResponseDeliverTx{Code: 0}, } - eventBus.PublishEventTx(types.EventDataTx{*txResult1}) + eventBus.PublishEventTx(types.EventDataTx{TxResult: *txResult1}) txResult2 := &types.TxResult{ Height: 1, Index: uint32(1), Tx: types.Tx("bar"), Result: abci.ResponseDeliverTx{Code: 0}, } - eventBus.PublishEventTx(types.EventDataTx{*txResult2}) + eventBus.PublishEventTx(types.EventDataTx{TxResult: *txResult2}) time.Sleep(100 * time.Millisecond) diff --git a/types/block.go b/types/block.go index 313eb6b7..55709ad6 100644 --- a/types/block.go +++ b/types/block.go @@ -198,7 +198,7 @@ func (b *Block) Hash() cmn.HexBytes { b.mtx.Lock() defer b.mtx.Unlock() - if b == nil || b.LastCommit == nil { + if b.LastCommit == nil { return nil } b.fillHeader() diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 64caa3f4..833c7dc3 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -90,7 +90,7 @@ func TestABCIHeader(t *testing.T) { height, numTxs, []byte("lastCommitHash"), []byte("dataHash"), []byte("evidenceHash"), ) - protocolVersion := version.Consensus{7, 8} + protocolVersion := version.Consensus{Block: 7, App: 8} timestamp := time.Now() lastBlockID := BlockID{ Hash: []byte("hash"), From 2e5b2a9537fd68d32c39693b810d2da61c94b73b Mon Sep 17 00:00:00 2001 From: needkane Date: Fri, 21 Jun 2019 19:18:49 +0800 Subject: [PATCH 05/27] abci/examples: switch from hex to base64 pubkey in kvstore (#3641) * abci/example: use base64 for update validator set * update kvstore/README.md * update CHANGELOG_PENDING.md and abci/example/kvstore/README.md --- CHANGELOG_PENDING.md | 1 + abci/example/kvstore/README.md | 6 +++--- abci/example/kvstore/persistent_kvstore.go | 24 ++++++++++++---------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ed835aeb..74aa72c6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -34,6 +34,7 @@ ### IMPROVEMENTS: - [p2p] \#3666 Add per channel telemetry to improve reactor observability - [rpc] [\#3686](https://github.com/tendermint/tendermint/pull/3686) `HTTPClient#Call` returns wrapped errors, so a caller could use `errors.Cause` to retrieve an error code. (@wooparadog) +- [abci/examples] \#3659 Change validator update tx format (incl. expected pubkey format, which is base64 now) (@needkane) ### BUG FIXES: - [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) diff --git a/abci/example/kvstore/README.md b/abci/example/kvstore/README.md index e988eadb..bed81a59 100644 --- a/abci/example/kvstore/README.md +++ b/abci/example/kvstore/README.md @@ -22,10 +22,10 @@ and the Handshake allows any necessary blocks to be replayed. Validator set changes are effected using the following transaction format: ``` -val:pubkey1/power1,addr2/power2,addr3/power3" +"val:pubkey1!power1,pubkey2!power2,pubkey3!power3" ``` -where `power1` is the new voting power for the validator with `pubkey1` (possibly a new one). +where `pubkeyN` is a base64-encoded 32-byte ed25519 key and `powerN` is a new voting power for the validator with `pubkeyN` (possibly a new one). +To remove a validator from the validator set, set power to `0`. There is no sybil protection against new validators joining. -Validators can be removed by setting their power to `0`. diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index b7484a4a..ba0b5389 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -2,7 +2,7 @@ package kvstore import ( "bytes" - "encoding/hex" + "encoding/base64" "fmt" "strconv" "strings" @@ -60,10 +60,10 @@ func (app *PersistentKVStoreApplication) SetOption(req types.RequestSetOption) t return app.app.SetOption(req) } -// tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes +// tx is either "val:pubkey!power" or "key=value" or just arbitrary bytes func (app *PersistentKVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { // if it starts with "val:", update the validator set - // format is "val:pubkey/power" + // format is "val:pubkey!power" if isValidatorTx(req.Tx) { // update validators in the merkle tree // and in app.ValUpdates @@ -129,33 +129,34 @@ func (app *PersistentKVStoreApplication) Validators() (validators []types.Valida } func MakeValSetChangeTx(pubkey types.PubKey, power int64) []byte { - return []byte(fmt.Sprintf("val:%X/%d", pubkey.Data, power)) + pubStr := base64.StdEncoding.EncodeToString(pubkey.Data) + return []byte(fmt.Sprintf("val:%s!%d", pubStr, power)) } func isValidatorTx(tx []byte) bool { return strings.HasPrefix(string(tx), ValidatorSetChangePrefix) } -// format is "val:pubkey/power" -// pubkey is raw 32-byte ed25519 key +// format is "val:pubkey!power" +// pubkey is a base64-encoded 32-byte ed25519 key func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx { tx = tx[len(ValidatorSetChangePrefix):] //get the pubkey and power - pubKeyAndPower := strings.Split(string(tx), "/") + pubKeyAndPower := strings.Split(string(tx), "!") if len(pubKeyAndPower) != 2 { return types.ResponseDeliverTx{ Code: code.CodeTypeEncodingError, - Log: fmt.Sprintf("Expected 'pubkey/power'. Got %v", pubKeyAndPower)} + Log: fmt.Sprintf("Expected 'pubkey!power'. Got %v", pubKeyAndPower)} } pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1] // decode the pubkey - pubkey, err := hex.DecodeString(pubkeyS) + pubkey, err := base64.StdEncoding.DecodeString(pubkeyS) if err != nil { return types.ResponseDeliverTx{ Code: code.CodeTypeEncodingError, - Log: fmt.Sprintf("Pubkey (%s) is invalid hex", pubkeyS)} + Log: fmt.Sprintf("Pubkey (%s) is invalid base64", pubkeyS)} } // decode the power @@ -176,9 +177,10 @@ func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate if v.Power == 0 { // remove validator if !app.app.state.db.Has(key) { + pubStr := base64.StdEncoding.EncodeToString(v.PubKey.Data) return types.ResponseDeliverTx{ Code: code.CodeTypeUnauthorized, - Log: fmt.Sprintf("Cannot remove non-existent validator %X", key)} + Log: fmt.Sprintf("Cannot remove non-existent validator %s", pubStr)} } app.app.state.db.Delete(key) } else { From 59497c362bdd21a3cb5b2dc1afe402496eac7776 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 21 Jun 2019 22:29:16 +0200 Subject: [PATCH 06/27] Prepare nuking develop (#3726) * Update Readme and contrib. guidelines: remove traces of master Signed-off-by: Ismail Khoffi * add simple example Signed-off-by: Ismail Khoffi * update contributing.md * add link * Update CONTRIBUTING.md * add a note on master and releases Signed-off-by: Ismail Khoffi * update readme --- CONTRIBUTING.md | 65 ++++++++++++++++++++++++++++++++----------------- README.md | 15 ++++++------ 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e68e6d1e..77a62550 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,8 @@ Thank you for considering making contributions to Tendermint and related repositories! Start by taking a look at the [coding repo](https://github.com/tendermint/coding) for overall information on repository workflow and standards. -Please follow standard github best practices: fork the repo, branch from the tip of develop, make some commits, and submit a pull request to develop. See the [open issues](https://github.com/tendermint/tendermint/issues) for things we need help with! +Please follow standard github best practices: fork the repo, branch from the tip of `master`, make some commits, and submit a pull request to `master`. +See the [open issues](https://github.com/tendermint/tendermint/issues) for things we need help with! Before making a pull request, please open an issue describing the change you would like to make. If an issue for your change already exists, @@ -112,32 +113,36 @@ removed from the header in rpc responses as well. ## Branching Model and Release -We follow a variant of [git flow](http://nvie.com/posts/a-successful-git-branching-model/). -This means that all pull-requests should be made against develop. Any merge to -master constitutes a tagged release. +The main development branch is master. -Note all pull requests should be squash merged except for merging to master and -merging master back to develop. This keeps the commit history clean and makes it +Every release is maintained in a release branch named `vX.Y.Z`. + +Note all pull requests should be squash merged except for merging to a release branch (named `vX.Y`). This keeps the commit history clean and makes it easy to reference the pull request where a change was introduced. -### Development Procedure: -- the latest state of development is on `develop` -- `develop` must never fail `make test` -- never --force onto `develop` (except when reverting a broken commit, which should seldom happen) +### Development Procedure + +- the latest state of development is on `master` +- `master` must never fail `make test` +- never --force onto `master` (except when reverting a broken commit, which should seldom happen) - create a development branch either on github.com/tendermint/tendermint, or your fork (using `git remote add origin`) - make changes and update the `CHANGELOG_PENDING.md` to record your change -- before submitting a pull request, run `git rebase` on top of the latest `develop` +- before submitting a pull request, run `git rebase` on top of the latest `master` -### Pull Merge Procedure: -- ensure pull branch is based on a recent develop +### Pull Merge Procedure + +- ensure pull branch is based on a recent `master` - run `make test` to ensure that all tests pass - squash merge pull request - the `unstable` branch may be used to aggregate pull merges before fixing tests -### Release Procedure: -- start on `develop` -- run integration tests (see `test_integrations` in Makefile) -- prepare release in a pull request against develop (to be squash merged): +### Release Procedure + +#### Major Release + +1. start on `master` +2. run integration tests (see `test_integrations` in Makefile) +3. prepare release in a pull request against `master` (to be squash merged): - copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md` - run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues @@ -147,14 +152,28 @@ easy to reference the pull request where a change was introduced. ./scripts/authors.sh ` - reset the `CHANGELOG_PENDING.md` - bump versions -- push latest develop with prepared release details to release/vX.X.X to run the extended integration tests on the CI -- if necessary, make pull requests against release/vX.X.X and squash merge them -- merge to master (don't squash merge!) -- merge master back to develop (don't squash merge!) +4. push your changes with prepared release details to `vX.X` (this will trigger the release `vX.X.0`) +5. merge back to master (don't squash merge!) -### Hotfix Procedure: +#### Minor Release -- follow the normal development and release procedure without any differences +If there were no breaking changes and you need to create a release nonetheless, +the procedure is almost exactly like with a new release above. + +The only difference is that in the end you create a pull request against the existing `X.X` branch. +The branch name should match the release number you want to create. +Merging this PR will trigger the next release. +For example, if the PR is against an existing 0.34 branch which already contains a v0.34.0 release/tag, +the patch version will be incremented and the created release will be v0.34.1. + +#### Backport Release + +1. start from the existing release branch you want to backport changes to (e.g. v0.30) +Branch to a release/vX.X.X branch locally (e.g. release/v0.30.7) +2. cherry pick the commit(s) that contain the changes you want to backport (usually these commits are from squash-merged PRs which were already reviewed) +3. steps 2 and 3 from [Major Release](#major-release) +4. push changes to release/vX.X.X branch +5. open a PR against the existing vX.X branch ## Testing diff --git a/README.md b/README.md index ad4fc130..ec7b28cc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/6874 Branch | Tests | Coverage ----------|-------|---------- master | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/master.svg?style=shield)](https://circleci.com/gh/tendermint/tendermint/tree/master) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/master/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) -develop | [![CircleCI](https://circleci.com/gh/tendermint/tendermint/tree/develop.svg?style=shield)](https://circleci.com/gh/tendermint/tendermint/tree/develop) | [![codecov](https://codecov.io/gh/tendermint/tendermint/branch/develop/graph/badge.svg)](https://codecov.io/gh/tendermint/tendermint) Tendermint Core is Byzantine Fault Tolerant (BFT) middleware that takes a state transition machine - written in any programming language - and securely replicates it on many machines. @@ -27,13 +26,15 @@ For protocol details, see [the specification](/docs/spec). For detailed analysis of the consensus protocol, including safety and liveness proofs, see our recent paper, "[The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938)". -## A Note on Production Readiness +## Releases -While Tendermint is being used in production in private, permissioned -environments, we are still working actively to harden and audit it in preparation -for use in public blockchains, such as the [Cosmos Network](https://cosmos.network/). -We are also still making breaking changes to the protocol and the APIs. -Thus, we tag the releases as *alpha software*. +NOTE: The master branch is now an active development branch (starting with `v0.32`). Please, do not depend on it and +use [releases](https://github.com/tendermint/tendermint/releases) instead. + +Tendermint is being used in production in both private and public environments, +most notably the blockchains of the [Cosmos Network](https://cosmos.network/). +However, we are still making breaking changes to the protocol and the APIs and have not yet released v1.0. +See below for more details about [versioning](#versioning). In any case, if you intend to run Tendermint in production, please [contact us](mailto:partners@tendermint.com) and [join the chat](https://riot.im/app/#/room/#tendermint:matrix.org). From 228bba799ddc14604788899c3493cf3534e2fdfd Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Fri, 21 Jun 2019 17:29:29 -0400 Subject: [PATCH 07/27] state: add more tests for block validation (#3674) * Expose priv validators for use in testing * Generalize block header validation test past height 1 * Remove ineffectual assignment * Remove redundant SaveState call * Reorder comment for clarity * Use the block executor ApplyBlock function instead of implementing a stripped-down version of it * Remove commented-out code * Remove unnecessary test The required tests already appear to be implemented (implicitly) through the TestValidateBlockHeader test. * Allow for catching of specific error types during TestValidateBlockCommit * Make return error testable * Clean up and add TestValidateBlockCommit code * Fix formatting * Extract function to create a new mock test app * Update comment for clarity * Fix comment * Add skeleton code for evidence-related test * Allow for addressing priv val by address * Generalize test beyond a single validator * Generalize TestValidateBlockEvidence past first height * Reorder code to clearly separate tests and utility code * Use a common constant for stop height for testing in state/validation_test.go * Refactor errors to resemble existing conventions * Fix formatting * Extract common helper functions Having the tests littered with helper functions makes them less easily readable imho, so I've pulled them out into a separate file. This also makes it easier to see what helper functions are available during testing, so we minimize the chance of duplication when writing new tests. * Remove unused parameter * Remove unused parameters * Add field keys * Remove unused height constant * Fix typo * Fix incorrect return error * Add field keys * Use separate package for tests This refactors all of the state package's tests into a state_test package, so as to keep any usage of the state package's internal methods explicit. Any internal methods/constants used by tests are now explicitly exported in state/export_test.go * Refactor: extract helper function to make, validate, execute and commit a block * Rename state function to makeState * Remove redundant constant for number of validators * Refactor mock evidence registration into TestMain * Remove extraneous nVals variable * Replace function-level TODOs with file-level TODO and explanation * Remove extraneous comment * Fix linting issues brought up by GolangCI (pulled in from latest merge from develop) --- state/execution_test.go | 117 ++-------------- state/export_test.go | 62 +++++++++ state/helpers_test.go | 280 +++++++++++++++++++++++++++++++++++++++ state/main_test.go | 13 ++ state/services.go | 2 +- state/state_test.go | 219 ++++++++++-------------------- state/store_test.go | 31 ++--- state/tx_filter_test.go | 19 +-- state/validation.go | 5 +- state/validation_test.go | 213 ++++++++++++++++++----------- types/errors.go | 41 ++++++ types/validator_set.go | 4 +- 12 files changed, 636 insertions(+), 370 deletions(-) create mode 100644 state/export_test.go create mode 100644 state/helpers_test.go create mode 100644 state/main_test.go create mode 100644 types/errors.go diff --git a/state/execution_test.go b/state/execution_test.go index ac7b9cc3..38301df7 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -1,22 +1,20 @@ -package state +package state_test import ( "context" - "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/mock" "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -34,10 +32,10 @@ func TestApplyBlock(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) + state, stateDB, _ := makeState(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), - mock.Mempool{}, MockEvidencePool{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), + mock.Mempool{}, sm.MockEvidencePool{}) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} @@ -58,7 +56,7 @@ func TestBeginBlockValidators(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(2, 2) + state, stateDB, _ := makeState(2, 2) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} @@ -84,7 +82,7 @@ func TestBeginBlockValidators(t *testing.T) { // block for height 2 block, _ := state.MakeBlock(2, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) - _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) + _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app receives a list of validators with a bool indicating if they signed @@ -111,7 +109,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(2, 12) + state, stateDB, _ := makeState(2, 12) prevHash := state.LastBlockID.Hash prevParts := types.PartSetHeader{} @@ -145,7 +143,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) block.Time = now block.Evidence.Evidence = tc.evidence - _, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) + _, err = sm.ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), stateDB) require.Nil(t, err, tc.desc) // -> app must receive an index of the byzantine validator @@ -213,7 +211,7 @@ func TestValidateValidatorUpdates(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := validateValidatorUpdates(tc.abciUpdates, tc.validatorParams) + err := sm.ValidateValidatorUpdates(tc.abciUpdates, tc.validatorParams) if tc.shouldErr { assert.Error(t, err) } else { @@ -307,9 +305,9 @@ func TestEndBlockValidatorUpdates(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) + state, stateDB, _ := makeState(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, MockEvidencePool{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) eventBus := types.NewEventBus() err = eventBus.Start() @@ -365,8 +363,8 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { require.Nil(t, err) defer proxyApp.Stop() - state, stateDB := state(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, MockEvidencePool{}) + state, stateDB, _ := makeState(1, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) block := makeBlock(state, 1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} @@ -381,90 +379,3 @@ func TestEndBlockValidatorUpdatesResultingInEmptySet(t *testing.T) { assert.NotEmpty(t, state.NextValidators.Validators) } - -//---------------------------------------------------------------------------- - -// make some bogus txs -func makeTxs(height int64) (txs []types.Tx) { - for i := 0; i < nTxsPerBlock; i++ { - txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) - } - return txs -} - -func state(nVals, height int) (State, dbm.DB) { - vals := make([]types.GenesisValidator, nVals) - for i := 0; i < nVals; i++ { - secret := []byte(fmt.Sprintf("test%d", i)) - pk := ed25519.GenPrivKeyFromSecret(secret) - vals[i] = types.GenesisValidator{ - Address: pk.PubKey().Address(), - PubKey: pk.PubKey(), - Power: 1000, - Name: fmt.Sprintf("test%d", i), - } - } - s, _ := MakeGenesisState(&types.GenesisDoc{ - ChainID: chainID, - Validators: vals, - AppHash: nil, - }) - - // save validators to db for 2 heights - stateDB := dbm.NewMemDB() - SaveState(stateDB, s) - - for i := 1; i < height; i++ { - s.LastBlockHeight++ - s.LastValidators = s.Validators.Copy() - SaveState(stateDB, s) - } - return s, stateDB -} - -func makeBlock(state State, height int64) *types.Block { - block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit), nil, state.Validators.GetProposer().Address) - return block -} - -//---------------------------------------------------------------------------- - -type testApp struct { - abci.BaseApplication - - CommitVotes []abci.VoteInfo - ByzantineValidators []abci.Evidence - ValidatorUpdates []abci.ValidatorUpdate -} - -var _ abci.Application = (*testApp)(nil) - -func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { - return abci.ResponseInfo{} -} - -func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { - app.CommitVotes = req.LastCommitInfo.Votes - app.ByzantineValidators = req.ByzantineValidators - return abci.ResponseBeginBlock{} -} - -func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { - return abci.ResponseEndBlock{ValidatorUpdates: app.ValidatorUpdates} -} - -func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { - return abci.ResponseDeliverTx{Events: []abci.Event{}} -} - -func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { - return abci.ResponseCheckTx{} -} - -func (app *testApp) Commit() abci.ResponseCommit { - return abci.ResponseCommit{} -} - -func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - return -} diff --git a/state/export_test.go b/state/export_test.go new file mode 100644 index 00000000..af7f5cc2 --- /dev/null +++ b/state/export_test.go @@ -0,0 +1,62 @@ +package state + +import ( + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/types" +) + +// +// TODO: Remove dependence on all entities exported from this file. +// +// Every entity exported here is dependent on a private entity from the `state` +// package. Currently, these functions are only made available to tests in the +// `state_test` package, but we should not be relying on them for our testing. +// Instead, we should be exclusively relying on exported entities for our +// testing, and should be refactoring exported entities to make them more +// easily testable from outside of the package. +// + +const ValSetCheckpointInterval = valSetCheckpointInterval + +// UpdateState is an alias for updateState exported from execution.go, +// exclusively and explicitly for testing. +func UpdateState( + state State, + blockID types.BlockID, + header *types.Header, + abciResponses *ABCIResponses, + validatorUpdates []*types.Validator, +) (State, error) { + return updateState(state, blockID, header, abciResponses, validatorUpdates) +} + +// ValidateValidatorUpdates is an alias for validateValidatorUpdates exported +// from execution.go, exclusively and explicitly for testing. +func ValidateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, params types.ValidatorParams) error { + return validateValidatorUpdates(abciUpdates, params) +} + +// CalcValidatorsKey is an alias for the private calcValidatorsKey method in +// store.go, exported exclusively and explicitly for testing. +func CalcValidatorsKey(height int64) []byte { + return calcValidatorsKey(height) +} + +// SaveABCIResponses is an alias for the private saveABCIResponses method in +// store.go, exported exclusively and explicitly for testing. +func SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) { + saveABCIResponses(db, height, abciResponses) +} + +// SaveConsensusParamsInfo is an alias for the private saveConsensusParamsInfo +// method in store.go, exported exclusively and explicitly for testing. +func SaveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params types.ConsensusParams) { + saveConsensusParamsInfo(db, nextHeight, changeHeight, params) +} + +// SaveValidatorsInfo is an alias for the private saveValidatorsInfo method in +// store.go, exported exclusively and explicitly for testing. +func SaveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) { + saveValidatorsInfo(db, height, lastHeightChanged, valSet) +} diff --git a/state/helpers_test.go b/state/helpers_test.go new file mode 100644 index 00000000..e8cb2758 --- /dev/null +++ b/state/helpers_test.go @@ -0,0 +1,280 @@ +package state_test + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/proxy" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +type paramsChangeTestCase struct { + height int64 + params types.ConsensusParams +} + +// always returns true if asked if any evidence was already committed. +type mockEvPoolAlwaysCommitted struct{} + +func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil } +func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil } +func (m mockEvPoolAlwaysCommitted) Update(*types.Block, sm.State) {} +func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true } + +func newTestApp() proxy.AppConns { + app := &testApp{} + cc := proxy.NewLocalClientCreator(app) + return proxy.NewAppConns(cc) +} + +func makeAndCommitGoodBlock( + state sm.State, + height int64, + lastCommit *types.Commit, + proposerAddr []byte, + blockExec *sm.BlockExecutor, + privVals map[string]types.PrivValidator, + evidence []types.Evidence) (sm.State, types.BlockID, *types.Commit, error) { + // A good block passes + state, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence) + if err != nil { + return state, types.BlockID{}, nil, err + } + + // Simulate a lastCommit for this block from all validators for the next height + commit, err := makeValidCommit(height, blockID, state.Validators, privVals) + if err != nil { + return state, types.BlockID{}, nil, err + } + return state, blockID, commit, nil +} + +func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte, + blockExec *sm.BlockExecutor, evidence []types.Evidence) (sm.State, types.BlockID, error) { + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) + if err := blockExec.ValidateBlock(state, block); err != nil { + return state, types.BlockID{}, err + } + blockID := types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}} + state, err := blockExec.ApplyBlock(state, blockID, block) + if err != nil { + return state, types.BlockID{}, err + } + return state, blockID, nil +} + +func makeVote(height int64, blockID types.BlockID, valSet *types.ValidatorSet, privVal types.PrivValidator) (*types.Vote, error) { + addr := privVal.GetPubKey().Address() + idx, _ := valSet.GetByAddress(addr) + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: idx, + Height: height, + Round: 0, + Timestamp: tmtime.Now(), + Type: types.PrecommitType, + BlockID: blockID, + } + if err := privVal.SignVote(chainID, vote); err != nil { + return nil, err + } + return vote, nil +} + +func makeValidCommit(height int64, blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator) (*types.Commit, error) { + sigs := make([]*types.CommitSig, 0) + for i := 0; i < vals.Size(); i++ { + _, val := vals.GetByIndex(i) + vote, err := makeVote(height, blockID, vals, privVals[val.Address.String()]) + if err != nil { + return nil, err + } + sigs = append(sigs, vote.CommitSig()) + } + return types.NewCommit(blockID, sigs), nil +} + +// make some bogus txs +func makeTxs(height int64) (txs []types.Tx) { + for i := 0; i < nTxsPerBlock; i++ { + txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) + } + return txs +} + +func makeState(nVals, height int) (sm.State, dbm.DB, map[string]types.PrivValidator) { + vals := make([]types.GenesisValidator, nVals) + privVals := make(map[string]types.PrivValidator, nVals) + for i := 0; i < nVals; i++ { + secret := []byte(fmt.Sprintf("test%d", i)) + pk := ed25519.GenPrivKeyFromSecret(secret) + valAddr := pk.PubKey().Address() + vals[i] = types.GenesisValidator{ + Address: valAddr, + PubKey: pk.PubKey(), + Power: 1000, + Name: fmt.Sprintf("test%d", i), + } + privVals[valAddr.String()] = types.NewMockPVWithParams(pk, false, false) + } + s, _ := sm.MakeGenesisState(&types.GenesisDoc{ + ChainID: chainID, + Validators: vals, + AppHash: nil, + }) + + stateDB := dbm.NewMemDB() + sm.SaveState(stateDB, s) + + for i := 1; i < height; i++ { + s.LastBlockHeight++ + s.LastValidators = s.Validators.Copy() + sm.SaveState(stateDB, s) + } + return s, stateDB, privVals +} + +func makeBlock(state sm.State, height int64) *types.Block { + block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit), nil, state.Validators.GetProposer().Address) + return block +} + +func genValSet(size int) *types.ValidatorSet { + vals := make([]*types.Validator, size) + for i := 0; i < size; i++ { + vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) + } + return types.NewValidatorSet(vals) +} + +func makeConsensusParams( + blockBytes, blockGas int64, + blockTimeIotaMs int64, + evidenceAge int64, +) types.ConsensusParams { + return types.ConsensusParams{ + Block: types.BlockParams{ + MaxBytes: blockBytes, + MaxGas: blockGas, + TimeIotaMs: blockTimeIotaMs, + }, + Evidence: types.EvidenceParams{ + MaxAge: evidenceAge, + }, + } +} + +func makeHeaderPartsResponsesValPubKeyChange(state sm.State, pubkey crypto.PubKey) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + + // If the pubkey is new, remove the old and add the new. + _, val := state.NextValidators.GetByIndex(0) + if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { + abciResponses.EndBlock = &abci.ResponseEndBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{ + types.TM2PB.NewValidatorUpdate(val.PubKey, 0), + types.TM2PB.NewValidatorUpdate(pubkey, 10), + }, + } + } + + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func makeHeaderPartsResponsesValPowerChange(state sm.State, power int64) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + + // If the pubkey is new, remove the old and add the new. + _, val := state.NextValidators.GetByIndex(0) + if val.VotingPower != power { + abciResponses.EndBlock = &abci.ResponseEndBlock{ + ValidatorUpdates: []abci.ValidatorUpdate{ + types.TM2PB.NewValidatorUpdate(val.PubKey, power), + }, + } + } + + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func makeHeaderPartsResponsesParams(state sm.State, params types.ConsensusParams) (types.Header, types.BlockID, *sm.ABCIResponses) { + + block := makeBlock(state, state.LastBlockHeight+1) + abciResponses := &sm.ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, + } + return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses +} + +func randomGenesisDoc() *types.GenesisDoc { + pubkey := ed25519.GenPrivKey().PubKey() + return &types.GenesisDoc{ + GenesisTime: tmtime.Now(), + ChainID: "abc", + Validators: []types.GenesisValidator{ + { + Address: pubkey.Address(), + PubKey: pubkey, + Power: 10, + Name: "myval", + }, + }, + ConsensusParams: types.DefaultConsensusParams(), + } +} + +//---------------------------------------------------------------------------- + +type testApp struct { + abci.BaseApplication + + CommitVotes []abci.VoteInfo + ByzantineValidators []abci.Evidence + ValidatorUpdates []abci.ValidatorUpdate +} + +var _ abci.Application = (*testApp)(nil) + +func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { + return abci.ResponseInfo{} +} + +func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { + app.CommitVotes = req.LastCommitInfo.Votes + app.ByzantineValidators = req.ByzantineValidators + return abci.ResponseBeginBlock{} +} + +func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { + return abci.ResponseEndBlock{ValidatorUpdates: app.ValidatorUpdates} +} + +func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + return abci.ResponseDeliverTx{Events: []abci.Event{}} +} + +func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + return abci.ResponseCheckTx{} +} + +func (app *testApp) Commit() abci.ResponseCommit { + return abci.ResponseCommit{} +} + +func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { + return +} diff --git a/state/main_test.go b/state/main_test.go new file mode 100644 index 00000000..00ecf268 --- /dev/null +++ b/state/main_test.go @@ -0,0 +1,13 @@ +package state_test + +import ( + "os" + "testing" + + "github.com/tendermint/tendermint/types" +) + +func TestMain(m *testing.M) { + types.RegisterMockEvidencesGlobal() + os.Exit(m.Run()) +} diff --git a/state/services.go b/state/services.go index 98f6afce..10b389ee 100644 --- a/state/services.go +++ b/state/services.go @@ -43,7 +43,7 @@ type EvidencePool interface { IsCommitted(types.Evidence) bool } -// MockEvidencePool is an empty implementation of a Mempool, useful for testing. +// MockEvidencePool is an empty implementation of EvidencePool, useful for testing. type MockEvidencePool struct{} func (m MockEvidencePool) PendingEvidence(int64) []types.Evidence { return nil } diff --git a/state/state_test.go b/state/state_test.go index 1ff09ccd..a0f7a4a2 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "bytes" @@ -10,23 +10,22 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" ) // setupTestCase does setup common to all test cases. -func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, State) { +func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, sm.State) { config := cfg.ResetTestRoot("state_") dbType := dbm.DBBackendType(config.DBBackend) stateDB := dbm.NewDB("state", dbType, config.DBDir()) - state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) assert.NoError(t, err, "expected no error on LoadStateFromDBOrGenesisFile") tearDown := func(t *testing.T) { os.RemoveAll(config.RootDir) } @@ -59,7 +58,7 @@ func TestMakeGenesisStateNilValidators(t *testing.T) { Validators: nil, } require.Nil(t, doc.ValidateAndComplete()) - state, err := MakeGenesisState(&doc) + state, err := sm.MakeGenesisState(&doc) require.Nil(t, err) require.Equal(t, 0, len(state.Validators.Validators)) require.Equal(t, 0, len(state.NextValidators.Validators)) @@ -73,9 +72,9 @@ func TestStateSaveLoad(t *testing.T) { assert := assert.New(t) state.LastBlockHeight++ - SaveState(stateDB, state) + sm.SaveState(stateDB, state) - loadedState := LoadState(stateDB) + loadedState := sm.LoadState(stateDB) assert.True(state.Equals(loadedState), fmt.Sprintf("expected state and its copy to be identical.\ngot: %v\nexpected: %v\n", loadedState, state)) @@ -92,15 +91,15 @@ func TestABCIResponsesSaveLoad1(t *testing.T) { // Build mock responses. block := makeBlock(state, 2) - abciResponses := NewABCIResponses(block) + abciResponses := sm.NewABCIResponses(block) abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Events: nil} abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Events: nil} abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{ types.TM2PB.NewValidatorUpdate(ed25519.GenPrivKey().PubKey(), 10), }} - saveABCIResponses(stateDB, block.Height, abciResponses) - loadedABCIResponses, err := LoadABCIResponses(stateDB, block.Height) + sm.SaveABCIResponses(stateDB, block.Height, abciResponses) + loadedABCIResponses, err := sm.LoadABCIResponses(stateDB, block.Height) assert.Nil(err) assert.Equal(abciResponses, loadedABCIResponses, fmt.Sprintf("ABCIResponses don't match:\ngot: %v\nexpected: %v\n", @@ -155,24 +154,24 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { // Query all before, this should return error. for i := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(stateDB, h) + res, err := sm.LoadABCIResponses(stateDB, h) assert.Error(err, "%d: %#v", i, res) } // Add all cases. for i, tc := range cases { h := int64(i + 1) // last block height, one below what we save - responses := &ABCIResponses{ + responses := &sm.ABCIResponses{ DeliverTx: tc.added, EndBlock: &abci.ResponseEndBlock{}, } - saveABCIResponses(stateDB, h, responses) + sm.SaveABCIResponses(stateDB, h, responses) } // Query all before, should return expected value. for i, tc := range cases { h := int64(i + 1) - res, err := LoadABCIResponses(stateDB, h) + res, err := sm.LoadABCIResponses(stateDB, h) assert.NoError(err, "%d", i) assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i) } @@ -186,26 +185,26 @@ func TestValidatorSimpleSaveLoad(t *testing.T) { assert := assert.New(t) // Can't load anything for height 0. - v, err := LoadValidators(stateDB, 0) - assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0") + v, err := sm.LoadValidators(stateDB, 0) + assert.IsType(sm.ErrNoValSetForHeight{}, err, "expected err at height 0") // Should be able to load for height 1. - v, err = LoadValidators(stateDB, 1) + v, err = sm.LoadValidators(stateDB, 1) assert.Nil(err, "expected no err at height 1") assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match") // Should be able to load for height 2. - v, err = LoadValidators(stateDB, 2) + v, err = sm.LoadValidators(stateDB, 2) assert.Nil(err, "expected no err at height 2") assert.Equal(v.Hash(), state.NextValidators.Hash(), "expected validator hashes to match") // Increment height, save; should be able to load for next & next next height. state.LastBlockHeight++ nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) - vp0, err := LoadValidators(stateDB, nextHeight+0) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + vp0, err := sm.LoadValidators(stateDB, nextHeight+0) assert.Nil(err, "expected no err") - vp1, err := LoadValidators(stateDB, nextHeight+1) + vp1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(err, "expected no err") assert.Equal(vp0.Hash(), state.Validators.Hash(), "expected validator hashes to match") assert.Equal(vp1.Hash(), state.NextValidators.Hash(), "expected next validator hashes to match") @@ -234,13 +233,13 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { changeIndex++ power++ } - header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) + header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, power) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.NoError(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) } // On each height change, increment the power by one. @@ -258,7 +257,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } for i, power := range testCases { - v, err := LoadValidators(stateDB, int64(i+1+1)) // +1 because vset changes delayed by 1 block. + v, err := sm.LoadValidators(stateDB, int64(i+1+1)) // +1 because vset changes delayed by 1 block. assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i)) assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size()) _, val := v.GetByIndex(0) @@ -405,12 +404,12 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { block := makeBlock(state, state.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) curTotal := val1VotingPower // one increment step and one validator: 0 + power - total_power == 0 @@ -422,7 +421,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val2VotingPower} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -461,7 +460,7 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { // this will cause the diff of priorities (77) // to be larger than threshold == 2*totalVotingPower (22): - updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState3.NextValidators.Validators), 2) @@ -516,13 +515,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { block := makeBlock(state, state.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 @@ -537,7 +536,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) - updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState2, err := sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) @@ -574,7 +573,7 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState3, err := sm.UpdateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) @@ -598,13 +597,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // no changes in voting power and both validators have same voting power // -> proposers should alternate: oldState := updatedState3 - abciResponses = &ABCIResponses{ + abciResponses = &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - oldState, err = updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + oldState, err = sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) expectedVal1Prio2 = 1 expectedVal2Prio2 = -1 @@ -613,13 +612,13 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { for i := 0; i < 1000; i++ { // no validator updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) // alternate (and cyclic priorities): assert.NotEqual(t, updatedState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) @@ -660,7 +659,7 @@ func TestLargeGenesisValidator(t *testing.T) { oldState := state for i := 0; i < 10; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -669,7 +668,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, // than -Total == -Voting) @@ -689,18 +688,18 @@ func TestLargeGenesisValidator(t *testing.T) { firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower} validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) assert.NoError(t, err) - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err := sm.UpdateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) lastState := updatedState for i := 0; i < 200; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -709,7 +708,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(lastState, lastState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedStateInner, err := updateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedStateInner, err := sm.UpdateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) lastState = updatedStateInner } @@ -734,26 +733,26 @@ func TestLargeGenesisValidator(t *testing.T) { validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) assert.NoError(t, err) - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, } block := makeBlock(oldState, oldState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) } require.Equal(t, 10+2, len(state.NextValidators.Validators)) // remove genesis validator: removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0} - abciResponses = &ABCIResponses{ + abciResponses = &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, } block = makeBlock(oldState, oldState.LastBlockHeight+1) blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) - updatedState, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err = sm.UpdateState(state, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) // only the first added val (not the genesis val) should be left assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) @@ -764,14 +763,14 @@ func TestLargeGenesisValidator(t *testing.T) { count := 0 isProposerUnchanged := true for isProposerUnchanged { - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) require.NoError(t, err) block = makeBlock(curState, curState.LastBlockHeight+1) blockID = types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - curState, err = updateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) + curState, err = sm.UpdateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { isProposerUnchanged = false @@ -787,7 +786,7 @@ func TestLargeGenesisValidator(t *testing.T) { proposers := make([]*types.Validator, numVals) for i := 0; i < 100; i++ { // no updates: - abciResponses := &ABCIResponses{ + abciResponses := &sm.ABCIResponses{ EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, } validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -796,7 +795,7 @@ func TestLargeGenesisValidator(t *testing.T) { block := makeBlock(updatedState, updatedState.LastBlockHeight+1) blockID := types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(testPartSize).Header()} - updatedState, err = updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + updatedState, err = sm.UpdateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) require.NoError(t, err) if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): if proposers[i%numVals] == nil { @@ -814,15 +813,15 @@ func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { defer tearDown(t) state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) nextHeight := state.LastBlockHeight + 1 - v0, err := LoadValidators(stateDB, nextHeight) + v0, err := sm.LoadValidators(stateDB, nextHeight) assert.Nil(t, err) acc0 := v0.Validators[0].ProposerPriority - v1, err := LoadValidators(stateDB, nextHeight+1) + v1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(t, err) acc1 := v1.Validators[0].ProposerPriority @@ -838,28 +837,27 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { require.Equal(t, int64(0), state.LastBlockHeight) state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) _, valOld := state.Validators.GetByIndex(0) var pubkeyOld = valOld.PubKey pubkey := ed25519.GenPrivKey().PubKey() - const height = 1 // Swap the first validator with a new one (validator set size stays the same). - header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey) + header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, pubkey) // Save state etc. var err error var validatorUpdates []*types.Validator validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) + sm.SaveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) // Load nextheight, it should be the oldpubkey. - v0, err := LoadValidators(stateDB, nextHeight) + v0, err := sm.LoadValidators(stateDB, nextHeight) assert.Nil(t, err) assert.Equal(t, valSetSize, v0.Size()) index, val := v0.GetByAddress(pubkeyOld.Address()) @@ -869,7 +867,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { } // Load nextheight+1, it should be the new pubkey. - v1, err := LoadValidators(stateDB, nextHeight+1) + v1, err := sm.LoadValidators(stateDB, nextHeight+1) assert.Nil(t, err) assert.Equal(t, valSetSize, v1.Size()) index, val = v1.GetByAddress(pubkey.Address()) @@ -879,14 +877,6 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { } } -func genValSet(size int) *types.ValidatorSet { - vals := make([]*types.Validator, size) - for i := 0; i < size; i++ { - vals[i] = types.NewValidator(ed25519.GenPrivKey().PubKey(), 10) - } - return types.NewValidatorSet(vals) -} - func TestStateMakeBlock(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) @@ -932,14 +922,14 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { changeIndex++ cp = params[changeIndex] } - header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp) + header, blockID, responses := makeHeaderPartsResponsesParams(state, cp) validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) require.NoError(t, err) - state, err = updateState(state, blockID, &header, responses, validatorUpdates) + state, err = sm.UpdateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 - saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) + sm.SaveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) } // Make all the test cases by using the same params until after the change. @@ -957,32 +947,15 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { } for _, testCase := range testCases { - p, err := LoadConsensusParams(stateDB, testCase.height) + p, err := sm.LoadConsensusParams(stateDB, testCase.height) assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height)) assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at height %d`, testCase.height)) } } -func makeParams( - blockBytes, blockGas int64, - blockTimeIotaMs int64, - evidenceAge int64, -) types.ConsensusParams { - return types.ConsensusParams{ - Block: types.BlockParams{ - MaxBytes: blockBytes, - MaxGas: blockGas, - TimeIotaMs: blockTimeIotaMs, - }, - Evidence: types.EvidenceParams{ - MaxAge: evidenceAge, - }, - } -} - func TestApplyUpdates(t *testing.T) { - initParams := makeParams(1, 2, 3, 4) + initParams := makeConsensusParams(1, 2, 3, 4) cases := [...]struct { init types.ConsensusParams @@ -998,14 +971,14 @@ func TestApplyUpdates(t *testing.T) { MaxGas: 55, }, }, - makeParams(44, 55, 3, 4)}, + makeConsensusParams(44, 55, 3, 4)}, 3: {initParams, abci.ConsensusParams{ Evidence: &abci.EvidenceParams{ MaxAge: 66, }, }, - makeParams(1, 2, 3, 66)}, + makeConsensusParams(1, 2, 3, 66)}, } for i, tc := range cases { @@ -1013,61 +986,3 @@ func TestApplyUpdates(t *testing.T) { assert.Equal(t, tc.expected, res, "case %d", i) } } - -func makeHeaderPartsResponsesValPubKeyChange(state State, height int64, - pubkey crypto.PubKey) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, - } - - // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) - if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { - abciResponses.EndBlock = &abci.ResponseEndBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{ - types.TM2PB.NewValidatorUpdate(val.PubKey, 0), - types.TM2PB.NewValidatorUpdate(pubkey, 10), - }, - } - } - - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -func makeHeaderPartsResponsesValPowerChange(state State, height int64, - power int64) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, - } - - // If the pubkey is new, remove the old and add the new. - _, val := state.NextValidators.GetByIndex(0) - if val.VotingPower != power { - abciResponses.EndBlock = &abci.ResponseEndBlock{ - ValidatorUpdates: []abci.ValidatorUpdate{ - types.TM2PB.NewValidatorUpdate(val.PubKey, power), - }, - } - } - - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -func makeHeaderPartsResponsesParams(state State, height int64, - params types.ConsensusParams) (types.Header, types.BlockID, *ABCIResponses) { - - block := makeBlock(state, state.LastBlockHeight+1) - abciResponses := &ABCIResponses{ - EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)}, - } - return block.Header, types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}, abciResponses -} - -type paramsChangeTestCase struct { - height int64 - params types.ConsensusParams -} diff --git a/state/store_test.go b/state/store_test.go index 06adeefa..0cf21772 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "fmt" @@ -10,6 +10,7 @@ import ( cfg "github.com/tendermint/tendermint/config" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -19,9 +20,9 @@ func TestStoreLoadValidators(t *testing.T) { vals := types.NewValidatorSet([]*types.Validator{val}) // 1) LoadValidators loads validators using a height where they were last changed - saveValidatorsInfo(stateDB, 1, 1, vals) - saveValidatorsInfo(stateDB, 2, 1, vals) - loadedVals, err := LoadValidators(stateDB, 2) + sm.SaveValidatorsInfo(stateDB, 1, 1, vals) + sm.SaveValidatorsInfo(stateDB, 2, 1, vals) + loadedVals, err := sm.LoadValidators(stateDB, 2) require.NoError(t, err) assert.NotZero(t, loadedVals.Size()) @@ -30,13 +31,13 @@ func TestStoreLoadValidators(t *testing.T) { // TODO(melekes): REMOVE in 0.33 release // https://github.com/tendermint/tendermint/issues/3543 // for releases prior to v0.31.4, it uses last height changed - valInfo := &ValidatorsInfo{ - LastHeightChanged: valSetCheckpointInterval, + valInfo := &sm.ValidatorsInfo{ + LastHeightChanged: sm.ValSetCheckpointInterval, } - stateDB.Set(calcValidatorsKey(valSetCheckpointInterval), valInfo.Bytes()) + stateDB.Set(sm.CalcValidatorsKey(sm.ValSetCheckpointInterval), valInfo.Bytes()) assert.NotPanics(t, func() { - saveValidatorsInfo(stateDB, valSetCheckpointInterval+1, 1, vals) - loadedVals, err := LoadValidators(stateDB, valSetCheckpointInterval+1) + sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval+1, 1, vals) + loadedVals, err := sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval+1) if err != nil { t.Fatal(err) } @@ -46,9 +47,9 @@ func TestStoreLoadValidators(t *testing.T) { }) // ENDREMOVE - saveValidatorsInfo(stateDB, valSetCheckpointInterval, 1, vals) + sm.SaveValidatorsInfo(stateDB, sm.ValSetCheckpointInterval, 1, vals) - loadedVals, err = LoadValidators(stateDB, valSetCheckpointInterval) + loadedVals, err = sm.LoadValidators(stateDB, sm.ValSetCheckpointInterval) require.NoError(t, err) assert.NotZero(t, loadedVals.Size()) } @@ -60,20 +61,20 @@ func BenchmarkLoadValidators(b *testing.B) { defer os.RemoveAll(config.RootDir) dbType := dbm.DBBackendType(config.DBBackend) stateDB := dbm.NewDB("state", dbType, config.DBDir()) - state, err := LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) if err != nil { b.Fatal(err) } state.Validators = genValSet(valSetSize) state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) - SaveState(stateDB, state) + sm.SaveState(stateDB, state) for i := 10; i < 10000000000; i *= 10 { // 10, 100, 1000, ... - saveValidatorsInfo(stateDB, int64(i), state.LastHeightValidatorsChanged, state.NextValidators) + sm.SaveValidatorsInfo(stateDB, int64(i), state.LastHeightValidatorsChanged, state.NextValidators) b.Run(fmt.Sprintf("height=%d", i), func(b *testing.B) { for n := 0; n < b.N; n++ { - _, err := LoadValidators(stateDB, int64(i)) + _, err := sm.LoadValidators(stateDB, int64(i)) if err != nil { b.Fatal(err) } diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index e48ad2c3..bd324316 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -1,4 +1,4 @@ -package state +package state_test import ( "os" @@ -7,11 +7,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - tmtime "github.com/tendermint/tendermint/types/time" ) func TestTxFilter(t *testing.T) { @@ -34,10 +33,10 @@ func TestTxFilter(t *testing.T) { for i, tc := range testCases { stateDB := dbm.NewDB("state", "memdb", os.TempDir()) - state, err := LoadStateFromDBOrGenesisDoc(stateDB, genDoc) + state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) require.NoError(t, err) - f := TxPreCheck(state) + f := sm.TxPreCheck(state) if tc.isErr { assert.NotNil(t, f(tc.tx), "#%v", i) } else { @@ -45,13 +44,3 @@ func TestTxFilter(t *testing.T) { } } } - -func randomGenesisDoc() *types.GenesisDoc { - pubkey := ed25519.GenPrivKey().PubKey() - return &types.GenesisDoc{ - GenesisTime: tmtime.Now(), - ChainID: "abc", - Validators: []types.GenesisValidator{{Address: pubkey.Address(), PubKey: pubkey, Power: 10, Name: "myval"}}, - ConsensusParams: types.DefaultConsensusParams(), - } -} diff --git a/state/validation.go b/state/validation.go index 3c63c35b..1d365e90 100644 --- a/state/validation.go +++ b/state/validation.go @@ -94,10 +94,7 @@ func validateBlock(evidencePool EvidencePool, stateDB dbm.DB, state State, block } } else { if len(block.LastCommit.Precommits) != state.LastValidators.Size() { - return fmt.Errorf("Invalid block commit size. Expected %v, got %v", - state.LastValidators.Size(), - len(block.LastCommit.Precommits), - ) + return types.NewErrInvalidCommitPrecommits(state.LastValidators.Size(), len(block.LastCommit.Precommits)) } err := state.LastValidators.VerifyCommit( state.ChainID, state.LastBlockID, block.Height-1, block.LastCommit) diff --git a/state/validation_test.go b/state/validation_test.go index 705f843d..c53cf010 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -1,31 +1,30 @@ -package state +package state_test import ( "testing" "time" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/mock" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) -// TODO(#2589): -// - generalize this past the first height -// - add txs and build up full State properly -// - test block.Time (see #2587 - there are no conditions on time for the first height) +const validationTestsStopHeight int64 = 10 + func TestValidateBlockHeader(t *testing.T) { - var height int64 = 1 // TODO(#2589): generalize - state, stateDB := state(1, int(height)) + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) - - // A good block passes. - block := makeBlock(state, height) - err := blockExec.ValidateBlock(state, block) - require.NoError(t, err) + state, stateDB, privVals := makeState(3, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) // some bad values wrongHash := tmhash.Sum([]byte("this hash is wrong")) @@ -43,7 +42,7 @@ func TestValidateBlockHeader(t *testing.T) { {"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }}, {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, {"Height wrong", func(block *types.Block) { block.Height += 10 }}, - {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 3600 * 24) }}, + {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }}, {"NumTxs wrong", func(block *types.Block) { block.NumTxs += 10 }}, {"TotalTxs wrong", func(block *types.Block) { block.TotalTxs += 10 }}, @@ -62,78 +61,145 @@ func TestValidateBlockHeader(t *testing.T) { {"Proposer invalid", func(block *types.Block) { block.ProposerAddress = []byte("wrong size") }}, } - for _, tc := range testCases { - block := makeBlock(state, height) - tc.malleateBlock(block) - err := blockExec.ValidateBlock(state, block) - require.Error(t, err, tc.name) + // Build up state for multiple heights + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + /* + Invalid blocks don't pass + */ + for _, tc := range testCases { + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, proposerAddr) + tc.malleateBlock(block) + err := blockExec.ValidateBlock(state, block) + require.Error(t, err, tc.name) + } + + /* + A good block passes + */ + var err error + state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil) + require.NoError(t, err, "height %d", height) } } -/* - TODO(#2589): - - test Block.Data.Hash() == Block.DataHash - - test len(Block.Data.Txs) == Block.NumTxs -*/ -func TestValidateBlockData(t *testing.T) { -} - -/* - TODO(#2589): - - test len(block.LastCommit.Precommits) == state.LastValidators.Size() - - test state.LastValidators.VerifyCommit -*/ func TestValidateBlockCommit(t *testing.T) { -} + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() -/* - TODO(#2589): - - test good/bad evidence in block -*/ -func TestValidateBlockEvidence(t *testing.T) { - var height int64 = 1 // TODO(#2589): generalize - state, stateDB := state(1, int(height)) + state, stateDB, privVals := makeState(1, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) + wrongPrecommitsCommit := types.NewCommit(types.BlockID{}, nil) + badPrivVal := types.NewMockPV() - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, nil) + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + if height > 1 { + /* + #2589: ensure state.LastValidators.VerifyCommit fails here + */ + // should be height-1 instead of height + wrongHeightVote, err := makeVote(height, state.LastBlockID, state.Validators, privVals[proposerAddr.String()]) + require.NoError(t, err, "height %d", height) + wrongHeightCommit := types.NewCommit(state.LastBlockID, []*types.CommitSig{wrongHeightVote.CommitSig()}) + block, _ := state.MakeBlock(height, makeTxs(height), wrongHeightCommit, nil, proposerAddr) + err = blockExec.ValidateBlock(state, block) + _, isErrInvalidCommitHeight := err.(types.ErrInvalidCommitHeight) + require.True(t, isErrInvalidCommitHeight, "expected ErrInvalidCommitHeight at height %d but got: %v", height, err) - // make some evidence - addr, _ := state.Validators.GetByIndex(0) - goodEvidence := types.NewMockGoodEvidence(height, 0, addr) + /* + #2589: test len(block.LastCommit.Precommits) == state.LastValidators.Size() + */ + block, _ = state.MakeBlock(height, makeTxs(height), wrongPrecommitsCommit, nil, proposerAddr) + err = blockExec.ValidateBlock(state, block) + _, isErrInvalidCommitPrecommits := err.(types.ErrInvalidCommitPrecommits) + require.True(t, isErrInvalidCommitPrecommits, "expected ErrInvalidCommitPrecommits at height %d but got: %v", height, err) + } - // A block with a couple pieces of evidence passes. - block := makeBlock(state, height) - block.Evidence.Evidence = []types.Evidence{goodEvidence, goodEvidence} - block.EvidenceHash = block.Evidence.Hash() - err := blockExec.ValidateBlock(state, block) - require.NoError(t, err) + /* + A good block passes + */ + var err error + var blockID types.BlockID + state, blockID, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil) + require.NoError(t, err, "height %d", height) - // A block with too much evidence fails. - maxBlockSize := state.ConsensusParams.Block.MaxBytes - maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) - require.True(t, maxNumEvidence > 2) - for i := int64(0); i < maxNumEvidence; i++ { - block.Evidence.Evidence = append(block.Evidence.Evidence, goodEvidence) + /* + wrongPrecommitsCommit is fine except for the extra bad precommit + */ + goodVote, err := makeVote(height, blockID, state.Validators, privVals[proposerAddr.String()]) + require.NoError(t, err, "height %d", height) + badVote := &types.Vote{ + ValidatorAddress: badPrivVal.GetPubKey().Address(), + ValidatorIndex: 0, + Height: height, + Round: 0, + Timestamp: tmtime.Now(), + Type: types.PrecommitType, + BlockID: blockID, + } + err = badPrivVal.SignVote(chainID, goodVote) + require.NoError(t, err, "height %d", height) + wrongPrecommitsCommit = types.NewCommit(blockID, []*types.CommitSig{goodVote.CommitSig(), badVote.CommitSig()}) } - block.EvidenceHash = block.Evidence.Hash() - err = blockExec.ValidateBlock(state, block) - require.Error(t, err) - _, ok := err.(*types.ErrEvidenceOverflow) - require.True(t, ok) } -// always returns true if asked if any evidence was already committed. -type mockEvPoolAlwaysCommitted struct{} +func TestValidateBlockEvidence(t *testing.T) { + proxyApp := newTestApp() + require.NoError(t, proxyApp.Start()) + defer proxyApp.Stop() -func (m mockEvPoolAlwaysCommitted) PendingEvidence(int64) []types.Evidence { return nil } -func (m mockEvPoolAlwaysCommitted) AddEvidence(types.Evidence) error { return nil } -func (m mockEvPoolAlwaysCommitted) Update(*types.Block, State) {} -func (m mockEvPoolAlwaysCommitted) IsCommitted(types.Evidence) bool { return true } + state, stateDB, privVals := makeState(3, 1) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{}) + lastCommit := types.NewCommit(types.BlockID{}, nil) + + for height := int64(1); height < validationTestsStopHeight; height++ { + proposerAddr := state.Validators.GetProposer().Address + proposerIdx, _ := state.Validators.GetByAddress(proposerAddr) + goodEvidence := types.NewMockGoodEvidence(height, proposerIdx, proposerAddr) + if height > 1 { + /* + A block with too much evidence fails + */ + maxBlockSize := state.ConsensusParams.Block.MaxBytes + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) + require.True(t, maxNumEvidence > 2) + evidence := make([]types.Evidence, 0) + // one more than the maximum allowed evidence + for i := int64(0); i <= maxNumEvidence; i++ { + evidence = append(evidence, goodEvidence) + } + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr) + err := blockExec.ValidateBlock(state, block) + _, ok := err.(*types.ErrEvidenceOverflow) + require.True(t, ok, "expected error to be of type ErrEvidenceOverflow at height %d", height) + } + + /* + A good block with several pieces of good evidence passes + */ + maxBlockSize := state.ConsensusParams.Block.MaxBytes + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) + require.True(t, maxNumEvidence > 2) + evidence := make([]types.Evidence, 0) + // precisely the amount of allowed evidence + for i := int64(0); i < maxNumEvidence; i++ { + evidence = append(evidence, goodEvidence) + } + + var err error + state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, evidence) + require.NoError(t, err, "height %d", height) + } +} func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { var height int64 = 1 - state, stateDB := state(1, int(height)) + state, stateDB, _ := makeState(1, int(height)) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, mockEvPoolAlwaysCommitted{}) + blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), nil, nil, mockEvPoolAlwaysCommitted{}) // A block with a couple pieces of evidence passes. block := makeBlock(state, height) addr, _ := state.Validators.GetByIndex(0) @@ -145,12 +211,3 @@ func TestValidateFailBlockOnCommittedEvidence(t *testing.T) { require.Error(t, err) require.IsType(t, err, &types.ErrEvidenceInvalid{}) } - -/* - TODO(#2589): - - test unmarshalling BlockParts that are too big into a Block that - (note this logic happens in the consensus, not in the validation here). - - test making blocks from the types.MaxXXX functions works/fails as expected -*/ -func TestValidateBlockSize(t *testing.T) { -} diff --git a/types/errors.go b/types/errors.go new file mode 100644 index 00000000..603ac51d --- /dev/null +++ b/types/errors.go @@ -0,0 +1,41 @@ +package types + +import "fmt" + +type ( + // ErrInvalidCommitHeight is returned when we encounter a commit with an + // unexpected height. + ErrInvalidCommitHeight struct { + Expected int64 + Actual int64 + } + + // ErrInvalidCommitPrecommits is returned when we encounter a commit where + // the number of precommits doesn't match the number of validators. + ErrInvalidCommitPrecommits struct { + Expected int + Actual int + } +) + +func NewErrInvalidCommitHeight(expected, actual int64) ErrInvalidCommitHeight { + return ErrInvalidCommitHeight{ + Expected: expected, + Actual: actual, + } +} + +func (e ErrInvalidCommitHeight) Error() string { + return fmt.Sprintf("Invalid commit -- wrong height: %v vs %v", e.Expected, e.Actual) +} + +func NewErrInvalidCommitPrecommits(expected, actual int) ErrInvalidCommitPrecommits { + return ErrInvalidCommitPrecommits{ + Expected: expected, + Actual: actual, + } +} + +func (e ErrInvalidCommitPrecommits) Error() string { + return fmt.Sprintf("Invalid commit -- wrong set size: %v vs %v", e.Expected, e.Actual) +} diff --git a/types/validator_set.go b/types/validator_set.go index 9e78fbc7..65358714 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -594,10 +594,10 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i return err } if vals.Size() != len(commit.Precommits) { - return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits)) + return NewErrInvalidCommitPrecommits(vals.Size(), len(commit.Precommits)) } if height != commit.Height() { - return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height()) + return NewErrInvalidCommitHeight(height, commit.Height()) } if !blockID.Equals(commit.BlockID) { return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v", From 8fc83684384423eafbf2a3f421de3ff09216c0bc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 22 Jun 2019 10:30:23 +0400 Subject: [PATCH 08/27] node: run whole func in goroutine, not just logger.Error fn (#3743) * node: run whole func in goroutine, not just logger.Error fn Fixes #3741 * add a changelog entry --- CHANGELOG_PENDING.md | 1 + node/node.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 74aa72c6..1bff64b6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -40,3 +40,4 @@ - [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) - [libs/db] \#3718 Fixed the BoltDB backend's Get and Iterator implementation (@Yawning) - [node] \#3716 Fix a bug where `nil` is recorded as node's address +- [node] \#3741 Fix profiler blocking the entire node diff --git a/node/node.go b/node/node.go index 85fef5ee..c992e242 100644 --- a/node/node.go +++ b/node/node.go @@ -630,7 +630,9 @@ func NewNode(config *cfg.Config, } if config.ProfListenAddress != "" { - go logger.Error("Profile server", "err", http.ListenAndServe(config.ProfListenAddress, nil)) + go func() { + logger.Error("Profile server", "err", http.ListenAndServe(config.ProfListenAddress, nil)) + }() } node := &Node{ From 1b77bf6f209fae35cb4693123572263f76c0a0bf Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 22 Jun 2019 19:44:12 +0400 Subject: [PATCH 09/27] rpc/lib: write a test for TLS server (#3703) * rpc/lib: write a test for TLS server Refs #3700 * do not regenerate certificates * add nolint --- Makefile | 29 ++++++++++------- libs/db/remotedb/test.crt | 40 ++++++++++++++---------- libs/db/remotedb/test.key | 50 +++++++++++++++--------------- libs/test.sh | 6 ---- rpc/lib/server/http_server_test.go | 33 +++++++++++++------- rpc/lib/server/test.crt | 25 +++++++++++++++ rpc/lib/server/test.key | 27 ++++++++++++++++ 7 files changed, 140 insertions(+), 70 deletions(-) create mode 100644 rpc/lib/server/test.crt create mode 100644 rpc/lib/server/test.key diff --git a/Makefile b/Makefile index 1980ac86..3dff35b9 100644 --- a/Makefile +++ b/Makefile @@ -115,24 +115,31 @@ get_deps_bin_size: protoc_libs: libs/common/types.pb.go +# generates certificates for TLS testing in remotedb and RPC server gen_certs: clean_certs - ## Generating certificates for TLS testing... certstrap init --common-name "tendermint.com" --passphrase "" - certstrap request-cert -ip "::" --passphrase "" - certstrap sign "::" --CA "tendermint.com" --passphrase "" - mv out/::.crt out/::.key db/remotedb - -clean_certs: - ## Cleaning TLS testing certificates... + certstrap request-cert --common-name "remotedb" -ip "127.0.0.1" --passphrase "" + certstrap sign "remotedb" --CA "tendermint.com" --passphrase "" + mv out/remotedb.crt libs/db/remotedb/test.crt + mv out/remotedb.key libs/db/remotedb/test.key + certstrap request-cert --common-name "server" -ip "127.0.0.1" --passphrase "" + certstrap sign "server" --CA "tendermint.com" --passphrase "" + mv out/server.crt rpc/lib/server/test.crt + mv out/server.key rpc/lib/server/test.key rm -rf out - rm -f db/remotedb/::.crt db/remotedb/::.key -test_libs: gen_certs +# deletes generated certificates +clean_certs: + rm -f libs/db/remotedb/test.crt + rm -f libs/db/remotedb/test.key + rm -f rpc/lib/server/test.crt + rm -f rpc/lib/server/test.key + +test_libs: go test -tags clevedb boltdb $(PACKAGES) - make clean_certs grpc_dbserver: - protoc -I db/remotedb/proto/ db/remotedb/proto/defs.proto --go_out=plugins=grpc:db/remotedb/proto + protoc -I libs/db/remotedb/proto/ libs/db/remotedb/proto/defs.proto --go_out=plugins=grpc:libs/db/remotedb/proto protoc_grpc: rpc/grpc/types.pb.go diff --git a/libs/db/remotedb/test.crt b/libs/db/remotedb/test.crt index 06ffec1d..1090e73d 100644 --- a/libs/db/remotedb/test.crt +++ b/libs/db/remotedb/test.crt @@ -1,19 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIDAjCCAeqgAwIBAgIJAOGCVedOwRbOMA0GCSqGSIb3DQEBBQUAMCExCzAJBgNV -BAYTAlVTMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTkwMjExMTU0NjQ5WhcNMjAw -MjExMTU0NjQ5WjAhMQswCQYDVQQGEwJVUzESMBAGA1UEAwwJbG9jYWxob3N0MIIB -IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA60S/fNUWoHm1PYI/yrlnZNtr -dRqDORHe0hPwl/lttLz7+a7HzQZFnpiXnuxbDJtpIq/h1vhAl0sFy86Ip26LhbWc -GjxJL24tVwiOwqYRzTPZ/rK3JYuNcIvcztXjMqdzPrHSZy5YZgrQB6yhTiqpBc4D -h/XgWjEt4DhpHwf/zuIK9XkJw0IaTWjFmoyKRoWW3q4bHzoKNxS9bXP117Tz7tn0 -AdsQCjt1GKcIROkcOGUHqByINJ2XlBkb7SQPjQVBLDVJKdRDUt+yHkkdbn97UDhq -HRTCt5UELWs/53Gj1ffNuhjECOVjG1HkZweLgZjJRQYe8X2OOLNOyfVY1KsDnQID -AQABoz0wOzAMBgNVHRMEBTADAQH/MCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIJbG9j -YWxob3N0hwQAAAAAhwR/AAABMA0GCSqGSIb3DQEBBQUAA4IBAQCe2A5gDc3jiZwT -a5TJrc2J2KouqxB/PCddw5VY8jPsZJfsr9gxHi+Xa5g8p3oqmEOIlqM5BVhrZRUG -RWHDmL+bCsuzMoA/vGHtHmUIwLeZQLWgT3kv12Dc8M9flNNjmXWxdMR9lOMwcL83 -F0CdElxSmaEbNvCIJBDetJJ7vMCqS2lnTLWurbH4ZGeGwvjzNgpgGCKwbyK/gU+j -UXiTQbVvPQ3WWACDnfH6rg0TpxU9jOBkd+4/9tUrBG7UclQBfGULk3sObLO9kx4N -8RxJmtp8jljIXVPX3udExI05pz039pAgvaeZWtP17QSbYcKF1jFtKo6ckrv2GKXX -M5OXGXdw +MIIEOjCCAiKgAwIBAgIQYO+jRR0Sbs+WzU/hj2aoxzANBgkqhkiG9w0BAQsFADAZ +MRcwFQYDVQQDEw50ZW5kZXJtaW50LmNvbTAeFw0xOTA2MDIxMTAyMDdaFw0yMDEy +MDIxMTAyMDRaMBMxETAPBgNVBAMTCHJlbW90ZWRiMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAt7YkYMJ5X5X3MT1tWG1KFO3uyZl962fInl+43xVESydp +qYYHYei7b3T8c/3Ww6f3aKkkCHrvPtqHZjU6o+wp/AQMNlyUoyRN89+6Oj67u2C7 +iZjzAJ+Pk87jMaStubvmZ9J+uk4op4rv5Rt4ns/Kg70RaMvqYR8tGqPcy3o8fWS+ +hCbuwAS8b65yp+AgbnThDEBUnieN3OFLfDV//45qw2OlqlM/gHOVT2JMRbl14Y7x +tW3/Xe+lsB7B3+OC6NQ2Nu7DEA1X+TBNyItIGnQH6DwK2ZBRtyQEk26FAWVj8fHd +A5I4+RcGWXz4T6gJmDZN7+47WHO0ProjARbUV0GIuQIDAQABo4GDMIGAMA4GA1Ud +DwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0O +BBYEFOA8wzCYhoZmy0WHgnv/0efijUMKMB8GA1UdIwQYMBaAFNSTPe743aIx7rIp +vn5HV3gJ4z1hMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggIBAKZf +EVo0i9nMZv6ZJjbmAlMfo5FH41/oBYC8pyGAnJKl42raXKJAbl45h80iGn3vNggf +7HJjN+znAHDFYjIwK2IV2WhHPyxK6uk+FA5uBR/aAPcw+zhRfXUMYdhNHr6KBlZZ +bvD7Iq4UALg+XFQz/fQkIi7QvTBwkYyPNA2+a/TGf6myMp26hoz73DQXklqm6Zle +myPs1Vp9bTgOv/3l64BMUV37FZ2TyiisBkV1qPEoDxT7Fbi8G1K8gMDLd0wu0jvX +nz96nk30TDnZewV1fhkMJVKKGiLbaIgHcu1lWsWJZ0tdc+MF7R9bLBO5T0cTDgNy +V8/51g+Cxu5SSHKjFkT0vBBONhjPmRqzJpxOQfHjiv8mmHwwiaNNy2VkJHj5GHer +64r67fQTSqAifzgwAbXYK+ObUbx4PnHvSYSF5dbcR1Oj6UTVtGAgdmN2Y03AIc1B +CiaojcMVuMRz/SvmPWl34GBvvT5/h9VCpHEB3vV6bQxJb5U1fLyo4GABA2Ic3DHr +kV5p7CZI06UNbyQyFtnEb5XoXywRa4Df7FzDIv3HL13MtyXrYrJqC1eAbn+3jGdh +bQa510mWYAlQQmzHSf/SLKott4QKR3SmhOGqGKNvquAYJ9XLdYdsPmKKGH6iGUD8 +n7yEi0KMD/BHsPQNNLatsR2SxqGDeLhbLR0w2hig -----END CERTIFICATE----- diff --git a/libs/db/remotedb/test.key b/libs/db/remotedb/test.key index e1adb3e1..b30bf809 100644 --- a/libs/db/remotedb/test.key +++ b/libs/db/remotedb/test.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/libs/test.sh b/libs/test.sh index 64898b0d..d0618768 100755 --- a/libs/test.sh +++ b/libs/test.sh @@ -4,9 +4,6 @@ set -e # run the linter # make lint -# setup certs -make gen_certs - # run the unit tests with coverage echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do @@ -16,6 +13,3 @@ for d in $(go list ./... | grep -v vendor); do rm profile.out fi done - -# cleanup certs -make clean_certs diff --git a/rpc/lib/server/http_server_test.go b/rpc/lib/server/http_server_test.go index 7f47a30b..b463aa6a 100644 --- a/rpc/lib/server/http_server_test.go +++ b/rpc/lib/server/http_server_test.go @@ -1,16 +1,18 @@ package rpcserver import ( + "crypto/tls" "fmt" "io" "io/ioutil" + "net" "net/http" - "os" "sync" "sync/atomic" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" @@ -66,18 +68,27 @@ func TestMaxOpenConnections(t *testing.T) { } func TestStartHTTPAndTLSServer(t *testing.T) { - config := DefaultConfig() - config.MaxOpenConnections = 1 - // set up fixtures - listenerAddr := "tcp://0.0.0.0:0" - listener, err := Listen(listenerAddr, config) + ln, err := net.Listen("tcp", "localhost:0") require.NoError(t, err) + defer ln.Close() + mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "some body") + }) - // test failure - err = StartHTTPAndTLSServer(listener, mux, "", "", log.TestingLogger(), config) - require.IsType(t, (*os.PathError)(nil), err) + go StartHTTPAndTLSServer(ln, mux, "test.crt", "test.key", log.TestingLogger(), DefaultConfig()) - // TODO: test that starting the server can actually work + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint: gosec + } + c := &http.Client{Transport: tr} + res, err := c.Get("https://" + ln.Addr().String()) + require.NoError(t, err) + defer res.Body.Close() + assert.Equal(t, http.StatusOK, res.StatusCode) + + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, []byte("some body"), body) } diff --git a/rpc/lib/server/test.crt b/rpc/lib/server/test.crt new file mode 100644 index 00000000..e4ab1965 --- /dev/null +++ b/rpc/lib/server/test.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEODCCAiCgAwIBAgIQWDHUrd4tOM2xExWhzOEJ7DANBgkqhkiG9w0BAQsFADAZ +MRcwFQYDVQQDEw50ZW5kZXJtaW50LmNvbTAeFw0xOTA2MDIxMTAyMDdaFw0yMDEy +MDIxMTAyMDRaMBExDzANBgNVBAMTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANBaa6dc9GZcIhAHWqVrx0LONYf+IlbvTP7yrV45ws0ix8TX +1NUOiDY1cwzKH8ay/HYX45e2fFLrtLidc9h+apsC55k3Vdcy00+Ksr/adjR8D4A/ +GpnTS+hVDHTlqINe9a7USok34Zr1rc3fh4Imu5RxEurjMwkA/36k6+OpXMp2qlKY +S1fGqwn2KGhXkp/yTWZILEMXBazNxGx4xfqYXzWm6boeyJAXpM2DNkv7dtwa/CWY +WacUQJApNInwn5+B8LLoo+pappkfZOjAD9/aHKsyFTSWmmWeg7V//ouB3u5vItqf +GP+3xmPgeYeEyOIe/P2f8bRuQs+GGwSCmi6F1GUCAwEAAaOBgzCBgDAOBgNVHQ8B +Af8EBAMCA7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW +BBSpBFIMbkBR4xVYQZtUJQQwzPmbHjAfBgNVHSMEGDAWgBTUkz3u+N2iMe6yKb5+ +R1d4CeM9YTAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQBCqdzS +tPHkMYWjYs6aREwob9whjyG8a4Qp6IkP1SYHCwpzsTeWLi9ybEcDRb3jZ4iRxbZg +7GFxjqHoWgBZHAIyICMsHupOJEtXq5hx86NuMwk/12bx1eNj0yTIAnVOA+em/ZtB +zR38OwB8xXmjKd0Ow1Y7zCh5zE2gU+sR0JOJSfxXUZrJvwDNrbcmZPQ+kwuq4cyv +fxZnvZf/owbyOLQFdbiPQbbiZ7JSv8q7GCMleULCEygrsWClYkULUByhKykCHJIU +wfq1owge9EqG/4CDCCjB9vBFmUyv3FJhgWnzd6tPQckFoHSoD0Bjsv/pQFcsGLcg ++e/Mm6hZgCXXwI2WHYbxqz5ToOaRQQYo6N77jWejOBMecOZmPDyQ2nz73aJd11GW +NiDT7pyMlBJA8W4wAvVP4ow2ugqsPjqZ6EyismIGFUTqMp+NtXOsLPK+sEMhKhJ9 +ulczRpPEf25roBt6aEk2fTAfAPmbpvNamBLSbBU23mzJ38RmfhxLOlOgCGbBBX4d +kE+/+En8UJO4X8CKaKRo/c5G2UZ6++2cjp6SPrsGENDMW5yBGegrDw+ow8/bLxIr +OjWpSe2cygovy3aHE6UBOgkxw9KIaSEqFgjQZ0i+xO6l6qQoljQgUGXfecVMR+7C +4KsyVVTMlK9/thA7Zfc8a5z8ZCtIKkT52XsJhw== +-----END CERTIFICATE----- diff --git a/rpc/lib/server/test.key b/rpc/lib/server/test.key new file mode 100644 index 00000000..bb9af06b --- /dev/null +++ b/rpc/lib/server/test.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQEA0Fprp1z0ZlwiEAdapWvHQs41h/4iVu9M/vKtXjnCzSLHxNfU +1Q6INjVzDMofxrL8dhfjl7Z8Uuu0uJ1z2H5qmwLnmTdV1zLTT4qyv9p2NHwPgD8a +mdNL6FUMdOWog171rtRKiTfhmvWtzd+Hgia7lHES6uMzCQD/fqTr46lcynaqUphL +V8arCfYoaFeSn/JNZkgsQxcFrM3EbHjF+phfNabpuh7IkBekzYM2S/t23Br8JZhZ +pxRAkCk0ifCfn4Hwsuij6lqmmR9k6MAP39ocqzIVNJaaZZ6DtX/+i4He7m8i2p8Y +/7fGY+B5h4TI4h78/Z/xtG5Cz4YbBIKaLoXUZQIDAQABAoH/NodzpVmunRt/zrIe +By0t+U3+tJjOY/I9NHxO41o6oXV40wupqBkljQpwEejUaCxv5nhaGFqqLwmBQs/y +gbaUL/2Sn4bb8HZc13R1U8DZLuNJK0dYrumd9DBOEkoI0FkJ87ebyk3VvbiOxFK8 +JFP+w9rUGKVdtf2M4JhJJEwu/M2Yawx9/8CrCIY2G6ufaylrIysLeQMsxrogF8n4 +hq7fyqveWRzxhqUxS2fp9Ynpx4jnd1lMzv+z3i8eEsW+gB9yke7UkXZMbtZg1xfB +JjiEfcDVfSwSihhgOYttgQ9hkIdohDUak7OzRSWVBuoxWUhMfrQxw/HZlgZJL9Vf +rGdlAoGBANOGmgEGky+acV33WTWGV5OdAw6B/SlBEoORJbj6UzQiUz3hFH/Tgpbj +JOKHWGbGd8OtOYbt9JoofGlNgHA/4nAEYAc2HGa+q0fBwMUflU0DudAxXis4jDmE +D76moGmyJoSgwVrp1W/vwNixA5RpcZ3Wst2nf9RKLr+DxypHTit/AoGBAPwpDeqc +rwXOTl0KR/080Nc11Z03VIVZAGfA59J73HmADF9bBVlmReQdkwX0lERchdzD0lfa +XqbqBLr4FS5Uqyn5f3DCaMnOeKfvtGw2z6LnY+w03mii4PEW/vNKLlB18NdduPwL +KeAc08Zh+qJFMKD1PoEQOH+Y7NybBbaQL8IbAoGAfPPUYaq6o7I+Kd4FysKTVVW5 +CobrP8V65FGH0R++qttkBPfDHkeZqvx/O3nsVLoE4YigpP5IMhCcfbAUoTp7zuQm +vdvPJzqW/4qLD2c60QXUbBHdqPZ8jzVd/6d6tzVP36T+02+yb69XYiofDTrErRK5 +EorxzjwMJYH40xbQLI0CgYBh7d/FucwPSSwN3ixPIQtKSVIImLBuiT4rDTP6/reF +SEGF1ueg7KNAEGxE59OdKQGj1zkdfWU9Fa14n1g6gg9nYcoolJf1qAYb0nAThsXk +0lBwL6ggowERIIkrGygZf3Rlb7SjzgIZU5i7dtnLo2tbV2NK5G3MwCtdEaeKWzzw ++QKBgQC7+JPHoqbnNgis2vCGLKMOU3HpJK/rYEU/8ZUegc9lshEFZYsRbtKQQJQs +nqsChrG8UoK84frujEBkO/Nzsil85p8ar79wZguGnVvswTWaTuKvl8H/qQQ/JSHZ +OHGQD4qwTCkdRr8Vf8NfuCoZlJDnHncLJZNWjrb5feqCnJ/YIQ== +-----END RSA PRIVATE KEY----- From 3e7752c29d33c1c11bb2ba0ba9ac6985d47d83e1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 22 Jun 2019 19:48:01 +0400 Subject: [PATCH 10/27] cs: exit if SwitchToConsensus fails (#3706) Refs #3656 --- CHANGELOG_PENDING.md | 1 + consensus/reactor.go | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 1bff64b6..5e9a222d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,6 +32,7 @@ ### FEATURES: ### IMPROVEMENTS: +- [consensus] \#3656 Exit if SwitchToConsensus fails - [p2p] \#3666 Add per channel telemetry to improve reactor observability - [rpc] [\#3686](https://github.com/tendermint/tendermint/pull/3686) `HTTPClient#Call` returns wrapped errors, so a caller could use `errors.Cause` to retrieve an error code. (@wooparadog) - [abci/examples] \#3659 Change validator update tx format (incl. expected pubkey format, which is base64 now) (@needkane) diff --git a/consensus/reactor.go b/consensus/reactor.go index f690a407..dc3514b2 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -116,8 +116,13 @@ func (conR *ConsensusReactor) SwitchToConsensus(state sm.State, blocksSynced int } err := conR.conS.Start() if err != nil { - conR.Logger.Error("Error starting conS", "err", err) - return + panic(fmt.Sprintf(`Failed to start consensus state: %v + +conS: +%+v + +conR: +%+v`, err, conR.conS, conR)) } } From a44c621d2dee57e083641df05b51ad4fd90f0c14 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 24 Jun 2019 15:48:21 +0200 Subject: [PATCH 11/27] Remove double lint (#3748) - Circel CI and Golangci were doing linting jobs, removed circleci Signed-off-by: Marko Baricevic --- .circleci/config.yml | 65 ++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04a03eb2..5836b454 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,22 +67,6 @@ jobs: export PATH="$GOBIN:$PATH" make build-slate - lint: - <<: *defaults - steps: - - attach_workspace: - at: /tmp/workspace - - restore_cache: - key: v4-pkg-cache - - restore_cache: - key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - run: - name: metalinter - command: | - set -ex - export PATH="$GOBIN:$PATH" - make lint - test_abci_apps: <<: *defaults steps: @@ -98,8 +82,8 @@ jobs: export PATH="$GOBIN:$PATH" bash abci/tests/test_app/test.sh -# if this test fails, fix it and update the docs at: -# https://github.com/tendermint/tendermint/blob/develop/docs/abci-cli.md + # if this test fails, fix it and update the docs at: + # https://github.com/tendermint/tendermint/blob/develop/docs/abci-cli.md test_abci_cli: <<: *defaults steps: @@ -169,24 +153,24 @@ jobs: command: bash test/persist/test_failure_indices.sh localnet: - working_directory: /home/circleci/.go_workspace/src/github.com/tendermint/tendermint - machine: - image: circleci/classic:latest - environment: - GOBIN: /home/circleci/.go_workspace/bin - GOPATH: /home/circleci/.go_workspace/ - GOOS: linux - GOARCH: amd64 - parallelism: 1 - steps: - - checkout - - run: - name: run localnet and exit on failure - command: | - set -x - docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux - make localnet-start & - ./scripts/localnet-blocks-test.sh 40 5 10 localhost + working_directory: /home/circleci/.go_workspace/src/github.com/tendermint/tendermint + machine: + image: circleci/classic:latest + environment: + GOBIN: /home/circleci/.go_workspace/bin + GOPATH: /home/circleci/.go_workspace/ + GOOS: linux + GOARCH: amd64 + parallelism: 1 + steps: + - checkout + - run: + name: run localnet and exit on failure + command: | + set -x + docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang make build-linux + make localnet-start & + ./scripts/localnet-blocks-test.sh 40 5 10 localhost test_p2p: environment: @@ -273,9 +257,9 @@ jobs: paths: - "release-version.source" - save_cache: - key: v2-release-deps-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" + key: v2-release-deps-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" build_artifacts: <<: *defaults @@ -358,9 +342,6 @@ workflows: - master - develop - setup_dependencies - - lint: - requires: - - setup_dependencies - test_abci_apps: requires: - setup_dependencies From c37faf3ac18b02b961430d0b1159197912ea7d2b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 24 Jun 2019 17:49:49 +0400 Subject: [PATCH 12/27] docs: (rpc/broadcast_tx_*) write expectations for a client (#3749) Refs #3322 --- rpc/core/mempool.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 967466e7..1a095443 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -19,6 +19,19 @@ import ( // Returns right away, with no response. Does not wait for CheckTx nor // DeliverTx results. // +// If you want to be sure that the transaction is included in a block, you can +// subscribe for the result using JSONRPC via a websocket. See +// https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html +// If you haven't received anything after a couple of blocks, resend it. If the +// same happens again, send it to some other node. A few reasons why it could +// happen: +// +// 1. malicious node can drop or pretend it had committed your tx +// 2. malicious proposer (not necessary the one you're communicating with) can +// drop transactions, which might become valid in the future +// (https://github.com/tendermint/tendermint/issues/3322) +// 3. node can be offline +// // Please refer to // https://tendermint.com/docs/tendermint-core/using-tendermint.html#formatting // for formatting/encoding rules. @@ -69,6 +82,18 @@ func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadca // Returns with the response from CheckTx. Does not wait for DeliverTx result. // +// If you want to be sure that the transaction is included in a block, you can +// subscribe for the result using JSONRPC via a websocket. See +// https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html +// If you haven't received anything after a couple of blocks, resend it. If the +// same happens again, send it to some other node. A few reasons why it could +// happen: +// +// 1. malicious node can drop or pretend it had committed your tx +// 2. malicious proposer (not necessary the one you're communicating with) can +// drop transactions, which might become valid in the future +// (https://github.com/tendermint/tendermint/issues/3322) +// // Please refer to // https://tendermint.com/docs/tendermint-core/using-tendermint.html#formatting // for formatting/encoding rules. From d88a639838d03dcc76c0b1f04b589267228db5cd Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 24 Jun 2019 10:32:12 -0400 Subject: [PATCH 13/27] Make RPC bind to localhost by default (#3746) * Make RPC bind to localhost by default * Add CHANGELOG_PENDING entry * Allow testnet command to override RPC listen address * Update localnet test to bind RPC to 0.0.0.0 * Update p2p test to bind RPC to 0.0.0.0 * Remove rpc-laddr parameter * Update localnet to use config template with RPC listen address override * Use config template override method for RPC listen address * Build config template into localnode image * Build localnode image locally before starting localnet * Move testnet config overrides into templates * Revert deletion of config overrides * Remove extraneous config parameter overrides --- CHANGELOG_PENDING.md | 1 + Makefile | 4 ++-- config/config.go | 2 +- networks/local/localnode/Dockerfile | 2 +- networks/local/localnode/config-template.toml | 2 ++ test/docker/Dockerfile | 7 ++++++- test/docker/config-template.toml | 2 ++ 7 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 networks/local/localnode/config-template.toml create mode 100644 test/docker/config-template.toml diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5e9a222d..26fb14d1 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -36,6 +36,7 @@ - [p2p] \#3666 Add per channel telemetry to improve reactor observability - [rpc] [\#3686](https://github.com/tendermint/tendermint/pull/3686) `HTTPClient#Call` returns wrapped errors, so a caller could use `errors.Cause` to retrieve an error code. (@wooparadog) - [abci/examples] \#3659 Change validator update tx format (incl. expected pubkey format, which is base64 now) (@needkane) +- [rpc] \#3724 RPC now binds to `127.0.0.1` by default instead of `0.0.0.0` ### BUG FIXES: - [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) diff --git a/Makefile b/Makefile index 3dff35b9..35baf7d3 100644 --- a/Makefile +++ b/Makefile @@ -275,8 +275,8 @@ build-docker-localnode: @cd networks/local && make # Run a 4-node testnet locally -localnet-start: localnet-stop - @if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 4 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2 ; fi +localnet-start: localnet-stop build-docker-localnode + @if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --config /etc/tendermint/config-template.toml --v 4 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2; fi docker-compose up # Stop testnet diff --git a/config/config.go b/config/config.go index 6c4654fe..aa25cff6 100644 --- a/config/config.go +++ b/config/config.go @@ -369,7 +369,7 @@ type RPCConfig struct { // DefaultRPCConfig returns a default configuration for the RPC server func DefaultRPCConfig() *RPCConfig { return &RPCConfig{ - ListenAddress: "tcp://0.0.0.0:26657", + ListenAddress: "tcp://127.0.0.1:26657", CORSAllowedOrigins: []string{}, CORSAllowedMethods: []string{"HEAD", "GET", "POST"}, CORSAllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"}, diff --git a/networks/local/localnode/Dockerfile b/networks/local/localnode/Dockerfile index 3942cecd..03af5aa3 100644 --- a/networks/local/localnode/Dockerfile +++ b/networks/local/localnode/Dockerfile @@ -13,4 +13,4 @@ CMD ["node", "--proxy_app", "kvstore"] STOPSIGNAL SIGTERM COPY wrapper.sh /usr/bin/wrapper.sh - +COPY config-template.toml /etc/tendermint/config-template.toml diff --git a/networks/local/localnode/config-template.toml b/networks/local/localnode/config-template.toml new file mode 100644 index 00000000..a90eb7bd --- /dev/null +++ b/networks/local/localnode/config-template.toml @@ -0,0 +1,2 @@ +[rpc] +laddr = "tcp://0.0.0.0:26657" diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 77cc515e..b39277bd 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -27,7 +27,12 @@ RUN make install_abci # install Tendermint RUN make install -RUN tendermint testnet --node-dir-prefix="mach" --v=4 --populate-persistent-peers=false --o=$REPO/test/p2p/data +RUN tendermint testnet \ + --config $REPO/test/docker/config-template.toml \ + --node-dir-prefix="mach" \ + --v=4 \ + --populate-persistent-peers=false \ + --o=$REPO/test/p2p/data # Now copy in the code # NOTE: this will overwrite whatever is in vendor/ diff --git a/test/docker/config-template.toml b/test/docker/config-template.toml new file mode 100644 index 00000000..a90eb7bd --- /dev/null +++ b/test/docker/config-template.toml @@ -0,0 +1,2 @@ +[rpc] +laddr = "tcp://0.0.0.0:26657" From 4c4bf0f1316e0e9e1dfdda516cd1179a30d5adfb Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 24 Jun 2019 16:32:24 +0200 Subject: [PATCH 14/27] Added flags '-s -w' to buildflags (#3742) * Added flags '-s -w' to buildflags Signed-off-by: Marko Baricevic * added a condition for no strip * minor changes * on call discussions * on call discussion v2 --- Makefile | 3 ++- go.mod | 45 ++++++++++++++++++++++----------------------- go.sum | 24 ++++++++++++++++++++---- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 35baf7d3..f16e6256 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,8 @@ export GO111MODULE = on INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protobuf BUILD_TAGS?='tendermint' -BUILD_FLAGS = -mod=readonly -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" +LD_FLAGS = -X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD` -s -w +BUILD_FLAGS = -mod=readonly -ldflags "$(LD_FLAGS)" all: check build test install diff --git a/go.mod b/go.mod index 8fe1a124..f4a4c6dd 100644 --- a/go.mod +++ b/go.mod @@ -3,50 +3,49 @@ module github.com/tendermint/tendermint go 1.12 require ( - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/VividCortex/gohistogram v1.0.0 // indirect + github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a - github.com/davecgh/go-spew v1.1.1 github.com/etcd-io/bbolt v1.3.2 github.com/fortytw2/leaktest v1.2.0 - github.com/fsnotify/fsnotify v1.4.7 github.com/go-kit/kit v0.6.0 github.com/go-logfmt/logfmt v0.3.0 - github.com/go-stack/stack v1.8.0 + github.com/go-stack/stack v1.8.0 // indirect github.com/gogo/protobuf v1.2.1 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/protobuf v1.3.0 - github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/google/gofuzz v1.0.0 // indirect github.com/gorilla/websocket v1.2.0 - github.com/hashicorp/hcl v1.0.0 - github.com/inconshreveable/mousetrap v1.0.0 + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmhodges/levigo v1.0.0 - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect github.com/magiconair/properties v1.8.0 - github.com/matttproud/golang_protobuf_extensions v1.0.1 - github.com/mitchellh/mapstructure v1.1.2 - github.com/pelletier/go-toml v1.2.0 + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/pelletier/go-toml v1.2.0 // indirect github.com/pkg/errors v0.8.0 - github.com/pmezard/go-difflib v1.0.0 github.com/prometheus/client_golang v0.9.1 - github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 - github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 - github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d + github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect + github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 // indirect + github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.6.0 - github.com/spf13/afero v1.1.2 - github.com/spf13/cast v1.3.0 + github.com/spf13/afero v1.1.2 // indirect + github.com/spf13/cast v1.3.0 // indirect github.com/spf13/cobra v0.0.1 - github.com/spf13/jwalterweatherman v1.0.0 - github.com/spf13/pflag v1.0.3 + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/pflag v1.0.3 // indirect github.com/spf13/viper v1.0.0 github.com/stretchr/testify v1.2.2 github.com/syndtr/goleveldb v0.0.0-20181012014443-6b91fda63f2e github.com/tendermint/go-amino v0.14.1 + go.etcd.io/bbolt v1.3.3 // indirect golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd - golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a - golang.org/x/text v0.3.0 - google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 + google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 // indirect google.golang.org/grpc v1.13.0 - gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index fee691de..2a349d30 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,13 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180524032703-d4cc87b86016/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -29,23 +32,31 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -54,7 +65,9 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -86,22 +99,22 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.0.0 h1:RUA/ghS2i64rlnn4ydTfblY8Og8QzcPtCcHvgMn+w/I= github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/syndtr/goleveldb v0.0.0-20181012014443-6b91fda63f2e h1:91EeXI4y4ShkyzkMqZ7QP/ZTIqwXp3RuDu5WFzxcFAs= github.com/syndtr/goleveldb v0.0.0-20181012014443-6b91fda63f2e/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180710023853-292b43bbf7cb/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181030150119-7e31e0c00fa0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -112,8 +125,11 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 h1:67iHsV9djwGdZpd google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.13.0 h1:bHIbVsCwmvbArgCJmLdgOdHFXlKqTOVjbibbS19cXHc= google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From e93e8e730d8d3044db7d7ae78d20af036332fafa Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 24 Jun 2019 20:13:25 +0200 Subject: [PATCH 15/27] Added a disclaimer so the user is aware of binary (#3751) * Added a disaclaimer so the user is aware of binary - added disclaimer for `-s -w` Signed-off-by: Marko Baricevic * move up to compile --- docs/introduction/install.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/introduction/install.md b/docs/introduction/install.md index 4f35ffef..00e04fa0 100644 --- a/docs/introduction/install.md +++ b/docs/introduction/install.md @@ -45,6 +45,8 @@ make build to put the binary in `./build`. +_DISCLAIMER_ The binary of tendermint is build/installed without the DWARF symbol table. If you would like to build/install tendermint with the DWARF symbol and debug information, remove `-s -w` from `BUILD_FLAGS` in the make file. + The latest `tendermint version` is now installed. ## Run From eb08609de1b6a057af1d9e42591bb5215f5fb169 Mon Sep 17 00:00:00 2001 From: Mengjay Gao <1955889005@qq.com> Date: Tue, 25 Jun 2019 14:56:03 +0800 Subject: [PATCH 16/27] docs: update JS section of abci-cli.md (#3747) --- docs/app-dev/abci-cli.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/app-dev/abci-cli.md b/docs/app-dev/abci-cli.md index b09b9a11..7e9db91b 100644 --- a/docs/app-dev/abci-cli.md +++ b/docs/app-dev/abci-cli.md @@ -326,10 +326,18 @@ application easily in any language. We have implemented the counter in a number of languages [see the example directory](https://github.com/tendermint/tendermint/tree/develop/abci/example). -To run the Node JS version, `cd` to `example/js` and run +To run the Node.js version, fist download & install [the Javascript ABCI server](https://github.com/tendermint/js-abci): ``` -node app.js +git clone https://github.com/tendermint/js-abci.git +cd js-abci +npm install abci +``` + +Now you can start the app: + +```bash +node example/counter.js ``` (you'll have to kill the other counter application process). In another From 747f99fdc198d7ae6456b010c9b8857aae97e25f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 25 Jun 2019 07:57:50 -0400 Subject: [PATCH 17/27] changelog and version (#3750) * changelog and version * Add section on ABCI changes * Update ABCI upgrade section to include events examples * update upgrading * more upgrading and changelog * update changelog from pending * refer to #rpc_changes * minor word changes --- CHANGELOG.md | 63 ++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 31 +++---------- UPGRADING.md | 102 ++++++++++++++++++++++++++++++++++++++++++- version/version.go | 2 +- 4 files changed, 171 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 068d99c3..76f547a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,68 @@ # Changelog +## v0.32.0 + +*June 25, 2019* + +Special thanks to external contributors on this release: +@needkane, @SebastianElvis, @andynog, @Yawning, @wooparadog + +This release contains breaking changes to our build and release processes, ABCI, +and the RPC, namely: +- Use Go modules instead of dep +- Bring active development to the `master` Github branch +- ABCI Tags are now Events - see + [docs](https://github.com/tendermint/tendermint/blob/60827f75623b92eff132dc0eff5b49d2025c591e/docs/spec/abci/abci.md#events) +- Bind RPC to localhost by default, not to the public interface [UPGRADING/RPC_Changes](./UPGRADING.md#rpc_changes) + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BREAKING CHANGES: + +* CLI/RPC/Config + - [cli] \#3613 Switch from golang/dep to Go Modules to resolve dependencies: + It is recommended to switch to Go Modules if your project has tendermint as + a dependency. Read more on Modules here: + https://github.com/golang/go/wiki/Modules + - [config] [\#3632](https://github.com/tendermint/tendermint/pull/3632) Removed `leveldb` as generic + option for `db_backend`. Must be `goleveldb` or `cleveldb`. + - [rpc] \#3616 Fix field names for `/block_results` response (eg. `results.DeliverTx` + -> `results.deliver_tx`). See docs for details. + - [rpc] \#3724 RPC now binds to `127.0.0.1` by default instead of `0.0.0.0` + +* Apps + - [abci] \#1859 `ResponseCheckTx`, `ResponseDeliverTx`, `ResponseBeginBlock`, + and `ResponseEndBlock` now include `Events` instead of `Tags`. Each `Event` + contains a `type` and a list of `attributes` (list of key-value pairs) + allowing for inclusion of multiple distinct events in each response. + +* Go API + - [abci] \#3193 Use RequestDeliverTx and RequestCheckTx in the ABCI + Application interface + - [libs/db] [\#3632](https://github.com/tendermint/tendermint/pull/3632) Removed deprecated `LevelDBBackend` const + If you have `db_backend` set to `leveldb` in your config file, please + change it to `goleveldb` or `cleveldb`. + - [p2p] \#3521 Remove NewNetAddressStringWithOptionalID + +* Blockchain Protocol + +* P2P Protocol + +### FEATURES: + +### IMPROVEMENTS: +- [abci/examples] \#3659 Change validator update tx format in the `persistent_kvstore` to use base64 for pubkeys instead of hex (@needkane) +- [consensus] \#3656 Exit if SwitchToConsensus fails +- [p2p] \#3666 Add per channel telemetry to improve reactor observability +- [rpc] [\#3686](https://github.com/tendermint/tendermint/pull/3686) `HTTPClient#Call` returns wrapped errors, so a caller could use `errors.Cause` to retrieve an error code. (@wooparadog) + +### BUG FIXES: +- [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) +- [libs/db] \#3718 Fixed the BoltDB backend's Get and Iterator implementation (@Yawning) +- [node] \#3716 Fix a bug where `nil` is recorded as node's address +- [node] \#3741 Fix profiler blocking the entire node + ## v0.31.7 *June 3, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 26fb14d1..45d1c789 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,29 +1,19 @@ -## v0.31.8 +## v0.32.1 ** +Special thanks to external contributors on this release: + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + ### BREAKING CHANGES: * CLI/RPC/Config - - [cli] \#3613 Switch from golang/dep to Go Modules to resolve dependencies: - It is recommended to switch to Go Modules if your project has tendermint as - a dependency. Read more on Modules here: - https://github.com/golang/go/wiki/Modules - - [rpc] \#3616 Improve `/block_results` response format (`results.DeliverTx` - -> `results.deliver_tx`). See docs for details. * Apps - - [abci] \#1859 `ResponseCheckTx`, `ResponseDeliverTx`, `ResponseBeginBlock`, - and `ResponseEndBlock` now include `Events` instead of `Tags`. Each `Event` - contains a `type` and a list of `attributes` (list of key-value pairs) - allowing for inclusion of multiple distinct events in each response. * Go API - - [libs/db] [\#3632](https://github.com/tendermint/tendermint/pull/3632) Removed deprecated `LevelDBBackend` const - If you have `db_backend` set to `leveldb` in your config file, please - change it to `goleveldb` or `cleveldb`. - - [p2p] \#3521 Remove NewNetAddressStringWithOptionalID - - [abci] \#3193 Use RequestDeliverTx and RequestCheckTx in the ABCI interface * Blockchain Protocol @@ -32,14 +22,5 @@ ### FEATURES: ### IMPROVEMENTS: -- [consensus] \#3656 Exit if SwitchToConsensus fails -- [p2p] \#3666 Add per channel telemetry to improve reactor observability -- [rpc] [\#3686](https://github.com/tendermint/tendermint/pull/3686) `HTTPClient#Call` returns wrapped errors, so a caller could use `errors.Cause` to retrieve an error code. (@wooparadog) -- [abci/examples] \#3659 Change validator update tx format (incl. expected pubkey format, which is base64 now) (@needkane) -- [rpc] \#3724 RPC now binds to `127.0.0.1` by default instead of `0.0.0.0` ### BUG FIXES: -- [libs/db] \#3717 Fixed the BoltDB backend's Batch.Delete implementation (@Yawning) -- [libs/db] \#3718 Fixed the BoltDB backend's Get and Iterator implementation (@Yawning) -- [node] \#3716 Fix a bug where `nil` is recorded as node's address -- [node] \#3741 Fix profiler blocking the entire node diff --git a/UPGRADING.md b/UPGRADING.md index 5a77e072..af42d2a6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,14 +3,114 @@ This guide provides steps to be followed when you upgrade your applications to a newer version of Tendermint Core. - ## v0.32.0 +This release is compatible with previous blockchains, +however the new ABCI Events mechanism may create some complexity +for nodes wishing to continue operation with v0.32 from a previous version. +There are some minor breaking changes to the RPC. + ### Config Changes If you have `db_backend` set to `leveldb` in your config file, please change it to `goleveldb` or `cleveldb`. +### RPC Changes + +The default listen address for the RPC is now `127.0.0.1`. If you want to expose +it publicly, you have to explicitly configure it. Note exposing the RPC to the +public internet may not be safe - endpoints which return a lot of data may +enable resource exhaustion attacks on your node, causing the process to crash. + +Any consumers of `/block_results` need to be mindful of the change in all field +names from CamelCase to Snake case, eg. `results.DeliverTx` is now `results.deliver_tx`. +This is a fix, but it's breaking. + +### ABCI Changes + +ABCI responses which previously had a `Tags` field now have an `Events` field +instead. The original `Tags` field was simply a list of key-value pairs, where +each key effectively represented some attribute of an event occuring in the +blockchain, like `sender`, `receiver`, or `amount`. However, it was difficult to +represent the occurence of multiple events (for instance, multiple transfers) in a single list. +The new `Events` field contains a list of `Event`, where each `Event` is itself a list +of key-value pairs, allowing for more natural expression of multiple events in +eg. a single DeliverTx or EndBlock. Note each `Event` also includes a `Type`, which is meant to categorize the +event. + +For transaction indexing, the index key is +prefixed with the event type: `{eventType}.{attributeKey}`. +If the same event type and attribute key appear multiple times, the values are +appended in a list. + +To make queries, include the event type as a prefix. For instance if you +previously queried for `recipient = 'XYZ'`, and after the upgrade you name your event `transfer`, +the new query would be for `transfer.recipient = 'XYZ'`. + +Note that transactions indexed on a node before upgrading to v0.32 will still be indexed +using the old scheme. For instance, if a node upgraded at height 100, +transactions before 100 would be queried with `recipient = 'XYZ'` and +transactions after 100 would be queried with `transfer.recipient = 'XYZ'`. +While this presents additional complexity to clients, it avoids the need to +reindex. Of course, you can reset the node and sync from scratch to re-index +entirely using the new scheme. + +We illustrate further with a more complete example. + +Prior to the update, suppose your `ResponseDeliverTx` look like: + +```go +abci.ResponseDeliverTx{ + Tags: []cmn.KVPair{ + {Key: []byte("sender"), Value: []byte("foo")}, + {Key: []byte("recipient"), Value: []byte("bar")}, + {Key: []byte("amount"), Value: []byte("35")}, + } +} +``` + +The following queries would match this transaction: + +```go +query.MustParse("tm.event = 'Tx' AND sender = 'foo'") +query.MustParse("tm.event = 'Tx' AND recipient = 'bar'") +query.MustParse("tm.event = 'Tx' AND sender = 'foo' AND recipient = 'bar'") +``` + +Following the upgrade, your `ResponseDeliverTx` would look something like: +the following `Events`: + +```go +abci.ResponseDeliverTx{ + Events: []abci.Event{ + { + Type: "transfer", + Attributes: cmn.KVPairs{ + {Key: []byte("sender"), Value: []byte("foo")}, + {Key: []byte("recipient"), Value: []byte("bar")}, + {Key: []byte("amount"), Value: []byte("35")}, + }, + } +} +``` + +Now the following queries would match this transaction: + +```go +query.MustParse("tm.event = 'Tx' AND transfer.sender = 'foo'") +query.MustParse("tm.event = 'Tx' AND transfer.recipient = 'bar'") +query.MustParse("tm.event = 'Tx' AND transfer.sender = 'foo' AND transfer.recipient = 'bar'") +``` + +For further documentation on `Events`, see the [docs](https://github.com/tendermint/tendermint/blob/60827f75623b92eff132dc0eff5b49d2025c591e/docs/spec/abci/abci.md#events). + +### Go Applications + +The ABCI Application interface changed slightly so the CheckTx and DeliverTx +methods now take Request structs. The contents of these structs are just the raw +tx bytes, which were previously passed in as the argument. + + ## v0.31.6 There are no breaking changes in this release except Go API of p2p and diff --git a/version/version.go b/version/version.go index 1a15717f..2d1c41fd 100644 --- a/version/version.go +++ b/version/version.go @@ -20,7 +20,7 @@ const ( // Must be a string because scripts like dist.sh read this file. // XXX: Don't change the name of this variable or you will break // automation :) - TMCoreSemVer = "0.31.7" + TMCoreSemVer = "0.32.0" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.16.0" From 1014f8c84bb10300f43f246642d69f46bd8954fd Mon Sep 17 00:00:00 2001 From: Marko Date: Fri, 28 Jun 2019 17:32:15 +0200 Subject: [PATCH 18/27] docs: update to contributing.md (#3760) * Update to contributing.md - Add in section for Draft PR, instead of WIP - Add make lint as part of the commit process. Signed-off-by: Marko Baricevic * mistake * minor word change --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77a62550..ef992fee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,9 @@ to write a more detailed design document in the form of an Architectural Decision Record (ie. see [here](./docs/architecture/)) before submitting code changes. -Please make sure to use `gofmt` before every commit - the easiest way to do this is have your editor run it for you upon saving a file. +Please open a [Draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/), even if your contribution is incomplete, this inidicates to the community you're working on something and allows them to provide comments early in the development process. When the code is complete it can be marked as ready-for-review. + +Please make sure to use `gofmt` before every commit - the easiest way to do this is have your editor run it for you upon saving a file. Additionally please ensure that your code is lint compliant by running `make lint` ## Forking From b2afc6590858c0804bdca478a76ea9d36f0181c6 Mon Sep 17 00:00:00 2001 From: Peng Zhong <172531+nylira@users.noreply.github.com> Date: Sat, 29 Jun 2019 03:06:44 -0400 Subject: [PATCH 19/27] docs: add readme image (#3763) --- README.md | 1 + docs/tendermint-core-image.jpg | Bin 0 -> 125536 bytes 2 files changed, 1 insertion(+) create mode 100755 docs/tendermint-core-image.jpg diff --git a/README.md b/README.md index ec7b28cc..994ca63b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Tendermint +![banner](docs/tendermint-core-image.jpg) [Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance) [State Machines](https://en.wikipedia.org/wiki/State_machine_replication). diff --git a/docs/tendermint-core-image.jpg b/docs/tendermint-core-image.jpg new file mode 100755 index 0000000000000000000000000000000000000000..75832e602dbb36010e1812de3e5dd0c5969a47f9 GIT binary patch literal 125536 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%0Dx$6!G)u1{;V=V=17T`y9g)& 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 Date: Sun, 30 Jun 2019 11:46:31 +0400 Subject: [PATCH 20/27] docs: remove confusing statement from contributing.md (#3764) --- CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef992fee..832156bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,8 +41,6 @@ To pull in updates from the origin repo, run * `git fetch upstream` * `git rebase upstream/master` (or whatever branch you want) -Please don't make Pull Requests to `master`. - ## Dependencies We use [go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies. From d4cf2040877aea2e863265a56fe1d91e93bf021a Mon Sep 17 00:00:00 2001 From: Marko Date: Sun, 30 Jun 2019 09:56:24 +0200 Subject: [PATCH 21/27] libs: remove commented and unneeded code (#3757) - libs/errors: commented out errors.go - libs/common: unused colors.go - libs/db: unused debugDB Signed-off-by: Marko Baricevic --- CHANGELOG_PENDING.md | 2 + libs/common/colors.go | 95 ---------------- libs/db/db_test.go | 3 +- libs/db/debug_db.go | 257 ------------------------------------------ libs/errors/errors.go | 21 ---- 5 files changed, 3 insertions(+), 375 deletions(-) delete mode 100644 libs/common/colors.go delete mode 100644 libs/db/debug_db.go delete mode 100644 libs/errors/errors.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 45d1c789..d57446a1 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -14,6 +14,8 @@ program](https://hackerone.com/tendermint). * Apps * Go API + - [libs] Remove unused `db/debugDB` and `common/colors.go` & `errors/errors.go` files (@marbar3778) + * Blockchain Protocol diff --git a/libs/common/colors.go b/libs/common/colors.go deleted file mode 100644 index 89dda2c9..00000000 --- a/libs/common/colors.go +++ /dev/null @@ -1,95 +0,0 @@ -package common - -import ( - "fmt" - "strings" -) - -const ( - ANSIReset = "\x1b[0m" - ANSIBright = "\x1b[1m" - ANSIDim = "\x1b[2m" - ANSIUnderscore = "\x1b[4m" - ANSIBlink = "\x1b[5m" - ANSIReverse = "\x1b[7m" - ANSIHidden = "\x1b[8m" - - ANSIFgBlack = "\x1b[30m" - ANSIFgRed = "\x1b[31m" - ANSIFgGreen = "\x1b[32m" - ANSIFgYellow = "\x1b[33m" - ANSIFgBlue = "\x1b[34m" - ANSIFgMagenta = "\x1b[35m" - ANSIFgCyan = "\x1b[36m" - ANSIFgWhite = "\x1b[37m" - - ANSIBgBlack = "\x1b[40m" - ANSIBgRed = "\x1b[41m" - ANSIBgGreen = "\x1b[42m" - ANSIBgYellow = "\x1b[43m" - ANSIBgBlue = "\x1b[44m" - ANSIBgMagenta = "\x1b[45m" - ANSIBgCyan = "\x1b[46m" - ANSIBgWhite = "\x1b[47m" -) - -// color the string s with color 'color' -// unless s is already colored -func treat(s string, color string) string { - if len(s) > 2 && s[:2] == "\x1b[" { - return s - } - return color + s + ANSIReset -} - -func treatAll(color string, args ...interface{}) string { - parts := make([]string, 0, len(args)) - for _, arg := range args { - parts = append(parts, treat(fmt.Sprintf("%v", arg), color)) - } - return strings.Join(parts, "") -} - -func Black(args ...interface{}) string { - return treatAll(ANSIFgBlack, args...) -} - -func Red(args ...interface{}) string { - return treatAll(ANSIFgRed, args...) -} - -func Green(args ...interface{}) string { - return treatAll(ANSIFgGreen, args...) -} - -func Yellow(args ...interface{}) string { - return treatAll(ANSIFgYellow, args...) -} - -func Blue(args ...interface{}) string { - return treatAll(ANSIFgBlue, args...) -} - -func Magenta(args ...interface{}) string { - return treatAll(ANSIFgMagenta, args...) -} - -func Cyan(args ...interface{}) string { - return treatAll(ANSIFgCyan, args...) -} - -func White(args ...interface{}) string { - return treatAll(ANSIFgWhite, args...) -} - -func ColoredBytes(data []byte, textColor, bytesColor func(...interface{}) string) string { - s := "" - for _, b := range data { - if 0x21 <= b && b < 0x7F { - s += textColor(string(b)) - } else { - s += bytesColor(fmt.Sprintf("%02X", b)) - } - } - return s -} diff --git a/libs/db/db_test.go b/libs/db/db_test.go index 7cb721b2..22b781f9 100644 --- a/libs/db/db_test.go +++ b/libs/db/db_test.go @@ -182,8 +182,7 @@ func TestDBBatchWrite(t *testing.T) { for i, tc := range testCases { mdb := newMockDB() - ddb := NewDebugDB(t.Name(), mdb) - batch := ddb.NewBatch() + batch := mdb.NewBatch() tc.modify(batch) diff --git a/libs/db/debug_db.go b/libs/db/debug_db.go deleted file mode 100644 index 658cd055..00000000 --- a/libs/db/debug_db.go +++ /dev/null @@ -1,257 +0,0 @@ -package db - -import ( - "fmt" - "sync" - - cmn "github.com/tendermint/tendermint/libs/common" -) - -//---------------------------------------- -// debugDB - -type debugDB struct { - label string - db DB -} - -// For printing all operationgs to the console for debugging. -func NewDebugDB(label string, db DB) debugDB { - return debugDB{ - label: label, - db: db, - } -} - -// Implements atomicSetDeleter. -func (ddb debugDB) Mutex() *sync.Mutex { return nil } - -// Implements DB. -func (ddb debugDB) Get(key []byte) (value []byte) { - defer func() { - fmt.Printf("%v.Get(%v) %v\n", ddb.label, - cmn.ColoredBytes(key, cmn.Cyan, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - }() - value = ddb.db.Get(key) - return -} - -// Implements DB. -func (ddb debugDB) Has(key []byte) (has bool) { - defer func() { - fmt.Printf("%v.Has(%v) %v\n", ddb.label, - cmn.ColoredBytes(key, cmn.Cyan, cmn.Blue), has) - }() - return ddb.db.Has(key) -} - -// Implements DB. -func (ddb debugDB) Set(key []byte, value []byte) { - fmt.Printf("%v.Set(%v, %v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Yellow, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - ddb.db.Set(key, value) -} - -// Implements DB. -func (ddb debugDB) SetSync(key []byte, value []byte) { - fmt.Printf("%v.SetSync(%v, %v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Yellow, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - ddb.db.SetSync(key, value) -} - -// Implements atomicSetDeleter. -func (ddb debugDB) SetNoLock(key []byte, value []byte) { - fmt.Printf("%v.SetNoLock(%v, %v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Yellow, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - ddb.db.(atomicSetDeleter).SetNoLock(key, value) -} - -// Implements atomicSetDeleter. -func (ddb debugDB) SetNoLockSync(key []byte, value []byte) { - fmt.Printf("%v.SetNoLockSync(%v, %v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Yellow, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - ddb.db.(atomicSetDeleter).SetNoLockSync(key, value) -} - -// Implements DB. -func (ddb debugDB) Delete(key []byte) { - fmt.Printf("%v.Delete(%v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Red, cmn.Yellow)) - ddb.db.Delete(key) -} - -// Implements DB. -func (ddb debugDB) DeleteSync(key []byte) { - fmt.Printf("%v.DeleteSync(%v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Red, cmn.Yellow)) - ddb.db.DeleteSync(key) -} - -// Implements atomicSetDeleter. -func (ddb debugDB) DeleteNoLock(key []byte) { - fmt.Printf("%v.DeleteNoLock(%v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Red, cmn.Yellow)) - ddb.db.(atomicSetDeleter).DeleteNoLock(key) -} - -// Implements atomicSetDeleter. -func (ddb debugDB) DeleteNoLockSync(key []byte) { - fmt.Printf("%v.DeleteNoLockSync(%v)\n", ddb.label, - cmn.ColoredBytes(key, cmn.Red, cmn.Yellow)) - ddb.db.(atomicSetDeleter).DeleteNoLockSync(key) -} - -// Implements DB. -func (ddb debugDB) Iterator(start, end []byte) Iterator { - fmt.Printf("%v.Iterator(%v, %v)\n", ddb.label, - cmn.ColoredBytes(start, cmn.Cyan, cmn.Blue), - cmn.ColoredBytes(end, cmn.Cyan, cmn.Blue)) - return NewDebugIterator(ddb.label, ddb.db.Iterator(start, end)) -} - -// Implements DB. -func (ddb debugDB) ReverseIterator(start, end []byte) Iterator { - fmt.Printf("%v.ReverseIterator(%v, %v)\n", ddb.label, - cmn.ColoredBytes(start, cmn.Cyan, cmn.Blue), - cmn.ColoredBytes(end, cmn.Cyan, cmn.Blue)) - return NewDebugIterator(ddb.label, ddb.db.ReverseIterator(start, end)) -} - -// Implements DB. -// Panics if the underlying db is not an -// atomicSetDeleter. -func (ddb debugDB) NewBatch() Batch { - fmt.Printf("%v.NewBatch()\n", ddb.label) - return NewDebugBatch(ddb.label, ddb.db.NewBatch()) -} - -// Implements DB. -func (ddb debugDB) Close() { - fmt.Printf("%v.Close()\n", ddb.label) - ddb.db.Close() -} - -// Implements DB. -func (ddb debugDB) Print() { - ddb.db.Print() -} - -// Implements DB. -func (ddb debugDB) Stats() map[string]string { - return ddb.db.Stats() -} - -//---------------------------------------- -// debugIterator - -type debugIterator struct { - label string - itr Iterator -} - -// For printing all operationgs to the console for debugging. -func NewDebugIterator(label string, itr Iterator) debugIterator { - return debugIterator{ - label: label, - itr: itr, - } -} - -// Implements Iterator. -func (ditr debugIterator) Domain() (start []byte, end []byte) { - defer func() { - fmt.Printf("%v.itr.Domain() (%X,%X)\n", ditr.label, start, end) - }() - start, end = ditr.itr.Domain() - return -} - -// Implements Iterator. -func (ditr debugIterator) Valid() (ok bool) { - defer func() { - fmt.Printf("%v.itr.Valid() %v\n", ditr.label, ok) - }() - ok = ditr.itr.Valid() - return -} - -// Implements Iterator. -func (ditr debugIterator) Next() { - fmt.Printf("%v.itr.Next()\n", ditr.label) - ditr.itr.Next() -} - -// Implements Iterator. -func (ditr debugIterator) Key() (key []byte) { - key = ditr.itr.Key() - fmt.Printf("%v.itr.Key() %v\n", ditr.label, - cmn.ColoredBytes(key, cmn.Cyan, cmn.Blue)) - return -} - -// Implements Iterator. -func (ditr debugIterator) Value() (value []byte) { - value = ditr.itr.Value() - fmt.Printf("%v.itr.Value() %v\n", ditr.label, - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - return -} - -// Implements Iterator. -func (ditr debugIterator) Close() { - fmt.Printf("%v.itr.Close()\n", ditr.label) - ditr.itr.Close() -} - -//---------------------------------------- -// debugBatch - -type debugBatch struct { - label string - bch Batch -} - -// For printing all operationgs to the console for debugging. -func NewDebugBatch(label string, bch Batch) debugBatch { - return debugBatch{ - label: label, - bch: bch, - } -} - -// Implements Batch. -func (dbch debugBatch) Set(key, value []byte) { - fmt.Printf("%v.batch.Set(%v, %v)\n", dbch.label, - cmn.ColoredBytes(key, cmn.Yellow, cmn.Blue), - cmn.ColoredBytes(value, cmn.Green, cmn.Blue)) - dbch.bch.Set(key, value) -} - -// Implements Batch. -func (dbch debugBatch) Delete(key []byte) { - fmt.Printf("%v.batch.Delete(%v)\n", dbch.label, - cmn.ColoredBytes(key, cmn.Red, cmn.Yellow)) - dbch.bch.Delete(key) -} - -// Implements Batch. -func (dbch debugBatch) Write() { - fmt.Printf("%v.batch.Write()\n", dbch.label) - dbch.bch.Write() -} - -// Implements Batch. -func (dbch debugBatch) WriteSync() { - fmt.Printf("%v.batch.WriteSync()\n", dbch.label) - dbch.bch.WriteSync() -} - -// Implements Batch. -func (dbch debugBatch) Close() { - dbch.bch.Close() -} diff --git a/libs/errors/errors.go b/libs/errors/errors.go deleted file mode 100644 index a0338278..00000000 --- a/libs/errors/errors.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package errors contains errors that are thrown across packages. -package errors - -// // ErrPermissionsChanged occurs if the file permission have changed since the file was created. -// type ErrPermissionsChanged struct { -// name string -// got, want os.FileMode -// } - -// func NewErrPermissionsChanged(name string, got, want os.FileMode) *ErrPermissionsChanged { -// return &ErrPermissionsChanged{name: name, got: got, want: want} -// } - -// func (e ErrPermissionsChanged) Error() string { -// return fmt.Sprintf( -// "file: [%v]\nexpected file permissions: %v, got: %v", -// e.name, -// e.want, -// e.got, -// ) -// } From d9481e3648450cb99e15c6a070c1fb69aa0c255b Mon Sep 17 00:00:00 2001 From: Ivan Kushmantsev Date: Mon, 1 Jul 2019 12:48:54 +0400 Subject: [PATCH 22/27] config: make possible to set absolute paths for TLS cert and key (#3765) --- CHANGELOG_PENDING.md | 1 + config/config.go | 18 ++++++++++++++---- config/config_test.go | 16 ++++++++++++++++ config/toml.go | 6 ++++-- docs/tendermint-core/configuration.md | 6 ++++-- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d57446a1..d81e815f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,5 +24,6 @@ program](https://hackerone.com/tendermint). ### FEATURES: ### IMPROVEMENTS: + - [rpc] \#3700 Make possible to set absolute paths for TLS cert and key (@climber73) ### BUG FIXES: diff --git a/config/config.go b/config/config.go index aa25cff6..32e37f3e 100644 --- a/config/config.go +++ b/config/config.go @@ -351,7 +351,8 @@ type RPCConfig struct { // See https://github.com/tendermint/tendermint/issues/3435 TimeoutBroadcastTxCommit time.Duration `mapstructure:"timeout_broadcast_tx_commit"` - // The name of a file containing certificate that is used to create the HTTPS server. + // The path to a file containing certificate that is used to create the HTTPS server. + // Migth be either absolute path or path related to tendermint's config directory. // // If the certificate is signed by a certificate authority, // the certFile should be the concatenation of the server's certificate, any intermediates, @@ -360,7 +361,8 @@ type RPCConfig struct { // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. TLSCertFile string `mapstructure:"tls_cert_file"` - // The name of a file containing matching private key that is used to create the HTTPS server. + // The path to a file containing matching private key that is used to create the HTTPS server. + // Migth be either absolute path or path related to tendermint's config directory. // // NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. TLSKeyFile string `mapstructure:"tls_key_file"` @@ -424,11 +426,19 @@ func (cfg *RPCConfig) IsCorsEnabled() bool { } func (cfg RPCConfig) KeyFile() string { - return rootify(filepath.Join(defaultConfigDir, cfg.TLSKeyFile), cfg.RootDir) + path := cfg.TLSKeyFile + if filepath.IsAbs(path) { + return path + } + return rootify(filepath.Join(defaultConfigDir, path), cfg.RootDir) } func (cfg RPCConfig) CertFile() string { - return rootify(filepath.Join(defaultConfigDir, cfg.TLSCertFile), cfg.RootDir) + path := cfg.TLSCertFile + if filepath.IsAbs(path) { + return path + } + return rootify(filepath.Join(defaultConfigDir, path), cfg.RootDir) } func (cfg RPCConfig) IsTLSEnabled() bool { diff --git a/config/config_test.go b/config/config_test.go index afdbed18..6f9e3783 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -36,3 +36,19 @@ func TestConfigValidateBasic(t *testing.T) { cfg.Consensus.TimeoutPropose = -10 * time.Second assert.Error(t, cfg.ValidateBasic()) } + +func TestTLSConfiguration(t *testing.T) { + assert := assert.New(t) + cfg := DefaultConfig() + cfg.SetRoot("/home/user") + + cfg.RPC.TLSCertFile = "file.crt" + assert.Equal("/home/user/config/file.crt", cfg.RPC.CertFile()) + cfg.RPC.TLSKeyFile = "file.key" + assert.Equal("/home/user/config/file.key", cfg.RPC.KeyFile()) + + cfg.RPC.TLSCertFile = "/abs/path/to/file.crt" + assert.Equal("/abs/path/to/file.crt", cfg.RPC.CertFile()) + cfg.RPC.TLSKeyFile = "/abs/path/to/file.key" + assert.Equal("/abs/path/to/file.key", cfg.RPC.KeyFile()) +} diff --git a/config/toml.go b/config/toml.go index b1541c05..09117a0f 100644 --- a/config/toml.go +++ b/config/toml.go @@ -192,14 +192,16 @@ max_subscriptions_per_client = {{ .RPC.MaxSubscriptionsPerClient }} # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "{{ .RPC.TimeoutBroadcastTxCommit }}" -# The name of a file containing certificate that is used to create the HTTPS server. +# The path to a file containing certificate that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_cert_file = "{{ .RPC.TLSCertFile }}" -# The name of a file containing matching private key that is used to create the HTTPS server. +# The path to a file containing matching private key that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "{{ .RPC.TLSKeyFile }}" diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index f24e76d6..df05f7c5 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -138,14 +138,16 @@ max_subscriptions_per_client = 5 # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "10s" -# The name of a file containing certificate that is used to create the HTTPS server. +# The path to a file containing certificate that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, # the certFile should be the concatenation of the server's certificate, any intermediates, # and the CA's certificate. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_cert_file = "" -# The name of a file containing matching private key that is used to create the HTTPS server. +# The path to a file containing matching private key that is used to create the HTTPS server. +# Migth be either absolute path or path related to tendermint's config directory. # NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server. Otherwise, HTTP server is run. tls_key_file = "" From 62f97a69e97262b5feb57b1c2498f0c1e0e297b3 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Tue, 2 Jul 2019 10:14:53 -0400 Subject: [PATCH 23/27] abci: Refactor CheckTx to notify of recheck (#3744) As per #2127, this refactors the RequestCheckTx ProtoBuf struct to allow for a flag indicating whether a query is a recheck or not (and allows for possible future, more nuanced states). In order to pass this extended information through to the ABCI app, the proxy.AppConnMempool (and, for consistency, the proxy.AppConnConsensus) interface seems to need to be refactored along with abcicli.Client. And, as per this comment, I've made the following modification to the protobuf definition for the RequestCheckTx structure: enum CheckTxType { New = 0; Recheck = 1; } message RequestCheckTx { bytes tx = 1; CheckTxType type = 2; } * Refactor ABCI CheckTx to notify of recheck As per #2127, this refactors the `RequestCheckTx` ProtoBuf struct to allow for: 1. a flag indicating whether a query is a recheck or not (and allows for possible future, more nuanced states) 2. an `additional_data` bytes array to provide information for those more nuanced states. In order to pass this extended information through to the ABCI app, the `proxy.AppConnMempool` (and, for consistency, the `proxy.AppConnConsensus`) interface seems to need to be refactored. Commits: * Fix linting issue * Add CHANGELOG_PENDING entry * Remove extraneous explicit initialization * Update ABCI spec doc to include new CheckTx params * Rename method param for consistency * Rename CheckTxType enum values and remove additional_data param --- CHANGELOG_PENDING.md | 6 +- abci/client/client.go | 8 +- abci/client/grpc_client.go | 16 +- abci/client/local_client.go | 20 +- abci/client/socket_client.go | 16 +- abci/cmd/abci-cli/abci-cli.go | 4 +- abci/example/example_test.go | 2 +- abci/example/kvstore/kvstore_test.go | 4 +- abci/tests/server/client.go | 4 +- abci/tests/test_app/app.go | 2 +- abci/types/messages.go | 8 +- abci/types/types.pb.go | 448 +++++++++++++++------------ abci/types/types.proto | 6 + consensus/replay_test.go | 2 +- docs/spec/abci/abci.md | 6 + mempool/clist_mempool.go | 7 +- mempool/clist_mempool_test.go | 4 +- proxy/app_conn.go | 12 +- state/execution.go | 2 +- 19 files changed, 331 insertions(+), 246 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d81e815f..9c94c55e 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -14,9 +14,13 @@ program](https://hackerone.com/tendermint). * Apps * Go API + - [abci] \#2127 ABCI / mempool: Add a "Recheck Tx" indicator. Breaks the ABCI + client interface (`abcicli.Client`) to allow for supplying the ABCI + `types.RequestCheckTx` and `types.RequestDeliverTx` structs, and lets the + mempool indicate to the ABCI app whether a CheckTx request is a recheck or + not. - [libs] Remove unused `db/debugDB` and `common/colors.go` & `errors/errors.go` files (@marbar3778) - * Blockchain Protocol * P2P Protocol diff --git a/abci/client/client.go b/abci/client/client.go index e1eea5d4..5f5ad786 100644 --- a/abci/client/client.go +++ b/abci/client/client.go @@ -28,8 +28,8 @@ type Client interface { EchoAsync(msg string) *ReqRes InfoAsync(types.RequestInfo) *ReqRes SetOptionAsync(types.RequestSetOption) *ReqRes - DeliverTxAsync(tx []byte) *ReqRes - CheckTxAsync(tx []byte) *ReqRes + DeliverTxAsync(types.RequestDeliverTx) *ReqRes + CheckTxAsync(types.RequestCheckTx) *ReqRes QueryAsync(types.RequestQuery) *ReqRes CommitAsync() *ReqRes InitChainAsync(types.RequestInitChain) *ReqRes @@ -40,8 +40,8 @@ type Client interface { EchoSync(msg string) (*types.ResponseEcho, error) InfoSync(types.RequestInfo) (*types.ResponseInfo, error) SetOptionSync(types.RequestSetOption) (*types.ResponseSetOption, error) - DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) - CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) + DeliverTxSync(types.RequestDeliverTx) (*types.ResponseDeliverTx, error) + CheckTxSync(types.RequestCheckTx) (*types.ResponseCheckTx, error) QuerySync(types.RequestQuery) (*types.ResponseQuery, error) CommitSync() (*types.ResponseCommit, error) InitChainSync(types.RequestInitChain) (*types.ResponseInitChain, error) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index d04f42b6..23d79055 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -159,8 +159,8 @@ func (cli *grpcClient) SetOptionAsync(params types.RequestSetOption) *ReqRes { return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_SetOption{SetOption: res}}) } -func (cli *grpcClient) DeliverTxAsync(tx []byte) *ReqRes { - req := types.ToRequestDeliverTx(tx) +func (cli *grpcClient) DeliverTxAsync(params types.RequestDeliverTx) *ReqRes { + req := types.ToRequestDeliverTx(params) res, err := cli.client.DeliverTx(context.Background(), req.GetDeliverTx(), grpc.FailFast(true)) if err != nil { cli.StopForError(err) @@ -168,8 +168,8 @@ func (cli *grpcClient) DeliverTxAsync(tx []byte) *ReqRes { return cli.finishAsyncCall(req, &types.Response{Value: &types.Response_DeliverTx{DeliverTx: res}}) } -func (cli *grpcClient) CheckTxAsync(tx []byte) *ReqRes { - req := types.ToRequestCheckTx(tx) +func (cli *grpcClient) CheckTxAsync(params types.RequestCheckTx) *ReqRes { + req := types.ToRequestCheckTx(params) res, err := cli.client.CheckTx(context.Background(), req.GetCheckTx(), grpc.FailFast(true)) if err != nil { cli.StopForError(err) @@ -265,13 +265,13 @@ func (cli *grpcClient) SetOptionSync(req types.RequestSetOption) (*types.Respons return reqres.Response.GetSetOption(), cli.Error() } -func (cli *grpcClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { - reqres := cli.DeliverTxAsync(tx) +func (cli *grpcClient) DeliverTxSync(params types.RequestDeliverTx) (*types.ResponseDeliverTx, error) { + reqres := cli.DeliverTxAsync(params) return reqres.Response.GetDeliverTx(), cli.Error() } -func (cli *grpcClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { - reqres := cli.CheckTxAsync(tx) +func (cli *grpcClient) CheckTxSync(params types.RequestCheckTx) (*types.ResponseCheckTx, error) { + reqres := cli.CheckTxAsync(params) return reqres.Response.GetCheckTx(), cli.Error() } diff --git a/abci/client/local_client.go b/abci/client/local_client.go index 4a3e6a5e..bb009173 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -81,24 +81,24 @@ func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { ) } -func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes { +func (app *localClient) DeliverTxAsync(params types.RequestDeliverTx) *ReqRes { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.DeliverTx(types.RequestDeliverTx{Tx: tx}) + res := app.Application.DeliverTx(params) return app.callback( - types.ToRequestDeliverTx(tx), + types.ToRequestDeliverTx(params), types.ToResponseDeliverTx(res), ) } -func (app *localClient) CheckTxAsync(tx []byte) *ReqRes { +func (app *localClient) CheckTxAsync(req types.RequestCheckTx) *ReqRes { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.CheckTx(types.RequestCheckTx{Tx: tx}) + res := app.Application.CheckTx(req) return app.callback( - types.ToRequestCheckTx(tx), + types.ToRequestCheckTx(req), types.ToResponseCheckTx(res), ) } @@ -184,19 +184,19 @@ func (app *localClient) SetOptionSync(req types.RequestSetOption) (*types.Respon return &res, nil } -func (app *localClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { +func (app *localClient) DeliverTxSync(req types.RequestDeliverTx) (*types.ResponseDeliverTx, error) { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.DeliverTx(types.RequestDeliverTx{Tx: tx}) + res := app.Application.DeliverTx(req) return &res, nil } -func (app *localClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { +func (app *localClient) CheckTxSync(req types.RequestCheckTx) (*types.ResponseCheckTx, error) { app.mtx.Lock() defer app.mtx.Unlock() - res := app.Application.CheckTx(types.RequestCheckTx{Tx: tx}) + res := app.Application.CheckTx(req) return &res, nil } diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index 3b401bd3..16e39bf3 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -243,12 +243,12 @@ func (cli *socketClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { return cli.queueRequest(types.ToRequestSetOption(req)) } -func (cli *socketClient) DeliverTxAsync(tx []byte) *ReqRes { - return cli.queueRequest(types.ToRequestDeliverTx(tx)) +func (cli *socketClient) DeliverTxAsync(req types.RequestDeliverTx) *ReqRes { + return cli.queueRequest(types.ToRequestDeliverTx(req)) } -func (cli *socketClient) CheckTxAsync(tx []byte) *ReqRes { - return cli.queueRequest(types.ToRequestCheckTx(tx)) +func (cli *socketClient) CheckTxAsync(req types.RequestCheckTx) *ReqRes { + return cli.queueRequest(types.ToRequestCheckTx(req)) } func (cli *socketClient) QueryAsync(req types.RequestQuery) *ReqRes { @@ -300,14 +300,14 @@ func (cli *socketClient) SetOptionSync(req types.RequestSetOption) (*types.Respo return reqres.Response.GetSetOption(), cli.Error() } -func (cli *socketClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { - reqres := cli.queueRequest(types.ToRequestDeliverTx(tx)) +func (cli *socketClient) DeliverTxSync(req types.RequestDeliverTx) (*types.ResponseDeliverTx, error) { + reqres := cli.queueRequest(types.ToRequestDeliverTx(req)) cli.FlushSync() return reqres.Response.GetDeliverTx(), cli.Error() } -func (cli *socketClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { - reqres := cli.queueRequest(types.ToRequestCheckTx(tx)) +func (cli *socketClient) CheckTxSync(req types.RequestCheckTx) (*types.ResponseCheckTx, error) { + reqres := cli.queueRequest(types.ToRequestCheckTx(req)) cli.FlushSync() return reqres.Response.GetCheckTx(), cli.Error() } diff --git a/abci/cmd/abci-cli/abci-cli.go b/abci/cmd/abci-cli/abci-cli.go index 7e55569c..cd0a6fd1 100644 --- a/abci/cmd/abci-cli/abci-cli.go +++ b/abci/cmd/abci-cli/abci-cli.go @@ -546,7 +546,7 @@ func cmdDeliverTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - res, err := client.DeliverTxSync(txBytes) + res, err := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes}) if err != nil { return err } @@ -572,7 +572,7 @@ func cmdCheckTx(cmd *cobra.Command, args []string) error { if err != nil { return err } - res, err := client.CheckTxSync(txBytes) + res, err := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes}) if err != nil { return err } diff --git a/abci/example/example_test.go b/abci/example/example_test.go index 677a2a48..6282f3a4 100644 --- a/abci/example/example_test.go +++ b/abci/example/example_test.go @@ -87,7 +87,7 @@ func testStream(t *testing.T, app types.Application) { // Write requests for counter := 0; counter < numDeliverTxs; counter++ { // Send request - reqRes := client.DeliverTxAsync([]byte("test")) + reqRes := client.DeliverTxAsync(types.RequestDeliverTx{Tx: []byte("test")}) _ = reqRes // check err ? diff --git a/abci/example/kvstore/kvstore_test.go b/abci/example/kvstore/kvstore_test.go index 074baa49..1649d3e8 100644 --- a/abci/example/kvstore/kvstore_test.go +++ b/abci/example/kvstore/kvstore_test.go @@ -283,11 +283,11 @@ func runClientTests(t *testing.T, client abcicli.Client) { } func testClient(t *testing.T, app abcicli.Client, tx []byte, key, value string) { - ar, err := app.DeliverTxSync(tx) + ar, err := app.DeliverTxSync(types.RequestDeliverTx{Tx: tx}) require.NoError(t, err) require.False(t, ar.IsErr(), ar) // repeating tx doesn't raise error - ar, err = app.DeliverTxSync(tx) + ar, err = app.DeliverTxSync(types.RequestDeliverTx{Tx: tx}) require.NoError(t, err) require.False(t, ar.IsErr(), ar) diff --git a/abci/tests/server/client.go b/abci/tests/server/client.go index 5daa1e6a..58a413a4 100644 --- a/abci/tests/server/client.go +++ b/abci/tests/server/client.go @@ -58,7 +58,7 @@ func Commit(client abcicli.Client, hashExp []byte) error { } func DeliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { - res, _ := client.DeliverTxSync(txBytes) + res, _ := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes}) code, data, log := res.Code, res.Data, res.Log if code != codeExp { fmt.Println("Failed test: DeliverTx") @@ -77,7 +77,7 @@ func DeliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp [] } func CheckTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { - res, _ := client.CheckTxSync(txBytes) + res, _ := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes}) code, data, log := res.Code, res.Data, res.Log if code != codeExp { fmt.Println("Failed test: CheckTx") diff --git a/abci/tests/test_app/app.go b/abci/tests/test_app/app.go index 25ed2f58..9c32fcc7 100644 --- a/abci/tests/test_app/app.go +++ b/abci/tests/test_app/app.go @@ -43,7 +43,7 @@ func commit(client abcicli.Client, hashExp []byte) { } func deliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) { - res, err := client.DeliverTxSync(txBytes) + res, err := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes}) if err != nil { panicf("client error: %v", err) } diff --git a/abci/types/messages.go b/abci/types/messages.go index cb64a15d..ad18727a 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -93,15 +93,15 @@ func ToRequestSetOption(req RequestSetOption) *Request { } } -func ToRequestDeliverTx(tx []byte) *Request { +func ToRequestDeliverTx(req RequestDeliverTx) *Request { return &Request{ - Value: &Request_DeliverTx{&RequestDeliverTx{Tx: tx}}, + Value: &Request_DeliverTx{&req}, } } -func ToRequestCheckTx(tx []byte) *Request { +func ToRequestCheckTx(req RequestCheckTx) *Request { return &Request{ - Value: &Request_CheckTx{&RequestCheckTx{Tx: tx}}, + Value: &Request_CheckTx{&req}, } } diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index a7455b52..926d528a 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -38,6 +38,29 @@ var _ = time.Kitchen // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +type CheckTxType int32 + +const ( + CheckTxType_New CheckTxType = 0 + CheckTxType_Recheck CheckTxType = 1 +) + +var CheckTxType_name = map[int32]string{ + 0: "New", + 1: "Recheck", +} +var CheckTxType_value = map[string]int32{ + "New": 0, + "Recheck": 1, +} + +func (x CheckTxType) String() string { + return proto.EnumName(CheckTxType_name, int32(x)) +} +func (CheckTxType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_types_30d8160a6576aafe, []int{0} +} + type Request struct { // Types that are valid to be assigned to Value: // *Request_Echo @@ -61,7 +84,7 @@ func (m *Request) Reset() { *m = Request{} } func (m *Request) String() string { return proto.CompactTextString(m) } func (*Request) ProtoMessage() {} func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{0} + return fileDescriptor_types_30d8160a6576aafe, []int{0} } func (m *Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -483,7 +506,7 @@ func (m *RequestEcho) Reset() { *m = RequestEcho{} } func (m *RequestEcho) String() string { return proto.CompactTextString(m) } func (*RequestEcho) ProtoMessage() {} func (*RequestEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{1} + return fileDescriptor_types_30d8160a6576aafe, []int{1} } func (m *RequestEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -529,7 +552,7 @@ func (m *RequestFlush) Reset() { *m = RequestFlush{} } func (m *RequestFlush) String() string { return proto.CompactTextString(m) } func (*RequestFlush) ProtoMessage() {} func (*RequestFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{2} + return fileDescriptor_types_30d8160a6576aafe, []int{2} } func (m *RequestFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -571,7 +594,7 @@ func (m *RequestInfo) Reset() { *m = RequestInfo{} } func (m *RequestInfo) String() string { return proto.CompactTextString(m) } func (*RequestInfo) ProtoMessage() {} func (*RequestInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{3} + return fileDescriptor_types_30d8160a6576aafe, []int{3} } func (m *RequestInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -634,7 +657,7 @@ func (m *RequestSetOption) Reset() { *m = RequestSetOption{} } func (m *RequestSetOption) String() string { return proto.CompactTextString(m) } func (*RequestSetOption) ProtoMessage() {} func (*RequestSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{4} + return fileDescriptor_types_30d8160a6576aafe, []int{4} } func (m *RequestSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -692,7 +715,7 @@ func (m *RequestInitChain) Reset() { *m = RequestInitChain{} } func (m *RequestInitChain) String() string { return proto.CompactTextString(m) } func (*RequestInitChain) ProtoMessage() {} func (*RequestInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{5} + return fileDescriptor_types_30d8160a6576aafe, []int{5} } func (m *RequestInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -770,7 +793,7 @@ func (m *RequestQuery) Reset() { *m = RequestQuery{} } func (m *RequestQuery) String() string { return proto.CompactTextString(m) } func (*RequestQuery) ProtoMessage() {} func (*RequestQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{6} + return fileDescriptor_types_30d8160a6576aafe, []int{6} } func (m *RequestQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -841,7 +864,7 @@ func (m *RequestBeginBlock) Reset() { *m = RequestBeginBlock{} } func (m *RequestBeginBlock) String() string { return proto.CompactTextString(m) } func (*RequestBeginBlock) ProtoMessage() {} func (*RequestBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{7} + return fileDescriptor_types_30d8160a6576aafe, []int{7} } func (m *RequestBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -899,17 +922,18 @@ func (m *RequestBeginBlock) GetByzantineValidators() []Evidence { } type RequestCheckTx struct { - Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` + Type CheckTxType `protobuf:"varint,2,opt,name=type,proto3,enum=types.CheckTxType" json:"type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *RequestCheckTx) Reset() { *m = RequestCheckTx{} } func (m *RequestCheckTx) String() string { return proto.CompactTextString(m) } func (*RequestCheckTx) ProtoMessage() {} func (*RequestCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{8} + return fileDescriptor_types_30d8160a6576aafe, []int{8} } func (m *RequestCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -945,6 +969,13 @@ func (m *RequestCheckTx) GetTx() []byte { return nil } +func (m *RequestCheckTx) GetType() CheckTxType { + if m != nil { + return m.Type + } + return CheckTxType_New +} + type RequestDeliverTx struct { Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -956,7 +987,7 @@ func (m *RequestDeliverTx) Reset() { *m = RequestDeliverTx{} } func (m *RequestDeliverTx) String() string { return proto.CompactTextString(m) } func (*RequestDeliverTx) ProtoMessage() {} func (*RequestDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{9} + return fileDescriptor_types_30d8160a6576aafe, []int{9} } func (m *RequestDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1003,7 +1034,7 @@ func (m *RequestEndBlock) Reset() { *m = RequestEndBlock{} } func (m *RequestEndBlock) String() string { return proto.CompactTextString(m) } func (*RequestEndBlock) ProtoMessage() {} func (*RequestEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{10} + return fileDescriptor_types_30d8160a6576aafe, []int{10} } func (m *RequestEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1049,7 +1080,7 @@ func (m *RequestCommit) Reset() { *m = RequestCommit{} } func (m *RequestCommit) String() string { return proto.CompactTextString(m) } func (*RequestCommit) ProtoMessage() {} func (*RequestCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{11} + return fileDescriptor_types_30d8160a6576aafe, []int{11} } func (m *RequestCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1102,7 +1133,7 @@ func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{12} + return fileDescriptor_types_30d8160a6576aafe, []int{12} } func (m *Response) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1555,7 +1586,7 @@ func (m *ResponseException) Reset() { *m = ResponseException{} } func (m *ResponseException) String() string { return proto.CompactTextString(m) } func (*ResponseException) ProtoMessage() {} func (*ResponseException) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{13} + return fileDescriptor_types_30d8160a6576aafe, []int{13} } func (m *ResponseException) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1602,7 +1633,7 @@ func (m *ResponseEcho) Reset() { *m = ResponseEcho{} } func (m *ResponseEcho) String() string { return proto.CompactTextString(m) } func (*ResponseEcho) ProtoMessage() {} func (*ResponseEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{14} + return fileDescriptor_types_30d8160a6576aafe, []int{14} } func (m *ResponseEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1648,7 +1679,7 @@ func (m *ResponseFlush) Reset() { *m = ResponseFlush{} } func (m *ResponseFlush) String() string { return proto.CompactTextString(m) } func (*ResponseFlush) ProtoMessage() {} func (*ResponseFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{15} + return fileDescriptor_types_30d8160a6576aafe, []int{15} } func (m *ResponseFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1692,7 +1723,7 @@ func (m *ResponseInfo) Reset() { *m = ResponseInfo{} } func (m *ResponseInfo) String() string { return proto.CompactTextString(m) } func (*ResponseInfo) ProtoMessage() {} func (*ResponseInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{16} + return fileDescriptor_types_30d8160a6576aafe, []int{16} } func (m *ResponseInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1771,7 +1802,7 @@ func (m *ResponseSetOption) Reset() { *m = ResponseSetOption{} } func (m *ResponseSetOption) String() string { return proto.CompactTextString(m) } func (*ResponseSetOption) ProtoMessage() {} func (*ResponseSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{17} + return fileDescriptor_types_30d8160a6576aafe, []int{17} } func (m *ResponseSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1833,7 +1864,7 @@ func (m *ResponseInitChain) Reset() { *m = ResponseInitChain{} } func (m *ResponseInitChain) String() string { return proto.CompactTextString(m) } func (*ResponseInitChain) ProtoMessage() {} func (*ResponseInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{18} + return fileDescriptor_types_30d8160a6576aafe, []int{18} } func (m *ResponseInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1896,7 +1927,7 @@ func (m *ResponseQuery) Reset() { *m = ResponseQuery{} } func (m *ResponseQuery) String() string { return proto.CompactTextString(m) } func (*ResponseQuery) ProtoMessage() {} func (*ResponseQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{19} + return fileDescriptor_types_30d8160a6576aafe, []int{19} } func (m *ResponseQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1999,7 +2030,7 @@ func (m *ResponseBeginBlock) Reset() { *m = ResponseBeginBlock{} } func (m *ResponseBeginBlock) String() string { return proto.CompactTextString(m) } func (*ResponseBeginBlock) ProtoMessage() {} func (*ResponseBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{20} + return fileDescriptor_types_30d8160a6576aafe, []int{20} } func (m *ResponseBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2053,7 +2084,7 @@ func (m *ResponseCheckTx) Reset() { *m = ResponseCheckTx{} } func (m *ResponseCheckTx) String() string { return proto.CompactTextString(m) } func (*ResponseCheckTx) ProtoMessage() {} func (*ResponseCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{21} + return fileDescriptor_types_30d8160a6576aafe, []int{21} } func (m *ResponseCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2156,7 +2187,7 @@ func (m *ResponseDeliverTx) Reset() { *m = ResponseDeliverTx{} } func (m *ResponseDeliverTx) String() string { return proto.CompactTextString(m) } func (*ResponseDeliverTx) ProtoMessage() {} func (*ResponseDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{22} + return fileDescriptor_types_30d8160a6576aafe, []int{22} } func (m *ResponseDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2254,7 +2285,7 @@ func (m *ResponseEndBlock) Reset() { *m = ResponseEndBlock{} } func (m *ResponseEndBlock) String() string { return proto.CompactTextString(m) } func (*ResponseEndBlock) ProtoMessage() {} func (*ResponseEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{23} + return fileDescriptor_types_30d8160a6576aafe, []int{23} } func (m *ResponseEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2316,7 +2347,7 @@ func (m *ResponseCommit) Reset() { *m = ResponseCommit{} } func (m *ResponseCommit) String() string { return proto.CompactTextString(m) } func (*ResponseCommit) ProtoMessage() {} func (*ResponseCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{24} + return fileDescriptor_types_30d8160a6576aafe, []int{24} } func (m *ResponseCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2367,7 +2398,7 @@ func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } func (m *ConsensusParams) String() string { return proto.CompactTextString(m) } func (*ConsensusParams) ProtoMessage() {} func (*ConsensusParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{25} + return fileDescriptor_types_30d8160a6576aafe, []int{25} } func (m *ConsensusParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2417,7 +2448,7 @@ func (m *ConsensusParams) GetValidator() *ValidatorParams { return nil } -// BlockParams contains limits on the block size and timestamp. +// BlockParams contains limits on the block size. type BlockParams struct { // Note: must be greater than 0 MaxBytes int64 `protobuf:"varint,1,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` @@ -2432,7 +2463,7 @@ func (m *BlockParams) Reset() { *m = BlockParams{} } func (m *BlockParams) String() string { return proto.CompactTextString(m) } func (*BlockParams) ProtoMessage() {} func (*BlockParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{26} + return fileDescriptor_types_30d8160a6576aafe, []int{26} } func (m *BlockParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2488,7 +2519,7 @@ func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } func (m *EvidenceParams) String() string { return proto.CompactTextString(m) } func (*EvidenceParams) ProtoMessage() {} func (*EvidenceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{27} + return fileDescriptor_types_30d8160a6576aafe, []int{27} } func (m *EvidenceParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2536,7 +2567,7 @@ func (m *ValidatorParams) Reset() { *m = ValidatorParams{} } func (m *ValidatorParams) String() string { return proto.CompactTextString(m) } func (*ValidatorParams) ProtoMessage() {} func (*ValidatorParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{28} + return fileDescriptor_types_30d8160a6576aafe, []int{28} } func (m *ValidatorParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2584,7 +2615,7 @@ func (m *LastCommitInfo) Reset() { *m = LastCommitInfo{} } func (m *LastCommitInfo) String() string { return proto.CompactTextString(m) } func (*LastCommitInfo) ProtoMessage() {} func (*LastCommitInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{29} + return fileDescriptor_types_30d8160a6576aafe, []int{29} } func (m *LastCommitInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2639,7 +2670,7 @@ func (m *Event) Reset() { *m = Event{} } func (m *Event) String() string { return proto.CompactTextString(m) } func (*Event) ProtoMessage() {} func (*Event) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{30} + return fileDescriptor_types_30d8160a6576aafe, []int{30} } func (m *Event) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2713,7 +2744,7 @@ func (m *Header) Reset() { *m = Header{} } func (m *Header) String() string { return proto.CompactTextString(m) } func (*Header) ProtoMessage() {} func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{31} + return fileDescriptor_types_30d8160a6576aafe, []int{31} } func (m *Header) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2866,7 +2897,7 @@ func (m *Version) Reset() { *m = Version{} } func (m *Version) String() string { return proto.CompactTextString(m) } func (*Version) ProtoMessage() {} func (*Version) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{32} + return fileDescriptor_types_30d8160a6576aafe, []int{32} } func (m *Version) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2921,7 +2952,7 @@ func (m *BlockID) Reset() { *m = BlockID{} } func (m *BlockID) String() string { return proto.CompactTextString(m) } func (*BlockID) ProtoMessage() {} func (*BlockID) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{33} + return fileDescriptor_types_30d8160a6576aafe, []int{33} } func (m *BlockID) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2976,7 +3007,7 @@ func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } func (*PartSetHeader) ProtoMessage() {} func (*PartSetHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{34} + return fileDescriptor_types_30d8160a6576aafe, []int{34} } func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3033,7 +3064,7 @@ func (m *Validator) Reset() { *m = Validator{} } func (m *Validator) String() string { return proto.CompactTextString(m) } func (*Validator) ProtoMessage() {} func (*Validator) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{35} + return fileDescriptor_types_30d8160a6576aafe, []int{35} } func (m *Validator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3089,7 +3120,7 @@ func (m *ValidatorUpdate) Reset() { *m = ValidatorUpdate{} } func (m *ValidatorUpdate) String() string { return proto.CompactTextString(m) } func (*ValidatorUpdate) ProtoMessage() {} func (*ValidatorUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{36} + return fileDescriptor_types_30d8160a6576aafe, []int{36} } func (m *ValidatorUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3145,7 +3176,7 @@ func (m *VoteInfo) Reset() { *m = VoteInfo{} } func (m *VoteInfo) String() string { return proto.CompactTextString(m) } func (*VoteInfo) ProtoMessage() {} func (*VoteInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{37} + return fileDescriptor_types_30d8160a6576aafe, []int{37} } func (m *VoteInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3200,7 +3231,7 @@ func (m *PubKey) Reset() { *m = PubKey{} } func (m *PubKey) String() string { return proto.CompactTextString(m) } func (*PubKey) ProtoMessage() {} func (*PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{38} + return fileDescriptor_types_30d8160a6576aafe, []int{38} } func (m *PubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3258,7 +3289,7 @@ func (m *Evidence) Reset() { *m = Evidence{} } func (m *Evidence) String() string { return proto.CompactTextString(m) } func (*Evidence) ProtoMessage() {} func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_types_62f0c59aeb977f78, []int{39} + return fileDescriptor_types_30d8160a6576aafe, []int{39} } func (m *Evidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3403,6 +3434,8 @@ func init() { golang_proto.RegisterType((*PubKey)(nil), "types.PubKey") proto.RegisterType((*Evidence)(nil), "types.Evidence") golang_proto.RegisterType((*Evidence)(nil), "types.Evidence") + proto.RegisterEnum("types.CheckTxType", CheckTxType_name, CheckTxType_value) + golang_proto.RegisterEnum("types.CheckTxType", CheckTxType_name, CheckTxType_value) } func (this *Request) Equal(that interface{}) bool { if that == nil { @@ -3958,6 +3991,9 @@ func (this *RequestCheckTx) Equal(that interface{}) bool { if !bytes.Equal(this.Tx, that1.Tx) { return false } + if this.Type != that1.Type { + return false + } if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { return false } @@ -6211,6 +6247,11 @@ func (m *RequestCheckTx) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(len(m.Tx))) i += copy(dAtA[i:], m.Tx) } + if m.Type != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.Type)) + } if m.XXX_unrecognized != nil { i += copy(dAtA[i:], m.XXX_unrecognized) } @@ -7872,8 +7913,9 @@ func NewPopulatedRequestCheckTx(r randyTypes, easy bool) *RequestCheckTx { for i := 0; i < v11; i++ { this.Tx[i] = byte(r.Intn(256)) } + this.Type = CheckTxType([]int32{0, 1}[r.Intn(2)]) if !easy && r.Intn(10) != 0 { - this.XXX_unrecognized = randUnrecognizedTypes(r, 2) + this.XXX_unrecognized = randUnrecognizedTypes(r, 3) } return this } @@ -8906,6 +8948,9 @@ func (m *RequestCheckTx) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.Type != 0 { + n += 1 + sovTypes(uint64(m.Type)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -11127,6 +11172,25 @@ func (m *RequestCheckTx) Unmarshal(dAtA []byte) error { m.Tx = []byte{} } iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= (CheckTxType(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -15638,152 +15702,154 @@ var ( ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") ) -func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_62f0c59aeb977f78) } +func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_30d8160a6576aafe) } func init() { - golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_62f0c59aeb977f78) + golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_30d8160a6576aafe) } -var fileDescriptor_types_62f0c59aeb977f78 = []byte{ - // 2241 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x58, 0x4b, 0x73, 0x1c, 0x49, - 0xf1, 0x57, 0xcf, 0xbb, 0x73, 0x34, 0x0f, 0x97, 0x65, 0x7b, 0x3c, 0x7f, 0xff, 0x25, 0x47, 0x1b, - 0x76, 0x25, 0xd6, 0x3b, 0xda, 0xd5, 0x62, 0x42, 0xc6, 0xcb, 0x46, 0x68, 0x6c, 0x83, 0x14, 0x6b, - 0x40, 0xb4, 0x6d, 0x71, 0x21, 0xa2, 0xa3, 0x66, 0xba, 0x3c, 0xd3, 0xe1, 0x99, 0xee, 0xde, 0xee, - 0x9a, 0xd9, 0x11, 0x47, 0xce, 0x7b, 0xd8, 0x03, 0x1f, 0x81, 0x03, 0x1f, 0x61, 0x8f, 0x9c, 0x88, - 0x3d, 0x72, 0xe0, 0x6c, 0x40, 0x04, 0x17, 0x22, 0x38, 0x03, 0x37, 0xa2, 0xb2, 0xaa, 0x9f, 0xea, - 0x31, 0xbb, 0x86, 0x1b, 0x17, 0xa9, 0xab, 0xf2, 0x97, 0xf5, 0xc8, 0xc9, 0xcc, 0x5f, 0x66, 0xc1, - 0x75, 0x3a, 0x1a, 0x3b, 0xfb, 0xfc, 0xdc, 0x67, 0xa1, 0xfc, 0x3b, 0xf0, 0x03, 0x8f, 0x7b, 0xa4, - 0x8a, 0x83, 0xfe, 0xbb, 0x13, 0x87, 0x4f, 0x17, 0xa3, 0xc1, 0xd8, 0x9b, 0xef, 0x4f, 0xbc, 0x89, - 0xb7, 0x8f, 0xd2, 0xd1, 0xe2, 0x05, 0x8e, 0x70, 0x80, 0x5f, 0x52, 0xab, 0xff, 0x20, 0x05, 0xe7, - 0xcc, 0xb5, 0x59, 0x30, 0x77, 0x5c, 0x9e, 0xfe, 0x1c, 0x07, 0xe7, 0x3e, 0xf7, 0xf6, 0xe7, 0x2c, - 0x78, 0x39, 0x63, 0xea, 0x9f, 0x52, 0x3e, 0xfc, 0xb7, 0xca, 0x33, 0x67, 0x14, 0xee, 0x8f, 0xbd, - 0xf9, 0xdc, 0x73, 0xd3, 0x87, 0xed, 0xef, 0x4c, 0x3c, 0x6f, 0x32, 0x63, 0xc9, 0xe1, 0xb8, 0x33, - 0x67, 0x21, 0xa7, 0x73, 0x5f, 0x02, 0x8c, 0xdf, 0x56, 0xa0, 0x6e, 0xb2, 0x4f, 0x16, 0x2c, 0xe4, - 0x64, 0x17, 0x2a, 0x6c, 0x3c, 0xf5, 0x7a, 0xa5, 0xdb, 0xda, 0x6e, 0xf3, 0x80, 0x0c, 0xe4, 0x42, - 0x4a, 0xfa, 0x78, 0x3c, 0xf5, 0x8e, 0x37, 0x4c, 0x44, 0x90, 0x77, 0xa0, 0xfa, 0x62, 0xb6, 0x08, - 0xa7, 0xbd, 0x32, 0x42, 0xaf, 0x66, 0xa1, 0xdf, 0x17, 0xa2, 0xe3, 0x0d, 0x53, 0x62, 0xc4, 0xb2, - 0x8e, 0xfb, 0xc2, 0xeb, 0x55, 0x8a, 0x96, 0x3d, 0x71, 0x5f, 0xe0, 0xb2, 0x02, 0x41, 0x0e, 0x01, - 0x42, 0xc6, 0x2d, 0xcf, 0xe7, 0x8e, 0xe7, 0xf6, 0xaa, 0x88, 0xbf, 0x91, 0xc5, 0x3f, 0x65, 0xfc, - 0xc7, 0x28, 0x3e, 0xde, 0x30, 0xf5, 0x30, 0x1a, 0x08, 0x4d, 0xc7, 0x75, 0xb8, 0x35, 0x9e, 0x52, - 0xc7, 0xed, 0xd5, 0x8a, 0x34, 0x4f, 0x5c, 0x87, 0x3f, 0x14, 0x62, 0xa1, 0xe9, 0x44, 0x03, 0x71, - 0x95, 0x4f, 0x16, 0x2c, 0x38, 0xef, 0xd5, 0x8b, 0xae, 0xf2, 0x13, 0x21, 0x12, 0x57, 0x41, 0x0c, - 0x79, 0x00, 0xcd, 0x11, 0x9b, 0x38, 0xae, 0x35, 0x9a, 0x79, 0xe3, 0x97, 0xbd, 0x06, 0xaa, 0xf4, - 0xb2, 0x2a, 0x43, 0x01, 0x18, 0x0a, 0xf9, 0xf1, 0x86, 0x09, 0xa3, 0x78, 0x44, 0x0e, 0xa0, 0x31, - 0x9e, 0xb2, 0xf1, 0x4b, 0x8b, 0xaf, 0x7a, 0x3a, 0x6a, 0x5e, 0xcb, 0x6a, 0x3e, 0x14, 0xd2, 0x67, - 0xab, 0xe3, 0x0d, 0xb3, 0x3e, 0x96, 0x9f, 0xe4, 0x1e, 0xe8, 0xcc, 0xb5, 0xd5, 0x76, 0x4d, 0x54, - 0xba, 0x9e, 0xfb, 0x5d, 0x5c, 0x3b, 0xda, 0xac, 0xc1, 0xd4, 0x37, 0x19, 0x40, 0x4d, 0x38, 0x83, - 0xc3, 0x7b, 0x9b, 0xa8, 0xb3, 0x95, 0xdb, 0x08, 0x65, 0xc7, 0x1b, 0xa6, 0x42, 0x09, 0xf3, 0xd9, - 0x6c, 0xe6, 0x2c, 0x59, 0x20, 0x0e, 0x77, 0xb5, 0xc8, 0x7c, 0x8f, 0xa4, 0x1c, 0x8f, 0xa7, 0xdb, - 0xd1, 0x60, 0x58, 0x87, 0xea, 0x92, 0xce, 0x16, 0xcc, 0x78, 0x1b, 0x9a, 0x29, 0x4f, 0x21, 0x3d, - 0xa8, 0xcf, 0x59, 0x18, 0xd2, 0x09, 0xeb, 0x69, 0xb7, 0xb5, 0x5d, 0xdd, 0x8c, 0x86, 0x46, 0x1b, - 0x36, 0xd3, 0x7e, 0x62, 0xcc, 0x63, 0x45, 0xe1, 0x0b, 0x42, 0x71, 0xc9, 0x82, 0x50, 0x38, 0x80, - 0x52, 0x54, 0x43, 0x72, 0x07, 0x5a, 0x68, 0x07, 0x2b, 0x92, 0x0b, 0x3f, 0xad, 0x98, 0x9b, 0x38, - 0x79, 0xa6, 0x40, 0x3b, 0xd0, 0xf4, 0x0f, 0xfc, 0x18, 0x52, 0x46, 0x08, 0xf8, 0x07, 0xbe, 0x02, - 0x18, 0xdf, 0x85, 0x6e, 0xde, 0x95, 0x48, 0x17, 0xca, 0x2f, 0xd9, 0xb9, 0xda, 0x4f, 0x7c, 0x92, - 0x2d, 0x75, 0x2d, 0xdc, 0x43, 0x37, 0xd5, 0x1d, 0x3f, 0x2f, 0xc5, 0xca, 0xb1, 0x37, 0x91, 0x43, - 0xa8, 0x88, 0xa0, 0x42, 0xed, 0xe6, 0x41, 0x7f, 0x20, 0x23, 0x6e, 0x10, 0x45, 0xdc, 0xe0, 0x59, - 0x14, 0x71, 0xc3, 0xc6, 0x97, 0xaf, 0x76, 0x36, 0x3e, 0xff, 0xc3, 0x8e, 0x66, 0xa2, 0x06, 0xb9, - 0x29, 0x1c, 0x82, 0x3a, 0xae, 0xe5, 0xd8, 0x6a, 0x9f, 0x3a, 0x8e, 0x4f, 0x6c, 0x72, 0x04, 0xdd, - 0xb1, 0xe7, 0x86, 0xcc, 0x0d, 0x17, 0xa1, 0xe5, 0xd3, 0x80, 0xce, 0x43, 0x15, 0x6b, 0xd1, 0xcf, - 0xff, 0x30, 0x12, 0x9f, 0xa2, 0xd4, 0xec, 0x8c, 0xb3, 0x13, 0xe4, 0x43, 0x80, 0x25, 0x9d, 0x39, - 0x36, 0xe5, 0x5e, 0x10, 0xf6, 0x2a, 0xb7, 0xcb, 0x29, 0xe5, 0xb3, 0x48, 0xf0, 0xdc, 0xb7, 0x29, - 0x67, 0xc3, 0x8a, 0x38, 0x99, 0x99, 0xc2, 0x93, 0xb7, 0xa0, 0x43, 0x7d, 0xdf, 0x0a, 0x39, 0xe5, - 0xcc, 0x1a, 0x9d, 0x73, 0x16, 0x62, 0x3c, 0x6e, 0x9a, 0x2d, 0xea, 0xfb, 0x4f, 0xc5, 0xec, 0x50, - 0x4c, 0x1a, 0x76, 0xfc, 0x6b, 0x62, 0xa8, 0x10, 0x02, 0x15, 0x9b, 0x72, 0x8a, 0xd6, 0xd8, 0x34, - 0xf1, 0x5b, 0xcc, 0xf9, 0x94, 0x4f, 0xd5, 0x1d, 0xf1, 0x9b, 0x5c, 0x87, 0xda, 0x94, 0x39, 0x93, - 0x29, 0xc7, 0x6b, 0x95, 0x4d, 0x35, 0x12, 0x86, 0xf7, 0x03, 0x6f, 0xc9, 0x30, 0x5b, 0x34, 0x4c, - 0x39, 0x30, 0xfe, 0xa2, 0xc1, 0x95, 0x4b, 0xe1, 0x25, 0xd6, 0x9d, 0xd2, 0x70, 0x1a, 0xed, 0x25, - 0xbe, 0xc9, 0x3b, 0x62, 0x5d, 0x6a, 0xb3, 0x40, 0x65, 0xb1, 0x96, 0xba, 0xf1, 0x31, 0x4e, 0xaa, - 0x8b, 0x2a, 0x08, 0x79, 0x0c, 0xdd, 0x19, 0x0d, 0xb9, 0x25, 0xa3, 0xc0, 0xc2, 0x2c, 0x55, 0xce, - 0x44, 0xe6, 0x13, 0x1a, 0x45, 0x8b, 0x70, 0x4e, 0xa5, 0xde, 0x9e, 0x65, 0x66, 0xc9, 0x31, 0x6c, - 0x8d, 0xce, 0x7f, 0x4e, 0x5d, 0xee, 0xb8, 0xcc, 0xba, 0x64, 0xf3, 0x8e, 0x5a, 0xea, 0xf1, 0xd2, - 0xb1, 0x99, 0x3b, 0x8e, 0x8c, 0x7d, 0x35, 0x56, 0x89, 0x7f, 0x8c, 0xd0, 0xb8, 0x0d, 0xed, 0x6c, - 0x2e, 0x20, 0x6d, 0x28, 0xf1, 0x95, 0xba, 0x61, 0x89, 0xaf, 0x0c, 0x23, 0xf6, 0xc0, 0x38, 0x20, - 0x2f, 0x61, 0xf6, 0xa0, 0x93, 0x4b, 0x0e, 0x29, 0x73, 0x6b, 0x69, 0x73, 0x1b, 0x1d, 0x68, 0x65, - 0x72, 0x82, 0xf1, 0x59, 0x15, 0x1a, 0x26, 0x0b, 0x7d, 0xe1, 0x4c, 0xe4, 0x10, 0x74, 0xb6, 0x1a, - 0x33, 0x99, 0x8e, 0xb5, 0x5c, 0xb2, 0x93, 0x98, 0xc7, 0x91, 0x5c, 0xa4, 0x85, 0x18, 0x4c, 0xf6, - 0x32, 0x54, 0x72, 0x35, 0xaf, 0x94, 0xe6, 0x92, 0xbb, 0x59, 0x2e, 0xd9, 0xca, 0x61, 0x73, 0x64, - 0xb2, 0x97, 0x21, 0x93, 0xfc, 0xc2, 0x19, 0x36, 0xb9, 0x5f, 0xc0, 0x26, 0xf9, 0xe3, 0xaf, 0xa1, - 0x93, 0xfb, 0x05, 0x74, 0xd2, 0xbb, 0xb4, 0x57, 0x21, 0x9f, 0xdc, 0xcd, 0xf2, 0x49, 0xfe, 0x3a, - 0x39, 0x42, 0xf9, 0xb0, 0x88, 0x50, 0x6e, 0xe6, 0x74, 0xd6, 0x32, 0xca, 0x07, 0x97, 0x18, 0xe5, - 0x7a, 0x4e, 0xb5, 0x80, 0x52, 0xee, 0x67, 0x72, 0x3d, 0x14, 0xde, 0xad, 0x38, 0xd9, 0x93, 0xef, - 0x5c, 0x66, 0xa3, 0x1b, 0xf9, 0x9f, 0xb6, 0x88, 0x8e, 0xf6, 0x73, 0x74, 0x74, 0x2d, 0x7f, 0xca, - 0x1c, 0x1f, 0x25, 0xac, 0xb2, 0x27, 0xe2, 0x3e, 0xe7, 0x69, 0x22, 0x47, 0xb0, 0x20, 0xf0, 0x02, - 0x95, 0xb0, 0xe5, 0xc0, 0xd8, 0x15, 0x99, 0x28, 0xf1, 0xaf, 0xd7, 0x30, 0x10, 0x3a, 0x7d, 0xca, - 0xbb, 0x8c, 0x2f, 0xb4, 0x44, 0x17, 0x23, 0x3a, 0x9d, 0xc5, 0x74, 0x95, 0xc5, 0x52, 0xc4, 0x54, - 0xca, 0x12, 0xd3, 0x0e, 0x34, 0x45, 0xae, 0xcc, 0x71, 0x0e, 0xf5, 0x23, 0xce, 0x21, 0xdf, 0x82, - 0x2b, 0x98, 0x67, 0x24, 0x7d, 0xa9, 0x40, 0xac, 0x60, 0x20, 0x76, 0x84, 0x40, 0x5a, 0x4c, 0x26, - 0xc0, 0x77, 0xe1, 0x6a, 0x0a, 0x2b, 0xd6, 0xc5, 0x1c, 0x27, 0x93, 0x6f, 0x37, 0x46, 0x1f, 0xf9, - 0xfe, 0x31, 0x0d, 0xa7, 0xc6, 0x0f, 0x13, 0x03, 0x25, 0x7c, 0x46, 0xa0, 0x32, 0xf6, 0x6c, 0x79, - 0xef, 0x96, 0x89, 0xdf, 0x82, 0xe3, 0x66, 0xde, 0x04, 0x0f, 0xa7, 0x9b, 0xe2, 0x53, 0xa0, 0xe2, - 0x50, 0xd2, 0x65, 0xcc, 0x18, 0xbf, 0xd4, 0x92, 0xf5, 0x12, 0x8a, 0x2b, 0x62, 0x23, 0xed, 0x3f, - 0x61, 0xa3, 0xd2, 0xd7, 0x63, 0x23, 0xe3, 0x42, 0x4b, 0x7e, 0xb2, 0x98, 0x67, 0xde, 0xec, 0x8a, - 0xc2, 0x7b, 0x1c, 0xd7, 0x66, 0x2b, 0x34, 0x69, 0xd9, 0x94, 0x83, 0xa8, 0x04, 0xa8, 0xa1, 0x99, - 0xb3, 0x25, 0x40, 0x1d, 0xe7, 0xe4, 0x80, 0xdc, 0x41, 0x7e, 0xf2, 0x5e, 0xa8, 0x50, 0x6d, 0x0d, - 0x54, 0xa1, 0x7e, 0x2a, 0x26, 0x4d, 0x29, 0x4b, 0x65, 0x5b, 0x3d, 0x43, 0x6e, 0xb7, 0x40, 0x17, - 0x07, 0x0d, 0x7d, 0x3a, 0x66, 0x18, 0x79, 0xba, 0x99, 0x4c, 0x18, 0xcf, 0x80, 0x5c, 0x8e, 0x78, - 0xf2, 0x11, 0xd4, 0xd8, 0x92, 0xb9, 0x5c, 0x58, 0x5c, 0x18, 0x6d, 0x33, 0xa6, 0x13, 0xe6, 0xf2, - 0x61, 0x4f, 0x98, 0xea, 0xaf, 0xaf, 0x76, 0xba, 0x12, 0x73, 0xd7, 0x9b, 0x3b, 0x9c, 0xcd, 0x7d, - 0x7e, 0x6e, 0x2a, 0x2d, 0xe3, 0xef, 0x9a, 0x60, 0x83, 0x4c, 0x36, 0x28, 0x34, 0x5e, 0xe4, 0xf2, - 0xa5, 0x14, 0x71, 0x7f, 0x35, 0x83, 0xfe, 0x3f, 0xc0, 0x84, 0x86, 0xd6, 0xa7, 0xd4, 0xe5, 0xcc, - 0x56, 0x56, 0xd5, 0x27, 0x34, 0xfc, 0x29, 0x4e, 0x88, 0x2a, 0x47, 0x88, 0x17, 0x21, 0xb3, 0xd1, - 0xbc, 0x65, 0xb3, 0x3e, 0xa1, 0xe1, 0xf3, 0x90, 0xd9, 0xa9, 0xbb, 0xd5, 0xdf, 0xe4, 0x6e, 0x59, - 0x7b, 0x36, 0xf2, 0xf6, 0xfc, 0x67, 0xca, 0x97, 0x13, 0xb2, 0xfc, 0xdf, 0xb8, 0xfb, 0xdf, 0x34, - 0x51, 0x27, 0x64, 0x53, 0x32, 0x39, 0x81, 0x2b, 0x71, 0x4c, 0x59, 0x0b, 0x8c, 0xb5, 0xc8, 0xab, - 0x5e, 0x1f, 0x8a, 0xdd, 0x65, 0x76, 0x3a, 0x24, 0x3f, 0x82, 0x1b, 0xb9, 0x8c, 0x10, 0x2f, 0x58, - 0x7a, 0x6d, 0x62, 0xb8, 0x96, 0x4d, 0x0c, 0xd1, 0x7a, 0x89, 0x35, 0xca, 0x6f, 0xe4, 0xe5, 0xdf, - 0x10, 0x85, 0x53, 0x9a, 0x4c, 0x8a, 0x7e, 0x53, 0xe3, 0x57, 0x1a, 0x74, 0x72, 0x07, 0x22, 0xbb, - 0x50, 0x95, 0x7c, 0xa6, 0x65, 0xda, 0x53, 0xb4, 0x98, 0x3a, 0xb3, 0x04, 0x90, 0xf7, 0xa1, 0xc1, - 0x54, 0x0d, 0xa7, 0x2e, 0x79, 0x2d, 0x57, 0xda, 0x29, 0x7c, 0x0c, 0x23, 0xdf, 0x06, 0x3d, 0x36, - 0x5d, 0xae, 0x7e, 0x8f, 0x2d, 0xad, 0x94, 0x12, 0xa0, 0xf1, 0x10, 0x9a, 0xa9, 0xed, 0xc9, 0xff, - 0x81, 0x3e, 0xa7, 0x2b, 0x55, 0x84, 0xcb, 0xf2, 0xad, 0x31, 0xa7, 0x2b, 0xac, 0xbf, 0xc9, 0x0d, - 0xa8, 0x0b, 0xe1, 0x84, 0x4a, 0xc3, 0x97, 0xcd, 0xda, 0x9c, 0xae, 0x7e, 0x40, 0x43, 0x63, 0x0f, - 0xda, 0xd9, 0x63, 0x45, 0xd0, 0x88, 0x10, 0x25, 0xf4, 0x68, 0xc2, 0x8c, 0x7b, 0xd0, 0xc9, 0x9d, - 0x86, 0x18, 0xd0, 0xf2, 0x17, 0x23, 0xeb, 0x25, 0x3b, 0xb7, 0xf0, 0xb8, 0xe8, 0x26, 0xba, 0xd9, - 0xf4, 0x17, 0xa3, 0x8f, 0xd9, 0xf9, 0x33, 0x31, 0x65, 0x3c, 0x85, 0x76, 0xb6, 0x3c, 0x16, 0x29, - 0x33, 0xf0, 0x16, 0xae, 0x8d, 0xeb, 0x57, 0x4d, 0x39, 0x10, 0x1d, 0xf6, 0xd2, 0x93, 0x9e, 0x91, - 0xae, 0x87, 0xcf, 0x3c, 0xce, 0x52, 0x45, 0xb5, 0xc4, 0x18, 0x0e, 0x54, 0xf1, 0x37, 0x17, 0xbf, - 0x9f, 0xc0, 0x45, 0x14, 0x2c, 0xbe, 0xc9, 0x13, 0x00, 0xca, 0x79, 0xe0, 0x8c, 0x16, 0xc9, 0x72, - 0xed, 0x81, 0x7c, 0xf6, 0x18, 0x7c, 0x7c, 0x76, 0x4a, 0x9d, 0x60, 0x78, 0x4b, 0xf9, 0xca, 0x56, - 0x82, 0x4c, 0xf9, 0x4b, 0x4a, 0xdf, 0xf8, 0x45, 0x15, 0x6a, 0xb2, 0x2d, 0x20, 0x83, 0x6c, 0xd3, - 0x29, 0x56, 0x55, 0x87, 0x94, 0xb3, 0xea, 0x8c, 0x31, 0xe3, 0xbf, 0x95, 0xef, 0xdc, 0x86, 0xcd, - 0x8b, 0x57, 0x3b, 0x75, 0x64, 0xcb, 0x93, 0x47, 0x49, 0x1b, 0xb7, 0xae, 0xcb, 0x89, 0x7a, 0xc6, - 0xca, 0xd7, 0xee, 0x19, 0x6f, 0x40, 0xdd, 0x5d, 0xcc, 0x2d, 0xbe, 0x0a, 0x55, 0xb6, 0xa9, 0xb9, - 0x8b, 0xf9, 0xb3, 0x15, 0x7a, 0x09, 0xf7, 0x38, 0x9d, 0xa1, 0x48, 0xe6, 0x9a, 0x06, 0x4e, 0x08, - 0xe1, 0x21, 0xb4, 0x52, 0x45, 0x85, 0x63, 0xab, 0xe2, 0xb4, 0x9d, 0x76, 0xf6, 0x93, 0x47, 0xea, - 0x96, 0xcd, 0xb8, 0xc8, 0x38, 0xb1, 0xc9, 0x6e, 0xb6, 0x45, 0xc2, 0x5a, 0xa4, 0x81, 0x21, 0x95, - 0xea, 0x82, 0x44, 0x25, 0x22, 0x0e, 0x20, 0x82, 0x4c, 0x42, 0x74, 0x84, 0x34, 0xc4, 0x04, 0x0a, - 0xdf, 0x86, 0x4e, 0x42, 0xe7, 0x12, 0x02, 0x72, 0x95, 0x64, 0x1a, 0x81, 0xef, 0xc1, 0x96, 0xcb, - 0x56, 0xdc, 0xca, 0xa3, 0x9b, 0x88, 0x26, 0x42, 0x76, 0x96, 0xd5, 0xf8, 0x26, 0xb4, 0x93, 0x54, - 0x84, 0xd8, 0x4d, 0xd9, 0xa8, 0xc6, 0xb3, 0x08, 0xbb, 0x09, 0x8d, 0xb8, 0x98, 0x6a, 0x21, 0xa0, - 0x4e, 0x65, 0x0d, 0x15, 0x97, 0x67, 0x01, 0x0b, 0x17, 0x33, 0xae, 0x16, 0x69, 0x23, 0x06, 0xcb, - 0x33, 0x53, 0xce, 0x23, 0xf6, 0x0e, 0xb4, 0xa2, 0xe8, 0x96, 0xb8, 0x0e, 0xe2, 0x36, 0xa3, 0x49, - 0x04, 0xed, 0x41, 0xd7, 0x0f, 0x3c, 0xdf, 0x0b, 0x59, 0x60, 0x51, 0xdb, 0x0e, 0x58, 0x18, 0xf6, - 0xba, 0x72, 0xbd, 0x68, 0xfe, 0x48, 0x4e, 0x1b, 0xef, 0x43, 0x3d, 0xaa, 0x12, 0xb7, 0xa0, 0x3a, - 0x8c, 0x33, 0x51, 0xc5, 0x94, 0x03, 0xc1, 0x43, 0x47, 0xbe, 0xaf, 0xde, 0x3a, 0xc4, 0xa7, 0xf1, - 0x33, 0xa8, 0xab, 0x1f, 0xac, 0xb0, 0x03, 0xfe, 0x1e, 0x6c, 0xfa, 0x34, 0x10, 0xd7, 0x48, 0xf7, - 0xc1, 0x51, 0x1f, 0x72, 0x4a, 0x03, 0xfe, 0x94, 0xf1, 0x4c, 0x3b, 0xdc, 0x44, 0xbc, 0x9c, 0x32, - 0xee, 0x43, 0x2b, 0x83, 0x11, 0xc7, 0x42, 0x3f, 0x8a, 0x82, 0x1a, 0x07, 0xf1, 0xce, 0xa5, 0x64, - 0x67, 0xe3, 0x01, 0xe8, 0xf1, 0x6f, 0x23, 0xca, 0xe5, 0xe8, 0xea, 0x9a, 0x32, 0xb7, 0x1c, 0x62, - 0x8b, 0xef, 0x7d, 0xca, 0x02, 0x15, 0x13, 0x72, 0x60, 0x3c, 0x4f, 0x25, 0x21, 0xc9, 0x0a, 0xe4, - 0x2e, 0xd4, 0x55, 0x12, 0x52, 0x51, 0x19, 0x35, 0xf3, 0xa7, 0x98, 0x85, 0xa2, 0x66, 0x5e, 0xe6, - 0xa4, 0x64, 0xd9, 0x52, 0x7a, 0xd9, 0x19, 0x34, 0xa2, 0x44, 0x93, 0xcd, 0xc6, 0x72, 0xc5, 0x6e, - 0x3e, 0x1b, 0xab, 0x45, 0x13, 0xa0, 0xf0, 0x8e, 0xd0, 0x99, 0xb8, 0xcc, 0xb6, 0x92, 0x10, 0xc2, - 0x3d, 0x1a, 0x66, 0x47, 0x0a, 0x9e, 0x44, 0xf1, 0x62, 0xbc, 0x07, 0x35, 0x79, 0xb6, 0xc2, 0xf4, - 0x55, 0x44, 0x49, 0xbf, 0xd7, 0xa0, 0x11, 0xe5, 0xe9, 0x42, 0xa5, 0xcc, 0xa1, 0x4b, 0x5f, 0xf5, - 0xd0, 0xff, 0xfd, 0xc4, 0x73, 0x17, 0x88, 0xcc, 0x2f, 0x4b, 0x8f, 0x3b, 0xee, 0xc4, 0x92, 0xb6, - 0x96, 0x39, 0xa8, 0x8b, 0x92, 0x33, 0x14, 0x9c, 0x8a, 0xf9, 0x83, 0xcf, 0xaa, 0xd0, 0x39, 0x1a, - 0x3e, 0x3c, 0x39, 0xf2, 0xfd, 0x99, 0x33, 0xa6, 0xd8, 0x95, 0xec, 0x43, 0x05, 0x1b, 0xb3, 0x82, - 0x87, 0xe5, 0x7e, 0xd1, 0x0b, 0x01, 0x39, 0x80, 0x2a, 0xf6, 0x67, 0xa4, 0xe8, 0x7d, 0xb9, 0x5f, - 0xf8, 0x50, 0x20, 0x36, 0x91, 0x1d, 0xdc, 0xe5, 0x67, 0xe6, 0x7e, 0xd1, 0x6b, 0x01, 0xf9, 0x08, - 0xf4, 0xa4, 0x71, 0x5a, 0xf7, 0xd8, 0xdc, 0x5f, 0xfb, 0x6e, 0x20, 0xf4, 0x93, 0xe2, 0x72, 0xdd, - 0x9b, 0x69, 0x7f, 0x6d, 0x83, 0x4d, 0x0e, 0xa1, 0x1e, 0x95, 0xe5, 0xc5, 0xcf, 0xc1, 0xfd, 0x35, - 0x3d, 0xbd, 0x30, 0x8f, 0xec, 0x85, 0x8a, 0xde, 0xac, 0xfb, 0x85, 0x0f, 0x0f, 0xe4, 0x1e, 0xd4, - 0x54, 0x7d, 0x54, 0xf8, 0x24, 0xdc, 0x2f, 0xee, 0xcc, 0xc5, 0x25, 0x93, 0x6e, 0x70, 0xdd, 0xbb, - 0x7a, 0x7f, 0xed, 0x0b, 0x09, 0x39, 0x02, 0x48, 0xb5, 0x34, 0x6b, 0x1f, 0xcc, 0xfb, 0xeb, 0x5f, - 0x3e, 0xc8, 0x03, 0x68, 0x24, 0xaf, 0x59, 0xc5, 0x4f, 0xe0, 0xfd, 0x75, 0x8f, 0x11, 0xc3, 0x5b, - 0xff, 0xf8, 0xd3, 0xb6, 0xf6, 0xeb, 0x8b, 0x6d, 0xed, 0x8b, 0x8b, 0x6d, 0xed, 0xcb, 0x8b, 0x6d, - 0xed, 0x77, 0x17, 0xdb, 0xda, 0x1f, 0x2f, 0xb6, 0xb5, 0xdf, 0xfc, 0x79, 0x5b, 0x1b, 0xd5, 0xd0, - 0xfd, 0x3f, 0xf8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x33, 0xc0, 0x3b, 0xf2, 0x19, 0x00, - 0x00, +var fileDescriptor_types_30d8160a6576aafe = []byte{ + // 2282 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x58, 0xcd, 0x73, 0x1c, 0x47, + 0x15, 0xd7, 0xec, 0xf7, 0xbc, 0xd5, 0x7e, 0xb8, 0x2d, 0xdb, 0xeb, 0xc5, 0x48, 0xae, 0x31, 0x38, + 0x52, 0xe2, 0xac, 0x12, 0x05, 0x53, 0x32, 0x0e, 0xa9, 0xd2, 0xda, 0x06, 0xa9, 0x62, 0x82, 0x18, + 0xdb, 0xe2, 0x42, 0xd5, 0x54, 0xef, 0x4e, 0x7b, 0x77, 0xca, 0xbb, 0x33, 0x93, 0x99, 0x5e, 0x79, + 0xc5, 0x91, 0x73, 0x0e, 0x39, 0xf0, 0x27, 0x70, 0xe0, 0x4f, 0xc8, 0x91, 0x13, 0x95, 0x23, 0x07, + 0xce, 0x06, 0x44, 0x71, 0xa1, 0x8a, 0x33, 0x70, 0xa3, 0xfa, 0x75, 0xcf, 0xa7, 0x66, 0x4d, 0x62, + 0xb8, 0xe5, 0xb2, 0x3b, 0xdd, 0xef, 0xf7, 0x7a, 0xba, 0xdf, 0xbc, 0xf7, 0x7e, 0xef, 0x35, 0x5c, + 0xa5, 0xa3, 0xb1, 0xb3, 0xcb, 0xcf, 0x7c, 0x16, 0xca, 0xdf, 0x81, 0x1f, 0x78, 0xdc, 0x23, 0x55, + 0x1c, 0xf4, 0xdf, 0x9d, 0x38, 0x7c, 0xba, 0x18, 0x0d, 0xc6, 0xde, 0x7c, 0x77, 0xe2, 0x4d, 0xbc, + 0x5d, 0x94, 0x8e, 0x16, 0xcf, 0x71, 0x84, 0x03, 0x7c, 0x92, 0x5a, 0xfd, 0xfb, 0x29, 0x38, 0x67, + 0xae, 0xcd, 0x82, 0xb9, 0xe3, 0xf2, 0xf4, 0xe3, 0x38, 0x38, 0xf3, 0xb9, 0xb7, 0x3b, 0x67, 0xc1, + 0x8b, 0x19, 0x53, 0x7f, 0x4a, 0x79, 0xff, 0xbf, 0x2a, 0xcf, 0x9c, 0x51, 0xb8, 0x3b, 0xf6, 0xe6, + 0x73, 0xcf, 0x4d, 0x6f, 0xb6, 0xbf, 0x35, 0xf1, 0xbc, 0xc9, 0x8c, 0x25, 0x9b, 0xe3, 0xce, 0x9c, + 0x85, 0x9c, 0xce, 0x7d, 0x09, 0x30, 0x7e, 0x5f, 0x81, 0xba, 0xc9, 0x3e, 0x5d, 0xb0, 0x90, 0x93, + 0x6d, 0xa8, 0xb0, 0xf1, 0xd4, 0xeb, 0x95, 0x6e, 0x6a, 0xdb, 0xcd, 0x3d, 0x32, 0x90, 0x0b, 0x29, + 0xe9, 0xa3, 0xf1, 0xd4, 0x3b, 0x5c, 0x33, 0x11, 0x41, 0xde, 0x81, 0xea, 0xf3, 0xd9, 0x22, 0x9c, + 0xf6, 0xca, 0x08, 0xbd, 0x9c, 0x85, 0xfe, 0x48, 0x88, 0x0e, 0xd7, 0x4c, 0x89, 0x11, 0xcb, 0x3a, + 0xee, 0x73, 0xaf, 0x57, 0x29, 0x5a, 0xf6, 0xc8, 0x7d, 0x8e, 0xcb, 0x0a, 0x04, 0xd9, 0x07, 0x08, + 0x19, 0xb7, 0x3c, 0x9f, 0x3b, 0x9e, 0xdb, 0xab, 0x22, 0xfe, 0x5a, 0x16, 0xff, 0x84, 0xf1, 0x9f, + 0xa2, 0xf8, 0x70, 0xcd, 0xd4, 0xc3, 0x68, 0x20, 0x34, 0x1d, 0xd7, 0xe1, 0xd6, 0x78, 0x4a, 0x1d, + 0xb7, 0x57, 0x2b, 0xd2, 0x3c, 0x72, 0x1d, 0xfe, 0x40, 0x88, 0x85, 0xa6, 0x13, 0x0d, 0xc4, 0x51, + 0x3e, 0x5d, 0xb0, 0xe0, 0xac, 0x57, 0x2f, 0x3a, 0xca, 0xcf, 0x84, 0x48, 0x1c, 0x05, 0x31, 0xe4, + 0x3e, 0x34, 0x47, 0x6c, 0xe2, 0xb8, 0xd6, 0x68, 0xe6, 0x8d, 0x5f, 0xf4, 0x1a, 0xa8, 0xd2, 0xcb, + 0xaa, 0x0c, 0x05, 0x60, 0x28, 0xe4, 0x87, 0x6b, 0x26, 0x8c, 0xe2, 0x11, 0xd9, 0x83, 0xc6, 0x78, + 0xca, 0xc6, 0x2f, 0x2c, 0xbe, 0xec, 0xe9, 0xa8, 0x79, 0x25, 0xab, 0xf9, 0x40, 0x48, 0x9f, 0x2e, + 0x0f, 0xd7, 0xcc, 0xfa, 0x58, 0x3e, 0x92, 0xbb, 0xa0, 0x33, 0xd7, 0x56, 0xaf, 0x6b, 0xa2, 0xd2, + 0xd5, 0xdc, 0x77, 0x71, 0xed, 0xe8, 0x65, 0x0d, 0xa6, 0x9e, 0xc9, 0x00, 0x6a, 0xc2, 0x19, 0x1c, + 0xde, 0x5b, 0x47, 0x9d, 0x8d, 0xdc, 0x8b, 0x50, 0x76, 0xb8, 0x66, 0x2a, 0x94, 0x30, 0x9f, 0xcd, + 0x66, 0xce, 0x29, 0x0b, 0xc4, 0xe6, 0x2e, 0x17, 0x99, 0xef, 0xa1, 0x94, 0xe3, 0xf6, 0x74, 0x3b, + 0x1a, 0x0c, 0xeb, 0x50, 0x3d, 0xa5, 0xb3, 0x05, 0x33, 0xde, 0x82, 0x66, 0xca, 0x53, 0x48, 0x0f, + 0xea, 0x73, 0x16, 0x86, 0x74, 0xc2, 0x7a, 0xda, 0x4d, 0x6d, 0x5b, 0x37, 0xa3, 0xa1, 0xd1, 0x86, + 0xf5, 0xb4, 0x9f, 0x18, 0xf3, 0x58, 0x51, 0xf8, 0x82, 0x50, 0x3c, 0x65, 0x41, 0x28, 0x1c, 0x40, + 0x29, 0xaa, 0x21, 0xb9, 0x05, 0x2d, 0xb4, 0x83, 0x15, 0xc9, 0x85, 0x9f, 0x56, 0xcc, 0x75, 0x9c, + 0x3c, 0x51, 0xa0, 0x2d, 0x68, 0xfa, 0x7b, 0x7e, 0x0c, 0x29, 0x23, 0x04, 0xfc, 0x3d, 0x5f, 0x01, + 0x8c, 0x1f, 0x40, 0x37, 0xef, 0x4a, 0xa4, 0x0b, 0xe5, 0x17, 0xec, 0x4c, 0xbd, 0x4f, 0x3c, 0x92, + 0x0d, 0x75, 0x2c, 0x7c, 0x87, 0x6e, 0xaa, 0x33, 0x7e, 0x5e, 0x8a, 0x95, 0x63, 0x6f, 0x22, 0xfb, + 0x50, 0x11, 0x41, 0x85, 0xda, 0xcd, 0xbd, 0xfe, 0x40, 0x46, 0xdc, 0x20, 0x8a, 0xb8, 0xc1, 0xd3, + 0x28, 0xe2, 0x86, 0x8d, 0x2f, 0x5f, 0x6d, 0xad, 0x7d, 0xfe, 0xa7, 0x2d, 0xcd, 0x44, 0x0d, 0x72, + 0x5d, 0x38, 0x04, 0x75, 0x5c, 0xcb, 0xb1, 0xd5, 0x7b, 0xea, 0x38, 0x3e, 0xb2, 0xc9, 0x01, 0x74, + 0xc7, 0x9e, 0x1b, 0x32, 0x37, 0x5c, 0x84, 0x96, 0x4f, 0x03, 0x3a, 0x0f, 0x55, 0xac, 0x45, 0x9f, + 0xff, 0x41, 0x24, 0x3e, 0x46, 0xa9, 0xd9, 0x19, 0x67, 0x27, 0xc8, 0x87, 0x00, 0xa7, 0x74, 0xe6, + 0xd8, 0x94, 0x7b, 0x41, 0xd8, 0xab, 0xdc, 0x2c, 0xa7, 0x94, 0x4f, 0x22, 0xc1, 0x33, 0xdf, 0xa6, + 0x9c, 0x0d, 0x2b, 0x62, 0x67, 0x66, 0x0a, 0x4f, 0x6e, 0x43, 0x87, 0xfa, 0xbe, 0x15, 0x72, 0xca, + 0x99, 0x35, 0x3a, 0xe3, 0x2c, 0xc4, 0x78, 0x5c, 0x37, 0x5b, 0xd4, 0xf7, 0x9f, 0x88, 0xd9, 0xa1, + 0x98, 0x34, 0xec, 0xf8, 0x6b, 0x62, 0xa8, 0x10, 0x02, 0x15, 0x9b, 0x72, 0x8a, 0xd6, 0x58, 0x37, + 0xf1, 0x59, 0xcc, 0xf9, 0x94, 0x4f, 0xd5, 0x19, 0xf1, 0x99, 0x5c, 0x85, 0xda, 0x94, 0x39, 0x93, + 0x29, 0xc7, 0x63, 0x95, 0x4d, 0x35, 0x12, 0x86, 0xf7, 0x03, 0xef, 0x94, 0x61, 0xb6, 0x68, 0x98, + 0x72, 0x60, 0xfc, 0x4d, 0x83, 0x4b, 0x17, 0xc2, 0x4b, 0xac, 0x3b, 0xa5, 0xe1, 0x34, 0x7a, 0x97, + 0x78, 0x26, 0xef, 0x88, 0x75, 0xa9, 0xcd, 0x02, 0x95, 0xc5, 0x5a, 0xea, 0xc4, 0x87, 0x38, 0xa9, + 0x0e, 0xaa, 0x20, 0xe4, 0x11, 0x74, 0x67, 0x34, 0xe4, 0x96, 0x8c, 0x02, 0x0b, 0xb3, 0x54, 0x39, + 0x13, 0x99, 0x8f, 0x69, 0x14, 0x2d, 0xc2, 0x39, 0x95, 0x7a, 0x7b, 0x96, 0x99, 0x25, 0x87, 0xb0, + 0x31, 0x3a, 0xfb, 0x25, 0x75, 0xb9, 0xe3, 0x32, 0xeb, 0x82, 0xcd, 0x3b, 0x6a, 0xa9, 0x47, 0xa7, + 0x8e, 0xcd, 0xdc, 0x71, 0x64, 0xec, 0xcb, 0xb1, 0x4a, 0xfc, 0x31, 0x42, 0xe3, 0x10, 0xda, 0xd9, + 0x5c, 0x40, 0xda, 0x50, 0xe2, 0x4b, 0x75, 0xc2, 0x12, 0x5f, 0x92, 0xdb, 0x50, 0x11, 0xcb, 0xe1, + 0xe9, 0xda, 0x71, 0x32, 0x55, 0xe8, 0xa7, 0x67, 0x3e, 0x33, 0x51, 0x6e, 0x18, 0xb1, 0xa7, 0xc6, + 0x81, 0x9b, 0x5f, 0xcb, 0xd8, 0x81, 0x4e, 0x2e, 0x89, 0xa4, 0x3e, 0x8b, 0x96, 0xfe, 0x2c, 0x46, + 0x07, 0x5a, 0x99, 0xdc, 0x61, 0x7c, 0x56, 0x85, 0x86, 0xc9, 0x42, 0x5f, 0x38, 0x1d, 0xd9, 0x07, + 0x9d, 0x2d, 0xc7, 0x4c, 0xa6, 0x6d, 0x2d, 0x97, 0x14, 0x25, 0xe6, 0x51, 0x24, 0x17, 0xe9, 0x23, + 0x06, 0x93, 0x9d, 0x0c, 0xe5, 0x5c, 0xce, 0x2b, 0xa5, 0x39, 0xe7, 0x4e, 0x96, 0x73, 0x36, 0x72, + 0xd8, 0x1c, 0xe9, 0xec, 0x64, 0x48, 0x27, 0xbf, 0x70, 0x86, 0x75, 0xee, 0x15, 0xb0, 0x4e, 0x7e, + 0xfb, 0x2b, 0x68, 0xe7, 0x5e, 0x01, 0xed, 0xf4, 0x2e, 0xbc, 0xab, 0x90, 0x77, 0xee, 0x64, 0x79, + 0x27, 0x7f, 0x9c, 0x1c, 0xf1, 0x7c, 0x58, 0x44, 0x3c, 0xd7, 0x73, 0x3a, 0x2b, 0x99, 0xe7, 0x83, + 0x0b, 0xcc, 0x73, 0x35, 0xa7, 0x5a, 0x40, 0x3d, 0xf7, 0x32, 0x9c, 0x00, 0x85, 0x67, 0x2b, 0x26, + 0x05, 0xf2, 0xfd, 0x8b, 0xac, 0x75, 0x2d, 0xff, 0x69, 0x8b, 0x68, 0x6b, 0x37, 0x47, 0x5b, 0x57, + 0xf2, 0xbb, 0xcc, 0xf1, 0x56, 0xc2, 0x3e, 0x3b, 0x22, 0x3f, 0xe4, 0x3c, 0x4d, 0xe4, 0x12, 0x16, + 0x04, 0x5e, 0xa0, 0x12, 0xbb, 0x1c, 0x18, 0xdb, 0x22, 0x63, 0x25, 0xfe, 0xf5, 0x1a, 0xa6, 0x42, + 0xa7, 0x4f, 0x79, 0x97, 0xf1, 0x85, 0x96, 0xe8, 0x62, 0xe4, 0xa7, 0xb3, 0x9d, 0xae, 0xb2, 0x5d, + 0x8a, 0xc0, 0x4a, 0x59, 0x02, 0xdb, 0x82, 0xa6, 0xc8, 0xa9, 0x39, 0x6e, 0xa2, 0x7e, 0xc4, 0x4d, + 0xe4, 0x6d, 0xb8, 0x84, 0xf9, 0x48, 0xd2, 0x9c, 0x0a, 0xc4, 0x0a, 0x06, 0x62, 0x47, 0x08, 0xa4, + 0xc5, 0x64, 0xa2, 0x7c, 0x17, 0x2e, 0xa7, 0xb0, 0x62, 0x5d, 0xcc, 0x85, 0x32, 0x49, 0x77, 0x63, + 0xf4, 0x81, 0xef, 0x1f, 0xd2, 0x70, 0x6a, 0xfc, 0x24, 0x31, 0x50, 0xc2, 0x7b, 0x04, 0x2a, 0x63, + 0xcf, 0x96, 0xe7, 0x6e, 0x99, 0xf8, 0x2c, 0xb8, 0x70, 0xe6, 0x4d, 0x70, 0x73, 0xba, 0x29, 0x1e, + 0x05, 0x2a, 0x0e, 0x25, 0x5d, 0xc6, 0x8c, 0xf1, 0x6b, 0x2d, 0x59, 0x2f, 0xa1, 0xc2, 0x22, 0xd6, + 0xd2, 0xfe, 0x17, 0xd6, 0x2a, 0x7d, 0x3d, 0xd6, 0x32, 0xce, 0xb5, 0xe4, 0x93, 0xc5, 0x7c, 0xf4, + 0x66, 0x47, 0x14, 0xde, 0xe3, 0xb8, 0x36, 0x5b, 0xa2, 0x49, 0xcb, 0xa6, 0x1c, 0x44, 0xa5, 0x42, + 0x0d, 0xcd, 0x9c, 0x2d, 0x15, 0xea, 0x38, 0x27, 0x07, 0xe4, 0x16, 0xf2, 0x98, 0xf7, 0x5c, 0x85, + 0x6a, 0x6b, 0xa0, 0x0a, 0xfa, 0x63, 0x31, 0x69, 0x4a, 0x59, 0x2a, 0xdb, 0xea, 0x19, 0x12, 0xbc, + 0x01, 0xba, 0xd8, 0x68, 0xe8, 0xd3, 0x31, 0xc3, 0xc8, 0xd3, 0xcd, 0x64, 0xc2, 0x78, 0x0a, 0xe4, + 0x62, 0xc4, 0x93, 0x8f, 0xa0, 0xc6, 0x4e, 0x99, 0xcb, 0x85, 0xc5, 0x85, 0xd1, 0xd6, 0x63, 0xda, + 0x61, 0x2e, 0x1f, 0xf6, 0x84, 0xa9, 0xfe, 0xfe, 0x6a, 0xab, 0x2b, 0x31, 0x77, 0xbc, 0xb9, 0xc3, + 0xd9, 0xdc, 0xe7, 0x67, 0xa6, 0xd2, 0x32, 0xfe, 0xa9, 0x09, 0x36, 0xc8, 0x64, 0x83, 0x42, 0xe3, + 0x45, 0x2e, 0x5f, 0x4a, 0x11, 0xfc, 0x57, 0x33, 0xe8, 0xb7, 0x01, 0x26, 0x34, 0xb4, 0x5e, 0x52, + 0x97, 0x33, 0x5b, 0x59, 0x55, 0x9f, 0xd0, 0xf0, 0xe7, 0x38, 0x21, 0xaa, 0x21, 0x21, 0x5e, 0x84, + 0xcc, 0x46, 0xf3, 0x96, 0xcd, 0xfa, 0x84, 0x86, 0xcf, 0x42, 0x66, 0xa7, 0xce, 0x56, 0x7f, 0x93, + 0xb3, 0x65, 0xed, 0xd9, 0xc8, 0xdb, 0xf3, 0xdf, 0x29, 0x5f, 0x4e, 0xc8, 0xf2, 0x9b, 0x71, 0xf6, + 0x7f, 0x68, 0xa2, 0x4e, 0xc8, 0xa6, 0x64, 0x72, 0x04, 0x97, 0xe2, 0x98, 0xb2, 0x16, 0x18, 0x6b, + 0x91, 0x57, 0xbd, 0x3e, 0x14, 0xbb, 0xa7, 0xd9, 0xe9, 0x90, 0x7c, 0x02, 0xd7, 0x72, 0x19, 0x21, + 0x5e, 0xb0, 0xf4, 0xda, 0xc4, 0x70, 0x25, 0x9b, 0x18, 0xa2, 0xf5, 0x12, 0x6b, 0x94, 0xdf, 0xc8, + 0xcb, 0xbf, 0x23, 0x0a, 0xac, 0x34, 0x99, 0x14, 0x7d, 0x53, 0xe3, 0x37, 0x1a, 0x74, 0x72, 0x1b, + 0x22, 0xdb, 0x50, 0x95, 0x7c, 0xa6, 0x65, 0xda, 0x58, 0xb4, 0x98, 0xda, 0xb3, 0x04, 0x90, 0xf7, + 0xa1, 0xc1, 0x54, 0xad, 0xa7, 0x0e, 0x79, 0x25, 0x57, 0x02, 0x2a, 0x7c, 0x0c, 0x23, 0xdf, 0x03, + 0x3d, 0x36, 0x5d, 0xae, 0xce, 0x8f, 0x2d, 0xad, 0x94, 0x12, 0xa0, 0xf1, 0x00, 0x9a, 0xa9, 0xd7, + 0x93, 0x6f, 0x81, 0x3e, 0xa7, 0x4b, 0x55, 0xac, 0xcb, 0xf2, 0xad, 0x31, 0xa7, 0x4b, 0xac, 0xd3, + 0xc9, 0x35, 0xa8, 0x0b, 0xe1, 0x84, 0x4a, 0xc3, 0x97, 0xcd, 0xda, 0x9c, 0x2e, 0x7f, 0x4c, 0x43, + 0x63, 0x07, 0xda, 0xd9, 0x6d, 0x45, 0xd0, 0x88, 0x10, 0x25, 0xf4, 0x60, 0xc2, 0x8c, 0xbb, 0xd0, + 0xc9, 0xed, 0x86, 0x18, 0xd0, 0xf2, 0x17, 0x23, 0xeb, 0x05, 0x3b, 0xb3, 0x70, 0xbb, 0xe8, 0x26, + 0xba, 0xd9, 0xf4, 0x17, 0xa3, 0x8f, 0xd9, 0x99, 0xa8, 0x47, 0x43, 0xe3, 0x09, 0xb4, 0xb3, 0x65, + 0xb4, 0x48, 0x99, 0x81, 0xb7, 0x70, 0x6d, 0x5c, 0xbf, 0x6a, 0xca, 0x81, 0xe8, 0xc4, 0x4f, 0x3d, + 0xe9, 0x19, 0xe9, 0xba, 0xf9, 0xc4, 0xe3, 0x2c, 0x55, 0x7c, 0x4b, 0x8c, 0xe1, 0x40, 0x15, 0xbf, + 0xb9, 0xf8, 0x7e, 0x58, 0x10, 0x2b, 0x0a, 0x16, 0xcf, 0xe4, 0x31, 0x00, 0xe5, 0x3c, 0x70, 0x46, + 0x8b, 0x64, 0xb9, 0xf6, 0x40, 0x5e, 0x8f, 0x0c, 0x3e, 0x3e, 0x39, 0xa6, 0x4e, 0x30, 0xbc, 0xa1, + 0x7c, 0x65, 0x23, 0x41, 0xa6, 0xfc, 0x25, 0xa5, 0x6f, 0xfc, 0xaa, 0x0a, 0x35, 0xd9, 0x3e, 0x90, + 0x41, 0xb6, 0x39, 0x15, 0xab, 0xaa, 0x4d, 0xca, 0x59, 0xb5, 0xc7, 0x98, 0xf1, 0x6f, 0xe7, 0x3b, + 0xbc, 0x61, 0xf3, 0xfc, 0xd5, 0x56, 0x1d, 0xd9, 0xf2, 0xe8, 0x61, 0xd2, 0xee, 0xad, 0xea, 0x86, + 0xa2, 0xde, 0xb2, 0xf2, 0xb5, 0x7b, 0xcb, 0x6b, 0x50, 0x77, 0x17, 0x73, 0x8b, 0x2f, 0x43, 0x95, + 0x6d, 0x6a, 0xee, 0x62, 0xfe, 0x74, 0x89, 0x5e, 0xc2, 0x3d, 0x4e, 0x67, 0x28, 0x92, 0xb9, 0xa6, + 0x81, 0x13, 0x42, 0xb8, 0x0f, 0xad, 0x54, 0x51, 0xe1, 0xd8, 0xaa, 0x38, 0x6d, 0xa7, 0x9d, 0xfd, + 0xe8, 0xa1, 0x3a, 0x65, 0x33, 0x2e, 0x32, 0x8e, 0x6c, 0xb2, 0x9d, 0x6d, 0xa5, 0xb0, 0x16, 0x69, + 0x60, 0x48, 0xa5, 0xba, 0x25, 0x51, 0x89, 0x88, 0x0d, 0x88, 0x20, 0x93, 0x10, 0x1d, 0x21, 0x0d, + 0x31, 0x81, 0xc2, 0xb7, 0xa0, 0x93, 0xd0, 0xb9, 0x84, 0x80, 0x5c, 0x25, 0x99, 0x46, 0xe0, 0x7b, + 0xb0, 0xe1, 0xb2, 0x25, 0xb7, 0xf2, 0xe8, 0x26, 0xa2, 0x89, 0x90, 0x9d, 0x64, 0x35, 0xbe, 0x0b, + 0xed, 0x24, 0x15, 0x21, 0x76, 0x5d, 0x36, 0xb4, 0xf1, 0x2c, 0xc2, 0xae, 0x43, 0x23, 0x2e, 0xa6, + 0x5a, 0x08, 0xa8, 0x53, 0x59, 0x43, 0xc5, 0xe5, 0x59, 0xc0, 0xc2, 0xc5, 0x8c, 0xab, 0x45, 0xda, + 0x88, 0xc1, 0xf2, 0xcc, 0x94, 0xf3, 0x88, 0xbd, 0x05, 0xad, 0x28, 0xba, 0x25, 0xae, 0x83, 0xb8, + 0xf5, 0x68, 0x12, 0x41, 0x3b, 0xd0, 0xf5, 0x03, 0xcf, 0xf7, 0x42, 0x16, 0x58, 0xd4, 0xb6, 0x03, + 0x16, 0x86, 0xbd, 0xae, 0x5c, 0x2f, 0x9a, 0x3f, 0x90, 0xd3, 0xc6, 0xfb, 0x50, 0x8f, 0xaa, 0xc4, + 0x0d, 0xa8, 0x0e, 0xe3, 0x4c, 0x54, 0x31, 0xe5, 0x40, 0xf0, 0xd0, 0x81, 0xef, 0xab, 0x3b, 0x11, + 0xf1, 0x68, 0xfc, 0x02, 0xea, 0xea, 0x83, 0x15, 0x76, 0xca, 0x3f, 0x84, 0x75, 0x9f, 0x06, 0xe2, + 0x18, 0xe9, 0x7e, 0x39, 0xea, 0x43, 0x8e, 0x69, 0xc0, 0x9f, 0x30, 0x9e, 0x69, 0x9b, 0x9b, 0x88, + 0x97, 0x53, 0xc6, 0x3d, 0x68, 0x65, 0x30, 0x62, 0x5b, 0xe8, 0x47, 0x51, 0x50, 0xe3, 0x20, 0x7e, + 0x73, 0x29, 0x79, 0xb3, 0x71, 0x1f, 0xf4, 0xf8, 0xdb, 0x88, 0x72, 0x39, 0x3a, 0xba, 0xa6, 0xcc, + 0x2d, 0x87, 0x78, 0x15, 0xe0, 0xbd, 0x64, 0x81, 0x8a, 0x09, 0x39, 0x30, 0x9e, 0xa5, 0x92, 0x90, + 0x64, 0x05, 0x72, 0x07, 0xea, 0x2a, 0x09, 0xa9, 0xa8, 0x8c, 0x9a, 0xfe, 0x63, 0xcc, 0x42, 0x51, + 0xd3, 0x2f, 0x73, 0x52, 0xb2, 0x6c, 0x29, 0xbd, 0xec, 0x0c, 0x1a, 0x51, 0xa2, 0xc9, 0x66, 0x63, + 0xb9, 0x62, 0x37, 0x9f, 0x8d, 0xd5, 0xa2, 0x09, 0x50, 0x78, 0x47, 0xe8, 0x4c, 0x5c, 0x66, 0x5b, + 0x49, 0x08, 0xe1, 0x3b, 0x1a, 0x66, 0x47, 0x0a, 0x1e, 0x47, 0xf1, 0x62, 0xbc, 0x07, 0x35, 0xb9, + 0xb7, 0xc2, 0xf4, 0x55, 0x44, 0x49, 0x7f, 0xd4, 0xa0, 0x11, 0xe5, 0xe9, 0x42, 0xa5, 0xcc, 0xa6, + 0x4b, 0x5f, 0x75, 0xd3, 0xff, 0xff, 0xc4, 0x73, 0x07, 0x88, 0xcc, 0x2f, 0xa7, 0x1e, 0x77, 0xdc, + 0x89, 0x25, 0x6d, 0x2d, 0x73, 0x50, 0x17, 0x25, 0x27, 0x28, 0x38, 0x16, 0xf3, 0x6f, 0xdf, 0x82, + 0x66, 0xea, 0xee, 0x82, 0xd4, 0xa1, 0xfc, 0x09, 0x7b, 0xd9, 0x5d, 0x23, 0x4d, 0xa8, 0x9b, 0x0c, + 0x3b, 0xd1, 0xae, 0xb6, 0xf7, 0x59, 0x15, 0x3a, 0x07, 0xc3, 0x07, 0x47, 0x07, 0xbe, 0x3f, 0x73, + 0xc6, 0x14, 0x5b, 0x97, 0x5d, 0xa8, 0x60, 0xf7, 0x56, 0x70, 0x4b, 0xdd, 0x2f, 0xba, 0x46, 0x20, + 0x7b, 0x50, 0xc5, 0x26, 0x8e, 0x14, 0x5d, 0x56, 0xf7, 0x0b, 0x6f, 0x13, 0xc4, 0x4b, 0x64, 0x9b, + 0x77, 0xf1, 0xce, 0xba, 0x5f, 0x74, 0xa5, 0x40, 0x3e, 0x02, 0x3d, 0xe9, 0xae, 0x56, 0xdd, 0x5c, + 0xf7, 0x57, 0x5e, 0x2e, 0x08, 0xfd, 0xa4, 0x02, 0x5d, 0x75, 0x01, 0xdb, 0x5f, 0xd9, 0x85, 0x93, + 0x7d, 0xa8, 0x47, 0xb5, 0x7b, 0xf1, 0xdd, 0x72, 0x7f, 0x45, 0xe3, 0x2f, 0xcc, 0x23, 0x1b, 0xa6, + 0xa2, 0x0b, 0xf0, 0x7e, 0xe1, 0xed, 0x04, 0xb9, 0x0b, 0x35, 0x55, 0x44, 0x15, 0xde, 0x2f, 0xf7, + 0x8b, 0xdb, 0x77, 0x71, 0xc8, 0xa4, 0x65, 0x5c, 0x75, 0x49, 0xdf, 0x5f, 0x79, 0x8d, 0x42, 0x0e, + 0x00, 0x52, 0x7d, 0xcf, 0xca, 0xdb, 0xf7, 0xfe, 0xea, 0xeb, 0x11, 0x72, 0x1f, 0x1a, 0xc9, 0x95, + 0x57, 0xf1, 0x7d, 0x7a, 0x7f, 0xd5, 0x8d, 0xc5, 0xf0, 0xc6, 0xbf, 0xfe, 0xb2, 0xa9, 0xfd, 0xf6, + 0x7c, 0x53, 0xfb, 0xe2, 0x7c, 0x53, 0xfb, 0xf2, 0x7c, 0x53, 0xfb, 0xc3, 0xf9, 0xa6, 0xf6, 0xe7, + 0xf3, 0x4d, 0xed, 0x77, 0x7f, 0xdd, 0xd4, 0x46, 0x35, 0x8c, 0x91, 0x0f, 0xfe, 0x13, 0x00, 0x00, + 0xff, 0xff, 0x92, 0xed, 0x9f, 0xca, 0x3f, 0x1a, 0x00, 0x00, } diff --git a/abci/types/types.proto b/abci/types/types.proto index 8a2da5b4..8f9dda83 100644 --- a/abci/types/types.proto +++ b/abci/types/types.proto @@ -81,8 +81,14 @@ message RequestBeginBlock { repeated Evidence byzantine_validators = 4 [(gogoproto.nullable)=false]; } +enum CheckTxType { + New = 0; + Recheck = 1; +} + message RequestCheckTx { bytes tx = 1; + CheckTxType type = 2; } message RequestDeliverTx { diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 724dd056..bbb5b667 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -565,7 +565,7 @@ func TestMockProxyApp(t *testing.T) { mock.SetResponseCallback(proxyCb) someTx := []byte("tx") - mock.DeliverTxAsync(someTx) + mock.DeliverTxAsync(abci.RequestDeliverTx{Tx: someTx}) }) assert.True(t, validTxs == 1) assert.True(t, invalidTxs == 0) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index b7b2e09f..abab7f54 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -296,6 +296,12 @@ Commit are included in the header of the next block. - **Request**: - `Tx ([]byte)`: The request transaction bytes + - `Type (CheckTxType)`: What type of `CheckTx` request is this? At present, + there are two possible values: `CheckTx_Unchecked` (the default, which says + that a full check is required), and `CheckTx_Checked` (when the mempool is + initiating a normal recheck of a transaction). + - `AdditionalData ([]byte)`: Reserved for future use. See + [here](https://github.com/tendermint/tendermint/issues/2127#issuecomment-456661420). - **Response**: - `Code (uint32)`: Response code - `Data ([]byte)`: Result bytes, if any. diff --git a/mempool/clist_mempool.go b/mempool/clist_mempool.go index 0d1f3c5b..4042e9b4 100644 --- a/mempool/clist_mempool.go +++ b/mempool/clist_mempool.go @@ -279,7 +279,7 @@ func (mem *CListMempool) CheckTxWithInfo(tx types.Tx, cb func(*abci.Response), t return err } - reqRes := mem.proxyAppConn.CheckTxAsync(tx) + reqRes := mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{Tx: tx}) reqRes.SetCallback(mem.reqResCb(tx, txInfo.SenderID, cb)) return nil @@ -591,7 +591,10 @@ func (mem *CListMempool) recheckTxs() { // NOTE: globalCb may be called concurrently. for e := mem.txs.Front(); e != nil; e = e.Next() { memTx := e.Value.(*mempoolTx) - mem.proxyAppConn.CheckTxAsync(memTx.tx) + mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{ + Tx: memTx.tx, + Type: abci.CheckTxType_Recheck, + }) } mem.proxyAppConn.FlushAsync() diff --git a/mempool/clist_mempool_test.go b/mempool/clist_mempool_test.go index db6e800b..90d0ed1a 100644 --- a/mempool/clist_mempool_test.go +++ b/mempool/clist_mempool_test.go @@ -314,7 +314,7 @@ func TestSerialReap(t *testing.T) { for i := start; i < end; i++ { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(i)) - res, err := appConnCon.DeliverTxSync(txBytes) + res, err := appConnCon.DeliverTxSync(abci.RequestDeliverTx{Tx: txBytes}) if err != nil { t.Errorf("Client error committing tx: %v", err) } @@ -522,7 +522,7 @@ func TestMempoolTxsBytes(t *testing.T) { err = appConnCon.Start() require.Nil(t, err) defer appConnCon.Stop() - res, err := appConnCon.DeliverTxSync(txBytes) + res, err := appConnCon.DeliverTxSync(abci.RequestDeliverTx{Tx: txBytes}) require.NoError(t, err) require.EqualValues(t, 0, res.Code) res2, err := appConnCon.CommitSync() diff --git a/proxy/app_conn.go b/proxy/app_conn.go index 2f792671..1698ab52 100644 --- a/proxy/app_conn.go +++ b/proxy/app_conn.go @@ -15,7 +15,7 @@ type AppConnConsensus interface { InitChainSync(types.RequestInitChain) (*types.ResponseInitChain, error) BeginBlockSync(types.RequestBeginBlock) (*types.ResponseBeginBlock, error) - DeliverTxAsync(tx []byte) *abcicli.ReqRes + DeliverTxAsync(types.RequestDeliverTx) *abcicli.ReqRes EndBlockSync(types.RequestEndBlock) (*types.ResponseEndBlock, error) CommitSync() (*types.ResponseCommit, error) } @@ -24,7 +24,7 @@ type AppConnMempool interface { SetResponseCallback(abcicli.Callback) Error() error - CheckTxAsync(tx []byte) *abcicli.ReqRes + CheckTxAsync(types.RequestCheckTx) *abcicli.ReqRes FlushAsync() *abcicli.ReqRes FlushSync() error @@ -69,8 +69,8 @@ func (app *appConnConsensus) BeginBlockSync(req types.RequestBeginBlock) (*types return app.appConn.BeginBlockSync(req) } -func (app *appConnConsensus) DeliverTxAsync(tx []byte) *abcicli.ReqRes { - return app.appConn.DeliverTxAsync(tx) +func (app *appConnConsensus) DeliverTxAsync(req types.RequestDeliverTx) *abcicli.ReqRes { + return app.appConn.DeliverTxAsync(req) } func (app *appConnConsensus) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) { @@ -110,8 +110,8 @@ func (app *appConnMempool) FlushSync() error { return app.appConn.FlushSync() } -func (app *appConnMempool) CheckTxAsync(tx []byte) *abcicli.ReqRes { - return app.appConn.CheckTxAsync(tx) +func (app *appConnMempool) CheckTxAsync(req types.RequestCheckTx) *abcicli.ReqRes { + return app.appConn.CheckTxAsync(req) } //------------------------------------------------ diff --git a/state/execution.go b/state/execution.go index 7e49a9ad..fd75b295 100644 --- a/state/execution.go +++ b/state/execution.go @@ -284,7 +284,7 @@ func execBlockOnProxyApp( // Run txs of block. for _, tx := range block.Txs { - proxyAppConn.DeliverTxAsync(tx) + proxyAppConn.DeliverTxAsync(abci.RequestDeliverTx{Tx: tx}) if err := proxyAppConn.Error(); err != nil { return nil, err } From d0414768195807044fa8614b07f4e0035fd5f9e1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 2 Jul 2019 21:19:58 +0400 Subject: [PATCH 24/27] p2p: dial addrs which came from seed instead of calling ensurePeers (#3762) Calling ensurePeers outside of ensurePeersRoutine can lead to nodes disconnecting from us due to "sent next PEX request too soon" error. Solution is to just dial addrs we got from src instead of calling ensurePeers. Refs #2093 Fixes #3338 --- CHANGELOG_PENDING.md | 2 ++ p2p/pex/pex_reactor.go | 29 ++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 9c94c55e..fa81d9bb 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -31,3 +31,5 @@ program](https://hackerone.com/tendermint). - [rpc] \#3700 Make possible to set absolute paths for TLS cert and key (@climber73) ### BUG FIXES: +- [p2p] \#3338 Prevent "sent next PEX request too soon" errors by not calling + ensurePeers outside of ensurePeersRoutine diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index e77fa8ea..20862d32 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -340,6 +340,15 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { if err != nil { return err } + + srcIsSeed := false + for _, seedAddr := range r.seedAddrs { + if seedAddr.Equals(srcAddr) { + srcIsSeed = true + break + } + } + for _, netAddr := range addrs { // Validate netAddr. Disconnect from a peer if it sends us invalid data. if netAddr == nil { @@ -365,13 +374,23 @@ func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { } // If this address came from a seed node, try to connect to it without - // waiting. - for _, seedAddr := range r.seedAddrs { - if seedAddr.Equals(srcAddr) { - r.ensurePeers() - } + // waiting (#2093) + if srcIsSeed { + r.Logger.Info("Will dial address, which came from seed", "addr", netAddr, "seed", srcAddr) + go func(addr *p2p.NetAddress) { + err := r.dialPeer(addr) + if err != nil { + switch err.(type) { + case errMaxAttemptsToDial, errTooEarlyToDial: + r.Logger.Debug(err.Error(), "addr", addr) + default: + r.Logger.Error(err.Error(), "addr", addr) + } + } + }(netAddr) } } + return nil } From e645442c9be7694a09fccfde206ff715ee462824 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 2 Jul 2019 22:01:29 +0400 Subject: [PATCH 25/27] abci: minor cleanups in the socket client (#3758) Follow up from #3512 Specifically: cli.conn.Close() need not be under the mutex (#3512 (comment)) call the reqRes callback after the resCb so they always happen in the same order (#3512) Fixes #3513 --- abci/client/socket_client.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index 16e39bf3..7b1e4cd8 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -55,10 +55,6 @@ func NewSocketClient(addr string, mustConnect bool) *socketClient { } func (cli *socketClient) OnStart() error { - if err := cli.BaseService.OnStart(); err != nil { - return err - } - var err error var conn net.Conn RETRY_LOOP: @@ -82,15 +78,12 @@ RETRY_LOOP: } func (cli *socketClient) OnStop() { - cli.BaseService.OnStop() - - cli.mtx.Lock() - defer cli.mtx.Unlock() if cli.conn != nil { - // does this really need a mutex? cli.conn.Close() } + cli.mtx.Lock() + defer cli.mtx.Unlock() cli.flushQueue() } @@ -209,19 +202,18 @@ func (cli *socketClient) didRecvResponse(res *types.Response) error { reqres.Done() // Release waiters cli.reqSent.Remove(next) // Pop first item from linked list - // Notify reqRes listener if set (request specific callback). - // NOTE: it is possible this callback isn't set on the reqres object. - // at this point, in which case it will be called after, when it is set. - // TODO: should we move this after the resCb call so the order is always consistent? - if cb := reqres.GetCallback(); cb != nil { - cb(res) - } - // Notify client listener if set (global callback). if cli.resCb != nil { cli.resCb(reqres.Request, res) } + // Notify reqRes listener if set (request specific callback). + // NOTE: it is possible this callback isn't set on the reqres object. + // at this point, in which case it will be called after, when it is set. + if cb := reqres.GetCallback(); cb != nil { + cb(res) + } + return nil } From f76684a05c9e179f8b5fcb528d90b74a1956b4b9 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 3 Jul 2019 17:17:59 +0400 Subject: [PATCH 26/27] node: allow registration of custom reactors while creating node (#3771) * change invocation of NewNode across * custom reactor name are prefixed with CUSTOM_ * upgate changelog pending * improve comments * node: refactor NewNode to use functional options --- CHANGELOG_PENDING.md | 4 ++++ node/node.go | 25 ++++++++++++++++++++++++- node/node_test.go | 34 +++++++++++++++++++++++++++++++++- p2p/mock/reactor.go | 23 +++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 p2p/mock/reactor.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index fa81d9bb..5a747650 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,6 +26,10 @@ program](https://hackerone.com/tendermint). * P2P Protocol ### FEATURES: +- [node] Refactor `NewNode` to use functional options to make it more flexible + and extensible in the future. +- [node] [\#3730](https://github.com/tendermint/tendermint/pull/3730) Add `CustomReactors` option to `NewNode` allowing caller to pass + custom reactors to run inside Tendermint node (@ParthDesai) ### IMPROVEMENTS: - [rpc] \#3700 Make possible to set absolute paths for TLS cert and key (@climber73) diff --git a/node/node.go b/node/node.go index c992e242..9beb0669 100644 --- a/node/node.go +++ b/node/node.go @@ -47,6 +47,10 @@ import ( "github.com/tendermint/tendermint/version" ) +// CustomReactorNamePrefix is a prefix for all custom reactors to prevent +// clashes with built-in reactors. +const CustomReactorNamePrefix = "CUSTOM_" + //------------------------------------------------------------------------------ // DBContext specifies config information for loading a new DB. @@ -136,6 +140,18 @@ func DefaultMetricsProvider(config *cfg.InstrumentationConfig) MetricsProvider { } } +// Option sets a parameter for the node. +type Option func(*Node) + +// CustomReactors allows you to add custom reactors to the node's Switch. +func CustomReactors(reactors map[string]p2p.Reactor) Option { + return func(n *Node) { + for name, reactor := range reactors { + n.sw.AddReactor(CustomReactorNamePrefix+name, reactor) + } + } +} + //------------------------------------------------------------------------------ // Node is the highest level interface to a full Tendermint node. @@ -433,6 +449,7 @@ func createSwitch(config *cfg.Config, sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) sw.AddReactor("EVIDENCE", evidenceReactor) + sw.SetNodeInfo(nodeInfo) sw.SetNodeKey(nodeKey) @@ -495,7 +512,8 @@ func NewNode(config *cfg.Config, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, metricsProvider MetricsProvider, - logger log.Logger) (*Node, error) { + logger log.Logger, + options ...Option) (*Node, error) { blockStore, stateDB, err := initDBs(config, dbProvider) if err != nil { @@ -661,6 +679,11 @@ func NewNode(config *cfg.Config, eventBus: eventBus, } node.BaseService = *cmn.NewBaseService(logger, "Node", node) + + for _, option := range options { + option(node) + } + return node, nil } diff --git a/node/node_test.go b/node/node_test.go index ce4e82c2..841a0468 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -21,6 +21,7 @@ import ( "github.com/tendermint/tendermint/libs/log" mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" + p2pmock "github.com/tendermint/tendermint/p2p/mock" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" @@ -100,7 +101,10 @@ func TestNodeDelayedStart(t *testing.T) { n.GenesisDoc().GenesisTime = now.Add(2 * time.Second) require.NoError(t, err) - n.Start() + err = n.Start() + require.NoError(t, err) + defer n.Stop() + startTime := tmtime.Now() assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime)) } @@ -279,6 +283,34 @@ func TestCreateProposalBlock(t *testing.T) { assert.NoError(t, err) } +func TestNodeNewNodeCustomReactors(t *testing.T) { + config := cfg.ResetTestRoot("node_new_node_custom_reactors_test") + defer os.RemoveAll(config.RootDir) + + cr := p2pmock.NewReactor() + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + require.NoError(t, err) + + n, err := NewNode(config, + privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), + nodeKey, + proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), + DefaultGenesisDocProviderFunc(config), + DefaultDBProvider, + DefaultMetricsProvider(config.Instrumentation), + log.TestingLogger(), + CustomReactors(map[string]p2p.Reactor{"FOO": cr}), + ) + require.NoError(t, err) + + err = n.Start() + require.NoError(t, err) + defer n.Stop() + + assert.True(t, cr.IsRunning()) +} + func state(nVals int, height int64) (sm.State, dbm.DB) { vals := make([]types.GenesisValidator, nVals) for i := 0; i < nVals; i++ { diff --git a/p2p/mock/reactor.go b/p2p/mock/reactor.go new file mode 100644 index 00000000..cfce12bd --- /dev/null +++ b/p2p/mock/reactor.go @@ -0,0 +1,23 @@ +package mock + +import ( + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p/conn" +) + +type Reactor struct { + p2p.BaseReactor +} + +func NewReactor() *Reactor { + r := &Reactor{} + r.BaseReactor = *p2p.NewBaseReactor("Reactor", r) + r.SetLogger(log.TestingLogger()) + return r +} + +func (r *Reactor) GetChannels() []*conn.ChannelDescriptor { return []*conn.ChannelDescriptor{} } +func (r *Reactor) AddPeer(peer p2p.Peer) {} +func (r *Reactor) RemovePeer(peer p2p.Peer, reason interface{}) {} +func (r *Reactor) Receive(chID byte, peer p2p.Peer, msgBytes []byte) {} From 94e0176ac24090d8048c1fa71b51e84ad8e77701 Mon Sep 17 00:00:00 2001 From: Jay Namsayin <31609693+jim380@users.noreply.github.com> Date: Thu, 4 Jul 2019 00:26:13 -0700 Subject: [PATCH 27/27] behaviour: return correct reason in MessageOutOfOrder (#3772) --- CHANGELOG_PENDING.md | 2 ++ behaviour/peer_behaviour.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5a747650..9c4c8b68 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -37,3 +37,5 @@ program](https://hackerone.com/tendermint). ### BUG FIXES: - [p2p] \#3338 Prevent "sent next PEX request too soon" errors by not calling ensurePeers outside of ensurePeersRoutine +- [behaviour] Return correct reason in MessageOutOfOrder (@jim380) + diff --git a/behaviour/peer_behaviour.go b/behaviour/peer_behaviour.go index 36630f46..f7cfd00f 100644 --- a/behaviour/peer_behaviour.go +++ b/behaviour/peer_behaviour.go @@ -27,7 +27,7 @@ type messageOutOfOrder struct { // MessageOutOfOrder returns a messagOutOfOrder PeerBehaviour. func MessageOutOfOrder(peerID p2p.ID, explanation string) PeerBehaviour { - return PeerBehaviour{peerID: peerID, reason: badMessage{explanation}} + return PeerBehaviour{peerID: peerID, reason: messageOutOfOrder{explanation}} } type consensusVote struct {