import stew/[byteutils, endians2], nimcrypto/[keccak, hash], eth/[common, rlp], eth/trie/[trie_defs, nibbles, db], faststreams/output_stream, ./witness_types, ../nimbus/constants, ../nimbus/db/storage_types, ./multi_keys type DB = TrieDatabaseRef WitnessBuilder* = object db*: DB root: KeccakHash output: OutputStream flags: WitnessFlags StackElem = object node: seq[byte] parentGroup: Group keys: MultikeysRef depth: int storageMode: bool proc initWitnessBuilder*(db: DB, rootHash: KeccakHash, flags: WitnessFlags = {}): WitnessBuilder = result.db = db result.root = rootHash result.output = memoryOutput().s result.flags = flags 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) else: @(get(wb.db, elem.expectHash)) 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 proc writeU32(wb: var WitnessBuilder, x: uint32) = wb.output.append(toBytesBE(x)) proc writeNibbles(wb: var WitnessBuilder; n: NibblesSeq, withLen: bool = true) = # convert the NibblesSeq into left aligned byte seq # perhaps we can optimize it if the NibblesSeq already left aligned 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..= 2 and <= 16 wb.output.append(((mask shr 8) and 0xFF).byte) wb.output.append((mask and 0xFF).byte) when defined(debugDepth): wb.output.append(depth.byte) when defined(debugHash): wb.output.append(keccak(node).data) proc writeHashNode(wb: var WitnessBuilder, node: openArray[byte]) = # write type wb.output.append(HashNodeType.byte) wb.output.append(node) proc getBranchRecurseAux(wb: var WitnessBuilder, z: var StackElem) {.raises: [ContractCodeError, IOError, Defect, CatchableError, Exception].} proc writeAccountNode(wb: var WitnessBuilder, storageKeys: MultikeysRef, address: EthAddress, acc: Account, nibbles: NibblesSeq, node: openArray[byte], depth: int) {.raises: [ContractCodeError, IOError, Defect, CatchableError, Exception].} = # write type wb.output.append(AccountNodeType.byte) when defined(debugHash): wb.writeU32(node.len.uint32) wb.output.append(node) when defined(debugDepth): wb.output.append(depth.byte) doAssert(nibbles.len == 64 - depth) let accountType = if acc.codeHash == blankStringHash and acc.storageRoot == emptyRlpHash: SimpleAccountType else: ExtendedAccountType wb.output.append(accountType.byte) wb.writeNibbles(nibbles, false) wb.output.append(address) wb.output.append(acc.balance.toBytesBE) wb.output.append(acc.nonce.u256.toBytesBE) if accountType == ExtendedAccountType: if acc.codeHash != blankStringHash: let code = get(wb.db, contractHashKey(acc.codeHash).toOpenArray) if wfEIP170 in wb.flags and code.len > EIP170_CODE_SIZE_LIMIT: raise newException(ContractCodeError, "code len exceed EIP170 code size limit") wb.writeU32(code.len.uint32) wb.output.append(code) else: wb.writeU32(0'u32) if storageKeys.isNil: # we have storage but not touched by EVM wb.writeHashNode(acc.storageRoot.data) elif acc.storageRoot != emptyRlpHash: var zz = StackElem( node: wb.db.get(acc.storageRoot.data), parentGroup: storageKeys.initGroup(), keys: storageKeys, depth: 0, # reset depth storageMode: true # switch to storage mode ) getBranchRecurseAux(wb, zz) else: wb.writeHashNode(emptyRlpHash.data) #0x00 pathnibbles: address:
balance: nonce: #0x01 pathnibbles: address:
balance: nonce: bytecode: storage: proc writeAccountStorageLeafNode(wb: var WitnessBuilder, key: openArray[byte], val: UInt256, nibbles: NibblesSeq, node: openArray[byte], depth: int) = wb.output.append(StorageLeafNodeType.byte) when defined(debugHash): wb.writeU32(node.len.uint32) wb.output.append(node) when defined(debugDepth): wb.output.append(depth.byte) doAssert(nibbles.len == 64 - depth) wb.writeNibbles(nibbles, false) wb.output.append(key) wb.output.append(val.toBytesBE) # := pathnibbles: key: val: proc getBranchRecurseAux(wb: var WitnessBuilder, z: var StackElem) = if z.node.len == 0: return var nodeRlp = rlpFromBytes z.node case nodeRlp.listLen of 2: let (isLeaf, k) = nodeRlp.extensionNodeKey var match = false for mg in groups(z.keys, z.depth, k, z.parentGroup): if mg.match: doAssert(match == false) # should be only one match match = true let value = nodeRlp.listElem(1) if not isLeaf: # ExtensionNodeType writeExtensionNode(wb, k, z.depth, z.node) var zz = StackElem( node: value.getNode, parentGroup: mg.group, keys: z.keys, depth: z.depth + k.len, storageMode: z.storageMode ) getBranchRecurseAux(wb, zz) else: # this should be only one match # if there is more than one match # it means we encounter a rogue address for kd in keyDatas(z.keys, mg.group): if not match(kd, k, z.depth): continue # this is the rogue address kd.visited = true if z.storageMode: doAssert(kd.storageMode) writeAccountStorageLeafNode(wb, kd.storageSlot, value.toBytes.decode(UInt256), k, z.node, z.depth) else: doAssert(not kd.storageMode) writeAccountNode(wb, kd.storageKeys, kd.address, value.toBytes.decode(Account), k, z.node, z.depth) if not match: writeHashNode(wb, keccak(z.node).data) of 17: let branchMask = rlpListToBitmask(nodeRlp) writeBranchNode(wb, branchMask, z.depth, z.node) let path = groups(z.keys, z.parentGroup, z.depth) let notLeaf = z.depth != 63 # path.len == 0 for i in 0..<16: if branchMask.branchMaskBitIsSet(i): var branch = nodeRlp.listElem(i) if notLeaf and branchMaskBitIsSet(path.mask, i): var zz = StackElem( node: branch.getNode, parentGroup: path.groups[i], keys: z.keys, depth: z.depth + 1, storageMode: z.storageMode ) getBranchRecurseAux(wb, zz) else: if branch.isList: doAssert(false, "Short node should not exist in block witness") else: # this is a potential branch for multiproof writeHashNode(wb, branch.expectHash) # 17th elem should always empty doAssert branchMask.branchMaskBitIsSet(16) == false else: raise newException(CorruptedTrieDatabase, "HexaryTrie node with an unexpected number of children") proc buildWitness*(wb: var WitnessBuilder, keys: MultikeysRef, withVersion: bool = true): seq[byte] {.raises: [ContractCodeError, IOError, Defect, CatchableError, Exception].} = # witness version wb.output.append(BlockWitnessVersion.byte) # one or more trees # we only output one tree wb.output.append(MetadataNothing.byte) var z = StackElem( node: @(wb.db.get(wb.root.data)), parentGroup: keys.initGroup(), keys: keys, depth: 0, storageMode: false ) getBranchRecurseAux(wb, z) # result result = wb.output.getOutput(seq[byte])