From 2a1d836953f873bcf5b9a8a6c3ff853f971c5ee2 Mon Sep 17 00:00:00 2001 From: Dmitry Sergeev Date: Wed, 6 Jun 2018 07:25:50 +0500 Subject: [PATCH] entry class refactored, comments added --- .gitignore | 3 + ...reServerRunner.scala => ABCIHandler.scala} | 68 +++++++------------ .../src/main/scala/kvstore/MerkleUtil.scala | 3 + tmdemoapp/src/main/scala/kvstore/Node.scala | 12 ++++ .../src/main/scala/kvstore/ServerRunner.scala | 25 +++++++ 5 files changed, 68 insertions(+), 43 deletions(-) rename tmdemoapp/src/main/scala/kvstore/{KVStoreServerRunner.scala => ABCIHandler.scala} (64%) create mode 100644 tmdemoapp/src/main/scala/kvstore/ServerRunner.scala diff --git a/.gitignore b/.gitignore index 7bbc71c..65653f0 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,6 @@ ENV/ # mypy .mypy_cache/ + +# IntelliJ Idea +.idea \ No newline at end of file diff --git a/tmdemoapp/src/main/scala/kvstore/KVStoreServerRunner.scala b/tmdemoapp/src/main/scala/kvstore/ABCIHandler.scala similarity index 64% rename from tmdemoapp/src/main/scala/kvstore/KVStoreServerRunner.scala rename to tmdemoapp/src/main/scala/kvstore/ABCIHandler.scala index c3589e7..c8ff86f 100644 --- a/tmdemoapp/src/main/scala/kvstore/KVStoreServerRunner.scala +++ b/tmdemoapp/src/main/scala/kvstore/ABCIHandler.scala @@ -3,18 +3,23 @@ package kvstore import java.nio.ByteBuffer import com.github.jtendermint.jabci.api._ -import com.github.jtendermint.jabci.socket.TSocket import com.github.jtendermint.jabci.types.{ResponseCheckTx, _} import com.google.protobuf.ByteString import scala.collection.mutable.ArrayBuffer -object KVStoreServerRunner extends IDeliverTx with ICheckTx with ICommit with IQuery { - - def main(args: Array[String]): Unit = { - KVStoreServerRunner.start() - } - +/** + * Tendermint establishes 3 socket connections with the app: + * - Mempool for CheckTx + * - Consensus for DeliverTx and Commit + * - Info for Query + * + * According to specification the app maintains separate in-memory states for every connections: + * – [[ABCIHandler.consensusRoot]]: the latest state modified with every successful transaction on DeliverTx + * – [[ABCIHandler.storage]]: array of committed snapshots for Info connection + * – [[ABCIHandler.mempoolRoot]]: the latest committed snapshot for Mempool connection (not used currently) + */ +object ABCIHandler extends IDeliverTx with ICheckTx with ICommit with IQuery { private val storage: ArrayBuffer[Node] = new ArrayBuffer[Node]() private var consensusRoot: Node = Node.emptyNode @@ -22,20 +27,6 @@ object KVStoreServerRunner extends IDeliverTx with ICheckTx with ICommit with IQ @volatile private var mempoolRoot: Node = Node.emptyNode - def start(): Unit = { - System.out.println("starting KVStore") - val socket = new TSocket - - socket.registerListener(this) - - val t = new Thread(() => socket.start(46658)) - t.setName("KVStore server Main Thread") - t.start() - while (true) { - Thread.sleep(1000L) - } - } - override def receivedDeliverTx(req: RequestDeliverTx): ResponseDeliverTx = { val tx = req.getTx.toStringUtf8 val txPayload = tx.split("###")(0) @@ -72,7 +63,8 @@ object KVStoreServerRunner extends IDeliverTx with ICheckTx with ICommit with IQ } override def requestCheckTx(req: RequestCheckTx): ResponseCheckTx = { - // check mempoolRoot + // no transaction processing logic currently + // mempoolRoot is intended to be used here as the latest committed state val tx = req.getTx.toStringUtf8 if (tx == "BAD_CHECK") { @@ -104,28 +96,18 @@ object KVStoreServerRunner extends IDeliverTx with ICheckTx with ICommit with IQ val lsPattern = "ls:(.*)".r val query = req.getData.toStringUtf8 - query match { - case getPattern(key) => - val result = root.getValue(key) - val proof = if (result.isDefined && req.getProve) twoLevelMerkleListToString(root.getProof(key)) else "" - - ResponseQuery.newBuilder.setCode(CodeType.OK) - .setValue(ByteString.copyFromUtf8(result.getOrElse(""))) - .setProof(ByteString.copyFromUtf8(proof)) - .build - case lsPattern(key) => - val result = root.listChildren(key) - val proof = if (result.isDefined && req.getProve) twoLevelMerkleListToString(root.getProof(key)) else "" - - ResponseQuery.newBuilder.setCode(CodeType.OK) - .setValue(ByteString.copyFromUtf8(result.map(x => x.mkString(" ")).getOrElse(""))) - .setProof(ByteString.copyFromUtf8(proof)) - .build + val (key, result) = query match { + case getPattern(key) => (key, root.getValue(key)) + case lsPattern(key) => (key, root.listChildren(key).map(x => x.mkString(" "))) case _ => - ResponseQuery.newBuilder.setCode(CodeType.BAD).setLog("Invalid query path. Got " + query).build + return ResponseQuery.newBuilder.setCode(CodeType.BAD).setLog("Invalid query path. Got " + query).build } - } - private def twoLevelMerkleListToString(list: List[List[MerkleHash]]): String = - list.map(level => level.map(MerkleUtil.merkleHashToHex).mkString(" ")).mkString(", ") + val proof = if (result.isDefined && req.getProve) MerkleUtil.twoLevelMerkleListToString(root.getProof(key)) else "" + + ResponseQuery.newBuilder.setCode(CodeType.OK) + .setValue(ByteString.copyFromUtf8(result.getOrElse(""))) + .setProof(ByteString.copyFromUtf8(proof)) + .build + } } diff --git a/tmdemoapp/src/main/scala/kvstore/MerkleUtil.scala b/tmdemoapp/src/main/scala/kvstore/MerkleUtil.scala index 82bb0ba..605cdbb 100644 --- a/tmdemoapp/src/main/scala/kvstore/MerkleUtil.scala +++ b/tmdemoapp/src/main/scala/kvstore/MerkleUtil.scala @@ -19,4 +19,7 @@ object MerkleUtil { def merkleHashToHex(merkleHash: MerkleHash): String = merkleHash.map("%02x".format(_)).mkString + + def twoLevelMerkleListToString(list: List[List[MerkleHash]]): String = + list.map(level => level.map(MerkleUtil.merkleHashToHex).mkString(" ")).mkString(", ") } diff --git a/tmdemoapp/src/main/scala/kvstore/Node.scala b/tmdemoapp/src/main/scala/kvstore/Node.scala index 71b4b79..329767c 100644 --- a/tmdemoapp/src/main/scala/kvstore/Node.scala +++ b/tmdemoapp/src/main/scala/kvstore/Node.scala @@ -5,6 +5,15 @@ import kvstore.MerkleUtil._ import scala.collection.immutable.HashMap import scala.util.Try +/** + * Node + * + * merkleHash set to None when branch changed. Later None merkle hashes recalculated when [[Node.merkelize]] invoked + * + * @param children child nodes + * @param value assigned value, if exists + * @param merkleHash Merkle hash of a node's subtree, if calculated + */ case class Node(children: NodeStorage, value: Option[String], merkleHash: Option[MerkleHash]) { def merkelize(): Node = if (merkleHash.isDefined) @@ -34,6 +43,9 @@ case class Node(children: NodeStorage, value: Option[String], merkleHash: Option key match { case rangeKeyValuePattern(rangeStartStr, rangeEndStr, keyPattern) => + // range pattern allows to set multiple keys in single transaction + // range defined by starting and ending index, key pattern may contains hexadecimal digits of current index + val rangeStart = rangeStartStr.toInt val rangeEnd = rangeEndStr.toInt System.out.println(s"setting range from=$rangeStart to=$rangeEnd keyPattern=$keyPattern valuePattern=$value") diff --git a/tmdemoapp/src/main/scala/kvstore/ServerRunner.scala b/tmdemoapp/src/main/scala/kvstore/ServerRunner.scala new file mode 100644 index 0000000..c56a8ea --- /dev/null +++ b/tmdemoapp/src/main/scala/kvstore/ServerRunner.scala @@ -0,0 +1,25 @@ +package kvstore + +import com.github.jtendermint.jabci.socket.TSocket + +object ServerRunner { + + def main(args: Array[String]): Unit = { + val port = if (args.length > 0) args(0).toInt else 46658 + ServerRunner.start(port) + } + + def start(port: Int): Unit = { + System.out.println("starting KVStore") + val socket = new TSocket + + socket.registerListener(ABCIHandler) + + val t = new Thread(() => socket.start(port)) + t.setName("KVStore server Main Thread") + t.start() + while (true) { + Thread.sleep(1000L) + } + } +}