Skip to content

Commit 5e5af20

Browse files
author
Piotr Paradziński
authored
[ETCM-468] add getProof to MPT (#857)
[ETCM-468] add MPT getProof
1 parent bed419f commit 5e5af20

File tree

4 files changed

+234
-128
lines changed

4 files changed

+234
-128
lines changed

src/main/scala/io/iohk/ethereum/db/storage/StateStorage.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,11 @@ object StateStorage {
170170
}
171171
}
172172

173-
def getReadOnlyStorage(source: EphemDataSource): MptStorage = {
174-
new SerializingMptStorage(new ArchiveNodeStorage(new NodeStorage(source)))
175-
}
173+
def getReadOnlyStorage(source: EphemDataSource): MptStorage =
174+
mptStorageFromNodeStorage(new NodeStorage(source))
175+
176+
def mptStorageFromNodeStorage(storage: NodeStorage): SerializingMptStorage =
177+
new SerializingMptStorage(new ArchiveNodeStorage(storage))
176178

177179
def createTestStateStorage(
178180
source: DataSource,

src/main/scala/io/iohk/ethereum/mpt/MerklePatriciaTrie.scala

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,102 @@ class MerklePatriciaTrie[K, V] private (private[mpt] val rootNode: Option[MptNod
7575
lazy val getRootHash: Array[Byte] = rootNode.map(_.hash).getOrElse(EmptyRootHash)
7676

7777
/**
78-
* This function obtains the value asociated with the key passed, if there exists one.
78+
* Get the value associated with the key passed, if there exists one.
7979
*
8080
* @param key
8181
* @return Option object with value if there exists one.
8282
* @throws io.iohk.ethereum.mpt.MerklePatriciaTrie.MPTException if there is any inconsistency in how the trie is build.
8383
*/
8484
def get(key: K): Option[V] = {
85-
rootNode flatMap { rootNode =>
86-
val keyNibbles = HexPrefix.bytesToNibbles(bytes = kSerializer.toBytes(key))
87-
get(rootNode, keyNibbles).map(bytes => vSerializer.fromBytes(bytes))
85+
pathTraverse[Option[V]](None, mkKeyNibbles(key)) { case (_, node) =>
86+
node match {
87+
case LeafNode(_, value, _, _, _) =>
88+
Some(vSerializer.fromBytes(value.toArray[Byte]))
89+
90+
case BranchNode(_, terminator, _, _, _) =>
91+
terminator.map(term => vSerializer.fromBytes(term.toArray[Byte]))
92+
93+
case _ => None
94+
}
95+
}.flatten
96+
}
97+
98+
/**
99+
* Get the proof associated with the key passed, if there exists one.
100+
*
101+
* @param key
102+
* @return Option object with proof if there exists one.
103+
* @throws io.iohk.ethereum.mpt.MerklePatriciaTrie.MPTException if there is any inconsistency in how the trie is build.
104+
*/
105+
def getProof(key: K): Option[Vector[MptNode]] = {
106+
pathTraverse[Vector[MptNode]](Vector.empty, mkKeyNibbles(key)) { case (acc, node) =>
107+
node match {
108+
case nextNodeOnExt @ (_: BranchNode | _: ExtensionNode | _: LeafNode) => acc :+ nextNodeOnExt
109+
case _ => acc
110+
}
88111
}
89112
}
90113

114+
/**
115+
* Traverse given path from the root to value and accumulate data.
116+
* Only nodes which are significant for searching for value are taken into account.
117+
*
118+
* @param acc initial accumulator
119+
* @param searchKey search key
120+
* @param op accumulating operation
121+
* @tparam T accumulator type
122+
* @return accumulated data or None if key doesn't exist
123+
*/
124+
private def pathTraverse[T](acc: T, searchKey: Array[Byte])(op: (T, MptNode) => T): Option[T] = {
125+
126+
@tailrec
127+
def pathTraverse(acc: T, node: MptNode, searchKey: Array[Byte], op: (T, MptNode) => T): Option[T] = {
128+
node match {
129+
case LeafNode(key, _, _, _, _) =>
130+
if (key.toArray[Byte] sameElements searchKey) Some(op(acc, node)) else None
131+
132+
case extNode @ ExtensionNode(sharedKey, _, _, _, _) =>
133+
val (commonKey, remainingKey) = searchKey.splitAt(sharedKey.length)
134+
if (searchKey.length >= sharedKey.length && (sharedKey.toArray[Byte] sameElements commonKey)) {
135+
pathTraverse(op(acc, node), extNode.next, remainingKey, op)
136+
} else None
137+
138+
case branch: BranchNode =>
139+
if (searchKey.isEmpty) Some(op(acc, node))
140+
else
141+
pathTraverse(
142+
op(acc, node),
143+
branch.children(searchKey(0)),
144+
searchKey.slice(1, searchKey.length),
145+
op
146+
)
147+
148+
case HashNode(bytes) =>
149+
pathTraverse(acc, getFromHash(bytes, nodeStorage), searchKey, op)
150+
151+
case NullNode =>
152+
None
153+
}
154+
}
155+
156+
rootNode match {
157+
case Some(root) =>
158+
pathTraverse(acc, root, searchKey, op)
159+
case None =>
160+
None
161+
}
162+
}
163+
164+
private def getFromHash(nodeId: Array[Byte], source: MptStorage): MptNode = {
165+
val nodeEncoded = source.get(nodeId).encode
166+
MptTraversals
167+
.decodeNode(nodeEncoded)
168+
.withCachedHash(nodeId)
169+
.withCachedRlpEncoded(nodeEncoded)
170+
}
171+
172+
private def mkKeyNibbles(key: K): Array[Byte] = HexPrefix.bytesToNibbles(kSerializer.toBytes(key))
173+
91174
/**
92175
* This function inserts a (key-value) pair into the trie. If the key is already asociated with another value it is updated.
93176
*

src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
2020
val checkpoint = ObjectGenerators.fakeCheckpointGen(2, 5).sample.get
2121
val checkpointBlockGenerator = new CheckpointBlockGenerator
2222

23-
"Blockchain" should "be able to store a block and return if if queried by hash" in new EphemBlockchainTestSetup {
23+
"Blockchain" should "be able to store a block and return it if queried by hash" in new EphemBlockchainTestSetup {
2424
val validBlock = Fixtures.Blocks.ValidBlock.block
2525
blockchain.storeBlock(validBlock).commit()
2626
val block = blockchain.getBlockByHash(validBlock.header.hash)

0 commit comments

Comments
 (0)