2020-04-21 10:20:39 +00:00
|
|
|
import
|
2020-04-22 04:21:39 +00:00
|
|
|
stew/[byteutils, endians2],
|
2020-04-28 02:30:10 +00:00
|
|
|
nimcrypto/[keccak, hash], eth/[common, rlp],
|
2020-04-21 10:20:39 +00:00
|
|
|
eth/trie/[trie_defs, nibbles, db],
|
2020-04-21 11:04:18 +00:00
|
|
|
faststreams/output_stream,
|
2020-04-29 05:12:24 +00:00
|
|
|
./witness_types, ../nimbus/constants,
|
|
|
|
../nimbus/db/storage_types
|
2020-04-21 10:20:39 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
DB = TrieDatabaseRef
|
|
|
|
|
|
|
|
WitnessBuilder* = object
|
|
|
|
db*: DB
|
|
|
|
root: KeccakHash
|
2020-04-22 04:21:39 +00:00
|
|
|
output: OutputStream
|
2020-04-21 10:20:39 +00:00
|
|
|
|
|
|
|
proc initWitnessBuilder*(db: DB, rootHash: KeccakHash = emptyRlpHash): WitnessBuilder =
|
|
|
|
result.db = db
|
|
|
|
result.root = rootHash
|
2020-04-22 04:21:39 +00:00
|
|
|
result.output = memoryOutput().s
|
2020-04-21 10:20:39 +00:00
|
|
|
|
|
|
|
template extensionNodeKey(r: Rlp): auto =
|
|
|
|
hexPrefixDecode r.listElem(0).toBytes
|
|
|
|
|
|
|
|
proc expectHash(r: Rlp): seq[byte] =
|
|
|
|
result = r.toBytes
|
|
|
|
if result.len != 32:
|
|
|
|
raise newException(RlpTypeMismatch,
|
|
|
|
"RLP expected to be a Keccak hash value, but has an incorrect length")
|
|
|
|
|
|
|
|
template getNode(elem: untyped): untyped =
|
|
|
|
if elem.isList: @(elem.rawData)
|
2020-04-22 11:04:19 +00:00
|
|
|
else: get(wb.db, elem.expectHash)
|
2020-04-21 10:20:39 +00:00
|
|
|
|
2020-04-22 04:21:39 +00:00
|
|
|
proc rlpListToBitmask(r: var Rlp): uint =
|
|
|
|
var i = 0
|
|
|
|
for branch in r:
|
|
|
|
if not branch.isEmpty:
|
|
|
|
result.setBranchMaskBit(i)
|
|
|
|
inc i
|
|
|
|
r.position = 0
|
|
|
|
|
2020-04-24 08:56:03 +00:00
|
|
|
proc writeU32(wb: var WitnessBuilder, x: uint32) =
|
|
|
|
wb.output.append(toBytesBE(x))
|
|
|
|
|
2020-04-28 02:30:10 +00:00
|
|
|
proc writeNibbles(wb: var WitnessBuilder; n: NibblesSeq, withLen: bool = true) =
|
2020-04-28 14:06:37 +00:00
|
|
|
# convert the NibblesSeq into left aligned byte seq
|
|
|
|
# perhaps we can optimize it if the NibblesSeq already left aligned
|
2020-04-22 04:21:39 +00:00
|
|
|
let nibblesLen = n.len
|
|
|
|
let numBytes = nibblesLen div 2 + nibblesLen mod 2
|
|
|
|
var bytes: array[32, byte]
|
|
|
|
doAssert(nibblesLen >= 1)
|
|
|
|
doAssert(numBytes >= 0 and numBytes <= 64)
|
|
|
|
for pos in 0..<n.len:
|
|
|
|
if (pos and 1) != 0:
|
|
|
|
bytes[pos div 2] = bytes[pos div 2] or n[pos]
|
|
|
|
else:
|
|
|
|
bytes[pos div 2] = bytes[pos div 2] or (n[pos] shl 4)
|
|
|
|
|
2020-04-28 02:30:10 +00:00
|
|
|
if withLen:
|
|
|
|
# write nibblesLen
|
|
|
|
wb.output.append(nibblesLen.byte)
|
2020-04-22 04:21:39 +00:00
|
|
|
# write nibbles
|
|
|
|
wb.output.append(bytes.toOpenArray(0, numBytes-1))
|
|
|
|
|
2020-04-22 11:04:19 +00:00
|
|
|
proc writeExtensionNode(wb: var WitnessBuilder, n: NibblesSeq, depth: int, node: openArray[byte]) =
|
2020-04-22 04:21:39 +00:00
|
|
|
# write type
|
|
|
|
wb.output.append(ExtensionNodeType.byte)
|
|
|
|
# write nibbles
|
|
|
|
wb.writeNibbles(n)
|
|
|
|
|
2020-04-22 11:04:19 +00:00
|
|
|
when defined(debugDepth):
|
|
|
|
wb.output.append(depth.byte)
|
|
|
|
|
|
|
|
when defined(debugHash):
|
|
|
|
wb.output.append(keccak(node).data)
|
|
|
|
|
|
|
|
proc writeBranchNode(wb: var WitnessBuilder, mask: uint, depth: int, node: openArray[byte]) =
|
2020-04-22 04:21:39 +00:00
|
|
|
# write type
|
2020-04-24 09:04:43 +00:00
|
|
|
# branch node 17th elem should always empty
|
|
|
|
doAssert mask.branchMaskBitIsSet(16) == false
|
|
|
|
wb.output.append(BranchNodeType.byte)
|
2020-04-22 04:21:39 +00:00
|
|
|
# write branch mask
|
2020-04-28 14:06:37 +00:00
|
|
|
# countOnes(branch mask) >= 2 and <= 16
|
2020-04-22 04:21:39 +00:00
|
|
|
wb.output.append(((mask shr 8) and 0xFF).byte)
|
|
|
|
wb.output.append((mask and 0xFF).byte)
|
|
|
|
|
2020-04-22 11:04:19 +00:00
|
|
|
when defined(debugDepth):
|
|
|
|
wb.output.append(depth.byte)
|
|
|
|
|
|
|
|
when defined(debugHash):
|
|
|
|
wb.output.append(keccak(node).data)
|
|
|
|
|
2020-04-28 07:27:55 +00:00
|
|
|
proc writeHashNode(wb: var WitnessBuilder, node: openArray[byte]) =
|
|
|
|
# write type
|
|
|
|
wb.output.append(HashNodeType.byte)
|
|
|
|
wb.output.append(node)
|
|
|
|
|
2020-04-28 14:06:37 +00:00
|
|
|
proc getBranchRecurseAux(wb: var WitnessBuilder, node: openArray[byte], path: NibblesSeq, depth: int, storageMode: bool)
|
|
|
|
|
2020-04-28 02:30:10 +00:00
|
|
|
proc writeAccountNode(wb: var WitnessBuilder, acc: Account, nibbles: NibblesSeq, node: openArray[byte], depth: int) =
|
2020-04-22 04:21:39 +00:00
|
|
|
# write type
|
|
|
|
wb.output.append(AccountNodeType.byte)
|
2020-04-28 09:58:16 +00:00
|
|
|
|
|
|
|
when defined(debugHash):
|
|
|
|
wb.writeU32(node.len.uint32)
|
|
|
|
wb.output.append(node)
|
2020-04-22 04:21:39 +00:00
|
|
|
|
2020-04-22 11:04:19 +00:00
|
|
|
when defined(debugDepth):
|
|
|
|
wb.output.append(depth.byte)
|
|
|
|
|
2020-04-28 02:30:10 +00:00
|
|
|
doAssert(nibbles.len == 64 - depth)
|
2020-04-28 07:27:55 +00:00
|
|
|
let accountType = if acc.codeHash == blankStringHash and acc.storageRoot == emptyRlpHash: SimpleAccountType
|
2020-04-28 02:30:10 +00:00
|
|
|
else: ExtendedAccountType
|
|
|
|
|
2020-04-28 07:27:55 +00:00
|
|
|
wb.output.append(accountType.byte)
|
|
|
|
wb.writeNibbles(nibbles, false)
|
2020-04-28 14:06:37 +00:00
|
|
|
# TODO: where the address come from?
|
|
|
|
# single proof is easy, but multiproof will be harder
|
|
|
|
# concat the path and then look into LUT?
|
|
|
|
# wb.output.append(acc.address)
|
2020-04-28 07:27:55 +00:00
|
|
|
wb.output.append(acc.balance.toBytesBE)
|
|
|
|
wb.output.append(acc.nonce.u256.toBytesBE)
|
|
|
|
|
|
|
|
if accountType == ExtendedAccountType:
|
|
|
|
if acc.codeHash != blankStringHash:
|
2020-04-29 05:12:24 +00:00
|
|
|
let code = get(wb.db, contractHashKey(acc.codeHash).toOpenArray)
|
2020-04-28 07:27:55 +00:00
|
|
|
if code.len > EIP170_CODE_SIZE_LIMIT:
|
|
|
|
raise newException(ValueError, "code len exceed EIP170 code size limit")
|
|
|
|
wb.writeU32(code.len.uint32)
|
|
|
|
wb.output.append(code)
|
|
|
|
else:
|
|
|
|
wb.writeU32(0'u32)
|
2020-04-28 02:30:10 +00:00
|
|
|
|
2020-04-28 07:27:55 +00:00
|
|
|
if acc.storageRoot != emptyRlpHash:
|
2020-04-28 14:06:37 +00:00
|
|
|
# switch to account mode
|
|
|
|
var node = wb.db.get(acc.storageRoot.data)
|
|
|
|
var key = keccak(0.u256.toByteArrayBE)
|
|
|
|
getBranchRecurseAux(wb, node, initNibbleRange(key.data), 0, true)
|
2020-04-28 07:27:55 +00:00
|
|
|
else:
|
|
|
|
wb.writeHashNode(emptyRlpHash.data)
|
2020-04-28 02:30:10 +00:00
|
|
|
|
|
|
|
#0x00 pathnibbles:<Nibbles(64-d)> address:<Address> balance:<Bytes32> nonce:<Bytes32>
|
|
|
|
#0x01 pathnibbles:<Nibbles(64-d)> address:<Address> balance:<Bytes32> nonce:<Bytes32> bytecode:<Bytecode> storage:<Tree_Node(0,1)>
|
|
|
|
|
2020-04-28 14:06:37 +00:00
|
|
|
proc writeAccountStorageLeafNode(wb: var WitnessBuilder, val: UInt256, nibbles: NibblesSeq, node: openArray[byte], depth: int) =
|
|
|
|
wb.output.append(StorageLeafNodeType.byte)
|
|
|
|
doAssert(nibbles.len == 64 - depth)
|
|
|
|
wb.writeNibbles(nibbles, false)
|
|
|
|
|
|
|
|
# TODO: write key
|
|
|
|
# wb.output.append(key.toByteArrayBE)
|
|
|
|
wb.output.append(val.toByteArrayBE)
|
|
|
|
|
|
|
|
#<Storage_Leaf_Node(d<65)> := pathnibbles:<Nibbles(64-d))> key:<Bytes32> val:<Bytes32>
|
|
|
|
|
|
|
|
proc writeShortNode(wb: var WitnessBuilder, node: openArray[byte], depth: int, storageMode: bool) =
|
2020-04-22 11:04:19 +00:00
|
|
|
var nodeRlp = rlpFromBytes node
|
|
|
|
if not nodeRlp.hasData or nodeRlp.isEmpty: return
|
|
|
|
case nodeRlp.listLen
|
|
|
|
of 2:
|
|
|
|
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
|
|
|
if isLeaf:
|
2020-04-28 14:06:37 +00:00
|
|
|
if storageMode:
|
|
|
|
let val = nodeRlp.listElem(1).toBytes.decode(UInt256)
|
|
|
|
writeAccountStorageLeafNode(wb, val, k, node, depth)
|
|
|
|
else:
|
|
|
|
let acc = nodeRlp.listElem(1).toBytes.decode(Account)
|
|
|
|
writeAccountNode(wb, acc, k, node, depth)
|
2020-04-22 11:04:19 +00:00
|
|
|
else:
|
2020-04-24 12:15:15 +00:00
|
|
|
# why this short extension node have no
|
|
|
|
# child and still valid when we reconstruct
|
|
|
|
# the trie on the other side?
|
|
|
|
# a bug in hexary trie algo?
|
|
|
|
# or a bug in nim hexary trie implementation?
|
2020-04-22 11:04:19 +00:00
|
|
|
writeExtensionNode(wb, k, depth, node)
|
|
|
|
of 17:
|
|
|
|
let branchMask = rlpListToBitmask(nodeRlp)
|
|
|
|
writeBranchNode(wb, branchMask, depth, node)
|
|
|
|
|
|
|
|
for i in 0..<16:
|
|
|
|
if branchMask.branchMaskBitIsSet(i):
|
|
|
|
var branch = nodeRlp.listElem(i)
|
|
|
|
let nextLookup = branch.getNode
|
2020-04-28 14:06:37 +00:00
|
|
|
writeShortNode(wb, nextLookup, depth + 1, storageMode)
|
2020-04-22 11:04:19 +00:00
|
|
|
|
2020-04-24 12:15:15 +00:00
|
|
|
# contrary to yellow paper spec,
|
|
|
|
# the 17th elem never exist in reality.
|
|
|
|
# block witness spec also omit it.
|
|
|
|
# probably a flaw in hexary trie design
|
2020-04-24 09:04:43 +00:00
|
|
|
# 17th elem should always empty
|
|
|
|
doAssert branchMask.branchMaskBitIsSet(16) == false
|
2020-04-22 11:04:19 +00:00
|
|
|
else:
|
|
|
|
raise newException(CorruptedTrieDatabase, "Bad Short Node")
|
|
|
|
|
2020-04-28 14:06:37 +00:00
|
|
|
proc getBranchRecurseAux(wb: var WitnessBuilder, node: openArray[byte], path: NibblesSeq, depth: int, storageMode: bool) =
|
2020-04-21 10:20:39 +00:00
|
|
|
var nodeRlp = rlpFromBytes node
|
|
|
|
if not nodeRlp.hasData or nodeRlp.isEmpty: return
|
|
|
|
|
|
|
|
case nodeRlp.listLen
|
|
|
|
of 2:
|
|
|
|
let (isLeaf, k) = nodeRlp.extensionNodeKey
|
|
|
|
let sharedNibbles = sharedPrefixLen(path, k)
|
|
|
|
if sharedNibbles == k.len:
|
|
|
|
let value = nodeRlp.listElem(1)
|
|
|
|
if not isLeaf:
|
2020-04-22 04:21:39 +00:00
|
|
|
# ExtensionNodeType
|
2020-04-22 11:04:19 +00:00
|
|
|
writeExtensionNode(wb, k, depth, node)
|
2020-04-21 10:20:39 +00:00
|
|
|
let nextLookup = value.getNode
|
2020-04-28 14:06:37 +00:00
|
|
|
getBranchRecurseAux(wb, nextLookup, path.slice(sharedNibbles), depth + sharedNibbles, storageMode)
|
2020-04-22 04:21:39 +00:00
|
|
|
else:
|
|
|
|
# AccountNodeType
|
2020-04-28 14:06:37 +00:00
|
|
|
if storageMode:
|
|
|
|
writeAccountStorageLeafNode(wb, value.toBytes.decode(UInt256), k, node, depth)
|
|
|
|
else:
|
|
|
|
writeAccountNode(wb, value.toBytes.decode(Account), k, node, depth)
|
2020-04-22 04:21:39 +00:00
|
|
|
else:
|
2020-04-24 12:15:15 +00:00
|
|
|
# this is a potential branch for multiproof
|
2020-04-22 04:21:39 +00:00
|
|
|
writeHashNode(wb, keccak(node).data)
|
2020-04-21 10:20:39 +00:00
|
|
|
of 17:
|
2020-04-22 04:21:39 +00:00
|
|
|
let branchMask = rlpListToBitmask(nodeRlp)
|
2020-04-22 11:04:19 +00:00
|
|
|
writeBranchNode(wb, branchMask, depth, node)
|
|
|
|
|
2020-04-22 04:28:16 +00:00
|
|
|
let notLeaf = path.len != 0
|
|
|
|
for i in 0..<16:
|
|
|
|
if branchMask.branchMaskBitIsSet(i):
|
|
|
|
var branch = nodeRlp.listElem(i)
|
|
|
|
if notLeaf and i == path[0].int:
|
|
|
|
let nextLookup = branch.getNode
|
2020-04-28 14:06:37 +00:00
|
|
|
getBranchRecurseAux(wb, nextLookup, path.slice(1), depth + 1, storageMode)
|
2020-04-22 04:28:16 +00:00
|
|
|
else:
|
2020-04-22 11:04:19 +00:00
|
|
|
if branch.isList:
|
|
|
|
let nextLookup = branch.getNode
|
2020-04-28 14:06:37 +00:00
|
|
|
writeShortNode(wb, nextLookup, depth + 1, storageMode)
|
2020-04-22 11:04:19 +00:00
|
|
|
else:
|
2020-04-24 12:15:15 +00:00
|
|
|
# this is a potential branch for multiproof
|
2020-04-22 11:04:19 +00:00
|
|
|
writeHashNode(wb, branch.expectHash)
|
2020-04-22 04:21:39 +00:00
|
|
|
|
2020-04-24 09:04:43 +00:00
|
|
|
# 17th elem should always empty
|
|
|
|
doAssert branchMask.branchMaskBitIsSet(16) == false
|
2020-04-21 10:20:39 +00:00
|
|
|
else:
|
|
|
|
raise newException(CorruptedTrieDatabase,
|
|
|
|
"HexaryTrie node with an unexpected number of children")
|
|
|
|
|
2020-04-29 04:12:15 +00:00
|
|
|
proc buildWitness*(wb: var WitnessBuilder; address: EthAddress): seq[byte] =
|
|
|
|
let key = keccak(address)
|
2020-04-22 04:21:39 +00:00
|
|
|
var node = wb.db.get(wb.root.data)
|
2020-04-29 04:12:15 +00:00
|
|
|
getBranchRecurseAux(wb, node, initNibbleRange(key.data), 0, false)
|
2020-04-24 11:55:58 +00:00
|
|
|
result = wb.output.getOutput(seq[byte])
|