Processing math: 81%
ouroboros-consensus-0.26.0.0: Consensus layer for the Ouroboros blockchain protocol
Safe HaskellNone
LanguageHaskell2010

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 provide LedgerTables 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 the last k in-memory ledger states. This data type is impemented 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 fully saturated DbChangelog (i.e. that contains k states), the ledger state at the anchor is dropped and the oldest element in the sequence becomes the new anchor, as it has become immutable. Note that we only refer here to the in-memory states, as the diffs from the anchor will remain in the DbChangelog until flushing happens. This maintains the invariant that only the last k in-memory ledger states are stored, excluding the ledger state at the anchor. This means that in practice, k+1 ledger states will be kept in memory. When the DbChangelog contains fewer than k elements, new ones are appended without shifting the anchor until it is saturated.

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 LedgerTables 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

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):

lastFlushedstatestableDiffs
L0L0 :> [ ] [ ]
L0L0 :> [ L1 ] [ D1 ]
L0L0 :> [ L1, L2 ] [ D1, D2 ]
L0L0 :> [ L1, L2, L3 ] [ D1, D2, D3 ]
L0L1 :> [ L2, L3, L4 ] [ D1, D2, D3, D4 ]
L0L2 :> [ L3, L4, L5 ] [ D1, D2, D3, D4, D5 ] -- (*)
L2L2 :> [ L3, L4, L5 ] [ D3, D4, D5 ] -- flush (**)
L2L3 :> [ 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:

L0L2 :> [ ][ D1, D2 ]

And a rollback of k blocks at (**) will look something like this:

L2L2 :> [ ][ ]

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

Instances details
Generic (DbChangelog l) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Associated Types

type Rep (DbChangelog l) 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

type Rep (DbChangelog l) = D1 ('MetaData "DbChangelog" "Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog" "ouroboros-consensus-0.26.0.0-inplace" 'False) (C1 ('MetaCons "DbChangelog" 'PrefixI 'True) (S1 ('MetaSel ('Just "changelogLastFlushedState") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (l EmptyMK)) :*: (S1 ('MetaSel ('Just "changelogDiffs") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (LedgerTables l SeqDiffMK)) :*: S1 ('MetaSel ('Just "changelogStates") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (AnchoredSeq (WithOrigin SlotNo) (l EmptyMK) (l EmptyMK))))))

Methods

from โˆท DbChangelog l โ†’ Rep (DbChangelog l) x #

to โˆท Rep (DbChangelog l) x โ†’ DbChangelog l #

(Show (TxIn l), Show (TxOut l), Show (l EmptyMK)) โ‡’ Show (DbChangelog l) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Methods

showsPrec โˆท Int โ†’ DbChangelog l โ†’ ShowS #

show โˆท DbChangelog l โ†’ String #

showList โˆท [DbChangelog l] โ†’ ShowS #

(Eq (TxIn l), Eq (TxOut l), Eq (l EmptyMK)) โ‡’ Eq (DbChangelog l) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Methods

(==) โˆท DbChangelog l โ†’ DbChangelog l โ†’ Bool #

(/=) โˆท DbChangelog l โ†’ DbChangelog l โ†’ Bool #

(NoThunks (TxIn l), NoThunks (TxOut l), NoThunks (l EmptyMK)) โ‡’ NoThunks (DbChangelog l) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Methods

noThunks โˆท Context โ†’ DbChangelog l โ†’ IO (Maybe ThunkInfo) Source #

wNoThunks โˆท Context โ†’ DbChangelog l โ†’ IO (Maybe ThunkInfo) Source #

showTypeOf โˆท Proxy (DbChangelog l) โ†’ String Source #

IsLedger l โ‡’ GetTip (K (DbChangelog l) โˆท MapKind โ†’ Type) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Methods

getTip โˆท โˆ€ (mk โˆท MapKind). K (DbChangelog l) mk โ†’ Point (K (DbChangelog l) โˆท MapKind โ†’ Type) Source #

type Rep (DbChangelog l) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

type Rep (DbChangelog l) = D1 ('MetaData "DbChangelog" "Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog" "ouroboros-consensus-0.26.0.0-inplace" 'False) (C1 ('MetaCons "DbChangelog" 'PrefixI 'True) (S1 ('MetaSel ('Just "changelogLastFlushedState") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (l EmptyMK)) :*: (S1 ('MetaSel ('Just "changelogDiffs") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (LedgerTables l SeqDiffMK)) :*: S1 ('MetaSel ('Just "changelogStates") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (AnchoredSeq (WithOrigin SlotNo) (l EmptyMK) (l EmptyMK))))))
type HeaderHash (K (DbChangelog l) โˆท MapKind โ†’ Type) Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

type HeaderHash (K (DbChangelog l) โˆท MapKind โ†’ Type) = HeaderHash l

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.

lastFlushedstatestableDiffs
L0L0 :> [ L1, L2, L3, L4 ][ D1, D2, D3, D4 ]
>> pruneToImmTipOnly
L0L4 :> [ ][ 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:

  1. 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.
  2. Query the BackingStore for the actual values for the requested keys.
  3. 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 #

Forward

data RewindReadFwdError Source #

The DbChangelog and the BackingStore got out of sync. This is a critical error, we cannot recover from this.

Instances

Instances details
Show RewindReadFwdError Source # 
Instance details

Defined in Ouroboros.Consensus.Storage.LedgerDB.V1.DbChangelog

Methods

showsPrec โˆท Int โ†’ RewindReadFwdError โ†’ ShowS #

show โˆท RewindReadFwdError โ†’ String #

showList โˆท [RewindReadFwdError] โ†’ ShowS #

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.

lastFlushedstatestableDiffs
L2L3 :> [ L4, L5, L6 ][ D2, D3, D4, D5, D6 ]
>> splitForFlushing
L2--[ D2, D3 ] -- this is a DiffsToFlush
L3L3 :> [ 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).

current โˆท GetTip l โ‡’ DbChangelog l โ†’ l EmptyMK Source #

The ledger state at the tip of the chain

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 #

Get a past ledger state

O(log(min

When no ledger state (or anchor) has the given Point, Nothing is returned.

rollback โˆท โˆ€ blk (l โˆท LedgerStateKind). (HasHeader blk, IsLedger l, HeaderHash l ~ HeaderHash blk, StandardHash l, HasLedgerTables l) โ‡’ Point blk โ†’ DbChangelog l โ†’ Maybe (DbChangelog l) Source #

Get a prefix of the DbChangelog that ends at the given point

O(\log(\min(i,n-i))

When no ledger state (or anchor) has the given Point, Nothing is returned.

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.

L2L2 :> [ L3, L4, L5 ][ D3, D4, D5 ]
>> extend L6 (D6)
L2L2 :> [ 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 until at we have at most k in the DbChangelog, excluding the one stored at the anchor.

lastFlushedstatestableDiffs
L0L0 :> [ L1, L2, L3, L4 ][ D1, D2, D3, D4 ]
>> prune (SecurityParam 3)
L0L2 :> [ L3, L4 ][ D1, D2, D3, D4 ]

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.

lastFlushedstatestableDiffs
L2L3 :> [ L4, L5, L6 ][ D2, D3, D4, D5, D6 ]
>> rollback 3
L2L3 :> [ ] [ 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 #

Orphan instances

GetTip l โ‡’ Anchorable (WithOrigin SlotNo) (l EmptyMK) (l EmptyMK) Source # 
Instance details

Methods

asAnchor โˆท l EmptyMK โ†’ l EmptyMK Source #

getAnchorMeasure โˆท Proxy (l EmptyMK) โ†’ l EmptyMK โ†’ WithOrigin SlotNo Source #