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:
parent
dc81863c3a
commit
90dd86be9a
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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(",") & "}"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue