nimbus-eth1/nimbus/sync/snap/worker/db/hexary_import.nim
Jordan Hrycaj fe3a6d67c6
Prepare snap server client test scenario cont2 (#1487)
* Clean up some function prototypes

why:
  Simplify polymorphic prototype variances for easier maintenance.

* Fix fringe condition crash when importing bogus RLP node

why:
  Accessing non-list RLP entry as a list causes `Defect`

* Fix left boundary proof at range extractor

why:
  Was insufficient. The main problem was that there was no unit test for
  the validity of the generated left boundary.

* Handle incomplete left boundary proofs early

why:
  Attempt to do it later leads to overly complex code in order to prevent
  looping when the same peer repeats to send the same incomplete proof.

  Contrary, gaps in the leaf sequence can be handled gracefully with
  registering the gaps

* Implement a manual pivot setup mechanism for snap sync

why:
  For a test scenario it is convenient to set the pivot to something
  lower than the beacon header from the consensus layer. This does not
  need rely on any RPC mechanism.

details:
  The file containing the pivot specs is specified by the
  `--sync-ctrl-file` option. It is regularly parsed for updates.

* Fix calculation error

why:
  Prevent from calculating negative square root
2023-03-07 14:23:22 +00:00

223 lines
7.0 KiB
Nim

# 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.
{.push raises: [].}
import
std/[sets, tables],
eth/[common, trie/nibbles],
../../range_desc,
"."/[hexary_desc, hexary_error]
# ------------------------------------------------------------------------------
# Private debugging helpers
# ------------------------------------------------------------------------------
#proc pp(q: openArray[byte]): string =
# q.toSeq.mapIt(it.toHex(2)).join.toLowerAscii.pp(hex = true)
# ------------------------------------------------------------------------------
# Public
# ------------------------------------------------------------------------------
proc hexaryImport*(
db: HexaryTreeDbRef; ## Contains node table
recData: Blob; ## Node to add
unrefNodes: var HashSet[RepairKey]; ## Keep track of freestanding nodes
nodeRefs: var HashSet[RepairKey]; ## Ditto
): HexaryNodeReport
{.gcsafe, raises: [RlpError, KeyError].} =
## Decode a single trie item for adding to the table and add it to the
## database. Branch and exrension record links are collected.
if recData.len == 0:
return HexaryNodeReport(error: RlpNonEmptyBlobExpected)
let
nodeKey = recData.digestTo(NodeKey)
repairKey = nodeKey.to(RepairKey) # for repair table
var
rlp = recData.rlpFromBytes
blobs = newSeq[Blob](2) # temporary, cache
links: array[16,RepairKey] # reconstruct branch node
blob16: Blob # reconstruct branch node
top = 0 # count entries
rNode: RNodeRef # repair tree node
if not rlp.isList:
# Otherwise `rlp.items` will raise a `Defect`
return HexaryNodeReport(error: Rlp2Or17ListEntries)
# Collect lists of either 2 or 17 blob entries.
for w in rlp.items:
case top
of 0, 1:
if not w.isBlob:
return HexaryNodeReport(error: RlpBlobExpected)
blobs[top] = rlp.read(Blob)
of 2 .. 15:
var key: NodeKey
if not key.init(rlp.read(Blob)):
return HexaryNodeReport(error: RlpBranchLinkExpected)
# Update ref pool
links[top] = key.to(RepairKey)
unrefNodes.excl links[top] # is referenced, now (if any)
nodeRefs.incl links[top]
of 16:
if not w.isBlob:
return HexaryNodeReport(error: RlpBlobExpected)
blob16 = rlp.read(Blob)
else:
return HexaryNodeReport(error: Rlp2Or17ListEntries)
top.inc
# Verify extension data
case top
of 2:
if blobs[0].len == 0:
return HexaryNodeReport(error: RlpNonEmptyBlobExpected)
let (isLeaf, pathSegment) = hexPrefixDecode blobs[0]
if isLeaf:
rNode = RNodeRef(
kind: Leaf,
lPfx: pathSegment,
lData: blobs[1])
else:
var key: NodeKey
if not key.init(blobs[1]):
return HexaryNodeReport(error: RlpExtPathEncoding)
# Update ref pool
rNode = RNodeRef(
kind: Extension,
ePfx: pathSegment,
eLink: key.to(RepairKey))
unrefNodes.excl rNode.eLink # is referenced, now (if any)
nodeRefs.incl rNode.eLink
of 17:
for n in [0,1]:
var key: NodeKey
if not key.init(blobs[n]):
return HexaryNodeReport(error: RlpBranchLinkExpected)
# Update ref pool
links[n] = key.to(RepairKey)
unrefNodes.excl links[n] # is referenced, now (if any)
nodeRefs.incl links[n]
rNode = RNodeRef(
kind: Branch,
bLink: links,
bData: blob16)
else:
discard
# Add to database
if not db.tab.hasKey(repairKey):
db.tab[repairKey] = rNode
# Update unreferenced nodes list
if repairKey notin nodeRefs:
unrefNodes.incl repairKey # keep track of stray nodes
elif db.tab[repairKey].convertTo(Blob) != recData:
return HexaryNodeReport(error: DifferentNodeValueExists)
HexaryNodeReport(kind: some(rNode.kind))
proc hexaryImport*(
db: HexaryTreeDbRef; ## Contains node table
rec: NodeSpecs; ## Expected key and value data pair
): HexaryNodeReport
{.gcsafe, raises: [RlpError, KeyError].} =
## Ditto without referece checks but expected node key argument.
if rec.data.len == 0:
return HexaryNodeReport(error: RlpNonEmptyBlobExpected)
if rec.nodeKey != rec.data.digestTo(NodeKey):
return HexaryNodeReport(error: ExpectedNodeKeyDiffers)
let
repairKey = rec.nodeKey.to(RepairKey) # for repair table
var
rlp = rec.data.rlpFromBytes
blobs = newSeq[Blob](2) # temporary, cache
links: array[16,RepairKey] # reconstruct branch node
blob16: Blob # reconstruct branch node
top = 0 # count entries
rNode: RNodeRef # repair tree node
if not rlp.isList:
# Otherwise `rlp.items` will raise a `Defect`
return HexaryNodeReport(error: Rlp2Or17ListEntries)
# Collect lists of either 2 or 17 blob entries.
for w in rlp.items:
case top
of 0, 1:
if not w.isBlob:
return HexaryNodeReport(error: RlpBlobExpected)
blobs[top] = rlp.read(Blob)
of 2 .. 15:
var key: NodeKey
if not key.init(rlp.read(Blob)):
return HexaryNodeReport(error: RlpBranchLinkExpected)
# Update ref pool
links[top] = key.to(RepairKey)
of 16:
if not w.isBlob:
return HexaryNodeReport(error: RlpBlobExpected)
blob16 = rlp.read(Blob)
else:
return HexaryNodeReport(error: Rlp2Or17ListEntries)
top.inc
# Verify extension data
case top
of 2:
if blobs[0].len == 0:
return HexaryNodeReport(error: RlpNonEmptyBlobExpected)
let (isLeaf, pathSegment) = hexPrefixDecode blobs[0]
if isLeaf:
rNode = RNodeRef(
kind: Leaf,
lPfx: pathSegment,
lData: blobs[1])
else:
var key: NodeKey
if not key.init(blobs[1]):
return HexaryNodeReport(error: RlpExtPathEncoding)
# Update ref pool
rNode = RNodeRef(
kind: Extension,
ePfx: pathSegment,
eLink: key.to(RepairKey))
of 17:
for n in [0,1]:
var key: NodeKey
if not key.init(blobs[n]):
return HexaryNodeReport(error: RlpBranchLinkExpected)
# Update ref pool
links[n] = key.to(RepairKey)
rNode = RNodeRef(
kind: Branch,
bLink: links,
bData: blob16)
else:
discard
# Add to database
if not db.tab.hasKey(repairKey):
db.tab[repairKey] = rNode
elif db.tab[repairKey].convertTo(Blob) != rec.data:
return HexaryNodeReport(error: DifferentNodeValueExists)
HexaryNodeReport(kind: some(rNode.kind))
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------