nimbus-eth1/nimbus/db/aristo/aristo_desc/desc_identifiers.nim

290 lines
11 KiB
Nim
Raw Normal View History

# nimbus-eth1
# Copyright (c) 2021 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.
## Aristo DB -- Identifier types
## =============================
##
{.push raises: [].}
import
std/[sequtils, strutils, hashes],
eth/[common, trie/nibbles],
stint
type
ByteArray32* = array[32,byte]
## Used for 32 byte hash components repurposed as Merkle hash labels.
QueueID* = distinct uint64
## Identifier used to tag filter logs stored on the backend.
FilterID* = distinct uint64
## Identifier used to identify a particular filter. It is generatied with
## the filter when stored to database.
VertexID* = distinct uint64
## Unique identifier for a vertex of the `Aristo Trie`. The vertex is the
## prefix tree (aka `Patricia Trie`) component. When augmented by hash
## keys, the vertex component will be called a node. On the persistent
## backend of the database, there is no other reference to the node than
## the very same `VertexID`.
HashID* = distinct UInt256
## Variant of a `Hash256` object that can be used in a order relation
## (i.e. it can be sorted.) Among temporary conversions for sorting, the
## `HashID` type is consistently used for addressing leaf vertices (see
## below `LeafTie`.)
HashKey* = distinct ByteArray32
## Dedicated `Hash256` object variant that is used for labelling the
## vertices of the `Patricia Trie` in order to make it a
## `Merkle Patricia Tree`.
# ----------
LeafTie* = object
## Unique access key for a leaf vertex. It identifies a root vertex
## followed by a nibble path along the `Patricia Trie` down to a leaf
## vertex. So this implies an obvious injection from the set of `LeafTie`
## objects *into* the set of `VertexID` obvious (which is typically *into*
## only, not a bijection.)
##
## Note that `LeafTie` objects have no representation in the `Aristo Trie`.
## They are used temporarily and in caches or backlog tables.
root*: VertexID ## Root ID for the sub-trie
path*: HashID ## Path into the `Patricia Trie`
HashLabel* = object
## Merkle hash key uniquely associated with a vertex ID. As hashes in a
## `Merkle Patricia Tree` are unique only on a particular sub-trie, the
## hash key is paired with the top vertex of the relevant sub-trie. This
## construction is similar to the one of a `LeafTie` object.
##
## Note that `HashLabel` objects have no representation in the
## `Aristo Trie`. They are used temporarily and in caches or backlog
## tables.
root*: VertexID ## Root ID for the sub-trie.
key*: HashKey ## Merkle hash tacked to a vertex.
static:
# Not that there is no doubt about this ...
doAssert HashKey.default.ByteArray32.initNibbleRange.len == 64
# ------------------------------------------------------------------------------
# Public helpers: `VertexID` scalar data model
# ------------------------------------------------------------------------------
func `<`*(a, b: VertexID): bool {.borrow.}
func `<=`*(a, b: VertexID): bool {.borrow.}
func `==`*(a, b: VertexID): bool {.borrow.}
func cmp*(a, b: VertexID): int {.borrow.}
func `$`*(a: VertexID): string {.borrow.}
func `==`*(a: VertexID; b: static[uint]): bool = (a == VertexID(b))
# Scalar model extension as in `IntervalSetRef[VertexID,uint64]`
func `+`*(a: VertexID; b: uint64): VertexID = (a.uint64+b).VertexID
func `-`*(a: VertexID; b: uint64): VertexID = (a.uint64-b).VertexID
func `-`*(a, b: VertexID): uint64 = (a.uint64 - b.uint64)
# ------------------------------------------------------------------------------
# Public helpers: `QueueID` scalar data model
# ------------------------------------------------------------------------------
func `<`*(a, b: QueueID): bool {.borrow.}
func `<=`*(a, b: QueueID): bool {.borrow.}
func `==`*(a, b: QueueID): bool {.borrow.}
func cmp*(a, b: QueueID): int {.borrow.}
func `$`*(a: QueueID): string {.borrow.}
func `==`*(a: QueueID; b: static[uint]): bool = (a == QueueID(b))
func `+`*(a: QueueID; b: uint64): QueueID = (a.uint64+b).QueueID
func `-`*(a: QueueID; b: uint64): QueueID = (a.uint64-b).QueueID
func `-`*(a, b: QueueID): uint64 = (a.uint64 - b.uint64)
# ------------------------------------------------------------------------------
# Public helpers: `FilterID` scalar data model
# ------------------------------------------------------------------------------
func `<`*(a, b: FilterID): bool {.borrow.}
func `<=`*(a, b: FilterID): bool {.borrow.}
func `==`*(a, b: FilterID): bool {.borrow.}
func `$`*(a: FilterID): string {.borrow.}
func `==`*(a: FilterID; b: static[uint]): bool = (a == FilterID(b))
func `+`*(a: FilterID; b: uint64): FilterID = (a.uint64+b).FilterID
func `-`*(a: FilterID; b: uint64): FilterID = (a.uint64-b).FilterID
func `-`*(a, b: FilterID): uint64 = (a.uint64 - b.uint64)
# ------------------------------------------------------------------------------
# Public helpers: `HashID` scalar data model
# ------------------------------------------------------------------------------
func u256*(lp: HashID): UInt256 = lp.UInt256
func low*(T: type HashID): T = low(UInt256).T
func high*(T: type HashID): T = high(UInt256).T
func `+`*(a: HashID; b: UInt256): HashID = (a.u256+b).HashID
func `-`*(a: HashID; b: UInt256): HashID = (a.u256-b).HashID
func `-`*(a, b: HashID): UInt256 = (a.u256 - b.u256)
func `==`*(a, b: HashID): bool = a.u256 == b.u256
func `<=`*(a, b: HashID): bool = a.u256 <= b.u256
func `<`*(a, b: HashID): bool = a.u256 < b.u256
func cmp*(x, y: HashID): int = cmp(x.UInt256, y.UInt256)
# ------------------------------------------------------------------------------
# Public helpers: `LeafTie`
# ------------------------------------------------------------------------------
func high*(_: type LeafTie; root = VertexID(1)): LeafTie =
## Highest possible `LeafTie` object for given root vertex.
LeafTie(root: root, path: high(HashID))
func low*(_: type LeafTie; root = VertexID(1)): LeafTie =
## Lowest possible `LeafTie` object for given root vertex.
LeafTie(root: root, path: low(HashID))
func `+`*(lty: LeafTie, n: int): LeafTie =
## Return a `LeafTie` object with incremented path field. This function
## will not check for a path field overflow. Neither it will verify that
## the argument `n` is non-negative.
LeafTie(root: lty.root, path: HashID(lty.path.u256 + n.u256))
func `-`*(lty: LeafTie, n: int): LeafTie =
## Return a `LeafTie` object with decremented path field. This function
## will not check for a path field underflow. Neither it will verify that
## the argument `n` is non-negative.
LeafTie(root: lty.root, path: HashID(lty.path.u256 - n.u256))
# ------------------------------------------------------------------------------
# Public helpers: Conversions between `HashID`, `HashKey`, `Hash256`
# ------------------------------------------------------------------------------
func to*(hid: HashID; T: type Hash256): T =
result.data = hid.UInt256.toBytesBE
Aristo db api extensions for use as core db backend (#1754) * Update docu * Update Aristo/Kvt constructor prototype why: Previous version used an `enum` value to indicate what backend is to be used. This was replaced by using the backend object type. * Rewrite `hikeUp()` return code into `Result[Hike,(Hike,AristoError)]` why: Better code maintenance. Previously, the `Hike` object was returned. It had an internal error field so partial success was also available on a failure. This error field has been removed. * Use `openArray[byte]` rather than `Blob` in functions prototypes * Provide synchronised multi instance transactions why: The `CoreDB` object was geared towards the legacy DB which used a single transaction for the key-value backend DB. Different state roots are provided by the backend database, so all instances work directly on the same backend. Aristo db instances have different in-memory mappings (aka different state roots) and the transactions are on top of there mappings. So each instance might run different transactions. Multi instance transactions are a compromise to converge towards the legacy behaviour. The synchronised transactions span over all instances available at the time when base transaction was opened. Instances created later are unaffected. * Provide key-value pair database iterator why: Needed in `CoreDB` for `replicate()` emulation also: Some update of internal code * Extend API (i.e. prototype variants) why: Needed for `CoreDB` geared towards the legacy backend which has a more basic API than Aristo.
2023-09-15 15:23:53 +00:00
func to*(hid: HashID; T: type HashKey): T =
hid.UInt256.toBytesBE.T
func to*(key: HashKey; T: type HashID): T =
UInt256.fromBytesBE(key.ByteArray32).T
func to*(key: HashKey; T: type Hash256): T =
T(data: ByteArray32(key))
func to*(hash: Hash256; T: type HashKey): T =
hash.data.T
func to*(key: Hash256; T: type HashID): T =
key.data.HashKey.to(T)
# ------------------------------------------------------------------------------
# Public helpers: Miscellaneous mappings
# ------------------------------------------------------------------------------
func to*(key: HashKey; T: type Blob): T =
## Representation of a `HashKey` as `Blob` (preserving full information)
key.ByteArray32.toSeq
Aristo db api extensions for use as core db backend (#1754) * Update docu * Update Aristo/Kvt constructor prototype why: Previous version used an `enum` value to indicate what backend is to be used. This was replaced by using the backend object type. * Rewrite `hikeUp()` return code into `Result[Hike,(Hike,AristoError)]` why: Better code maintenance. Previously, the `Hike` object was returned. It had an internal error field so partial success was also available on a failure. This error field has been removed. * Use `openArray[byte]` rather than `Blob` in functions prototypes * Provide synchronised multi instance transactions why: The `CoreDB` object was geared towards the legacy DB which used a single transaction for the key-value backend DB. Different state roots are provided by the backend database, so all instances work directly on the same backend. Aristo db instances have different in-memory mappings (aka different state roots) and the transactions are on top of there mappings. So each instance might run different transactions. Multi instance transactions are a compromise to converge towards the legacy behaviour. The synchronised transactions span over all instances available at the time when base transaction was opened. Instances created later are unaffected. * Provide key-value pair database iterator why: Needed in `CoreDB` for `replicate()` emulation also: Some update of internal code * Extend API (i.e. prototype variants) why: Needed for `CoreDB` geared towards the legacy backend which has a more basic API than Aristo.
2023-09-15 15:23:53 +00:00
func to*(hid: HashID; T: type Blob): T =
## Representation of a `HashID` as `Blob` (preserving full information)
hid.UInt256.toBytesBE.toSeq
func to*(key: HashKey; T: type NibblesSeq): T =
## Representation of a `HashKey` as `NibbleSeq` (preserving full information)
key.ByteArray32.initNibbleRange()
func to*(hid: HashID; T: type NibblesSeq): T =
## Representation of a `HashKey` as `NibbleSeq` (preserving full information)
ByteArray32(hid.to(HashKey)).initNibbleRange()
func to*(n: SomeUnsignedInt|UInt256; T: type HashID): T =
## Representation of a scalar as `HashID` (preserving full information)
n.u256.T
func digestTo*(data: openArray[byte]; T: type HashKey): T =
## Keccak hash of a `Blob` like argument, represented as a `HashKey`
keccakHash(data).data.T
# ------------------------------------------------------------------------------
# Public helpers: `Tables` and `Rlp` support
# ------------------------------------------------------------------------------
func hash*(a: HashID): Hash =
## Table/KeyedQueue mixin
a.to(HashKey).ByteArray32.hash
func hash*(a: HashKey): Hash =
## Table/KeyedQueue mixin
a.ByteArray32.hash
func `==`*(a, b: HashKey): bool =
## Table/KeyedQueue mixin
a.ByteArray32 == b.ByteArray32
func read*[T: HashID|HashKey](
rlp: var Rlp;
W: type T;
): T
{.gcsafe, raises: [RlpError].} =
rlp.read(Hash256).to(T)
func append*(writer: var RlpWriter, val: HashID|HashKey) =
writer.append(val.to(Hash256))
# ------------------------------------------------------------------------------
# Public helpers: `LeafTie` scalar data model
# ------------------------------------------------------------------------------
func `<`*(a, b: LeafTie): bool =
a.root < b.root or (a.root == b.root and a.path < b.path)
func `==`*(a, b: LeafTie): bool =
a.root == b.root and a.path == b.path
func cmp*(a, b: LeafTie): int =
if a < b: -1 elif a == b: 0 else: 1
func `$`*(a: LeafTie): string =
let w = $a.root.uint64.toHex & ":" & $a.path.Uint256.toHex
w.strip(leading=true, trailing=false, chars={'0'}).toLowerAscii
# ------------------------------------------------------------------------------
# Miscellaneous helpers
# ------------------------------------------------------------------------------
func `$`*(hid: HashID): string =
if hid == high(HashID):
"2^256-1"
elif hid == 0.u256.HashID:
"0"
elif hid == 2.u256.pow(255).HashID:
"2^255" # 800...
elif hid == 2.u256.pow(254).HashID:
"2^254" # 400..
elif hid == 2.u256.pow(253).HashID:
"2^253" # 200...
elif hid == 2.u256.pow(251).HashID:
"2^252" # 100...
else:
hid.UInt256.toHex
func `$`*(key: HashKey): string =
$key.to(HashID)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------