diff --git a/README.md b/README.md index f011cf2..4793eec 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # Tendermint research Research artifacts and tools for Tendermint -[Tendermint demo application](https://github.com/fluencelabs/tendermint_demo/tree/master/tmdemoapp) \ No newline at end of file +[Tendermint demo application](tmdemoapp/docs) \ No newline at end of file diff --git a/tmdemoapp/README.md b/tmdemoapp/README.md deleted file mode 100644 index cb3b0bf..0000000 --- a/tmdemoapp/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Tendermint Demo ABCI KVStore on Scala -This is demo application implementing Tendermint ABCI interface. It models in-memory key-value string storage. Key here are hierarchical, `/`-separated. This key hierarchy is *merkelized*, so every node stores Merkle hash of its associated value (if present) and its children. -The application is compatible with `Tendermint v0.19.x` and uses `com.github.jtendermint.jabci` for Java ABCI definitions. - -## Installation and running -For single-node run just launch the application: -```bash -sbt run -``` -And launch Tendermint: -```bash -# uncomment line below to initialize Tendermint -#tendermint init - -# uncomment line below to clear all Tendermint data -#tendermint unsafe_reset_all - -tendermint node --consensus.create_empty_blocks=false -``` - -In case Tendermint launched first, it would periodically try to connect the app until the app started. - -## Changing and observing the application state: transactions and queries -Tendermint offers two main ways of interaction with the app: transactions and queries. - -Transactions are treated by Tendermint just like arrays of bytes and stored in the blockchain after block formation also just like arrays of bytes. The transaction semantics only make sense for the application once Tendermint delivers a transaction to it. A transaction could (and usually does) change the application state upon being committed and could provide some metadata to verify that it's actually added to the blockchain and applied to the state. However in order to get some trustful information about the committed transaction result one needs to query the blockchain explicitly. - -Queries, in comparison with transactions, do not change the state and are not stored in the blockchain. Queries can only be applied to already committed state that's why they could be used in order to get trustful information (signed by quorum during voting for one of existing blocks) just requesting only a single node. - -For working with transactions and queries use Python scripts in [`parse`](https://github.com/fluencelabs/tendermint_research/tree/master/parse) directory. - -## Making transactions -To set a new key-value mapping use: -```bash -python query.py localhost:46657 tx a/b=10 -... -OK -HEIGHT: 2 -INFO: 10 -``` -This would create hierarchical key `a/b` (if necessary) and map it to `10`. `HEIGHT` value could be used later to verify the `INFO` by querying the blockchain. - -This script would output the height value corresponding to provided transaction. The height is available upon executing because `query.py` script uses `broadcast_tx_commit` RPC to send transactions to Tendermint. You can later find the latest transactions by running: -```bash -python parse_chain.py localhost:46657 -``` -This command would output last 50 non-empty blocks in chain with short summary about transactions. Here you can ensure that provided transaction indeed included in the block with height from response. This fact verifies that Tendermint majority (more than 2/3 of configured validator nodes) agreed on including this transaction in the mentioned block which certified by their signatures. Signature details (including information about all Consensus rounds and phases) can be found by requesting Tendermint RPC: -```bash -curl -s 'localhost:46657/block?height=_' # replace _ with actual height number -``` - -`get` transaction allows to copy a value from one key to another: -```bash -python query.py localhost:46657 tx a/c=get:a/b -... -INFO: 10 -``` - -Submitting an `increment` transaction would increment the referenced key value and copy the old referenced key value to target key: -```bash -python query.py localhost:46657 tx a/d=increment:a/c -... -INFO: 10 -``` -To prevent Tendermint from declining transaction that repeats one of the previous applied transactions, it's possible to put any characters after `###` at the end of transaction string, this part of string would be ignored: -```bash -python query.py localhost:46657 tx a/d=increment:a/c###again -... -INFO: 11 -``` - -`sum` transaction would sum the values of references keys and assign the result to the target key: -```bash -python query.py localhost:46657 tx a/e=sum:a/c,a/d -... -INFO: 23 -``` - -`factorial` transaction would calculate the factorial of the referenced key value: -```bash -python query.py localhost:46657 tx a/f=factorial:a/b -... -INFO: 3628800 -``` - -`hiersum` transaction would calculate the sum of non-empty values for the referenced key and its descendants by hierarchy (all non-empty values should be integer): -```bash -python query.py localhost:46657 tx c/asum=hiersum:a -... -INFO: 3628856 -``` - -Transactions are not applied in case of wrong arguments (non-integer values to `increment`, `sum`, `factorial` or wrong number of arguments). Transactions with a target key like `get`, `increment`, `sum`, `factorial` return the new value of the target key as `INFO`, but this values cannot be trusted if the serving node is not reliable. To verify the returned `INFO` one needs to `query` the target key explicitly. - -In case of massive broadcasting of multiple transactions via `broadcast_tx_sync` or `broadcast_tx_async` RPC, the app would not calculate Merkle hashes during `DeliverTx` processing. Instead it would modify key tree and mark changed paths by clearing Merkle hashes until ABCI `Commit` processing. On `Commit` the app would recalculate Merkle hash along changed paths only. Finally the app would return the resulting root Merkle hash to Tendermint and this hash would be stored as `app_hash` for corresponding height in the blockchain. - -Note that described merkelized structure is just for demo purposes and not self-balanced, it would remain efficient only until it the user transactions keep it relatively balanced. Something like [Patricia tree](https://github.com/ethereum/wiki/wiki/Patricia-Tree) should be more appropriate to achieve self-balancing. - -## Making queries -Use `get:` queries to read values from KVStore: -```bash -python query.py localhost:46657 query get:a/e -... -RESULT: 23 -``` -Use `ls:` queries to read key hierarchy: -```bash -python query.py localhost:46657 query ls:a -... -RESULT: e f b c d -``` -These commands implemented by requesting `abci_query` RPC (which immediately proxies to ABCI `Query` in the app). Together with requested information the app method would return Merkle proof of this information. This Merkle proof is comma-separated list (`,,...`) of level proofs along the path to the requested key. For this implementation SHA-3 of a level in the list is exactly: -* either one of the space-separated item from the upper (the previous in comma-separated list) level proof; -* or the root app hash for the uppermost (the first) level proof. - -The app stores historical changes and handle queries for any particular height. The requested height (the latest by default) and the corresponding `app_hash` also returned for `query` Python script. This combination (result, Merkle proof and `app_hash` from the blockchain) verifies the correctness of the result (because this `app_hash` could only appear in the blockchain as a result of Tendermint quorum consistent decision). - -## Heavy-weight transactions -Applying simple transactions with different target keys makes the sizes of the blockchain (which contains transaction list) and the app state relatively close to each other. If target keys are often repeated, the blockchain size would become much larger than the app state size. To demonstrate the opposite situating (the app state much larger than the blockchain) *range* transactions are supported: -```bash -python query.py localhost:46657 tx 0-200:b/@1/@0=1 -... -INFO: 1 -``` -Here `0-200:` prefix means that this transaction should consist of 200 subsequent key-value mappings, each of them obtained by applying a template `b/@1/@0=1` to a counter from 0 to 199, inclusive. `@0` and `@1` are substitution markers for the two lowermost hexadecimal digits of the counter. I. e. this transaction would create 200 keys: `b/0/0`, `b/0/1`, ..., `b/c/7` and put `1` to each of them. - -We can check the result by querying the hierarchical sum of `b` children: -```bash -python query.py localhost:46657 tx c/bsum=hiersum:b -... -INFO: 200 -``` \ No newline at end of file diff --git a/tmdemoapp/docs/Behavior.md b/tmdemoapp/docs/Behavior.md deleted file mode 100644 index a2ac4b3..0000000 --- a/tmdemoapp/docs/Behavior.md +++ /dev/null @@ -1,99 +0,0 @@ -# Fluence cluster typical operation processing - -Fluence is distributed computations platform. It contains following components: -* Client proxy (Proxy) -* Node Tendermint (TM) with important modules: Mempool, Consensus and Query -* Node ABCI App (App) - -Clients typically interact with Fluence via local Proxy. Basically Proxy provides some API like: -```scala -def doSomeOperation(req: SomeRequest): SomeResponse -``` - -## Normal-case operation - -### A. How client sees operation processing -From the client point of view it just calls API function with similar signature synchronously (in blocking mode) or call asynchronous API request function (with provided response callback). - -### B. How Proxy sees operation processing -Let's observe how operation processing looks like. -1. Proxy gets API call from the client. -2. Proxy decomposes operation into 2 interactions with cluster: transaction submit and response query. -3. Obtains some state key `opTarget` (it is chosen from some pool of such temporary target keys). -4. For transaction submit Proxy: - * Serializes API call to some string `opTx` like: "opTarget=SomeOperation(reqParam1,reqParam2)". Its binary representation is *transaction* in terms of Tendermint. - * Queries some TM via RPC call: `http://:46678/broadcast_tx_commit?tx=`. - * In case of correct (without error messages and not timed out) TM response it treats `height` from it as `opHeight` and considers transaction committed (but yet not validated) and proceeds to the next step. -5. Proxy check whether `opHeight`-th block contains `opTx` indeed: - * Queries `http:///block?height=`. - * In case of correct TM response it checks for `opTx` existence in transaction list section of response and checks block signature. - * Upon leaving this step Proxy is sure that the cluster has already performed the operation, committed it to the state, but it has no information about reaching consensus for the operation result. -6. Proxy waits for `opHeight+1`-th block to ensure cluster consensus for resulting app hash: - * Waits some small time. - * Starts periodically querying `http:///block?height=`. - * Once getting successful response, it checks block signatures. - * It also get `app_hash` for response (it corresponds to app hash after `height`-th block). - * Query loop in this step can be replaced with `NewBlock` subscription via WebSocket RPC. - * Upon leaving this step Proxy is sure that the cluster has already performed the operation, wrote it to `opTarget` and reached consensus about `opTarget` value. -7. Proxy queries `opTarget` value: - * It makes RPC call for key-value read with explicit height and claim for proof `http:///abci_query?height=&prove=true&path=`. - * It got response containing `value` (interpreted as `opResult`) and `proof`. - * It checks that `opResult`, `proof` and `app_hash` are consistent with each other. -8. Proxy deserialize `opResult` as SomeResponse and returns it to the client. - -### C. How Tendermint sees transaction submit -Let's look how Tendermint on some node (say, N) treats transaction submit (step B4) and makes some post-submit checks (B5, B6). -1. TM gets `broadcast_tx_commit` RPC call with `opTx` binary string from Proxy. -2. Mempool processing: - * TM's RPC endpoint tranfers the transaction to TM's *Mempool* module. - * Mempool prepares a callback to provide some RPC response when transaction would be committed or rejected. - * Mempool invokes local App's `CheckTx` ABCI method. If App returns non-zero code then the transaction is considered rejected, this information is sending to the client via callback and no further action happens. - * If App returns zero code the transaction gossip begins: the `opTx` starts spreading through other nodes. - * Also Mempool caches the transaction (in order to not accept repeated broadcasts of `opTx`). -3. Consensus processing: - * When the current TM proposer (*Consensus* module some node PN, PN is possibly N) is ready to create new block it grabs some amount of the oldest yet not committed transactions from local Mempool. If the transaction rate is intensive enough or even exceed TM/App throughput, it is possible that `opTx` may 'wait' during several block formation before it would be grabbed by Consensus. - * As soon as `opTx` and other transactions reaches Consensus module, block election starts. Proposer creates block proposal (that describes all transactions in the current block) for current *round*, then other nodes makes votes. In order to reach consensus for the block, election should pass all consensus stages (propose, pre-vote, pre-commit) with the majority of TM votes (more that 2/3 of TM's). If this doesn't work by some reason (votes time out, Byzantive proposer), proposer changed and a new round starts (possibly with another transaction set for the current block). -4. Post-consensus interaction with the local App: - * When election successfully passed all stages each corrent TM undertands that consensus is reached. Then it invokes App's ABCI methods: `BeginBlock`, `DeliverTx` (for each transaction), `EndBlock`, `Commit`. - * An information from `opTx`' `DeliverTx` call then sent back to Proxy via callback prepared on Step C2. - * `app_hash` field from `Commit` call is stored by TM before making the next block. -5. The new block metadata and transaction set now gets associated via height `height` and becomes available via RPC's like `block`, `blockchain`, `status` (including call in Step B5). However the recently obtained from App block app_hash yet not stored in the blockchain (because an App hash for some block stored in the blockchain metadata for the next block). -6. Next block processing: - * Steps 2-5 repeated for the next, `height+1`-th block. It may take some time, depending on new transactions availability and rate and commit timeout settings. - * The consensus for `height+1`-th block is only possible if the majority (more that 2/3 of TM's) agree about `height`-th block app has. So `app_hash` information in `height+1`-th block header refers to `app_hash` provided on Step C4 for `height`-th block (which is check on Step B6). - -### D. How ABCI App sees transaction submit -Now we dig into details of processing the transaction on App side (on node N). -1. On Step C2 TM asks App via `CheckTx` call. This is lightweight checking that works well if some signification part of transaction might be rejected by App by some reason (for example it is inconsistent after applying some recently committed other transaction). This would safe transaction gossip and need of permanent storing this transaction in the blockchain after commit. On this step App can return non-zero code in case of some check made against the latest committed state fails (`CheckTx` is not intended to make some changes, even against temporary internal structure). - * In case `CheckTx` invoked once but `opTx` is not grabbed by proposer's Consensus module for the next block, `CheckTx` would be reinvoked for every subsequent block until `opTx` would evnetually grabbed by proposer (because after some block commit, `opTx` might become incorrect). -2. On Step C4 TM invokes App's ABCI `DeliverTx` method. - * App can reject the transaction (it's OK because lightweight `CheckTx` does not necessary chech any possible failure cases), change nothing and return non-zero code. It this case TM would store the transaction anyway (because the block already formed), but would pass error code and any information from App to Proxy calling `broadcast_tx_commit` RPC. - * Normally App returns zero return code and apply the tranaction to it's state. It maintains the 'real-time' state that already applied all previous changes not only from previous blocks' transactions but even for all previous transactions in the current block. -3. On Step C4 TM also invokes App's ABCI `Commit` method that signals that block commit is over. App must return the actual state hash (*app hash*) as the result. As said before, this app hash would correspond to `height`-th block and be stored in the `height+1`-th block metadata. - -Note that Step D2 and D3 behavior should be purely deterministic which should guarantee in normal (non-Byzantine) case scenario both the same app hash from different nodes and the same app hash from a single node after replaying transactions by TM (for example after node fail). This determinism includes transaction acceptance status (accepted or rejected, transaction application to the real-time state and app hash computation logic. - -### E. How Tendermint sees response query -Response query initiated by Proxy on Step B7. Queries are processed by TM's Query module. Processing is very straightforward: it's just proxying the query to the local App. - -### F. How ABCI App sees response query -Query processing on the App performed in the following way: -1. App gets `height`, `prove` flag and `path` from the query. -2. The query should be applied to the state exactly corresponded to `height`-th block (this is not 'real-time' consensus state and in general not 'mempool' state). - * In case App do not store all block states and `height` is too old, it might reject the query. - * Otherwise it applies query to the corresponding state. Queries might be complex enough but not every query might be proved efficiently that why it's expected that queries are usually look like specific value's read or hierarchical structure scan. - * In case of read query on Step B7 App just reads `opTarget` value previously written by applying `opTx` and committed in `height`-th block. -3. If proof flag requested (as on Step B7) App also produce Merkle path (or some other provable information) that supply `opTarget` value verification with respect to given `height` and it's app hash (from `height+1` block metadata) -4. The response containing value, Merkle proof and any other information are sent back to the local TM. - -## Dispute cases -The next sections describe behavior in case of disagreement between cluster nodes about `height`-s app hash. - -### Dispute case 1: honest quorum, some nodes dishonest or not available -TODO - -### Dispute case 2: some nodes honest, some not, no quorum -TODO - -### Dispute case 2: dishonest quorum, minority of honest nodes -TODO diff --git a/tmdemoapp/docs/TM Demo App.md b/tmdemoapp/docs/README.md similarity index 94% rename from tmdemoapp/docs/TM Demo App.md rename to tmdemoapp/docs/README.md index a900ae4..1267c06 100644 --- a/tmdemoapp/docs/TM Demo App.md +++ b/tmdemoapp/docs/README.md @@ -1,18 +1,18 @@ -# Tendermint Demo Key-Value Store on Scala +# Tendermint Verifiable Computation and Storage Demo -This is demo application modeling in-memory key-value distributed storage. It allows to store and modify key-value pairs, to request them and to make some operations with their values. -![Key-values in cluster](cluster_key_value.png) +This demo application shows how verifiable computations might be processed by a distributed cluster of nodes. It comes with a set of hardcoded operations that can be invoked by the client. Each requested operation is computed by every (ignoring failures or Byzantine cases) cluster node, and if any node disagrees with the computation outcome it can submit a dispute to an external Judge. + +Results of each computation are stored on the cluster nodes and can be later on retrieved by the client. The storage of the results is secured with Merkle proofs, so malicious nodes can't substitute them with bogus data. + +Because every computation is verified by the cluster nodes and computation outcomes are verified using Merkle proofs, the client normally doesn't have to interact with the entire cluster. Moreover, the client can interact with as little as a single node – this won't change safety properties. However, liveness might be compromised – for example if the node the client is interacting with is silently dropping incoming requests. -A *distributed* property means that the app might be deployed across a cluster of several machines (nodes) and tolerate failures of some subset of those machines. At the same time, the client typically interacts with only a single node and the interaction protocol provides some guarantees of availability and consistency. ![Nodes in cluster](cluster_nodes.png) ## Motivation -The application is intended to show a proof-of-concept of a system that provides the following properties: -* Support of arbitrary deterministic operations (including simple reads/writes and complex aggregations, time-consuming calculations etc.) +The application is a proof-of-concept of a system with the following properties: +* Support of arbitrary deterministic operations: simple reads/writes as well as complex and time-consuming calculations * Having high throughput (1000 transaction per second) and low latency (1-2 seconds) of operations * Having every operation response verifiable (and thus trusted by the client) - * Either validated by storing all operation data in the blockchain (in this case such data signed by the majority of nodes) - * Or validated by providing Merkle proofs to the client (in this case the client has all required information to validate the response) * Ability to restore liveness and even safety after violating typical Byzantine quorum requirements (1/3 of failed nodes and more) – every node could rapidly detect problems in the blockchain or disagreement with the rest of nodes ## Architecture overview @@ -30,13 +30,15 @@ The application is written in Scala 2.12. It is compatible with `Tendermint v0.1 It models in-memory key-value string storage. Keys here are hierarchical, `/`-separated. This key hierarchy is *merkelized*, so every node stores Merkle hash of its associated value (if present) and its children. -![Architecture](architecture.png) +![Key-values in cluster](cluster_key_value.png) The entire application consists of the following components: * **Client** proxy (**Proxy**) -* Node Tendermint (**TM** or **TM Core**) with important modules: Mempool, Consensus and Query +* Node Tendermint (**TM** or **TM Core**) with notable modules: Mempool, Consensus and Query * Node ABCI Application itself (**App** or **ABCI App**) +![Architecture](architecture.png) + ### Operations Clients typically interact with Fluence via some local **Proxy**. This Proxy might be implemented in any language (because it communicates with TM Core by queries RPC endpoints), for example, Scala implementation of *some operation* may look like `def doSomeOperation(req: SomeRequest): SomeResponse`. This application uses simple (but powerful) Python `query.sh` script as Proxy to perform arbitrary operations, including: * Write transactions diff --git a/tmdemoapp/docs/architecture.png b/tmdemoapp/docs/architecture.png index fcbb904..03de8d1 100644 Binary files a/tmdemoapp/docs/architecture.png and b/tmdemoapp/docs/architecture.png differ diff --git a/tmdemoapp/docs/beh_consensus.png b/tmdemoapp/docs/beh_consensus.png new file mode 100644 index 0000000..2348cb4 Binary files /dev/null and b/tmdemoapp/docs/beh_consensus.png differ diff --git a/tmdemoapp/docs/beh_mempool.png b/tmdemoapp/docs/beh_mempool.png new file mode 100644 index 0000000..ab81e38 Binary files /dev/null and b/tmdemoapp/docs/beh_mempool.png differ diff --git a/tmdemoapp/docs/beh_query.png b/tmdemoapp/docs/beh_query.png new file mode 100644 index 0000000..23e6311 Binary files /dev/null and b/tmdemoapp/docs/beh_query.png differ diff --git a/tmdemoapp/docs/blocks.png b/tmdemoapp/docs/blocks.png index f98877d..66b3873 100644 Binary files a/tmdemoapp/docs/blocks.png and b/tmdemoapp/docs/blocks.png differ diff --git a/tmdemoapp/docs/cluster_key_value.png b/tmdemoapp/docs/cluster_key_value.png index 5586468..ac5b484 100644 Binary files a/tmdemoapp/docs/cluster_key_value.png and b/tmdemoapp/docs/cluster_key_value.png differ diff --git a/tmdemoapp/docs/cluster_nodes.png b/tmdemoapp/docs/cluster_nodes.png index c2c9538..4b80074 100644 Binary files a/tmdemoapp/docs/cluster_nodes.png and b/tmdemoapp/docs/cluster_nodes.png differ diff --git a/tmdemoapp/docs/consensus.png b/tmdemoapp/docs/consensus.png deleted file mode 100644 index 9bd72f8..0000000 Binary files a/tmdemoapp/docs/consensus.png and /dev/null differ diff --git a/tmdemoapp/docs/images.xml b/tmdemoapp/docs/images.xml index a79bc13..68fe3c9 100644 --- a/tmdemoapp/docs/images.xml +++ b/tmdemoapp/docs/images.xml @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/tmdemoapp/docs/keys_commit.png b/tmdemoapp/docs/keys_commit.png index 6a657be..6d7fc9b 100644 Binary files a/tmdemoapp/docs/keys_commit.png and b/tmdemoapp/docs/keys_commit.png differ diff --git a/tmdemoapp/docs/keys_delivertx.png b/tmdemoapp/docs/keys_delivertx.png index 616f84d..5020da6 100644 Binary files a/tmdemoapp/docs/keys_delivertx.png and b/tmdemoapp/docs/keys_delivertx.png differ diff --git a/tmdemoapp/docs/mempool.png b/tmdemoapp/docs/mempool.png deleted file mode 100644 index 2e4a966..0000000 Binary files a/tmdemoapp/docs/mempool.png and /dev/null differ diff --git a/tmdemoapp/docs/query.png b/tmdemoapp/docs/query.png deleted file mode 100644 index 338a2d1..0000000 Binary files a/tmdemoapp/docs/query.png and /dev/null differ