Fc module can update base also when on parent arc (#2911)

* Re-org internal descriptor `CanonicalDesc` as `PivotArc`

why:
  Despite its name, `CanonicalDesc` contained a cursor arc (or leg) from
  the base tree with a designated block (or Header) on its arc members
  (aka blocks.) The type is used more generally than only for s block on
  the canonical cursor.

  Also, the `PivotArc` provides some more fields for caching intermediate
  data. This simplifies managing extra arguments for some functions.

* Remove cruft

details:
  No need to find cursor arc if it is given as function argument.

* Rename prototype variables `head: PivotArc` to `pvarc`

why:
  Better reading

* Function and code massage, adjust names

details:
  Avoid the syllable `canonical` in function names that do not strictly
  apply to the canonical chain. So renaming
  * findCanonicalHead() => findCursorArc()
  * canonicalChain() => findHeader()
  * trimCanonicalChain() => trimCursorArc()

* Combine `updateBase()` function-args into single `PivotArgs` object

why:
  Will generalise action for more complex scenarios in future.

* update `calculateNewBase()` return code type => `PivotArc`

why:
  So it can directly be used as argument into `updateBase()`

* Update `calculateNewBase()` for target on parent arc

* Update unit tests
This commit is contained in:
Jordan Hrycaj 2024-12-05 06:01:57 +00:00 committed by GitHub
parent dc81863c3a
commit 90dd86be9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 211 additions and 148 deletions

View File

@ -228,10 +228,7 @@ proc writeBaggage(c: ForkedChainRef, target: Hash32) =
baseNumber = c.baseHeader.number, baseNumber = c.baseHeader.number,
baseHash = c.baseHash.short baseHash = c.baseHash.short
func updateBase(c: ForkedChainRef, func updateBase(c: ForkedChainRef, pvarc: PivotArc) =
newBaseHash: Hash32,
newBaseHeader: Header,
canonicalCursorHash: Hash32) =
## Remove obsolete chains, example: ## Remove obsolete chains, example:
## ##
## A1 - A2 - A3 D5 - D6 ## A1 - A2 - A3 D5 - D6
@ -240,21 +237,23 @@ func updateBase(c: ForkedChainRef,
## \ \ ## \ \
## C2 - C3 E4 - E5 ## C2 - C3 E4 - E5
## ##
## where `B5` is the `canonicalCursor` head. When the `base` is moved to ## where `B1..B5` is the `pvarc.cursor` arc and `[B5]` is the `pvarc.pv`.
## position `[B3]`, both chains `A` and `C` will be removed but not so for #
## `D` and `E`, and chain `B` will be curtailed below `B4`. ## The `base` will be moved to position `[B3]`. Both chains `A` and `C`
## will be removed but not so for `D` and `E`, and `pivot` arc `B` will
## be curtailed below `B4`.
## ##
var newCursorHeads: seq[CursorDesc] # Will become new `c.cursorHeads` var newCursorHeads: seq[CursorDesc] # Will become new `c.cursorHeads`
for ch in c.cursorHeads: for ch in c.cursorHeads:
if newBaseHeader.number < ch.forkJunction: if pvarc.pvNumber < ch.forkJunction:
# On the example, this would be any of chain `D` or `E`. # On the example, this would be any of chain `D` or `E`.
newCursorHeads.add ch newCursorHeads.add ch
elif ch.hash == canonicalCursorHash: elif ch.hash == pvarc.cursor.hash:
# On the example, this would be chain `B`. # On the example, this would be chain `B`.
newCursorHeads.add CursorDesc( newCursorHeads.add CursorDesc(
hash: ch.hash, hash: ch.hash,
forkJunction: newBaseHeader.number + 1) forkJunction: pvarc.pvNumber + 1)
else: else:
# On the example, this would be either chain `A` or `B`. # On the example, this would be either chain `A` or `B`.
@ -263,22 +262,27 @@ func updateBase(c: ForkedChainRef,
# Cleanup in-memory blocks starting from newBase backward # Cleanup in-memory blocks starting from newBase backward
# while blocks from newBase+1 to canonicalCursor not deleted # while blocks from newBase+1 to canonicalCursor not deleted
# e.g. B4 onward # e.g. B4 onward
c.deleteLineage newBaseHash c.deleteLineage pvarc.pvHash
# Implied deletion of chain heads (if any) # Implied deletion of chain heads (if any)
c.cursorHeads.swap newCursorHeads c.cursorHeads.swap newCursorHeads
c.baseHeader = newBaseHeader c.baseHeader = pvarc.pvHeader
c.baseHash = newBaseHash c.baseHash = pvarc.pvHash
func findCanonicalHead(c: ForkedChainRef, func findCursorArc(c: ForkedChainRef, hash: Hash32): Result[PivotArc, string] =
hash: Hash32): Result[CanonicalDesc, string] =
## Find the `cursor` arc that contains the block relative to the ## Find the `cursor` arc that contains the block relative to the
## argument `hash`. ## argument `hash`.
##
if hash == c.baseHash: if hash == c.baseHash:
# The cursorHash here should not be used for next step # The cursorHash here should not be used for next step
# because it not point to any active chain # because it not point to any active chain
return ok(CanonicalDesc(cursorHash: c.baseHash, header: c.baseHeader)) return ok PivotArc(
pvHash: c.baseHash,
pvHeader: c.baseHeader,
cursor: CursorDesc(
forkJunction: c.baseHeader.number,
hash: c.baseHash))
for ch in c.cursorHeads: for ch in c.cursorHeads:
var top = ch.hash var top = ch.hash
@ -286,7 +290,10 @@ func findCanonicalHead(c: ForkedChainRef,
c.blocks.withValue(top, val): c.blocks.withValue(top, val):
if ch.forkJunction <= val.blk.header.number: if ch.forkJunction <= val.blk.header.number:
if top == hash: if top == hash:
return ok CanonicalDesc(cursorHash: ch.hash, header: val.blk.header) return ok PivotArc(
pvHash: hash,
pvHeader: val.blk.header,
cursor: ch)
if ch.forkJunction < val.blk.header.number: if ch.forkJunction < val.blk.header.number:
top = val.blk.header.parentHash top = val.blk.header.parentHash
continue continue
@ -294,84 +301,106 @@ func findCanonicalHead(c: ForkedChainRef,
err("Block hash is not part of any active chain") err("Block hash is not part of any active chain")
func canonicalChain(c: ForkedChainRef, func findHeader(
hash: Hash32, c: ForkedChainRef;
headHash: Hash32): Result[Header, string] = itHash: Hash32;
if hash == c.baseHash: headHash: Hash32;
): Result[Header, string] =
## Find header for argument `itHash` on argument `headHash` ancestor chain.
##
if itHash == c.baseHash:
return ok(c.baseHeader) return ok(c.baseHeader)
shouldNotKeyError "canonicalChain": # Find `pvHash` on the ancestor lineage of `headHash`
var prevHash = headHash
while prevHash != c.baseHash:
var header = c.blocks[prevHash].blk.header
if prevHash == hash:
return ok(header)
prevHash = header.parentHash
err("Block hash not in canonical chain")
func calculateNewBase(c: ForkedChainRef,
finalized: BlockNumber,
headHash: Hash32,
head: CanonicalDesc): BaseDesc =
## Search for `finalized` searching backwards starting at `head` to find an
## entry with this block number on the arc (or leg) terminating at `head`.
##
## It is required that `finalized` is within the `head` arc, i.e.
## `cursorHead.forkJunction <= finalized` for the relevant `cursorHead`.
##
## The `finalized` entry might be moved backwards so that a minimum
## distance applies.
##
## Discussion:
## If the distance `finalized..head` is too short, `finalized` will be
## adjusted backwards and may fall outside the `head` arc. In that case,
## base remains un-moved.
##
# It's important to have base at least `baseDistance` behind head
# so we can answer state queries about history that deep.
let target = min(finalized,
max(head.header.number, c.baseDistance) - c.baseDistance)
# The distance is less than `baseDistance`, don't move the base
if target <= c.baseHeader.number + c.baseDistance:
return BaseDesc(hash: c.baseHash, header: c.baseHeader)
# Verify that `target` does not fall outside the `head` arc. It is
# assumed that `finalized` is within the `head` arc.
if target < finalized:
block verifyTarget:
# Find cursor realive to the `head` argument
for ch in c.cursorHeads:
if ch.hash == head.cursorHash:
if ch.forkJunction <= target:
break verifyTarget
break
# Noting to do here
return BaseDesc(hash: c.baseHash, header: c.baseHeader)
var prevHash = headHash var prevHash = headHash
while true: while true:
c.blocks.withValue(prevHash, val): c.blocks.withValue(prevHash, val):
# Note that `cursorHead.forkJunction <= target` if prevHash == itHash:
return ok(val.blk.header)
prevHash = val.blk.header.parentHash
continue
break
err("Block not in argument head ancestor lineage")
func calculateNewBase(
c: ForkedChainRef;
finalized: BlockNumber;
pvarc: PivotArc;
): PivotArc =
## It is required that the `finalized` argument is on the `pvarc` arc, i.e.
## it ranges beween `pvarc.cursor.forkJunction` and
## `c.blocks[pvarc.cursor.head].number`.
##
## The function returns a cursor arc containing a new base position. It is
## calculated as follows.
##
## Starting at the argument `pvarc.pvHead` searching backwards, the new base
## is the position of the block with number `finalized`.
##
## Before searching backwards, the `finalized` argument might be adjusted
## and made smaller so that a minimum distance to the head on the cursor arc
## applies.
##
# It's important to have base at least `baseDistance` behind head
# so we can answer state queries about history that deep.
let target = min(finalized,
max(pvarc.pvNumber, c.baseDistance) - c.baseDistance)
# Can only increase base block number.
if target <= c.baseHeader.number:
return PivotArc(
pvHash: c.baseHash,
pvHeader: c.baseHeader,
cursor: CursorDesc(
forkJunction: c.baseHeader.number,
hash: c.baseHash))
var prevHash = pvarc.pvHash
while true:
c.blocks.withValue(prevHash, val):
if target == val.blk.header.number: if target == val.blk.header.number:
return BaseDesc(hash: prevHash, header: val.blk.header) if pvarc.cursor.forkJunction <= target:
# OK, new base stays on the argument pivot arc.
# ::
# B1 - B2 - B3 - B4
# / ^ ^ ^
# base - A1 - A2 - A3 | | |
# | pv CCH
# |
# target
#
return PivotArc(
pvHash: prevHash,
pvHeader: val.blk.header,
cursor: pvarc.cursor)
else:
# The new base (aka target) falls out of the argument pivot branch,
# ending up somewhere on a parent branch.
# ::
# B1 - B2 - B3 - B4
# / ^ ^
# base - A1 - A2 - A3 | |
# ^ pv CCH
# |
# target
#
return c.findCursorArc(prevHash).expect "valid cursor arc"
prevHash = val.blk.header.parentHash prevHash = val.blk.header.parentHash
continue continue
break break
doAssert(false, "Unreachable code, finalized block outside cursor arc") doAssert(false, "Unreachable code, finalized block outside cursor arc")
func trimCanonicalChain(c: ForkedChainRef, func trimCursorArc(c: ForkedChainRef, pvarc: PivotArc) =
head: CanonicalDesc, ## Curb argument `pvarc.cursor` head so that it ends up at `pvarc.pv`.
headHash: Hash32) = ##
# Maybe the current active chain is longer than canonical chain # Maybe the current active chain is longer than canonical chain
shouldNotKeyError "trimCanonicalChain": shouldNotKeyError "trimCanonicalChain":
var prevHash = head.cursorHash var prevHash = pvarc.cursor.hash
while prevHash != c.baseHash: while prevHash != c.baseHash:
let header = c.blocks[prevHash].blk.header let header = c.blocks[prevHash].blk.header
if header.number > head.header.number: if header.number > pvarc.pvNumber:
c.blocks.del(prevHash) c.blocks.del(prevHash)
else: else:
break break
@ -382,43 +411,40 @@ func trimCanonicalChain(c: ForkedChainRef,
# Update cursorHeads if indeed we trim # Update cursorHeads if indeed we trim
for i in 0..<c.cursorHeads.len: for i in 0..<c.cursorHeads.len:
if c.cursorHeads[i].hash == head.cursorHash: if c.cursorHeads[i].hash == pvarc.cursor.hash:
c.cursorHeads[i].hash = headHash c.cursorHeads[i].hash = pvarc.pvHash
return return
doAssert(false, "Unreachable code") doAssert(false, "Unreachable code")
proc setHead(c: ForkedChainRef, proc setHead(c: ForkedChainRef, pvarc: PivotArc) =
headHash: Hash32,
number: BlockNumber) =
# TODO: db.setHead should not read from db anymore # TODO: db.setHead should not read from db anymore
# all canonical chain marking # all canonical chain marking
# should be done from here. # should be done from here.
discard c.db.setHead(headHash) discard c.db.setHead(pvarc.pvHash)
# update global syncHighest # update global syncHighest
c.com.syncHighest = number c.com.syncHighest = pvarc.pvNumber
proc updateHeadIfNecessary(c: ForkedChainRef, proc updateHeadIfNecessary(c: ForkedChainRef, pvarc: PivotArc) =
head: CanonicalDesc, headHash: Hash32) =
# update head if the new head is different # update head if the new head is different
# from current head or current chain # from current head or current chain
if c.cursorHash != head.cursorHash: if c.cursorHash != pvarc.cursor.hash:
if not c.stagingTx.isNil: if not c.stagingTx.isNil:
c.stagingTx.rollback() c.stagingTx.rollback()
c.stagingTx = c.db.ctx.newTransaction() c.stagingTx = c.db.ctx.newTransaction()
c.replaySegment(headHash) c.replaySegment(pvarc.pvHash)
c.trimCanonicalChain(head, headHash) c.trimCursorArc(pvarc)
if c.cursorHash != headHash: if c.cursorHash != pvarc.pvHash:
c.cursorHeader = head.header c.cursorHeader = pvarc.pvHeader
c.cursorHash = headHash c.cursorHash = pvarc.pvHash
if c.stagingTx.isNil: if c.stagingTx.isNil:
# setHead below don't go straight to db # setHead below don't go straight to db
c.stagingTx = c.db.ctx.newTransaction() c.stagingTx = c.db.ctx.newTransaction()
c.setHead(headHash, head.header.number) c.setHead(pvarc)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public functions # Public functions
@ -518,73 +544,73 @@ proc importBlock*(c: ForkedChainRef, blk: Block): Result[void, string] =
proc forkChoice*(c: ForkedChainRef, proc forkChoice*(c: ForkedChainRef,
headHash: Hash32, headHash: Hash32,
finalizedHash: Hash32): Result[void, string] = finalizedHash: Hash32): Result[void, string] =
if headHash == c.cursorHash and finalizedHash == static(default(Hash32)): if headHash == c.cursorHash and finalizedHash == static(default(Hash32)):
# Do nothing if the new head already our current head # Do nothing if the new head already our current head
# and there is no request to new finality # and there is no request to new finality
return ok() return ok()
# If there are multiple heads, find which chain headHash belongs to # Find the unique cursor arc where `headHash` is a member of.
let head = ?c.findCanonicalHead(headHash) let pvarc = ?c.findCursorArc(headHash)
if finalizedHash == static(default(Hash32)): if finalizedHash == static(default(Hash32)):
# skip newBase calculation and skip chain finalization # skip newBase calculation and skip chain finalization
# if finalizedHash is zero # if finalizedHash is zero
c.updateHeadIfNecessary(head, headHash) c.updateHeadIfNecessary(pvarc)
return ok() return ok()
# Finalized block must be part of canonical chain # Finalized block must be parent or on the new canonical chain which is
let finalizedHeader = ?c.canonicalChain(finalizedHash, headHash) # represented by `pvarc`.
let finalizedHeader = ?c.findHeader(finalizedHash, pvarc.pvHash)
let newBase = c.calculateNewBase(finalizedHeader.number, headHash, head) let newBase = c.calculateNewBase(finalizedHeader.number, pvarc)
if newBase.hash == c.baseHash: if newBase.pvHash == c.baseHash:
# The base is not updated but the cursor maybe need update # The base is not updated but the cursor maybe need update
c.updateHeadIfNecessary(head, headHash) c.updateHeadIfNecessary(pvarc)
return ok() return ok()
# At this point cursorHeader.number > baseHeader.number # At this point cursorHeader.number > baseHeader.number
if newBase.hash == c.cursorHash: if newBase.pvHash == c.cursorHash:
# Paranoid check, guaranteed by `newBase.hash == c.cursorHash` # Paranoid check, guaranteed by `newBase.hash == c.cursorHash`
doAssert(not c.stagingTx.isNil) doAssert(not c.stagingTx.isNil)
# CL decide to move backward and then forward? # CL decide to move backward and then forward?
if c.cursorHeader.number < head.header.number: if c.cursorHeader.number < pvarc.pvNumber:
c.replaySegment(headHash, c.cursorHeader, c.cursorHash) c.replaySegment(pvarc.pvHash, c.cursorHeader, c.cursorHash)
# Current segment is canonical chain # Current segment is canonical chain
c.writeBaggage(newBase.hash) c.writeBaggage(newBase.pvHash)
c.setHead(headHash, head.header.number) c.setHead(pvarc)
c.stagingTx.commit() c.stagingTx.commit()
c.stagingTx = nil c.stagingTx = nil
# Move base to newBase # Move base to newBase
c.updateBase(newBase.hash, c.cursorHeader, head.cursorHash) c.updateBase(newBase)
# Save and record the block number before the last saved block state. # Save and record the block number before the last saved block state.
c.db.persistent(newBase.header.number).isOkOr: c.db.persistent(newBase.pvNumber).isOkOr:
return err("Failed to save state: " & $$error) return err("Failed to save state: " & $$error)
return ok() return ok()
# At this point finalizedHeader.number is <= headHeader.number # At this point finalizedHeader.number is <= headHeader.number
# and possibly switched to other chain beside the one with cursor # and possibly switched to other chain beside the one with cursor
doAssert(finalizedHeader.number <= head.header.number) doAssert(finalizedHeader.number <= pvarc.pvNumber)
doAssert(newBase.header.number <= finalizedHeader.number) doAssert(newBase.pvNumber <= finalizedHeader.number)
# Write segment from base+1 to newBase into database # Write segment from base+1 to newBase into database
c.stagingTx.rollback() c.stagingTx.rollback()
c.stagingTx = c.db.ctx.newTransaction() c.stagingTx = c.db.ctx.newTransaction()
if newBase.header.number > c.baseHeader.number: if newBase.pvNumber > c.baseHeader.number:
c.replaySegment(newBase.hash) c.replaySegment(newBase.pvHash)
c.writeBaggage(newBase.hash) c.writeBaggage(newBase.pvHash)
c.stagingTx.commit() c.stagingTx.commit()
c.stagingTx = nil c.stagingTx = nil
# Update base forward to newBase # Update base forward to newBase
c.updateBase(newBase.hash, newBase.header, head.cursorHash) c.updateBase(newBase)
c.db.persistent(newBase.header.number).isOkOr: c.db.persistent(newBase.pvNumber).isOkOr:
return err("Failed to save state: " & $$error) return err("Failed to save state: " & $$error)
if c.stagingTx.isNil: if c.stagingTx.isNil:
@ -593,16 +619,16 @@ proc forkChoice*(c: ForkedChainRef,
c.stagingTx = c.db.ctx.newTransaction() c.stagingTx = c.db.ctx.newTransaction()
# Move chain state forward to current head # Move chain state forward to current head
if newBase.header.number < head.header.number: if newBase.pvNumber < pvarc.pvNumber:
c.replaySegment(headHash) c.replaySegment(pvarc.pvHash)
c.setHead(headHash, head.header.number) c.setHead(pvarc)
# Move cursor to current head # Move cursor to current head
c.trimCanonicalChain(head, headHash) c.trimCursorArc(pvarc)
if c.cursorHash != headHash: if c.cursorHash != pvarc.pvHash:
c.cursorHeader = head.header c.cursorHeader = pvarc.pvHeader
c.cursorHash = headHash c.cursorHash = pvarc.pvHash
ok() ok()

View File

@ -17,22 +17,17 @@ import
type type
CursorDesc* = object CursorDesc* = object
forkJunction*: BlockNumber forkJunction*: BlockNumber ## Bottom or left end of cursor arc
hash*: Hash32 hash*: Hash32 ## Top or right end of cursor arc
BlockDesc* = object BlockDesc* = object
blk*: Block blk*: Block
receipts*: seq[Receipt] receipts*: seq[Receipt]
BaseDesc* = object PivotArc* = object
hash*: Hash32 pvHash*: Hash32 ## Pivot item on cursor arc (e.g. new base)
header*: Header pvHeader*: Header ## Ditto
cursor*: CursorDesc ## Cursor arc containing `pv` item
CanonicalDesc* = object
## Designate some `header` entry on a `CursorDesc` sub-chain named
## `cursorDesc` identified by `cursorHash == cursorDesc.hash`.
cursorHash*: Hash32
header*: Header
ForkedChainRef* = ref object ForkedChainRef* = ref object
stagingTx*: CoreDbTxRef stagingTx*: CoreDbTxRef
@ -50,6 +45,10 @@ type
# ---------------- # ----------------
func pvNumber*(pva: PivotArc): BlockNumber =
## Getter
pva.pvHeader.number
func txRecords*(c: ForkedChainRef): var Table[Hash32, (Hash32, uint64)] = func txRecords*(c: ForkedChainRef): var Table[Hash32, (Hash32, uint64)] =
## Avoid clash with `forked_chain.txRecords()` ## Avoid clash with `forked_chain.txRecords()`
c.txRecords c.txRecords

View File

@ -241,7 +241,7 @@ proc forkedChainMain*() =
check com.wdWritten(blk3) == 3 check com.wdWritten(blk3) == 3
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase == oldBase, fork and keep on that fork": test "newBase == oldBase, fork and stay on that fork":
const info = "newBase == oldBase, fork .." const info = "newBase == oldBase, fork .."
let com = env.newCom() let com = env.newCom()
@ -266,7 +266,7 @@ proc forkedChainMain*() =
check chain.latestHash == B7.blockHash check chain.latestHash == B7.blockHash
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase == cursor, fork and keep on that fork": test "newBase == cursor, fork and stay on that fork":
const info = "newBase == cursor, fork .." const info = "newBase == cursor, fork .."
let com = env.newCom() let com = env.newCom()
@ -294,8 +294,8 @@ proc forkedChainMain*() =
check chain.latestHash == B7.blockHash check chain.latestHash == B7.blockHash
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase between oldBase and cursor, fork and keep on that fork": test "newBase on shorter canonical arc, discard arc with oldBase":
const info = "newBase between oldBase .." const info = "newBase on shorter canonical .."
let com = env.newCom() let com = env.newCom()
var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3) var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3)
@ -318,6 +318,37 @@ proc forkedChainMain*() =
check com.headHash == B7.blockHash check com.headHash == B7.blockHash
check chain.latestHash == B7.blockHash check chain.latestHash == B7.blockHash
check chain.baseNumber >= B4.header.number
check chain.cursorHeads.len == 1
check chain.validate info & " (9)"
test "newBase on curbed non-canonical arc":
const info = "newBase on curbed non-canonical .."
let com = env.newCom()
var chain = newForkedChain(com, com.genesisHeader, baseDistance = 5)
check chain.importBlock(blk1).isOk
check chain.importBlock(blk2).isOk
check chain.importBlock(blk3).isOk
check chain.importBlock(blk4).isOk
check chain.importBlock(blk5).isOk
check chain.importBlock(blk6).isOk
check chain.importBlock(blk7).isOk
check chain.importBlock(B4).isOk
check chain.importBlock(B5).isOk
check chain.importBlock(B6).isOk
check chain.importBlock(B7).isOk
check chain.validate info & " (1)"
check chain.forkChoice(B7.blockHash, B5.blockHash).isOk
check chain.validate info & " (2)"
check com.headHash == B7.blockHash
check chain.latestHash == B7.blockHash
check chain.baseNumber > 0
check chain.baseNumber < B4.header.number
check chain.cursorHeads.len == 2
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase == oldBase, fork and return to old chain": test "newBase == oldBase, fork and return to old chain":
@ -374,8 +405,9 @@ proc forkedChainMain*() =
check chain.latestHash == blk7.blockHash check chain.latestHash == blk7.blockHash
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase between oldBase and cursor, fork and return to old chain, switch to new chain": test "newBase on shorter canonical arc, discard arc with oldBase" &
const info = "newBase between oldBase and .." " (ign dup block)":
const info = "newBase on shorter canonical .."
let com = env.newCom() let com = env.newCom()
var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3) var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3)
@ -400,10 +432,12 @@ proc forkedChainMain*() =
check com.headHash == B7.blockHash check com.headHash == B7.blockHash
check chain.latestHash == B7.blockHash check chain.latestHash == B7.blockHash
check chain.baseNumber >= B4.header.number
check chain.cursorHeads.len == 1
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "newBase between oldBase and cursor, fork and return to old chain": test "newBase on longer canonical arc, discard arc with oldBase":
const info = "newBase between oldBase and .." const info = "newBase on longer canonical .."
let com = env.newCom() let com = env.newCom()
var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3) var chain = newForkedChain(com, com.genesisHeader, baseDistance = 3)
@ -426,6 +460,9 @@ proc forkedChainMain*() =
check com.headHash == blk7.blockHash check com.headHash == blk7.blockHash
check chain.latestHash == blk7.blockHash check chain.latestHash == blk7.blockHash
check chain.baseNumber > 0
check chain.baseNumber < blk5.header.number
check chain.cursorHeads.len == 1
check chain.validate info & " (9)" check chain.validate info & " (9)"
test "headerByNumber": test "headerByNumber":

View File

@ -90,7 +90,8 @@ func pp*(n: BlockNumber): string = n.bnStr
func pp*(h: Header): string = h.bnStr func pp*(h: Header): string = h.bnStr
func pp*(b: Block): string = b.bnStr func pp*(b: Block): string = b.bnStr
func pp*(h: Hash32): string = h.short func pp*(h: Hash32): string = h.short
func pp*(d: BaseDesc): string = d.header.pp func pp*(d: BlockDesc): string = d.blk.header.pp
func pp*(d: ptr BlockDesc): string = d[].pp
func pp*(q: openArray[Block]): string = q.ppImpl func pp*(q: openArray[Block]): string = q.ppImpl
func pp*(q: openArray[Header]): string = q.ppImpl func pp*(q: openArray[Header]): string = q.ppImpl
@ -107,15 +108,15 @@ func pp*(h: Hash32; c: ForkedChainRef): string =
return c.baseHeader.pp return c.baseHeader.pp
h.short h.short
func pp*(d: CanonicalDesc; c: ForkedChainRef): string =
"(" & d.cursorHash.header(c).number.pp & "," & d.header.pp & ")"
func pp*(d: CursorDesc; c: ForkedChainRef): string = func pp*(d: CursorDesc; c: ForkedChainRef): string =
let (a,b) = (d.forkJunction, d.hash.header(c).number) let (a,b) = (d.forkJunction, d.hash.header(c).number)
result = a.bnStr result = a.bnStr
if a != b: if a != b:
result &= ".." & (if b == 0: d.hash.pp else: b.pp) result &= ".." & (if b == 0: d.hash.pp else: b.pp)
func pp*(d: PivotArc; c: ForkedChainRef): string =
"(" & d.pvHeader.pp & "," & d.cursor.pp(c) & ")"
func pp*(q: openArray[CursorDesc]; c: ForkedChainRef): string = func pp*(q: openArray[CursorDesc]; c: ForkedChainRef): string =
"{" & q.sorted(c.cmp CursorDesc).mapIt(it.pp(c)).join(",") & "}" "{" & q.sorted(c.cmp CursorDesc).mapIt(it.pp(c)).join(",") & "}"