2023-11-01 03:32:09 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2020-2023 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
|
|
# http://opensource.org/licenses/MIT)
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except
|
|
|
|
# according to those terms.
|
|
|
|
|
2020-05-04 11:52:45 +00:00
|
|
|
import
|
2022-12-02 04:39:12 +00:00
|
|
|
eth/common, eth/trie/nibbles, algorithm,
|
2020-05-04 11:52:45 +00:00
|
|
|
./witness_types
|
2020-05-03 01:11:04 +00:00
|
|
|
|
|
|
|
type
|
2020-05-05 06:50:31 +00:00
|
|
|
KeyHash* = array[32, byte]
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-05-05 06:50:31 +00:00
|
|
|
KeyData* = object
|
|
|
|
visited*: bool
|
|
|
|
hash*: KeyHash
|
|
|
|
case storageMode*: bool
|
2020-05-05 02:01:15 +00:00
|
|
|
of true:
|
2020-05-05 06:50:31 +00:00
|
|
|
storageSlot*: StorageSlot
|
2020-05-05 02:01:15 +00:00
|
|
|
of false:
|
2020-05-05 06:50:31 +00:00
|
|
|
storageKeys*: MultikeysRef
|
|
|
|
address*: EthAddress
|
2020-05-05 08:05:17 +00:00
|
|
|
codeTouched*: bool
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-05-04 11:52:45 +00:00
|
|
|
Multikeys* = object
|
2020-05-05 08:05:17 +00:00
|
|
|
keys*: seq[KeyData]
|
2020-05-05 02:01:15 +00:00
|
|
|
|
|
|
|
MultikeysRef* = ref Multikeys
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-05-04 11:52:45 +00:00
|
|
|
Group* = object
|
2020-05-05 06:50:31 +00:00
|
|
|
first*, last*: int16
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-05-04 11:52:45 +00:00
|
|
|
BranchGroup* = object
|
|
|
|
mask*: uint
|
|
|
|
groups*: array[16, Group]
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-07-08 16:17:48 +00:00
|
|
|
AccountKey* = object
|
|
|
|
address*: EthAddress
|
|
|
|
codeTouched*: bool
|
|
|
|
storageKeys*: MultikeysRef
|
|
|
|
|
|
|
|
MatchGroup* = object
|
|
|
|
match*: bool
|
|
|
|
group*: Group
|
2020-05-03 01:11:04 +00:00
|
|
|
|
2020-05-04 11:52:45 +00:00
|
|
|
func cmpHash(a, b: KeyHash): int =
|
2020-05-03 01:11:04 +00:00
|
|
|
var i = 0
|
|
|
|
var m = min(a.len, b.len)
|
|
|
|
while i < m:
|
|
|
|
result = a[i].int - b[i].int
|
|
|
|
if result != 0: return
|
|
|
|
inc(i)
|
|
|
|
result = a.len - b.len
|
|
|
|
|
2020-05-05 02:01:15 +00:00
|
|
|
func cmpHash(a, b: KeyData): int =
|
2020-05-04 11:52:45 +00:00
|
|
|
cmpHash(a.hash, b.hash)
|
|
|
|
|
|
|
|
func getNibble(x: openArray[byte], i: int): byte =
|
|
|
|
if(i and 0x01) == 0x01:
|
|
|
|
result = x[i shr 1] and 0x0F
|
|
|
|
else:
|
|
|
|
result = x[i shr 1] shr 4
|
|
|
|
|
|
|
|
func compareNibbles(x: openArray[byte], start: int, n: NibblesSeq): bool =
|
|
|
|
var i = 0
|
|
|
|
while i < n.len:
|
|
|
|
if getNibble(x, start + i) != n[i]:
|
|
|
|
return false
|
|
|
|
inc i
|
|
|
|
result = true
|
|
|
|
|
2020-05-05 02:01:15 +00:00
|
|
|
proc newMultiKeys*(keys: openArray[AccountKey]): MultikeysRef =
|
|
|
|
result = new Multikeys
|
|
|
|
result.keys = newSeq[KeyData](keys.len)
|
|
|
|
for i, a in keys:
|
|
|
|
result.keys[i] = KeyData(
|
|
|
|
storageMode: false,
|
2022-09-03 18:15:35 +00:00
|
|
|
hash: keccakHash(a.address).data,
|
2020-05-05 02:01:15 +00:00
|
|
|
address: a.address,
|
2020-05-05 08:05:17 +00:00
|
|
|
codeTouched: a.codeTouched,
|
2020-05-05 02:01:15 +00:00
|
|
|
storageKeys: a.storageKeys)
|
|
|
|
result.keys.sort(cmpHash)
|
|
|
|
|
|
|
|
proc newMultiKeys*(keys: openArray[StorageSlot]): MultikeysRef =
|
|
|
|
result = new Multikeys
|
|
|
|
result.keys = newSeq[KeyData](keys.len)
|
|
|
|
for i, a in keys:
|
2022-09-03 18:15:35 +00:00
|
|
|
result.keys[i] = KeyData(storageMode: true, hash: keccakHash(a).data, storageSlot: a)
|
2020-05-05 02:01:15 +00:00
|
|
|
result.keys.sort(cmpHash)
|
|
|
|
|
2020-06-03 13:50:13 +00:00
|
|
|
# never mix storageMode!
|
|
|
|
proc add*(m: MultikeysRef, address: EthAddress, codeTouched: bool, storageKeys = MultikeysRef(nil)) =
|
|
|
|
m.keys.add KeyData(
|
|
|
|
storageMode: false,
|
2022-09-03 18:15:35 +00:00
|
|
|
hash: keccakHash(address).data,
|
2020-06-03 13:50:13 +00:00
|
|
|
address: address,
|
|
|
|
codeTouched: codeTouched,
|
|
|
|
storageKeys: storageKeys)
|
|
|
|
|
|
|
|
proc add*(m: MultikeysRef, slot: StorageSlot) =
|
2022-09-03 18:15:35 +00:00
|
|
|
m.keys.add KeyData(storageMode: true, hash: keccakHash(slot).data, storageSlot: slot)
|
2020-06-03 13:50:13 +00:00
|
|
|
|
|
|
|
proc sort*(m: MultikeysRef) =
|
|
|
|
m.keys.sort(cmpHash)
|
|
|
|
|
2020-05-05 02:01:15 +00:00
|
|
|
func initGroup*(m: MultikeysRef): Group =
|
2020-05-05 06:50:31 +00:00
|
|
|
type T = type result.last
|
2020-05-08 03:18:41 +00:00
|
|
|
result = Group(first: 0.T, last: (m.keys.len - 1).T)
|
2020-05-05 02:01:15 +00:00
|
|
|
|
|
|
|
func groups*(m: MultikeysRef, parentGroup: Group, depth: int): BranchGroup =
|
2020-05-04 11:52:45 +00:00
|
|
|
# similar to a branch node, the product of this func
|
|
|
|
# is a 16 bits bitmask and an array of max 16 groups
|
|
|
|
# if the bit is set, the n-th elem of array have a group
|
|
|
|
# each group consist of at least one key
|
2020-05-08 03:18:41 +00:00
|
|
|
var g = Group(first: parentGroup.first)
|
2020-05-05 02:01:15 +00:00
|
|
|
var nibble = getNibble(m.keys[g.first].hash, depth)
|
|
|
|
for i in parentGroup.first..parentGroup.last:
|
|
|
|
let currNibble = getNibble(m.keys[i].hash, depth)
|
|
|
|
if currNibble != nibble:
|
2020-05-08 03:18:41 +00:00
|
|
|
# close current group and start a new group
|
2020-05-05 02:01:15 +00:00
|
|
|
g.last = i - 1
|
|
|
|
setBranchMaskBit(result.mask, nibble.int)
|
|
|
|
result.groups[nibble.int] = g
|
|
|
|
nibble = currNibble
|
|
|
|
g.first = i
|
2020-05-08 03:18:41 +00:00
|
|
|
|
|
|
|
# always close the last group
|
|
|
|
g.last = parentGroup.last
|
|
|
|
setBranchMaskBit(result.mask, nibble.int)
|
|
|
|
result.groups[nibble.int] = g
|
2020-05-04 11:52:45 +00:00
|
|
|
|
2020-05-07 14:36:51 +00:00
|
|
|
func groups*(m: MultikeysRef, depth: int, n: NibblesSeq, parentGroup: Group): MatchGroup =
|
2020-05-24 04:40:01 +00:00
|
|
|
# using common-prefix comparison, this func
|
2020-05-07 14:36:51 +00:00
|
|
|
# will produce one match group or no match at all
|
|
|
|
var g = Group(first: parentGroup.first)
|
|
|
|
|
|
|
|
if compareNibbles(m.keys[g.first].hash, depth, n):
|
|
|
|
var i = g.first + 1
|
|
|
|
while i <= parentGroup.last:
|
|
|
|
if not compareNibbles(m.keys[i].hash, depth, n):
|
|
|
|
g.last = i - 1
|
2020-05-08 03:18:41 +00:00
|
|
|
# case 1: match and no match
|
2020-07-08 16:17:48 +00:00
|
|
|
return MatchGroup(match: true, group: g)
|
2020-05-07 14:36:51 +00:00
|
|
|
inc i
|
|
|
|
|
2020-05-08 03:18:41 +00:00
|
|
|
# case 2: all is a match group
|
2020-05-07 14:36:51 +00:00
|
|
|
g.last = parentGroup.last
|
2020-07-08 16:17:48 +00:00
|
|
|
return MatchGroup(match: true, group: g)
|
2020-05-07 14:36:51 +00:00
|
|
|
|
|
|
|
# no match came first, skip no match
|
|
|
|
# we only interested in a match group
|
|
|
|
var i = g.first + 1
|
|
|
|
while i <= parentGroup.last:
|
|
|
|
if compareNibbles(m.keys[i].hash, depth, n):
|
|
|
|
g.first = i
|
|
|
|
break
|
|
|
|
inc i
|
|
|
|
|
|
|
|
if i <= parentGroup.last:
|
|
|
|
while i <= parentGroup.last:
|
|
|
|
if not compareNibbles(m.keys[i].hash, depth, n):
|
2020-05-08 03:18:41 +00:00
|
|
|
# case 3: no match, match, and no match
|
2020-05-07 14:36:51 +00:00
|
|
|
g.last = i - 1
|
2020-07-08 16:17:48 +00:00
|
|
|
return MatchGroup(match: true, group: g)
|
2020-05-07 14:36:51 +00:00
|
|
|
inc i
|
|
|
|
|
2020-05-08 03:18:41 +00:00
|
|
|
# case 4: no match and match
|
2020-05-07 14:36:51 +00:00
|
|
|
g.last = parentGroup.last
|
2020-07-08 16:17:48 +00:00
|
|
|
return MatchGroup(match: true, group: g)
|
2020-05-07 14:36:51 +00:00
|
|
|
|
2020-05-08 03:18:41 +00:00
|
|
|
# case 5: no match at all
|
2020-07-08 16:17:48 +00:00
|
|
|
result = MatchGroup(match: false, group: g)
|
2020-05-07 14:36:51 +00:00
|
|
|
|
2020-05-08 03:18:41 +00:00
|
|
|
func isValidMatch(mg: MatchGroup): bool {.inline.} =
|
2020-05-07 14:36:51 +00:00
|
|
|
result = mg.match and mg.group.first == mg.group.last
|
|
|
|
|
2020-05-24 04:40:01 +00:00
|
|
|
proc visitMatch*(m: var MultikeysRef, mg: MatchGroup, depth: int): KeyData =
|
2020-06-02 04:24:41 +00:00
|
|
|
doAssert(mg.isValidMatch, "Multiple identical keys are not allowed")
|
2020-05-07 14:36:51 +00:00
|
|
|
m.keys[mg.group.first].visited = true
|
|
|
|
result = m.keys[mg.group.first]
|