From f665195c2be3ea7cb83b4230d477a07ca63e7e71 Mon Sep 17 00:00:00 2001 From: Anastasiia Pushkina Date: Wed, 20 Jan 2021 15:48:13 +0100 Subject: [PATCH 01/10] [ETCM-533] Proof of non-existence --- .../ethereum/mpt/MerklePatriciaTrie.scala | 42 +++++++++- .../iohk/ethereum/domain/BlockchainSpec.scala | 5 +- .../mpt/MerklePatriciaTrieSuite.scala | 76 +++++++++++++++---- 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala index 606e1d1f0a..6565e73376 100644 --- a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala +++ b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala @@ -103,7 +103,7 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod * @throws io.iohk.ethereum.mpt.MerklePatriciaTrie.MPTException if there is any inconsistency in how the trie is build. */ def getProof(key: K): Option[Vector[MptNode]] = { - pathTraverse[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => + pathTraverseWithProof[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => node match { case nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode) => acc :+ nextNodeOnExt case _ => acc @@ -161,6 +161,46 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod } } + private def pathTraverseWithProof[T](acc: T, searchKey: Array[Byte])(op: (T, MptNode) => T): Option[T] = { + + @tailrec + def pathTraverseWithProof(acc: T, node: MptNode, searchKey: Array[Byte], op: (T, MptNode) => T): Option[T] = { + node match { + case LeafNode(key, _, _, _, _) => + if (key.toArray[Byte] sameElements searchKey) Some(op(acc, node)) else Some(acc) + + case extNode @ ExtensionNode(sharedKey, _, _, _, _) => + val (commonKey, remainingKey) = searchKey.splitAt(sharedKey.length) + if (searchKey.length >= sharedKey.length && (sharedKey.toArray[Byte] sameElements commonKey)) { + pathTraverseWithProof(op(acc, node), extNode.next, remainingKey, op) + } else Some(acc) + + case branch: BranchNode => + if (searchKey.isEmpty) Some(op(acc, node)) + else + pathTraverseWithProof( + op(acc, node), + branch.children(searchKey(0)), + searchKey.slice(1, searchKey.length), + op + ) + + case HashNode(bytes) => + pathTraverseWithProof(acc, getFromHash(bytes, nodeStorage), searchKey, op) + + case NullNode => + Some(acc) + } + } + + rootNode match { + case Some(root) => + pathTraverseWithProof(acc, root, searchKey, op) + case None => + None + } + } + private def getFromHash(nodeId: Array[Byte], source: MptStorage): MptNode = { val nodeEncoded = source.get(nodeId).encode MptTraversals diff --git a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala index c6fb57ce39..d8f94daf91 100644 --- a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala +++ b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala @@ -152,7 +152,10 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh //unhappy path val wrongAddress = Address(666) val retrievedAccountProofWrong = blockchain.getAccountProof(wrongAddress, headerWithAcc.number) - retrievedAccountProofWrong.isDefined shouldBe false + //the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes that we iterated + retrievedAccountProofWrong.isDefined shouldBe true + retrievedAccountProofWrong.size shouldBe 1 + mptWithAcc.get(wrongAddress) shouldBe None //happy path val retrievedAccountProof = blockchain.getAccountProof(address, headerWithAcc.number) diff --git a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala index 942fd38f0e..f360d1e0bf 100644 --- a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala +++ b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala @@ -554,22 +554,26 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks assert(proof.isEmpty) } - test("getProof returns empty result for non-existing key") { - forAll(keyValueListGen()) { keyValueList: Seq[(Int, Int)] => - val input: Seq[(Array[Byte], Array[Byte])] = keyValueList - .map { case (k, v) => k.toString.getBytes() -> v.toString.getBytes() } - - val trie = input - .foldLeft(emptyMpt) { case (recTrie, (key, value)) => - recTrie.put(key, value) - } - - input.toList.foreach(x => { - val keyToFind = x._1 ++ Array(Byte.MaxValue) - val proof = trie.getProof(keyToFind) - assert(proof.isEmpty) - }) - } + test("getProof returns proof result for non-existing address") { + // given + val EmptyTrie = MerklePatriciaTrie[Array[Byte], Array[Byte]](emptyEphemNodeStorage) + val key1: Array[Byte] = Hex.decode("10000001") + val key2: Array[Byte] = Hex.decode("10000002") + val key3: Array[Byte] = Hex.decode("30000003") + val key4: Array[Byte] = Hex.decode("10000004") //a key that doesn't have a corresponding value in the trie + + val val1: Array[Byte] = Hex.decode("0101") + val val2: Array[Byte] = Hex.decode("0102") + val val3: Array[Byte] = Hex.decode("0103") + val trie = EmptyTrie + .put(key1, val1) + .put(key2, val2) + .put(key3, val3) + // when + val proof: Option[Vector[MptNode]] = trie.getProof(key4) + // then + assert(proof.isDefined) + proof.foreach(_.foreach(MptUtils.showMptNode(_, true, " "))) } test("getProof returns valid proof for existing key") { @@ -620,3 +624,43 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks assert(obtained.isEmpty) } } + +object MptUtils { + import io.iohk.ethereum.mpt._ + import org.bouncycastle.util.encoders.Hex + def show(val1: Array[Byte]): String = + Hex.encode(val1).map(_.toChar).mkString + def showKV(key1: Array[Byte], trie: MerklePatriciaTrie[Array[Byte], Array[Byte]]): String = + show(key1) + " -> " + trie.get(key1).map(show) + def showMptNode(n: MptNode, showNull: Boolean = false, indent: String): Unit = { + n match { + case LeafNode(key, value, cachedHash, cachedRlpEncoded, parsedRlp) => + if (showNull) println("\n") + println(s"$indent LeafNode(${show(key.toArray)} -> ${show(value.toArray)})") + case ExtensionNode(sharedKey, next, cachedHash, cachedRlpEncoded, parsedRlp) => + if (showNull) println("\n") + println(s"$indent ExtensionNode(sharedKey = ${show(sharedKey.toArray)}") + println(s"$indent next = ") + showMptNode(next, false, indent + " ") + println(s"$indent )") + case BranchNode(children, terminator, cachedHash, cachedRlpEncoded, parsedRlp) => + if (showNull) println("\n") + println( + s"$indent BranchNode(children = ${children.filterNot(n => n.isInstanceOf[NullNode.type]).size}, terminator = ${terminator + .map(e => show(e.toArray))}" + ) + println(s"$indent children: ") + children.map(showMptNode(_, false, indent + " ")) + println(s"$indent )") + case NullNode => + if (showNull) println(s"$indent NullNode") + case other => + if (showNull) println("\n") + println(s"$indent $other") + } + } + def showMptTrie[K, V](mpt: MerklePatriciaTrie[K, V]): Unit = { + println(s"MPT ROOT HASH ${mpt.getRootHash} rootNode: ") + mpt.rootNode.map(n => showMptNode(n, false, " ")) + } +} From 5df1b54233d327d7e238af5776e7ef5a8364f065 Mon Sep 17 00:00:00 2001 From: Anastasiia Pushkina Date: Thu, 21 Jan 2021 15:53:00 +0100 Subject: [PATCH 02/10] Change getStorageProof impl --- .../txExecTest/util/DumpChainApp.scala | 2 +- .../io/iohk/ethereum/domain/Blockchain.scala | 18 ++++++++++++------ .../EthProofJsonMethodsImplicits.scala | 2 -- .../ethereum/jsonrpc/EthProofService.scala | 19 +++++++------------ .../jsonrpc/EthProofServiceSpec.scala | 1 - .../jsonrpc/JsonRpcControllerEthSpec.scala | 8 +++++--- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala index 8b56c5a46c..df9898abbb 100644 --- a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala +++ b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala @@ -150,7 +150,7 @@ class BlockchainMock(genesisHash: ByteString) extends Blockchain { rootHash: NodeHash, position: BigInt, ethCompatibleStorage: Boolean - ): Option[(BigInt, Seq[MptNode])] = None + ): (BigInt, Seq[MptNode]) = (1, Seq.empty) override protected def getHashByBlockNumber(number: BigInt): Option[ByteString] = Some(genesisHash) diff --git a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala index 2d510c9a9b..b65345d173 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala @@ -95,11 +95,17 @@ trait Blockchain { */ def getAccountStorageAt(rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean): ByteString + /** + * Get a storage-value and its proof being the path from the root node until the last matching node. + * + * @param rootHash storage root hash + * @param position storage position + */ def getStorageProofAt( rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean - ): Option[(BigInt, Seq[MptNode])] + ): (BigInt, Seq[MptNode]) /** * Returns the receipts based on a block hash @@ -307,16 +313,16 @@ class BlockchainImpl( rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean - ): Option[(BigInt, Seq[MptNode])] = { + ): (BigInt, Seq[MptNode]) = { + val defaultValue = BigInt(0) val storage: MptStorage = stateStorage.getBackingStorage(0) val mpt: MerklePatriciaTrie[BigInt, BigInt] = { if (ethCompatibleStorage) domain.EthereumUInt256Mpt.storageMpt(rootHash, storage) else domain.ArbitraryIntegerMpt.storageMpt(rootHash, storage) } - for { - value <- mpt.get(position) - proof <- mpt.getProof(position) - } yield (value, proof) + val value = mpt.get(position).getOrElse(defaultValue) + val proof = mpt.getProof(position).getOrElse(Seq.empty) + (value, proof) } private def persistBestBlocksData(): Unit = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofJsonMethodsImplicits.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofJsonMethodsImplicits.scala index cdc64ac5e6..6fa31dcf9c 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofJsonMethodsImplicits.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofJsonMethodsImplicits.scala @@ -4,9 +4,7 @@ import io.iohk.ethereum.jsonrpc.JsonRpcError.InvalidParams import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, GetProofResponse, StorageProofKey} import io.iohk.ethereum.jsonrpc.serialization.JsonEncoder import io.iohk.ethereum.jsonrpc.serialization.JsonMethodDecoder -import org.json4s.Extraction import org.json4s.JsonAST.{JArray, JString, JValue, _} -import org.json4s.JsonDSL._ object EthProofJsonMethodsImplicits extends JsonMethodsImplicits { def extractStorageKeys(input: JValue): Either[JsonRpcError, Seq[StorageProofKey]] = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala index 3e19e6f55c..383609fbc3 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala @@ -143,14 +143,14 @@ class EthProofService(blockchain: Blockchain, blockGenerator: BlockGenerator, et blockchain.getAccountProof(address, blockNumber).map(_.map(asRlpSerializedNode)), noAccountProof(address, blockNumber) ) - storageProof <- getStorageProof(account, storageKeys) + storageProof <- Either.right(getStorageProof(account, storageKeys)) } yield ProofAccount(account, accountProof, storageProof, address) } def getStorageProof( account: Account, storageKeys: Seq[StorageProofKey] - ): Either[JsonRpcError, Seq[StorageProof]] = { + ): Seq[StorageProof] = { storageKeys.toList .map { storageKey => blockchain @@ -158,22 +158,17 @@ class EthProofService(blockchain: Blockchain, blockGenerator: BlockGenerator, et rootHash = account.storageRoot, position = storageKey.v, ethCompatibleStorage = ethCompatibleStorage - ) - .map { case (value, proof) => StorageProof(storageKey, value, proof.map(asRlpSerializedNode)) } - .toRight(noStorageProof(account, storageKey)) + ) match { + case (value, proof) => StorageProof(storageKey, value, proof.map(asRlpSerializedNode)) + } } - .sequence - .map(_.toSeq) } - private def noStorageProof(account: Account, storagekey: StorageProofKey): JsonRpcError = - JsonRpcError.LogicError(s"No storage proof for [${account.toString}] storage key [${storagekey.toString}]") - private def noAccount(address: Address, blockNumber: BigInt): JsonRpcError = - JsonRpcError.LogicError(s"No storage proof for Address [${address.toString}] blockNumber [${blockNumber.toString}]") + JsonRpcError.LogicError(s"No account found for Address [${address.toString}] blockNumber [${blockNumber.toString}]") private def noAccountProof(address: Address, blockNumber: BigInt): JsonRpcError = - JsonRpcError.LogicError(s"No storage proof for Address [${address.toString}] blockNumber [${blockNumber.toString}]") + JsonRpcError.LogicError(s"No account proof for Address [${address.toString}] blockNumber [${blockNumber.toString}]") private def asRlpSerializedNode(node: MptNode): ByteString = ByteString(MptTraversals.encodeNode(node)) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala index ed987ecc6d..0f36f5c2c4 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala @@ -25,7 +25,6 @@ import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers -import scala.concurrent.duration.{DurationInt, FiniteDuration} import io.iohk.ethereum.jsonrpc.EthUserService.GetBalanceResponse import io.iohk.ethereum.jsonrpc.EthUserService.GetBalanceRequest import io.iohk.ethereum.jsonrpc.EthUserService.GetTransactionCountRequest diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala index 5bb0af69e2..ddb27517dc 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala @@ -803,13 +803,15 @@ class JsonRpcControllerEthSpec } it should "decode and encode eth_getProof request and response" in new JsonRpcControllerFixture { + val address = "0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842" + val request: JsonRpcRequest = JsonRpcRequest( jsonrpc = "2.0", method = "eth_getProof", params = Some( JArray( List( - JString("0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842"), + JString(address), JArray(List(JString("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"))), JString("latest") ) @@ -819,14 +821,14 @@ class JsonRpcControllerEthSpec ) val expectedDecodedRequest = GetProofRequest( - address = Address("0x7f0d15c7faae65896648c8273b6d7e43f58fa842"), + address = Address(address), storageKeys = List(StorageProofKey(BigInt("39309028074332508661983559455579427211983204215636056653337583610388178777121"))), blockNumber = BlockParam.Latest ) val expectedEncodedResponse: GetProofResponse = GetProofResponse( ProofAccount( - address = Address("0x7f0d15c7faae65896648c8273b6d7e43f58fa842"), + address = Address(address), accountProof = Seq(ByteString(Hex.decode("1234"))), balance = BigInt(0x0), codeHash = ByteString(Hex.decode("123eeaa22a")), From 1c10a9551a6384bbad9f67b7a6a4c50fa9bbe349 Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Thu, 21 Jan 2021 18:14:16 +0100 Subject: [PATCH 03/10] [ETCM-533] testing the proof and value for non existing account and/or storage keys --- .../jsonrpc/EthProofServiceSpec.scala | 166 +++++++++++------- 1 file changed, 107 insertions(+), 59 deletions(-) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala index 0f36f5c2c4..812afcaf93 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala @@ -1,21 +1,18 @@ package io.iohk.ethereum.jsonrpc import akka.actor.ActorSystem -import akka.testkit.{TestKit, TestProbe} +import akka.testkit.TestKit import akka.util.ByteString import com.softwaremill.diffx.scalatest.DiffMatcher +import io.iohk.ethereum._ import io.iohk.ethereum.blockchain.sync.EphemBlockchainTestSetup -import io.iohk.ethereum.consensus._ import io.iohk.ethereum.consensus.ethash.blocks.EthashBlockGenerator -import io.iohk.ethereum.domain.{Account, Address, Block, EthereumUInt256Mpt, UInt256} -import io.iohk.ethereum.jsonrpc.server.controllers.JsonRpcBaseController.JsonRpcConfig -import io.iohk.ethereum.keystore.KeyStore -import io.iohk.ethereum.ledger.{Ledger, StxLedger} +import io.iohk.ethereum.domain._ +import io.iohk.ethereum.jsonrpc.EthUserService.{GetBalanceRequest, GetBalanceResponse, GetStorageAtRequest, GetTransactionCountRequest} +import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, StorageProofKey} +import io.iohk.ethereum.ledger.Ledger import io.iohk.ethereum.mpt.MerklePatriciaTrie import io.iohk.ethereum.nodebuilder.ApisBuilder -import io.iohk.ethereum.utils._ -import io.iohk.ethereum._ -import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, StorageProofKey} import monix.execution.Scheduler.Implicits.global import org.bouncycastle.util.encoders.Hex import org.scalactic.TypeCheckedTripleEquals @@ -24,11 +21,7 @@ import org.scalatest.OptionValues import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpecLike import org.scalatest.matchers.should.Matchers - -import io.iohk.ethereum.jsonrpc.EthUserService.GetBalanceResponse -import io.iohk.ethereum.jsonrpc.EthUserService.GetBalanceRequest -import io.iohk.ethereum.jsonrpc.EthUserService.GetTransactionCountRequest -import io.iohk.ethereum.jsonrpc.EthUserService.GetStorageAtRequest +import io.iohk.ethereum.mpt.MerklePatriciaTrie.defaultByteArraySerializable class EthProofServiceSpec extends TestKit(ActorSystem("EthGetProofSpec_ActorSystem")) @@ -43,49 +36,9 @@ class EthProofServiceSpec with DiffMatcher { "EthProofService" should "handle getStorageAt request" in new TestSetup { - // given - val address = Address(ByteString(Hex.decode("abbb6bebfa05aa13e908eaa492bd7a8343760477"))) - - val key = 333 - val value = 123 - - val storageMpt = EthereumUInt256Mpt - .storageMpt( - ByteString(MerklePatriciaTrie.EmptyRootHash), - storagesInstance.storages.stateStorage.getBackingStorage(0) - ) - .put(UInt256(key), UInt256(value)) - - val account = Account( - nonce = 0, - balance = UInt256(0), - storageRoot = ByteString(storageMpt.getRootHash), - codeHash = ByteString("") - ) - - import MerklePatriciaTrie.defaultByteArraySerializable - val mpt = - MerklePatriciaTrie[Array[Byte], Account](storagesInstance.storages.stateStorage.getBackingStorage(0)) - .put( - crypto.kec256(address.bytes.toArray[Byte]), - account - ) - - val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body) - val newBlockHeader = blockToRequest.header.copy(stateRoot = ByteString(mpt.getRootHash)) - val newblock = blockToRequest.copy(header = newBlockHeader) - blockchain.storeBlock(newblock).commit() - blockchain.saveBestKnownBlocks(newblock.header.number) - - val ethGetProof = new EthProofService(blockchain, blockGenerator, blockchainConfig.ethCompatibleStorage) - val storageKeys = Seq(StorageProofKey(key)) - val blockNumber = BlockParam.Latest val request = GetProofRequest(address, storageKeys, blockNumber) - - // when val result = ethGetProof.getProof(request) - // then val balanceResponse: GetBalanceResponse = ethUserService .getBalance(GetBalanceRequest(address, BlockParam.Latest)) .runSyncUnsafe() @@ -125,18 +78,113 @@ class EthProofServiceSpec } "EthProofService" should "return an error when the proof is requested for non-existing account" in new TestSetup { - val ethGetProof = new EthProofService(blockchain, blockGenerator, blockchainConfig.ethCompatibleStorage) - val key = 999 - val storageKeys = Seq(StorageProofKey(key)) - val blockNumber = BlockParam.Latest val wrongAddress = Address(666) val request = GetProofRequest(wrongAddress, storageKeys, blockNumber) val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - retrievedAccountProofWrong.runSyncUnsafe().isLeft shouldBe true + val result = retrievedAccountProofWrong.runSyncUnsafe() + + result.isLeft shouldBe true + result.fold(l => l.message should include("No account found for Address"), r => r) + } + + "EthProofService" should "return the proof with empty value for non-existing storage key" in new TestSetup { + val wrongStorageKey = Seq(StorageProofKey(321)) + val request = GetProofRequest(address, wrongStorageKey, blockNumber) + val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) + val result = retrievedAccountProofWrong.runSyncUnsafe() + result.isRight shouldBe true + result.fold(l => l, r => r.proofAccount.storageProof.map(v => { + v.proof.nonEmpty shouldBe true + v.value shouldBe BigInt(0) + })) + } + + "EthProofService" should "return the proof and value for existing storage key" in new TestSetup { + val storageKey = Seq(StorageProofKey(key)) + val request = GetProofRequest(address, storageKey, blockNumber) + val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) + val result = retrievedAccountProofWrong.runSyncUnsafe() + result.isRight shouldBe true + result.fold(l => l, r => r.proofAccount.storageProof.map(v => { + v.proof.nonEmpty shouldBe true + v.value shouldBe BigInt(value) + })) + } + + "EthProofService" should "return the proof and value for multiple existing storage keys" in new TestSetup { + val storageKey = Seq(StorageProofKey(key), StorageProofKey(key2)) + val expectedValueStorageKey = Seq(BigInt(value), BigInt(value2)) + val request = GetProofRequest(address, storageKey, blockNumber) + val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) + val result = retrievedAccountProofWrong.runSyncUnsafe() + result.isRight shouldBe true + result.fold(l => l, r => { + r.proofAccount.storageProof.size shouldBe 2 + r.proofAccount.storageProof.map(v => { + v.proof.nonEmpty shouldBe true + expectedValueStorageKey should contain(v.value) + }) + }) + } + + "EthProofService" should "return the proof for all storage keys provided, but value should be returned only for the existing ones" in new TestSetup { + val wrongStorageKey = StorageProofKey(321) + val storageKey = Seq(StorageProofKey(key), StorageProofKey(key2)) :+ wrongStorageKey + val expectedValueStorageKey = Seq(BigInt(value), BigInt(value2), BigInt(0)) + val request = GetProofRequest(address, storageKey, blockNumber) + val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) + val result = retrievedAccountProofWrong.runSyncUnsafe() + result.isRight shouldBe true + result.fold(l => l, r => { + r.proofAccount.storageProof.size shouldBe 3 + expectedValueStorageKey.forall(r.proofAccount.storageProof.map(_.value).contains) shouldBe true + }) } class TestSetup(implicit system: ActorSystem) extends MockFactory with EphemBlockchainTestSetup with ApisBuilder { + val blockGenerator = mock[EthashBlockGenerator] + val address = Address(ByteString(Hex.decode("abbb6bebfa05aa13e908eaa492bd7a8343760477"))) + + val key = 333 + val value = 123 + val key1 = 334 + val value1 = 124 + val key2 = 335 + val value2 = 125 + + val storageMpt = EthereumUInt256Mpt + .storageMpt( + ByteString(MerklePatriciaTrie.EmptyRootHash), + storagesInstance.storages.stateStorage.getBackingStorage(0) + ) + .put(UInt256(key), UInt256(value)) + .put(UInt256(key1), UInt256(value1)) + .put(UInt256(key2), UInt256(value2)) + + val account = Account( + nonce = 0, + balance = UInt256(0), + storageRoot = ByteString(storageMpt.getRootHash) + ) + + val mpt = + MerklePatriciaTrie[Array[Byte], Account](storagesInstance.storages.stateStorage.getBackingStorage(0)) + .put( + crypto.kec256(address.bytes.toArray[Byte]), + account + ) + + val blockToRequest = Block(Fixtures.Blocks.Block3125369.header, Fixtures.Blocks.Block3125369.body) + val newBlockHeader = blockToRequest.header.copy(stateRoot = ByteString(mpt.getRootHash)) + val newblock = blockToRequest.copy(header = newBlockHeader) + blockchain.storeBlock(newblock).commit() + blockchain.saveBestKnownBlocks(newblock.header.number) + + val ethGetProof = new EthProofService(blockchain, blockGenerator, blockchainConfig.ethCompatibleStorage) + + val storageKeys = Seq(StorageProofKey(key)) + val blockNumber = BlockParam.Latest override lazy val ledger = mock[Ledger] From 6bcc444e23b887e143e5a2669731ea33246ec5cd Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Fri, 22 Jan 2021 11:04:44 +0100 Subject: [PATCH 04/10] [ETCM-533] refactor pathTraverse in order to get the proof and value in both cases(existing/non existing key) --- .../txExecTest/util/DumpChainApp.scala | 2 +- .../ethereum/mpt/MerklePatriciaTrie.scala | 64 ++++--------------- .../mpt/MerklePatriciaTrieSuite.scala | 41 ------------ 3 files changed, 13 insertions(+), 94 deletions(-) diff --git a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala index df9898abbb..f0faed86dd 100644 --- a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala +++ b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala @@ -150,7 +150,7 @@ class BlockchainMock(genesisHash: ByteString) extends Blockchain { rootHash: NodeHash, position: BigInt, ethCompatibleStorage: Boolean - ): (BigInt, Seq[MptNode]) = (1, Seq.empty) + ): (BigInt, Seq[MptNode]) = (BigInt(0), Seq.empty) override protected def getHashByBlockNumber(number: BigInt): Option[ByteString] = Some(genesisHash) diff --git a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala index 6565e73376..3a4d3aeb14 100644 --- a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala +++ b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala @@ -84,10 +84,10 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod def get(key: K): Option[V] = { pathTraverse[Option[V]](None, mkKeyNibbles(key)) { case (_, node) => node match { - case LeafNode(_, value, _, _, _) => + case Some(LeafNode(_, value, _, _, _)) => Some(vSerializer.fromBytes(value.toArray[Byte])) - case BranchNode(_, terminator, _, _, _) => + case Some(BranchNode(_, terminator, _, _, _)) => terminator.map(term => vSerializer.fromBytes(term.toArray[Byte])) case _ => None @@ -103,9 +103,9 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod * @throws io.iohk.ethereum.mpt.MerklePatriciaTrie.MPTException if there is any inconsistency in how the trie is build. */ def getProof(key: K): Option[Vector[MptNode]] = { - pathTraverseWithProof[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => + pathTraverse[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => node match { - case nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode) => acc :+ nextNodeOnExt + case Some(nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode)) => acc :+ nextNodeOnExt case _ => acc } } @@ -121,25 +121,25 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod * @tparam T accumulator type * @return accumulated data or None if key doesn't exist */ - private def pathTraverse[T](acc: T, searchKey: Array[Byte])(op: (T, MptNode) => T): Option[T] = { + private def pathTraverse[T](acc: T, searchKey: Array[Byte])(op: (T, Option[MptNode]) => T): Option[T] = { @tailrec - def pathTraverse(acc: T, node: MptNode, searchKey: Array[Byte], op: (T, MptNode) => T): Option[T] = { + def pathTraverse(acc: T, node: MptNode, searchKey: Array[Byte], op: (T, Option[MptNode]) => T): Option[T] = { node match { case LeafNode(key, _, _, _, _) => - if (key.toArray[Byte] sameElements searchKey) Some(op(acc, node)) else None + if (key.toArray[Byte] sameElements searchKey) Some(op(acc, Some(node))) else Some(op(acc, None)) case extNode @ ExtensionNode(sharedKey, _, _, _, _) => val (commonKey, remainingKey) = searchKey.splitAt(sharedKey.length) if (searchKey.length >= sharedKey.length && (sharedKey.toArray[Byte] sameElements commonKey)) { - pathTraverse(op(acc, node), extNode.next, remainingKey, op) - } else None + pathTraverse(op(acc, Some(node)), extNode.next, remainingKey, op) + } else Some(op(acc, None)) case branch: BranchNode => - if (searchKey.isEmpty) Some(op(acc, node)) + if (searchKey.isEmpty) Some(op(acc, Some(node))) else pathTraverse( - op(acc, node), + op(acc, Some(node)), branch.children(searchKey(0)), searchKey.slice(1, searchKey.length), op @@ -149,7 +149,7 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod pathTraverse(acc, getFromHash(bytes, nodeStorage), searchKey, op) case NullNode => - None + Some(op(acc, None)) } } @@ -161,46 +161,6 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod } } - private def pathTraverseWithProof[T](acc: T, searchKey: Array[Byte])(op: (T, MptNode) => T): Option[T] = { - - @tailrec - def pathTraverseWithProof(acc: T, node: MptNode, searchKey: Array[Byte], op: (T, MptNode) => T): Option[T] = { - node match { - case LeafNode(key, _, _, _, _) => - if (key.toArray[Byte] sameElements searchKey) Some(op(acc, node)) else Some(acc) - - case extNode @ ExtensionNode(sharedKey, _, _, _, _) => - val (commonKey, remainingKey) = searchKey.splitAt(sharedKey.length) - if (searchKey.length >= sharedKey.length && (sharedKey.toArray[Byte] sameElements commonKey)) { - pathTraverseWithProof(op(acc, node), extNode.next, remainingKey, op) - } else Some(acc) - - case branch: BranchNode => - if (searchKey.isEmpty) Some(op(acc, node)) - else - pathTraverseWithProof( - op(acc, node), - branch.children(searchKey(0)), - searchKey.slice(1, searchKey.length), - op - ) - - case HashNode(bytes) => - pathTraverseWithProof(acc, getFromHash(bytes, nodeStorage), searchKey, op) - - case NullNode => - Some(acc) - } - } - - rootNode match { - case Some(root) => - pathTraverseWithProof(acc, root, searchKey, op) - case None => - None - } - } - private def getFromHash(nodeId: Array[Byte], source: MptStorage): MptNode = { val nodeEncoded = source.get(nodeId).encode MptTraversals diff --git a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala index f360d1e0bf..5132a665ff 100644 --- a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala +++ b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala @@ -573,7 +573,6 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks val proof: Option[Vector[MptNode]] = trie.getProof(key4) // then assert(proof.isDefined) - proof.foreach(_.foreach(MptUtils.showMptNode(_, true, " "))) } test("getProof returns valid proof for existing key") { @@ -624,43 +623,3 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks assert(obtained.isEmpty) } } - -object MptUtils { - import io.iohk.ethereum.mpt._ - import org.bouncycastle.util.encoders.Hex - def show(val1: Array[Byte]): String = - Hex.encode(val1).map(_.toChar).mkString - def showKV(key1: Array[Byte], trie: MerklePatriciaTrie[Array[Byte], Array[Byte]]): String = - show(key1) + " -> " + trie.get(key1).map(show) - def showMptNode(n: MptNode, showNull: Boolean = false, indent: String): Unit = { - n match { - case LeafNode(key, value, cachedHash, cachedRlpEncoded, parsedRlp) => - if (showNull) println("\n") - println(s"$indent LeafNode(${show(key.toArray)} -> ${show(value.toArray)})") - case ExtensionNode(sharedKey, next, cachedHash, cachedRlpEncoded, parsedRlp) => - if (showNull) println("\n") - println(s"$indent ExtensionNode(sharedKey = ${show(sharedKey.toArray)}") - println(s"$indent next = ") - showMptNode(next, false, indent + " ") - println(s"$indent )") - case BranchNode(children, terminator, cachedHash, cachedRlpEncoded, parsedRlp) => - if (showNull) println("\n") - println( - s"$indent BranchNode(children = ${children.filterNot(n => n.isInstanceOf[NullNode.type]).size}, terminator = ${terminator - .map(e => show(e.toArray))}" - ) - println(s"$indent children: ") - children.map(showMptNode(_, false, indent + " ")) - println(s"$indent )") - case NullNode => - if (showNull) println(s"$indent NullNode") - case other => - if (showNull) println("\n") - println(s"$indent $other") - } - } - def showMptTrie[K, V](mpt: MerklePatriciaTrie[K, V]): Unit = { - println(s"MPT ROOT HASH ${mpt.getRootHash} rootNode: ") - mpt.rootNode.map(n => showMptNode(n, false, " ")) - } -} From 8416b012583b2efb9c2dfd51d4a821c1fc072de7 Mon Sep 17 00:00:00 2001 From: Anastasiia Pushkina Date: Fri, 22 Jan 2021 15:53:22 +0100 Subject: [PATCH 05/10] [ETCM-533] fix comments --- .../txExecTest/util/DumpChainApp.scala | 4 +- .../io/iohk/ethereum/domain/Blockchain.scala | 19 ++++++---- .../ethereum/jsonrpc/EthProofService.scala | 38 ++++++++++--------- .../iohk/ethereum/domain/BlockchainSpec.scala | 18 +++++++++ .../jsonrpc/JsonRpcControllerEthSpec.scala | 4 +- .../mpt/MerklePatriciaTrieSuite.scala | 1 + 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala index f0faed86dd..11e9afe0a6 100644 --- a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala +++ b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala @@ -2,7 +2,6 @@ package io.iohk.ethereum.txExecTest.util import java.time.Clock import java.util.concurrent.atomic.AtomicReference - import akka.actor.ActorSystem import akka.util.ByteString import com.typesafe.config.ConfigFactory @@ -15,6 +14,7 @@ import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, PruningMode} import io.iohk.ethereum.db.storage.{AppStateStorage, StateStorage} import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefEmpty import io.iohk.ethereum.domain.{Blockchain, UInt256, _} +import io.iohk.ethereum.jsonrpc.ProofService.{StorageProof, StorageValueProof} import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage} import io.iohk.ethereum.mpt.MptNode import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo @@ -150,7 +150,7 @@ class BlockchainMock(genesisHash: ByteString) extends Blockchain { rootHash: NodeHash, position: BigInt, ethCompatibleStorage: Boolean - ): (BigInt, Seq[MptNode]) = (BigInt(0), Seq.empty) + ): StorageProof = StorageValueProof(position) override protected def getHashByBlockNumber(number: BigInt): Option[ByteString] = Some(genesisHash) diff --git a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala index b65345d173..ea6f931321 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala @@ -1,7 +1,6 @@ package io.iohk.ethereum.domain import java.util.concurrent.atomic.AtomicReference - import akka.util.ByteString import cats.syntax.flatMap._ import cats.instances.option._ @@ -13,6 +12,7 @@ import io.iohk.ethereum.db.storage._ import io.iohk.ethereum.db.storage.pruning.PruningMode import io.iohk.ethereum.domain import io.iohk.ethereum.domain.BlockchainImpl.BestBlockLatestCheckpointNumbers +import io.iohk.ethereum.jsonrpc.ProofService.{StorageProof, StorageValueProof} import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage} import io.iohk.ethereum.mpt.{MerklePatriciaTrie, MptNode} import io.iohk.ethereum.utils.{ByteStringUtils, Logger} @@ -105,7 +105,7 @@ trait Blockchain { rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean - ): (BigInt, Seq[MptNode]) + ): StorageProof /** * Returns the receipts based on a block hash @@ -313,16 +313,21 @@ class BlockchainImpl( rootHash: ByteString, position: BigInt, ethCompatibleStorage: Boolean - ): (BigInt, Seq[MptNode]) = { - val defaultValue = BigInt(0) + ): StorageProof = { val storage: MptStorage = stateStorage.getBackingStorage(0) val mpt: MerklePatriciaTrie[BigInt, BigInt] = { if (ethCompatibleStorage) domain.EthereumUInt256Mpt.storageMpt(rootHash, storage) else domain.ArbitraryIntegerMpt.storageMpt(rootHash, storage) } - val value = mpt.get(position).getOrElse(defaultValue) - val proof = mpt.getProof(position).getOrElse(Seq.empty) - (value, proof) + val value = mpt.get(position) + val proof = mpt.getProof(position) + + (value, proof) match { + case (Some(value), Some(proof)) => StorageValueProof(position, value, proof) + case (None, Some(proof)) => StorageValueProof(position, proof = proof) + case (Some(value), None) => StorageValueProof(position, value) + case (None, None) => StorageValueProof(position) + } } private def persistBestBlocksData(): Unit = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala index 383609fbc3..44fdd5dac4 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala @@ -4,13 +4,7 @@ import akka.util.ByteString import cats.implicits._ import io.iohk.ethereum.consensus.blocks.BlockGenerator import io.iohk.ethereum.domain.{Account, Address, Block, Blockchain, UInt256} -import io.iohk.ethereum.jsonrpc.ProofService.{ - GetProofRequest, - GetProofResponse, - ProofAccount, - StorageProof, - StorageProofKey -} +import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, GetProofResponse, ProofAccount, StorageProof, StorageProofKey, StorageValueProof} import io.iohk.ethereum.mpt.{MptNode, MptTraversals} import monix.eval.Task @@ -30,9 +24,11 @@ object ProofService { case class GetProofResponse(proofAccount: ProofAccount) - /** The key used to get the storage slot in its account tree */ - case class StorageProofKey(v: BigInt) extends AnyVal - + trait StorageProof { + val key: StorageProofKey + val value: BigInt + val proof: Seq[ByteString] + } /** * Object proving a relationship of a storage value to an account's storageHash * @@ -40,11 +36,21 @@ object ProofService { * @param value the value of the storage slot in its account tree * @param proof the set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value */ - case class StorageProof( + case class StorageValueProof( key: StorageProofKey, value: BigInt, - proof: Seq[ByteString] - ) + proof: Seq[ByteString]) extends StorageProof + + object StorageValueProof { + def apply(key: BigInt, value: BigInt = BigInt(0), proof: => Seq[MptNode] = Seq.empty[MptNode]): StorageValueProof = + new StorageValueProof(StorageProofKey(key), value, proof.map(asRlpSerializedNode)) + + def asRlpSerializedNode(node: MptNode): ByteString = + ByteString(MptTraversals.encodeNode(node)) + } + + /** The key used to get the storage slot in its account tree */ + case class StorageProofKey(v: BigInt) extends AnyVal /** * The merkle proofs of the specified account connecting them to the blockhash of the block specified. @@ -143,7 +149,7 @@ class EthProofService(blockchain: Blockchain, blockGenerator: BlockGenerator, et blockchain.getAccountProof(address, blockNumber).map(_.map(asRlpSerializedNode)), noAccountProof(address, blockNumber) ) - storageProof <- Either.right(getStorageProof(account, storageKeys)) + storageProof = getStorageProof(account, storageKeys) } yield ProofAccount(account, accountProof, storageProof, address) } @@ -158,9 +164,7 @@ class EthProofService(blockchain: Blockchain, blockGenerator: BlockGenerator, et rootHash = account.storageRoot, position = storageKey.v, ethCompatibleStorage = ethCompatibleStorage - ) match { - case (value, proof) => StorageProof(storageKey, value, proof.map(asRlpSerializedNode)) - } + ) } } diff --git a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala index d8f94daf91..ea1f809894 100644 --- a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala +++ b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala @@ -165,6 +165,24 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh } } + it should "return proof for non-existent account" in new EphemBlockchainTestSetup { + val emptyMpt = MerklePatriciaTrie[Address, Account]( + storagesInstance.storages.stateStorage.getBackingStorage(0) + ) + val mptWithAcc = emptyMpt.put(Address(42), Account.empty(UInt256(7))) + + val headerWithAcc = Fixtures.Blocks.ValidBlock.header.copy(stateRoot = ByteString(mptWithAcc.getRootHash)) + + blockchain.storeBlockHeader(headerWithAcc).commit() + + val wrongAddress = Address(666) + val retrievedAccountProofWrong = blockchain.getAccountProof(wrongAddress, headerWithAcc.number) + //the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes that we iterated + retrievedAccountProofWrong.isDefined shouldBe true + retrievedAccountProofWrong.size shouldBe 1 + mptWithAcc.get(wrongAddress) shouldBe None + } + it should "return correct best block number after applying and rollbacking blocks" in new TestSetup { forAll(intGen(min = 1: Int, max = maxNumberBlocksToImport)) { numberBlocksToImport => val testSetup = newSetup() diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala index ddb27517dc..1bb0f84e25 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/JsonRpcControllerEthSpec.scala @@ -18,7 +18,7 @@ import io.iohk.ethereum.jsonrpc.ProofService.{ GetProofRequest, GetProofResponse, ProofAccount, - StorageProof, + StorageValueProof, StorageProofKey } import io.iohk.ethereum.jsonrpc.serialization.JsonSerializers.{ @@ -835,7 +835,7 @@ class JsonRpcControllerEthSpec nonce = 0, storageHash = ByteString(Hex.decode("1a2b3c")), storageProof = Seq( - StorageProof( + StorageValueProof( key = StorageProofKey(42), value = BigInt(2000), proof = Seq( diff --git a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala index 5132a665ff..f115af2015 100644 --- a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala +++ b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala @@ -573,6 +573,7 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks val proof: Option[Vector[MptNode]] = trie.getProof(key4) // then assert(proof.isDefined) + assert(proof.get.nonEmpty) } test("getProof returns valid proof for existing key") { From 465b7216e58f75c70610bf5cfe3a140670dfcf7d Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Mon, 25 Jan 2021 16:30:51 +0100 Subject: [PATCH 06/10] [ETCM-533] return root nodeas part of the proof, add tests --- .../ethereum/mpt/MerklePatriciaTrie.scala | 4 +- .../io/iohk/ethereum/ObjectGenerators.scala | 5 ++- .../mpt/MerklePatriciaTrieSuite.scala | 41 ++++++++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala index 3a4d3aeb14..9dba0d9aff 100644 --- a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala +++ b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala @@ -105,7 +105,7 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod def getProof(key: K): Option[Vector[MptNode]] = { pathTraverse[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => node match { - case Some(nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode)) => acc :+ nextNodeOnExt + case Some(nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode | _: HashNode)) => acc :+ nextNodeOnExt case _ => acc } } @@ -155,7 +155,7 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod rootNode match { case Some(root) => - pathTraverse(acc, root, searchKey, op) + pathTraverse(op(acc, Some(root)), root, searchKey, op) case None => None } diff --git a/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala b/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala index 2c993c648a..652bb412a6 100644 --- a/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala +++ b/src/test/scala/io/iohk/ethereum/ObjectGenerators.scala @@ -50,9 +50,10 @@ trait ObjectGenerators { } yield (aByteList.toArray, t) } - def keyValueListGen(): Gen[List[(Int, Int)]] = { + def keyValueListGen(minValue: Int = Int.MinValue, maxValue: Int = Int.MaxValue): Gen[List[(Int, Int)]] = { for { - aKeyList <- Gen.nonEmptyListOf(Arbitrary.arbitrary[Int]).map(_.distinct) + values <- Gen.chooseNum(minValue, maxValue) + aKeyList <- Gen.nonEmptyListOf(values).map(_.distinct) } yield aKeyList.zip(aKeyList) } diff --git a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala index f115af2015..0e1ed47197 100644 --- a/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala +++ b/src/test/scala/io/iohk/ethereum/mpt/MerklePatriciaTrieSuite.scala @@ -554,7 +554,40 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks assert(proof.isEmpty) } - test("getProof returns proof result for non-existing address") { + test("PatriciaTrie can get proof(at least the root node) for all inserted key-value pairs") { + forAll(keyValueListGen()) { keyValueList: Seq[(Int, Int)] => + val trie = addEveryKeyValuePair(keyValueList) + assertCanGetProofForEveryKeyValue(trie, keyValueList) + } + } + + test("PatriciaTrie return root as proof when no common nibbles are found between MPT root hash and search key") { + forAll(keyValueListGen(1, 10)) { keyValueList: Seq[(Int, Int)] => + val trie = addEveryKeyValuePair(keyValueList) + val wrongKey = 22 + val proof = trie.getProof(wrongKey) + assert(proof.getOrElse(Vector.empty).toList match { + case _ @ HashNode(_) :: Nil => true + case _ => false + }) + } + } + + test("PatriciaTrie return proof when having all nibbles in common except the last one between MPT root hash and search key") { + + val key = 1111 + val wrongKey = 1112 + val emptyTrie = MerklePatriciaTrie[Int, Int](emptyEphemNodeStorage) + .put(key, 1) + .put(wrongKey, 2) + val proof = emptyTrie.getProof(key = wrongKey) + assert(proof.getOrElse(Vector.empty).toList match { + case _ @ HashNode(_) :: tail => tail.nonEmpty + case _ => false + }) + } + + test("getProof returns proof result for non-existing key") { // given val EmptyTrie = MerklePatriciaTrie[Array[Byte], Array[Byte]](emptyEphemNodeStorage) val key1: Array[Byte] = Hex.decode("10000001") @@ -611,6 +644,12 @@ class MerklePatriciaTrieSuite extends AnyFunSuite with ScalaCheckPropertyChecks assert(obtained.get == value) } + private def assertCanGetProofForEveryKeyValue[K, V](trie: MerklePatriciaTrie[K, V], kvs: Seq[(K, V)]): Unit = + kvs.foreach { case (key, _) => + val obtained = trie.getProof(key) + assert(obtained.getOrElse(Vector.empty).nonEmpty) + } + private def assertCanGetEveryKeyValues[K, V](trie: MerklePatriciaTrie[K, Array[V]], kvs: List[(K, Array[V])]): Unit = kvs.foreach { case (key, value) => val obtained = trie.get(key) From 3364789e66a3f973bfcda3676c27a3f384c9f609 Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Mon, 25 Jan 2021 19:23:00 +0100 Subject: [PATCH 07/10] [ETCM-533] refactoring based on review notes --- .../io/iohk/ethereum/domain/Blockchain.scala | 12 +++--------- .../ethereum/jsonrpc/EthProofService.scala | 19 ++++++++++++++----- .../ethereum/mpt/MerklePatriciaTrie.scala | 14 ++++++-------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala index ea6f931321..53b81a32ba 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala @@ -319,15 +319,9 @@ class BlockchainImpl( if (ethCompatibleStorage) domain.EthereumUInt256Mpt.storageMpt(rootHash, storage) else domain.ArbitraryIntegerMpt.storageMpt(rootHash, storage) } - val value = mpt.get(position) - val proof = mpt.getProof(position) - - (value, proof) match { - case (Some(value), Some(proof)) => StorageValueProof(position, value, proof) - case (None, Some(proof)) => StorageValueProof(position, proof = proof) - case (Some(value), None) => StorageValueProof(position, value) - case (None, None) => StorageValueProof(position) - } + val value: Option[BigInt] = mpt.get(position) + val proof: Option[Vector[MptNode]] = mpt.getProof(position) + StorageValueProof(position, value, proof) } private def persistBestBlocksData(): Unit = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala index 44fdd5dac4..f1eb87abda 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala @@ -4,6 +4,7 @@ import akka.util.ByteString import cats.implicits._ import io.iohk.ethereum.consensus.blocks.BlockGenerator import io.iohk.ethereum.domain.{Account, Address, Block, Blockchain, UInt256} +import io.iohk.ethereum.jsonrpc.ProofService.StorageValueProof.asRlpSerializedNode import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, GetProofResponse, ProofAccount, StorageProof, StorageProofKey, StorageValueProof} import io.iohk.ethereum.mpt.{MptNode, MptTraversals} import monix.eval.Task @@ -24,7 +25,7 @@ object ProofService { case class GetProofResponse(proofAccount: ProofAccount) - trait StorageProof { + sealed trait StorageProof { val key: StorageProofKey val value: BigInt val proof: Seq[ByteString] @@ -38,12 +39,20 @@ object ProofService { */ case class StorageValueProof( key: StorageProofKey, - value: BigInt, - proof: Seq[ByteString]) extends StorageProof + value: BigInt = BigInt(0), + proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode)) extends StorageProof object StorageValueProof { - def apply(key: BigInt, value: BigInt = BigInt(0), proof: => Seq[MptNode] = Seq.empty[MptNode]): StorageValueProof = - new StorageValueProof(StorageProofKey(key), value, proof.map(asRlpSerializedNode)) + def apply(position: BigInt, value: Option[BigInt], proof: Option[Vector[MptNode]]): StorageValueProof = + (value, proof) match { + case (Some(value), Some(proof)) => new StorageValueProof(key = StorageProofKey(position), value = value, proof = proof.map(asRlpSerializedNode)) + case (None, Some(proof)) => new StorageValueProof(key = StorageProofKey(position), proof = proof.map(asRlpSerializedNode)) + case (Some(value), None) => new StorageValueProof(key = StorageProofKey(position), value = value) + case (None, None) => new StorageValueProof(key = StorageProofKey(position)) + } + + def apply(position: BigInt): StorageValueProof = + new StorageValueProof(key = StorageProofKey(position)) def asRlpSerializedNode(node: MptNode): ByteString = ByteString(MptTraversals.encodeNode(node)) diff --git a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala index 9dba0d9aff..0da42cf8ed 100644 --- a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala +++ b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala @@ -82,16 +82,14 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod * @throws io.iohk.ethereum.mpt.MerklePatriciaTrie.MPTException if there is any inconsistency in how the trie is build. */ def get(key: K): Option[V] = { - pathTraverse[Option[V]](None, mkKeyNibbles(key)) { case (_, node) => - node match { - case Some(LeafNode(_, value, _, _, _)) => - Some(vSerializer.fromBytes(value.toArray[Byte])) + pathTraverse[Option[V]](None, mkKeyNibbles(key)) { + case (_, Some(LeafNode(_, value, _, _, _))) => + Some(vSerializer.fromBytes(value.toArray[Byte])) - case Some(BranchNode(_, terminator, _, _, _)) => - terminator.map(term => vSerializer.fromBytes(term.toArray[Byte])) + case (_, Some(BranchNode(_, terminator, _, _, _))) => + terminator.map(term => vSerializer.fromBytes(term.toArray[Byte])) - case _ => None - } + case _ => None }.flatten } From 7620a472ba995f24afbd72a52e66989c63919a5b Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Mon, 25 Jan 2021 20:22:11 +0100 Subject: [PATCH 08/10] [ETCM-533] refactoring EthProofServiceSpec --- .../jsonrpc/EthProofServiceSpec.scala | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala index 812afcaf93..af1d07be13 100644 --- a/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala +++ b/src/test/scala/io/iohk/ethereum/jsonrpc/EthProofServiceSpec.scala @@ -79,19 +79,14 @@ class EthProofServiceSpec "EthProofService" should "return an error when the proof is requested for non-existing account" in new TestSetup { val wrongAddress = Address(666) - val request = GetProofRequest(wrongAddress, storageKeys, blockNumber) - val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - val result = retrievedAccountProofWrong.runSyncUnsafe() - + val result = fetchProof(wrongAddress, storageKeys, blockNumber).runSyncUnsafe() result.isLeft shouldBe true result.fold(l => l.message should include("No account found for Address"), r => r) } "EthProofService" should "return the proof with empty value for non-existing storage key" in new TestSetup { val wrongStorageKey = Seq(StorageProofKey(321)) - val request = GetProofRequest(address, wrongStorageKey, blockNumber) - val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - val result = retrievedAccountProofWrong.runSyncUnsafe() + val result = fetchProof(address, wrongStorageKey, blockNumber).runSyncUnsafe() result.isRight shouldBe true result.fold(l => l, r => r.proofAccount.storageProof.map(v => { v.proof.nonEmpty shouldBe true @@ -101,9 +96,7 @@ class EthProofServiceSpec "EthProofService" should "return the proof and value for existing storage key" in new TestSetup { val storageKey = Seq(StorageProofKey(key)) - val request = GetProofRequest(address, storageKey, blockNumber) - val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - val result = retrievedAccountProofWrong.runSyncUnsafe() + val result = fetchProof(address, storageKey, blockNumber).runSyncUnsafe() result.isRight shouldBe true result.fold(l => l, r => r.proofAccount.storageProof.map(v => { v.proof.nonEmpty shouldBe true @@ -114,9 +107,7 @@ class EthProofServiceSpec "EthProofService" should "return the proof and value for multiple existing storage keys" in new TestSetup { val storageKey = Seq(StorageProofKey(key), StorageProofKey(key2)) val expectedValueStorageKey = Seq(BigInt(value), BigInt(value2)) - val request = GetProofRequest(address, storageKey, blockNumber) - val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - val result = retrievedAccountProofWrong.runSyncUnsafe() + val result = fetchProof(address, storageKey, blockNumber).runSyncUnsafe() result.isRight shouldBe true result.fold(l => l, r => { r.proofAccount.storageProof.size shouldBe 2 @@ -131,9 +122,7 @@ class EthProofServiceSpec val wrongStorageKey = StorageProofKey(321) val storageKey = Seq(StorageProofKey(key), StorageProofKey(key2)) :+ wrongStorageKey val expectedValueStorageKey = Seq(BigInt(value), BigInt(value2), BigInt(0)) - val request = GetProofRequest(address, storageKey, blockNumber) - val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) - val result = retrievedAccountProofWrong.runSyncUnsafe() + val result = fetchProof(address, storageKey, blockNumber).runSyncUnsafe() result.isRight shouldBe true result.fold(l => l, r => { r.proofAccount.storageProof.size shouldBe 3 @@ -188,6 +177,12 @@ class EthProofServiceSpec override lazy val ledger = mock[Ledger] + def fetchProof(address: Address, storageKeys: Seq[StorageProofKey], blockNumber: BlockParam): ServiceResponse[ProofService.GetProofResponse] = { + val request = GetProofRequest(address, storageKeys, blockNumber) + val retrievedAccountProofWrong: ServiceResponse[ProofService.GetProofResponse] = ethGetProof.getProof(request) + retrievedAccountProofWrong + } + val ethUserService = new EthUserService( blockchain, ledger, From 4ba7d45a2804f370fc29ac4a43db37be31207dc8 Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Tue, 26 Jan 2021 09:28:35 +0100 Subject: [PATCH 09/10] [ETCM-533] scalafmt --- .../ethereum/jsonrpc/EthProofService.scala | 19 +++++++++++++++---- .../ethereum/mpt/MerklePatriciaTrie.scala | 4 ++-- .../iohk/ethereum/domain/BlockchainSpec.scala | 10 ++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala index f1eb87abda..64bba036e2 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala @@ -5,7 +5,14 @@ import cats.implicits._ import io.iohk.ethereum.consensus.blocks.BlockGenerator import io.iohk.ethereum.domain.{Account, Address, Block, Blockchain, UInt256} import io.iohk.ethereum.jsonrpc.ProofService.StorageValueProof.asRlpSerializedNode -import io.iohk.ethereum.jsonrpc.ProofService.{GetProofRequest, GetProofResponse, ProofAccount, StorageProof, StorageProofKey, StorageValueProof} +import io.iohk.ethereum.jsonrpc.ProofService.{ + GetProofRequest, + GetProofResponse, + ProofAccount, + StorageProof, + StorageProofKey, + StorageValueProof +} import io.iohk.ethereum.mpt.{MptNode, MptTraversals} import monix.eval.Task @@ -30,6 +37,7 @@ object ProofService { val value: BigInt val proof: Seq[ByteString] } + /** * Object proving a relationship of a storage value to an account's storageHash * @@ -40,13 +48,16 @@ object ProofService { case class StorageValueProof( key: StorageProofKey, value: BigInt = BigInt(0), - proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode)) extends StorageProof + proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode) + ) extends StorageProof object StorageValueProof { def apply(position: BigInt, value: Option[BigInt], proof: Option[Vector[MptNode]]): StorageValueProof = (value, proof) match { - case (Some(value), Some(proof)) => new StorageValueProof(key = StorageProofKey(position), value = value, proof = proof.map(asRlpSerializedNode)) - case (None, Some(proof)) => new StorageValueProof(key = StorageProofKey(position), proof = proof.map(asRlpSerializedNode)) + case (Some(value), Some(proof)) => + new StorageValueProof(key = StorageProofKey(position), value = value, proof = proof.map(asRlpSerializedNode)) + case (None, Some(proof)) => + new StorageValueProof(key = StorageProofKey(position), proof = proof.map(asRlpSerializedNode)) case (Some(value), None) => new StorageValueProof(key = StorageProofKey(position), value = value) case (None, None) => new StorageValueProof(key = StorageProofKey(position)) } diff --git a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala index 0da42cf8ed..33783d0fef 100644 --- a/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala +++ b/src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala @@ -9,7 +9,6 @@ import io.iohk.ethereum.rlp.RLPImplicits._ import io.iohk.ethereum.rlp.{encode => encodeRLP} import org.bouncycastle.util.encoders.Hex import io.iohk.ethereum.utils.ByteUtils.matchingLength - import scala.annotation.tailrec object MerklePatriciaTrie { @@ -103,7 +102,8 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod def getProof(key: K): Option[Vector[MptNode]] = { pathTraverse[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) => node match { - case Some(nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode | _: HashNode)) => acc :+ nextNodeOnExt + case Some(nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode | _: HashNode)) => + acc :+ nextNodeOnExt case _ => acc } } diff --git a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala index ea1f809894..dd9e4e4392 100644 --- a/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala +++ b/src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala @@ -6,7 +6,7 @@ import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator import io.iohk.ethereum.db.dataSource.EphemDataSource import io.iohk.ethereum.db.storage.StateStorage import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefPostEcip1097 -import io.iohk.ethereum.mpt.MerklePatriciaTrie +import io.iohk.ethereum.mpt.{HashNode, MerklePatriciaTrie} import io.iohk.ethereum.{BlockHelpers, Fixtures, ObjectGenerators} import io.iohk.ethereum.ObjectGenerators._ import io.iohk.ethereum.proof.MptProofVerifier @@ -177,9 +177,11 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh val wrongAddress = Address(666) val retrievedAccountProofWrong = blockchain.getAccountProof(wrongAddress, headerWithAcc.number) - //the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes that we iterated - retrievedAccountProofWrong.isDefined shouldBe true - retrievedAccountProofWrong.size shouldBe 1 + //the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes(root node) that we iterated + (retrievedAccountProofWrong.getOrElse(Vector.empty).toList match { + case _ @HashNode(_) :: Nil => true + case _ => false + }) shouldBe true mptWithAcc.get(wrongAddress) shouldBe None } From 72b6a873840ff311e706ad902a2403c2e44d733b Mon Sep 17 00:00:00 2001 From: Bogdan Suierica Date: Tue, 26 Jan 2021 12:55:22 +0100 Subject: [PATCH 10/10] [ETCM-533] refactor StorageProof ADT --- .../txExecTest/util/DumpChainApp.scala | 4 +- .../io/iohk/ethereum/domain/Blockchain.scala | 4 +- .../ethereum/jsonrpc/EthProofService.scala | 55 ++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala index 11e9afe0a6..5da2f865b4 100644 --- a/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala +++ b/src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala @@ -14,7 +14,7 @@ import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, PruningMode} import io.iohk.ethereum.db.storage.{AppStateStorage, StateStorage} import io.iohk.ethereum.domain.BlockHeader.HeaderExtraFields.HefEmpty import io.iohk.ethereum.domain.{Blockchain, UInt256, _} -import io.iohk.ethereum.jsonrpc.ProofService.{StorageProof, StorageValueProof} +import io.iohk.ethereum.jsonrpc.ProofService.{EmptyStorageValueProof, StorageProof, StorageProofKey, StorageValueProof} import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage} import io.iohk.ethereum.mpt.MptNode import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo @@ -150,7 +150,7 @@ class BlockchainMock(genesisHash: ByteString) extends Blockchain { rootHash: NodeHash, position: BigInt, ethCompatibleStorage: Boolean - ): StorageProof = StorageValueProof(position) + ): StorageProof = EmptyStorageValueProof(StorageProofKey(position)) override protected def getHashByBlockNumber(number: BigInt): Option[ByteString] = Some(genesisHash) diff --git a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala index 53b81a32ba..9ba1d6da5a 100644 --- a/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala +++ b/src/main/scala/io/iohk/ethereum/domain/Blockchain.scala @@ -12,7 +12,7 @@ import io.iohk.ethereum.db.storage._ import io.iohk.ethereum.db.storage.pruning.PruningMode import io.iohk.ethereum.domain import io.iohk.ethereum.domain.BlockchainImpl.BestBlockLatestCheckpointNumbers -import io.iohk.ethereum.jsonrpc.ProofService.{StorageProof, StorageValueProof} +import io.iohk.ethereum.jsonrpc.ProofService.StorageProof import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage} import io.iohk.ethereum.mpt.{MerklePatriciaTrie, MptNode} import io.iohk.ethereum.utils.{ByteStringUtils, Logger} @@ -321,7 +321,7 @@ class BlockchainImpl( } val value: Option[BigInt] = mpt.get(position) val proof: Option[Vector[MptNode]] = mpt.getProof(position) - StorageValueProof(position, value, proof) + StorageProof(position, value, proof) } private def persistBestBlocksData(): Unit = { diff --git a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala index 64bba036e2..018a0eb592 100644 --- a/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala +++ b/src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala @@ -4,7 +4,7 @@ import akka.util.ByteString import cats.implicits._ import io.iohk.ethereum.consensus.blocks.BlockGenerator import io.iohk.ethereum.domain.{Account, Address, Block, Blockchain, UInt256} -import io.iohk.ethereum.jsonrpc.ProofService.StorageValueProof.asRlpSerializedNode +import io.iohk.ethereum.jsonrpc.ProofService.StorageProof.asRlpSerializedNode import io.iohk.ethereum.jsonrpc.ProofService.{ GetProofRequest, GetProofResponse, @@ -33,42 +33,45 @@ object ProofService { case class GetProofResponse(proofAccount: ProofAccount) sealed trait StorageProof { - val key: StorageProofKey - val value: BigInt - val proof: Seq[ByteString] + def key: StorageProofKey + def value: BigInt + def proof: Seq[ByteString] } - /** - * Object proving a relationship of a storage value to an account's storageHash - * - * @param key storage proof key - * @param value the value of the storage slot in its account tree - * @param proof the set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value - */ - case class StorageValueProof( - key: StorageProofKey, - value: BigInt = BigInt(0), - proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode) - ) extends StorageProof - - object StorageValueProof { - def apply(position: BigInt, value: Option[BigInt], proof: Option[Vector[MptNode]]): StorageValueProof = + object StorageProof { + def apply(position: BigInt, value: Option[BigInt], proof: Option[Vector[MptNode]]): StorageProof = (value, proof) match { case (Some(value), Some(proof)) => - new StorageValueProof(key = StorageProofKey(position), value = value, proof = proof.map(asRlpSerializedNode)) + StorageValueProof(StorageProofKey(position), value, proof.map(asRlpSerializedNode)) case (None, Some(proof)) => - new StorageValueProof(key = StorageProofKey(position), proof = proof.map(asRlpSerializedNode)) - case (Some(value), None) => new StorageValueProof(key = StorageProofKey(position), value = value) - case (None, None) => new StorageValueProof(key = StorageProofKey(position)) + EmptyStorageValue(StorageProofKey(position), proof.map(asRlpSerializedNode)) + case (Some(value), None) => EmptyStorageProof(StorageProofKey(position), value) + case (None, None) => EmptyStorageValueProof(StorageProofKey(position)) } - def apply(position: BigInt): StorageValueProof = - new StorageValueProof(key = StorageProofKey(position)) - def asRlpSerializedNode(node: MptNode): ByteString = ByteString(MptTraversals.encodeNode(node)) } + /** + * Object proving a relationship of a storage value to an account's storageHash + * + * @param key storage proof key + * @param value the value of the storage slot in its account tree + * @param proof the set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value + */ + case class EmptyStorageValueProof(key: StorageProofKey) extends StorageProof { + val value: BigInt = BigInt(0) + val proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode) + } + case class EmptyStorageValue(key: StorageProofKey, proof: Seq[ByteString]) extends StorageProof { + val value: BigInt = BigInt(0) + } + case class EmptyStorageProof(key: StorageProofKey, value: BigInt) extends StorageProof { + val proof: Seq[ByteString] = Seq.empty[MptNode].map(asRlpSerializedNode) + } + case class StorageValueProof(key: StorageProofKey, value: BigInt, proof: Seq[ByteString]) extends StorageProof + /** The key used to get the storage slot in its account tree */ case class StorageProofKey(v: BigInt) extends AnyVal