nimbus-eth1/nimbus/stateless/multi_keys.nim
web3-developer db8c5b90bd
Cleanup stateless and block witness code. (#2295)
* Cleanup unneeded stateless and block witness code. Keeping MultiKeys which is used in the eth_getProofsByBlockNumber RPC endpoint which is needed for the Fluffy state network bridge.

* Rename generateWitness flag to collectWitnessData to better describe what the flag does. We only collect the keys of the touched accounts and storage slots but no block witness generation is supported for now.

* Move remaining stateless code into nimbus directory.

* Add vmstate parameter to ChainRef to fix test.

* Exclude *.in from check copyright year

---------

Co-authored-by: jangko <jangko128@gmail.com>
2024-06-08 15:05:00 +07:00

189 lines
5.4 KiB
Nim

# Nimbus
# Copyright (c) 2020-2024 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.
import
eth/common, eth/trie/nibbles, algorithm
type
KeyHash* = array[32, byte]
StorageSlot* = array[32, byte]
KeyData* = object
visited*: bool
hash*: KeyHash
case storageMode*: bool
of true:
storageSlot*: StorageSlot
of false:
storageKeys*: MultiKeysRef
address*: EthAddress
codeTouched*: bool
MultiKeys* = object
keys*: seq[KeyData]
MultiKeysRef* = ref MultiKeys
Group* = object
first*, last*: int
BranchGroup* = object
mask*: uint
groups*: array[16, Group]
AccountKey* = object
address*: EthAddress
codeTouched*: bool
storageKeys*: MultiKeysRef
MatchGroup* = object
match*: bool
group*: Group
proc setBranchMaskBit(x: var uint, i: int) =
assert(i >= 0 and i < 17)
x = x or (1 shl i).uint
func cmpHash(a, b: KeyHash): int =
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
func cmpHash(a, b: KeyData): int =
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
proc newMultiKeys*(keys: openArray[AccountKey]): MultiKeysRef =
result = new MultiKeysRef
result.keys = newSeq[KeyData](keys.len)
for i, a in keys:
result.keys[i] = KeyData(
storageMode: false,
hash: keccakHash(a.address).data,
address: a.address,
codeTouched: a.codeTouched,
storageKeys: a.storageKeys)
result.keys.sort(cmpHash)
proc newMultiKeys*(keys: openArray[StorageSlot]): MultiKeysRef =
result = new MultiKeysRef
result.keys = newSeq[KeyData](keys.len)
for i, a in keys:
result.keys[i] = KeyData(storageMode: true, hash: keccakHash(a).data, storageSlot: a)
result.keys.sort(cmpHash)
# never mix storageMode!
proc add*(m: MultiKeysRef, address: EthAddress, codeTouched: bool, storageKeys = MultiKeysRef(nil)) =
m.keys.add KeyData(
storageMode: false,
hash: keccakHash(address).data,
address: address,
codeTouched: codeTouched,
storageKeys: storageKeys)
proc add*(m: MultiKeysRef, slot: StorageSlot) =
m.keys.add KeyData(storageMode: true, hash: keccakHash(slot).data, storageSlot: slot)
proc sort*(m: MultiKeysRef) =
m.keys.sort(cmpHash)
func initGroup*(m: MultiKeysRef): Group =
type T = type result.last
result = Group(first: 0.T, last: (m.keys.len - 1).T)
func groups*(m: MultiKeysRef, parentGroup: Group, depth: int): BranchGroup =
# 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
var g = Group(first: parentGroup.first)
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:
# close current group and start a new group
g.last = i - 1
setBranchMaskBit(result.mask, nibble.int)
result.groups[nibble.int] = g
nibble = currNibble
g.first = i
# always close the last group
g.last = parentGroup.last
setBranchMaskBit(result.mask, nibble.int)
result.groups[nibble.int] = g
func groups*(m: MultiKeysRef, depth: int, n: NibblesSeq, parentGroup: Group): MatchGroup =
# using common-prefix comparison, this func
# 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
# case 1: match and no match
return MatchGroup(match: true, group: g)
inc i
# case 2: all is a match group
g.last = parentGroup.last
return MatchGroup(match: true, group: g)
# 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):
# case 3: no match, match, and no match
g.last = i - 1
return MatchGroup(match: true, group: g)
inc i
# case 4: no match and match
g.last = parentGroup.last
return MatchGroup(match: true, group: g)
# case 5: no match at all
result = MatchGroup(match: false, group: g)
func isValidMatch(mg: MatchGroup): bool {.inline.} =
result = mg.match and mg.group.first == mg.group.last
proc visitMatch*(m: var MultiKeysRef, mg: MatchGroup, depth: int): KeyData =
doAssert(mg.isValidMatch, "Multiple identical keys are not allowed")
m.keys[mg.group.first].visited = true
result = m.keys[mg.group.first]