diff --git a/parse/parse_block.py b/parse/block_report.py similarity index 65% rename from parse/parse_block.py rename to parse/block_report.py index 940701d..826bc82 100644 --- a/parse/parse_block.py +++ b/parse/block_report.py @@ -1,14 +1,29 @@ import sys, urllib, json, datetime, time -from parse_common import uvarint, parseutc, formatbytes, readjson, getmaxheight +import matplotlib.pyplot as plt +from common_parse_utils import uvarint, parseutc, formatbytes, readjson, getmaxheight + +def get_num_txs(json): + return json["result"]["block"]["header"]["num_txs"] if len(sys.argv) < 2: - print "usage: python parse_block.py host:port minheight [maxheight]" + print "usage: python parse_block.py host:port [report_name [minheight [maxheight]]]" sys.exit() tmaddress = sys.argv[1] -minheight = int(sys.argv[2]) -maxheight = int(sys.argv[3]) if len(sys.argv) > 3 else getmaxheight(tmaddress) - +report_name = sys.argv[2] if len(sys.argv) > 2 else "" +if len(sys.argv) > 4: + maxheight = int(sys.argv[4]) +else: + maxheight = getmaxheight(tmaddress) + while maxheight >= 3 and get_num_txs(readjson(tmaddress + "/block?height=%d" % maxheight)) == 0: + maxheight -= 1 +if len(sys.argv) > 3: + minheight = int(sys.argv[3]) +else: + minheight = maxheight + while minheight >= 3 and get_num_txs(readjson(tmaddress + "/block?height=%d" % (minheight - 1))) > 0: + minheight -= 1 + accsize = 0 acclatency = 0 minlatency = 1e20 @@ -24,7 +39,7 @@ maxblocksize = 0 txstat = [] for height in range(minheight, maxheight + 1): data = readjson(tmaddress + "/block?height=%d" % height) - numtxs = data["result"]["block"]["header"]["num_txs"] + numtxs = get_num_txs(data) blocktimetxt = data["result"]["block"]["header"]["time"] blocktime = parseutc(blocktimetxt) @@ -39,7 +54,7 @@ for height in range(minheight, maxheight + 1): txs = data["result"]["block"]["data"]["txs"] if txs: for index, txhex in enumerate(txs): - txbytes = bytearray.fromhex(txhex) + txbytes = bytearray.fromhex(txhex)# if re.fullmatch(r"^[0-9a-fA-F]$", txhex) is not None key = chr(txbytes[0]) if chr(txbytes[1]) == '=' else "*" connindex = uvarint(txbytes[2:8]) txnumber = uvarint(txbytes[8:16]) @@ -63,11 +78,12 @@ for height in range(minheight, maxheight + 1): txstat.append((txtime, 1)) txstat.append((blocktime, -1)) if index < 5: - print key, connindex, txnumber, hostnamehash, txtimetxt, latency + print txtimetxt, latency + #print key, connindex, txnumber, hostnamehash, txtimetxt, latency print "Transactions: ", txcount, "=", formatbytes(accsize) print " ", "%.3f s" % (lasttx - firsttx), "from", datetime.datetime.fromtimestamp(firsttx), "to", datetime.datetime.fromtimestamp(lasttx) -print "Blocks: ", blockcount +print "Blocks: ", "%d: from %d to %d" % (blockcount, minheight, maxheight) print " ", "%.3f s" % (lastblock - firstblock), "from", datetime.datetime.fromtimestamp(firstblock), "to", datetime.datetime.fromtimestamp(lastblock) print "Tx send rate: ", "%.3f tx/s" % (txcount / (lasttx - firsttx)), "=", formatbytes(accsize / (lasttx - firsttx)) + "/s" print "Tx throughput: ", "%.3f tx/s" % (txcount / (lastblock - firsttx)), "=", formatbytes(accsize / (lastblock - firsttx)) + "/s" @@ -87,15 +103,20 @@ for i in range(steps + 1): cursum += txstat[curindex][1] curindex += 1 stepstat.append(cursum) -import matplotlib.pyplot as plt f = plt.figure(figsize=(15, 5)) plt.plot([i * (lastblock - firsttx) / steps for i in range(steps + 1)], stepstat) -plt.title("Duration: %.1f s, Tx size: %s, Tx send rate: %.3f tx/s = %s/s, Tx throughput: %.3f tx/s = %s/s" % - (lasttx - firsttx, formatbytes(accsize / txcount), - txcount / (lasttx - firsttx), formatbytes(accsize / (lasttx - firsttx)), - txcount / (lastblock - firsttx), formatbytes(accsize / (lastblock - firsttx)))) +long_title = "Duration: %.1f s, Tx size: %s, Tx send rate: %.3f tx/s = %s/s, Tx throughput: %.3f tx/s = %s/s" % \ + (lasttx - firsttx, formatbytes(accsize / txcount), \ + txcount / (lasttx - firsttx), formatbytes(accsize / (lasttx - firsttx)), \ + txcount / (lastblock - firsttx), formatbytes(accsize / (lastblock - firsttx))) +#plt.title(long_title) +plt.title(report_name) plt.xlabel("seconds from first tx") plt.ylabel("txs in backlog") -f.savefig("tdmnt-stat-%d-%d-%d-%.1f-%.0f-%.0f.pdf" % - (minheight, maxheight, maxblocksize, lasttx - firsttx, accsize / txcount, txcount / (lasttx - firsttx)), bbox_inches='tight') + +if report_name != "": + long_filename = "tdmnt-stat-%d-%d-%d-%.1f-%.0f-%.0f.png" % \ + (minheight, maxheight, maxblocksize, lasttx - firsttx, accsize / txcount, txcount / (lasttx - firsttx)) + #f.savefig(long_filename, bbox_inches='tight') + f.savefig(report_name + ".png", bbox_inches='tight') plt.show(block=True) \ No newline at end of file diff --git a/parse/parse_common.py b/parse/common_parse_utils.py similarity index 100% rename from parse/parse_common.py rename to parse/common_parse_utils.py diff --git a/parse/parse_chain.py b/parse/parse_chain.py index ceb197e..c9c7c17 100644 --- a/parse/parse_chain.py +++ b/parse/parse_chain.py @@ -1,5 +1,5 @@ import sys, urllib, json, datetime, time -from parse_common import parseutc, readjson, getmaxheight +from common_parse_utils import parseutc, readjson, getmaxheight if len(sys.argv) < 2: print "usage: python parse_chain.py host:port [minheight]" @@ -10,8 +10,9 @@ tmaddress = sys.argv[1] maxheight = getmaxheight(tmaddress) minheight = int(sys.argv[2]) if len(sys.argv) > 2 else max(1, maxheight - 49) -lastempty = -1 +lastnonempty = -1 last_fetched_height = minheight - 1 +print "%6s %26s %7s %7s %8s %30s %30s %30s %30s %30s" % ("height", "block time", "txs", "acc.txs", "app_hash", "tx1", "tx2", "tx3", "tx4", "tx5") for height in range(minheight, maxheight + 1): if height > last_fetched_height: last_fetched_height = min(height + blocks_fetch - 1, maxheight) @@ -26,11 +27,12 @@ for height in range(minheight, maxheight + 1): blocktimetxt = data["time"] blocktime = parseutc(blocktimetxt) - if numtxs > 0 or height == maxheight: + if numtxs > 0 or height == maxheight or height == lastnonempty + 1: blockdata = readjson(tmaddress + "/block?height=%d" % height) txs = blockdata["result"]["block"]["data"]["txs"] txsummary = "" if txs: + lastnonempty = height for tx in txs[0:5]: txstr = tx.decode('base64') if len(txstr) > 30: @@ -39,8 +41,7 @@ for height in range(minheight, maxheight + 1): txsummary += "%30s " % txstr if len(txs) > 5: txsummary += "..." - print "%5s: %s %7d %7d %s... %s" % (height, datetime.datetime.fromtimestamp(blocktime), numtxs, totaltxs, app_hash[0:6], txsummary) + print "%5s: %s %7d %7d" % (height, datetime.datetime.fromtimestamp(blocktime), numtxs, totaltxs), "0x" + app_hash[0:6], txsummary else: - if lastempty < height - 1: + if height == lastnonempty + 2: print "..." - lastempty = height \ No newline at end of file diff --git a/parse/report_to_file.sh b/parse/report_to_file.sh new file mode 100755 index 0000000..1813b73 --- /dev/null +++ b/parse/report_to_file.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python block_report.py $1 "$2" $3 $4 | tee "$2.txt" \ No newline at end of file diff --git a/tmdemoapp/docs/README.md b/tmdemoapp/docs/README.md index 1267c06..7ebd4fc 100644 --- a/tmdemoapp/docs/README.md +++ b/tmdemoapp/docs/README.md @@ -9,51 +9,58 @@ Because every computation is verified by the cluster nodes and computation outco ![Nodes in cluster](cluster_nodes.png) ## Motivation -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) -* 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 +The application is a proof-of-concept of a distributed system with the following properties: +* Support of arbitrary deterministic operations: simple reads/writes as well as complex and time-consuming calculations. +* High availability: tolerance to simultaneous failures or Byzantine actions of some subset of nodes. +* High throughput (1000 transactions per second) and low latency (1-2 seconds) of operations. +* Small blockchain finality time (several seconds). +* Extremely low probability of consistency violation. ## Architecture overview -The application use [Tendermint](https://github.com/tendermint/tendermint) platform which provides: -* Distributed transaction cache -* Blockchain (to store transactions persistently) -* Consensus logic (to reach agreement about the order of transactions) -* Peer-to-peer communication layer (between nodes) +The entire application is distributed over set of machines having the following roles: +* client-side **Proxy**, which originates the requests to the cluster +* cluster **Node**, which serves the requests +* the **Judge**, which is in charge of resolving complicated disagreements between nodes -The application implements Tendermint's [ABCI interface](http://tendermint.readthedocs.io/projects/tools/en/master/abci-spec.html) to follow Tendermint's architecture which decomposes the application logic into 2 main parts: -* Distributed replicated transaction log (managed by Tendermint) -* And state machine with business logic (manages by the application itself). +The application uses blockchain approach to decompose the application logic into 2 main parts: +* replicated transaction log +* state machine with the domain-specific logic +This decomposition allows to simplify development process. This modularization is not only logical but also phisical: transaction log and state machine run in separate processes, developed in different languages. -The application is written in Scala 2.12. It is compatible with `Tendermint v0.19.x` and uses `com.github.jtendermint.jabci` for Java ABCI definitions. +The application uses [Tendermint](https://github.com/tendermint/tendermint) platform which provides replicated transaction log components (**TM Core**), in particular: +* distributed transaction cache (*Mempool*) +* blockchain (to store transactions persistently) +* Byzantine-resistant **consensus** logic (to reach agreement about the order of transactions) +* peer-to-peer layer to communicate with another nodes +* entry point for client requests -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. - -![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 notable modules: Mempool, Consensus and Query -* Node ABCI Application itself (**App** or **ABCI App**) +To perform domain-specific logic the application uses its own **State machine** implementing Tendermint's [ABCI interface](http://tendermint.readthedocs.io/projects/tools/en/master/abci-spec.html) to follow Tendermint's architecture. It is written in Scala 2.12, compatible with `Tendermint v0.19.x` and uses `com.github.jtendermint.jabci` for Java ABCI definitions. ![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 -`tx a/b=10` -* Key read queries -`get a/b` -* Arbitrary operations -`op factorial:a/b` -* Writing results of arbitrary operations -`tx a/c=factorial:a/b` +As the application is intended to run normally in presence of some failures, including Byzantine failures, the following principles used: +* Every operation result is verifiable (and thus trusted by the client). +* The application uses Tendermint's implementation of Byzantine fault-tolerant consensus algorithms to provide **safety** and **liveness** without external interference to the cluster – while more than 2/3 of cluster nodes are correct (*quorum* exists). +* It can restore liveness and even safety after violating quorum requirements – every node could rapidly detect problems with the blockchain or disagreement with the rest of nodes and raise a dispute to the **Judge**. -In terms of Tendermint architecture, these operations implemented in the following way: -* All writes (simple and containing operations) are Tendermint *transactions*: a transaction changes the application state and stored to the blockchain (the correctness ensured by the consensus). -* Reads are Tendermint *ABCI queries*: they do not change the application state, App just return requested value together with Merkle proof (the correctness ensured by Merkle proof). -* Operations are combinations of writes and reads: to perform operation trustfully, Proxy first requests writing the result of operation to some key and then queries its value (the correctness ensured by both the consensus and Merkle proof). +The **State machine** maintains its state using in-memory key-value string storage. Keys here are hierarchical, `/`-separated. This key tree is *merkelized*, so every key stores Merkle hash of its associated value (if present) and its children keys. + +![Key-values in cluster](cluster_key_value.png) + +### Operations +Tendermint architecture suppose that the client typically interacts with the Application via the local **Proxy**. This application uses Python `query.py` script as client-side Proxy to request arbitrary operations to the cluster, including: +* Simple `put` requests which specify a target key and a constant as its new value: `put a/b=10`. +* Computational `put` requests which specify that a target key should be assigned to the result of some operation (with arguments) invocation: `put a/c=factorial:a/b`. +* Requests to obtain a result of running an arbitrary operation: `run factorial:a/b`. +* Requests to read the value of specified key: `get a/b`. + +`get` operations do not change the state of the application. They are implemented via Tendermint ABCI queries. As the result of a such query the **State machine** returns the value of the requested key together with the Merkle proof. + +`put` operations are *effectful* and change the application state explicitly. They are implemented via Tendermint **transactions** that combined into **blocks**. **TM Core** sends a transaction to the **State machine** and **State machine** applies this transaction to its state, typically changing the target key. + +`get` and `put` operations use different techniques to prove to the client that the operation is actually invoked and its result is correct. `get`s take advantage of Merkelized structure of the application state and provide Merkle proof of the result correctness. Any `put` invocation leads to adding the corresponding transaction to the blockchain, so observing this transaction in a correctly signed block means that there is a quorum in the cluster regarding this transaction's invocation. + +`run` operations are also *effectul*. They are implemented as combinations of `put`s and `get`s: to perform operation trustfully, **Proxy** first requests `put`-ting the result of operation to some key and then queries its value. Thus the correctness is ensured by both the consensus and Merkle proof. ## Installation and run For single-node run just launch the application: @@ -75,7 +82,7 @@ In case Tendermint launched first, it would periodically try to connect the app After successful launch the client can communicate with the application via sending RPC calls to TM Core on local `46678` port. -### Cluster +### Local cluster There are scripts that automate deployment and running 4 Application nodes on the local machine. ```bash diff --git a/tmdemoapp/docs/blocks.png b/tmdemoapp/docs/blocks.png index 3fdbad0..a6b123c 100644 Binary files a/tmdemoapp/docs/blocks.png and b/tmdemoapp/docs/blocks.png differ diff --git a/tmdemoapp/docs/images.xml b/tmdemoapp/docs/images.xml index f128058..eae88fa 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/src/main/scala/kvstore/ServerMonitor.scala b/tmdemoapp/src/main/scala/kvstore/ServerMonitor.scala index 6b92de8..6176ee7 100644 --- a/tmdemoapp/src/main/scala/kvstore/ServerMonitor.scala +++ b/tmdemoapp/src/main/scala/kvstore/ServerMonitor.scala @@ -28,7 +28,7 @@ class ServerMonitor(handler: ABCIHandler) extends Runnable { val localHash = state.lastVerifiableAppHash.map(MerkleUtil.merkleHashToHex).getOrElse("") if (clusterHash != localHash) { - throw new IllegalStateException("Cluster quorum has unexpected app hash for previous block") + throw new IllegalStateException(s"Cluster quorum has unexpected app hash for previous block '$clusterHash' '$localHash'") } val timeWaiting = timeWaitingForEmptyBlock(state)