Safe Haskell | None |
---|---|
Language | Haskell2010 |
Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog
Description
A DbChangelog
is the component of the
LedgerDB
implementation that
responsible for:
- Maintaining the last \(k\) in-memory ledger states without on-disk parks.
- Holding the in-memory ledger state that a snapshot would write to the disk.
- Providing sequences of differences from said state to any requested state
in the last \(k\) ledger states, which combined with the values in the
BackingStore
, can provideLedgerTable
s at any of those ledger states.
A DbChangelog
is said to be anchored at a BackingStore
when
the slot of the values in the backing store is the predecesor of the slots in
the sequence of differences, with the overall sequence of slots being defined
by the blocks on the chain.
This design is based on the technical report "Storing the Cardano ledger state on disk: API design concepts" by Duncan Coutts and Douglas Wilson.
Implementation details
The DbChangelog
is in fact a pure data structure, of which the LedgerDB
will carry a value in some mutable state, see
LedgerDBState
.
Carrying states
The DbChangelog
contains an instantiation of the AnchoredSeq
data type to
hold (at least) the last \(k\) in-memory ledger states. This data type is
implemented using the finger tree data structure and has the following time
complexities:
- Appending a new ledger state to the end in constant time.
- Rolling back to a previous ledger state in logarithmic time.
- Looking up a past ledger state by its point in logarithmic time.
One can think of AnchoredSeq
as a Seq
from Data.Sequence with a custom
finger tree measure allowing for efficient lookups by point, combined with
an anchor. When fully saturated, the sequence will contain \(k\) ledger
states. In case of a complete rollback of all \(k\) blocks and thus ledger
states, the sequence will become empty. A ledger state is still needed, i.e.,
one corresponding to the most recent immutable block that cannot be rolled
back. The ledger state at the anchor plays this role.
Appending in-memory states
When a new ledger state is appended to a DbChangelog
, the ledger state at
the anchor is now subject to pruning/garbage collection as they are
immutable. This means that in practice, slightly more than \(k + 1\) ledger
states will be kept in memory. When the DbChangelog
contains fewer than
\(k\) elements, new ones are appended without causing the ones near the
anchor to be pruned/garbage-collected.
Getting and appending differences
For the differences, the DbChangelog
contains a SeqDiffMK
(see
Ouroboros.Consensus.Storage.LedgerDB.V1.DiffSeq) which in turn is just an
instantiation of a root-measured finger tree (see
fingertree-rm)
which is a specialization of the finger trees that carries a root-measure
which is the monoidal sum of all the measures of all the elements.
This allows us to very efficiently lookup the combined difference of the
whole DbChangelog
, while still having a good complexity when splitting this
tree.
When a block is to be applied to a ledger state (which must be in the
DbChangelog
or application would directly fail), applying the root-measure
of the sub-sequence of differences from the backing store slot up to the
requested slot to the values read from the backing store will provide the
LedgerTable
s needed for applying the block.
Once a new ledger state is appended to the DbChangelog
, said ledger state
will carry DiffMK
tables (obtained by diffing the input and output ledger
tables when calling the Ledger rules). Adding those differences to the
DbChangelog
is just a matter of extending the carried SeqDiffMK
.
Only when flushing, the SeqDiffMK
is pruned, by extracting the differences
in between the last flushed state and the current immutable tip.
Synopsis
- data DbChangelog (l ∷ (Type → Type → Type) → Type) = DbChangelog {
- changelogLastFlushedState ∷ !(l EmptyMK)
- changelogDiffs ∷ !(LedgerTables l SeqDiffMK)
- changelogStates ∷ !(AnchoredSeq (WithOrigin SlotNo) (l EmptyMK) (l EmptyMK))
- type DbChangelog' blk = DbChangelog (ExtLedgerState blk)
- empty ∷ (HasLedgerTables l, GetTip l) ⇒ l EmptyMK → DbChangelog l
- pruneToImmTipOnly ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → DbChangelog l
- reapplyThenPush ∷ (Monad m, ApplyBlock l blk) ⇒ LedgerDbCfg l → blk → KeySetsReader m l → DbChangelog l → m (DbChangelog l)
- withKeysReadSets ∷ (HasLedgerTables l, Monad m, GetTip l) ⇒ l EmptyMK → KeySetsReader m l → DbChangelog l → LedgerTables l KeysMK → (l ValuesMK → m a) → m a
- type KeySetsReader (m ∷ Type → Type) (l ∷ (Type → Type → Type) → Type) = l EmptyMK → LedgerTables l KeysMK → m (UnforwardedReadSets l)
- data UnforwardedReadSets (l ∷ LedgerStateKind) = UnforwardedReadSets {
- ursSeqNo ∷ !(WithOrigin SlotNo)
- ursValues ∷ !(LedgerTables l ValuesMK)
- ursKeys ∷ !(LedgerTables l KeysMK)
- readKeySets ∷ IOLike m ⇒ LedgerBackingStore m l → KeySetsReader m l
- readKeySetsWith ∷ Monad m ⇒ LedgerBackingStoreValueHandle m l → KeySetsReader m l
- trivialKeySetsReader ∷ (Monad m, LedgerTablesAreTrivial l) ⇒ WithOrigin SlotNo → KeySetsReader m l
- data RewindReadFwdError = RewindReadFwdError {}
- forwardTableKeySets ∷ ∀ (l ∷ LedgerStateKind). (HasLedgerTables l, GetTip l) ⇒ DbChangelog l → UnforwardedReadSets l → Either RewindReadFwdError (LedgerTables l ValuesMK)
- forwardTableKeySets' ∷ ∀ (l ∷ LedgerStateKind). HasLedgerTables l ⇒ WithOrigin SlotNo → LedgerTables l SeqDiffMK → UnforwardedReadSets l → Either RewindReadFwdError (LedgerTables l ValuesMK)
- data DiffsToFlush (l ∷ LedgerStateKind) = DiffsToFlush {
- toFlushDiffs ∷ !(LedgerTables l DiffMK)
- toFlushState ∷ !(l EmptyMK, l EmptyMK)
- toFlushSlot ∷ !SlotNo
- splitForFlushing ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ DbChangelog l → (Maybe (DiffsToFlush l), DbChangelog l)
- anchor ∷ DbChangelog l → l EmptyMK
- current ∷ GetTip l ⇒ DbChangelog l → l EmptyMK
- flushableLength ∷ ∀ (l ∷ LedgerStateKind). (HasLedgerTables l, GetTip l) ⇒ DbChangelog l → Word64
- getPastLedgerAt ∷ (HasHeader blk, IsLedger l, HeaderHash l ~ HeaderHash blk, StandardHash l, HasLedgerTables l) ⇒ Point blk → DbChangelog l → Maybe (l EmptyMK)
- rollback ∷ ∀ blk (l ∷ LedgerStateKind). (HasHeader blk, IsLedger l, HeaderHash l ~ HeaderHash blk, StandardHash l, HasLedgerTables l) ⇒ Point blk → DbChangelog l → Maybe (DbChangelog l)
- snapshots ∷ DbChangelog l → [(Word64, l EmptyMK)]
- tip ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → Point l
- volatileStatesBimap ∷ Anchorable (WithOrigin SlotNo) a b ⇒ (l EmptyMK → a) → (l EmptyMK → b) → DbChangelog l → AnchoredSeq (WithOrigin SlotNo) a b
- extend ∷ (GetTip l, HasLedgerTables l) ⇒ l DiffMK → DbChangelog l → DbChangelog l
- immutableTipSlot ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → WithOrigin SlotNo
- isSaturated ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ SecurityParam → DbChangelog l → Bool
- maxRollback ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → Word64
- prune ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ LedgerDbPrune → DbChangelog l → DbChangelog l
- rollbackN ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ Word64 → DbChangelog l → Maybe (DbChangelog l)
- rollbackToAnchor ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ DbChangelog l → DbChangelog l
- rollbackToPoint ∷ ∀ (l ∷ LedgerStateKind). (StandardHash l, GetTip l, HasLedgerTables l) ⇒ Point l → DbChangelog l → Maybe (DbChangelog l)
- reapplyThenPush' ∷ ApplyBlock l blk ⇒ LedgerDbCfg l → blk → KeySetsReader Identity l → DbChangelog l → DbChangelog l
- reapplyThenPushMany' ∷ ∀ (l ∷ LedgerStateKind) blk. (ApplyBlock l blk, LedgerTablesAreTrivial l) ⇒ LedgerDbCfg l → [blk] → DbChangelog l → DbChangelog l
- switch ∷ (ApplyBlock l blk, Monad m) ⇒ LedgerDbCfg l → Word64 → [blk] → KeySetsReader m l → DbChangelog l → m (Either ExceededRollback (DbChangelog l))
- switch' ∷ ∀ (l ∷ LedgerStateKind) blk. (ApplyBlock l blk, LedgerTablesAreTrivial l) ⇒ LedgerDbCfg l → Word64 → [blk] → DbChangelog l → Maybe (DbChangelog l)
The DbChangelog
data DbChangelog (l ∷ (Type → Type → Type) → Type) Source #
Holds a sequence of split ledger states, where the in-memory part is in a
sequence and the on-disk part is represented by a sequence of differences
that need a BackingStore
as an anchor point.
We illustrate its contents below, where k = 3
(for a state Li
, the
corresponding set of differences is Di
), assuming that we prune after every
step:
lastFlushed | states | tableDiffs |
---|---|---|
L0 | L0 :> [ ] | [ ] |
L0 | L0 :> [ L1 ] | [ D1 ] |
L0 | L0 :> [ L1, L2 ] | [ D1, D2 ] |
L0 | L0 :> [ L1, L2, L3 ] | [ D1, D2, D3 ] |
L0 | L1 :> [ L2, L3, L4 ] | [ D1, D2, D3, D4 ] |
L0 | L2 :> [ L3, L4, L5 ] | [ D1, D2, D3, D4, D5 ] -- (*) |
L2 | L2 :> [ L3, L4, L5 ] | [ D3, D4, D5 ] -- flush (**) |
L2 | L3 :> [ L4, L5, L6 ] | [ D3, D4, D5, D6 ] |
Notice that length states
is usually k
except when rollbacks or data
corruption take place and will be less than k
when we just loaded a
snapshot. We cannot roll back more than k
blocks. This means that after a
rollback of k
blocks at (*)
, the changelog will look something like this:
L0 | L2 :> [ ] | [ D1, D2 ] |
And a rollback of k
blocks at (**)
will look something like this:
L2 | L2 :> [ ] | [ ] |
Notice how the states list always contains the in-memory state of the anchor, but the table differences might not contain the differences for that anchor if they have been flushed to the backend.
As said above, this DbChangelog
has to be coupled with a BackingStore
which provides the pointers to the on-disk data.
Constructors
DbChangelog | |
Fields
|
Instances
type DbChangelog' blk = DbChangelog (ExtLedgerState blk) Source #
Construction
empty ∷ (HasLedgerTables l, GetTip l) ⇒ l EmptyMK → DbChangelog l Source #
Creates an empty DbChangelog
.
pruneToImmTipOnly ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → DbChangelog l Source #
When creating a new DbChangelog
, we should load whichever snapshot we
find and then replay the chain up to the immutable tip. When we get there,
the DbChangelog
will have a k
-long sequence of states, which all come
from immutable blocks, so we just prune all of them and only keep the last
one as an anchor, as it is the immutable tip. Then we can proceed with
opening the VolatileDB.
If we didn't do this step, the DbChangelog
would accept rollbacks into the
immutable part of the chain, which must never be possible.
lastFlushed | states | tableDiffs |
---|---|---|
L0 | L0 :> [ L1, L2, L3, L4 ] | [ D1, D2, D3, D4 ] |
>> pruneToImmTipOnly | ||
L0 | L4 :> [ ] | [ D1, D2, D3, D4 ] |
Updating a DbChangelog
Applying blocks
Applying blocks to the DbChangelog
will extend it if the result is
successful.
In order to do so, we first need to find the particular block, then prepare the ledger tables by hydrating the ledger state and then finally call the ledger, which might throw errors.
reapplyThenPush ∷ (Monad m, ApplyBlock l blk) ⇒ LedgerDbCfg l → blk → KeySetsReader m l → DbChangelog l → m (DbChangelog l) Source #
Apply a block on top of the ledger state and extend the DbChangelog with the result ledger state.
Hydrating the ledger state
When trying to get tables at a specific ledger state, we must follow a process we call hydrating the ledger state. This process consists of 3 steps:
- Rewind the requested keys to the beginning of the DbChangelog. For UTxO entries this just means that we record at which slot the db changelog was when rewinding.
- Query the
BackingStore
for the actual values for the requested keys. - Forward those values by applying the differences in the
DbChangelog
up to the requested point.
withKeysReadSets ∷ (HasLedgerTables l, Monad m, GetTip l) ⇒ l EmptyMK → KeySetsReader m l → DbChangelog l → LedgerTables l KeysMK → (l ValuesMK → m a) → m a Source #
Read
type KeySetsReader (m ∷ Type → Type) (l ∷ (Type → Type → Type) → Type) = l EmptyMK → LedgerTables l KeysMK → m (UnforwardedReadSets l) Source #
data UnforwardedReadSets (l ∷ LedgerStateKind) Source #
Constructors
UnforwardedReadSets | |
Fields
|
readKeySets ∷ IOLike m ⇒ LedgerBackingStore m l → KeySetsReader m l Source #
readKeySetsWith ∷ Monad m ⇒ LedgerBackingStoreValueHandle m l → KeySetsReader m l Source #
trivialKeySetsReader ∷ (Monad m, LedgerTablesAreTrivial l) ⇒ WithOrigin SlotNo → KeySetsReader m l Source #
Forward
data RewindReadFwdError Source #
The DbChangelog and the BackingStore got out of sync. This is a critical error, we cannot recover from this.
Constructors
RewindReadFwdError | |
Fields |
Instances
Show RewindReadFwdError Source # | |
Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog Methods showsPrec ∷ Int → RewindReadFwdError → ShowS # show ∷ RewindReadFwdError → String # showList ∷ [RewindReadFwdError] → ShowS # |
forwardTableKeySets ∷ ∀ (l ∷ LedgerStateKind). (HasLedgerTables l, GetTip l) ⇒ DbChangelog l → UnforwardedReadSets l → Either RewindReadFwdError (LedgerTables l ValuesMK) Source #
forwardTableKeySets' ∷ ∀ (l ∷ LedgerStateKind). HasLedgerTables l ⇒ WithOrigin SlotNo → LedgerTables l SeqDiffMK → UnforwardedReadSets l → Either RewindReadFwdError (LedgerTables l ValuesMK) Source #
Flushing
data DiffsToFlush (l ∷ LedgerStateKind) Source #
A container for differences that are inteded to be flushed to a
BackingStore
Constructors
DiffsToFlush | |
Fields
|
splitForFlushing ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ DbChangelog l → (Maybe (DiffsToFlush l), DbChangelog l) Source #
" Flush " the DbChangelog
by splitting it into the diffs that should be
flushed and the new DbChangelog
.
lastFlushed | states | tableDiffs |
---|---|---|
L2 | L3 :> [ L4, L5, L6 ] | [ D2, D3, D4, D5, D6 ] |
>> splitForFlushing | ||
L2 | -- | [ D2, D3 ] -- this is a |
L3 | L3 :> [ L4, L5, L6 ] | [ D4, D5, D6 ] |
Queries
anchor ∷ DbChangelog l → l EmptyMK Source #
The ledger state at the anchor of the Volatile chain (i.e. the immutable tip).
flushableLength ∷ ∀ (l ∷ LedgerStateKind). (HasLedgerTables l, GetTip l) ⇒ DbChangelog l → Word64 Source #
How many diffs we can flush to the backing store?
NOTE: This will be wrong once we have more than one table.
getPastLedgerAt ∷ (HasHeader blk, IsLedger l, HeaderHash l ~ HeaderHash blk, StandardHash l, HasLedgerTables l) ⇒ Point blk → DbChangelog l → Maybe (l EmptyMK) Source #
rollback ∷ ∀ blk (l ∷ LedgerStateKind). (HasHeader blk, IsLedger l, HeaderHash l ~ HeaderHash blk, StandardHash l, HasLedgerTables l) ⇒ Point blk → DbChangelog l → Maybe (DbChangelog l) Source #
snapshots ∷ DbChangelog l → [(Word64, l EmptyMK)] Source #
All snapshots currently stored by the ledger DB (new to old)
This also includes the snapshot at the anchor. For each snapshot we also return the distance from the tip.
tip ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → Point l Source #
Reference to the block at the tip of the chain
volatileStatesBimap ∷ Anchorable (WithOrigin SlotNo) a b ⇒ (l EmptyMK → a) → (l EmptyMK → b) → DbChangelog l → AnchoredSeq (WithOrigin SlotNo) a b Source #
Transform the underlying volatile AnchoredSeq
using the given functions.
🧪 Testing
Internal
extend ∷ (GetTip l, HasLedgerTables l) ⇒ l DiffMK → DbChangelog l → DbChangelog l Source #
Extending the DbChangelog with a valid ledger state.
L2 | L2 :> [ L3, L4, L5 ] | [ D3, D4, D5 ] |
>> extend L6 (D6) | ||
L2 | L2 :> [ L3, L4, L5, L6 ] | [ D3, D4, D5, D6 ] |
immutableTipSlot ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → WithOrigin SlotNo Source #
isSaturated ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ SecurityParam → DbChangelog l → Bool Source #
Have we seen at least k
blocks?
maxRollback ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ DbChangelog l → Word64 Source #
How many blocks can we currently roll back?
prune ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ LedgerDbPrune → DbChangelog l → DbChangelog l Source #
Prune oldest ledger states according to the given LedgerDbPrune
strategy.
lastFlushed | states | tableDiffs |
---|---|---|
L0 | L0 :> [ L1, L2, L3, L4 ] | [ D1, D2, D3, D4 ] |
>> prune (LedgerDbPruneBeforeSlot 3) | ||
L0 | L2 :> [ L3, L4 ] | [ D1, D2, D3, D4 ] |
where the state LX
is from slot X
.
rollbackN ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ Word64 → DbChangelog l → Maybe (DbChangelog l) Source #
Rollback n
ledger states.
Returns Nothing
if maximum rollback (usually k
, but can be less on
startup or under corruption) is exceeded.
lastFlushed | states | tableDiffs |
---|---|---|
L2 | L3 :> [ L4, L5, L6 ] | [ D2, D3, D4, D5, D6 ] |
>> rollback 3 | ||
L2 | L3 :> [ ] | [ D2, D3 ] |
rollbackToAnchor ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ DbChangelog l → DbChangelog l Source #
Rollback the volatile states up to the volatile anchor.
rollbackToPoint ∷ ∀ (l ∷ LedgerStateKind). (StandardHash l, GetTip l, HasLedgerTables l) ⇒ Point l → DbChangelog l → Maybe (DbChangelog l) Source #
Roll back the volatile states up to the specified point.
Testing
reapplyThenPush' ∷ ApplyBlock l blk ⇒ LedgerDbCfg l → blk → KeySetsReader Identity l → DbChangelog l → DbChangelog l Source #
reapplyThenPushMany' ∷ ∀ (l ∷ LedgerStateKind) blk. (ApplyBlock l blk, LedgerTablesAreTrivial l) ⇒ LedgerDbCfg l → [blk] → DbChangelog l → DbChangelog l Source #
switch ∷ (ApplyBlock l blk, Monad m) ⇒ LedgerDbCfg l → Word64 → [blk] → KeySetsReader m l → DbChangelog l → m (Either ExceededRollback (DbChangelog l)) Source #
switch' ∷ ∀ (l ∷ LedgerStateKind) blk. (ApplyBlock l blk, LedgerTablesAreTrivial l) ⇒ LedgerDbCfg l → Word64 → [blk] → DbChangelog l → Maybe (DbChangelog l) Source #