ouroboros-consensus-0.27.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 (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 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 ∷ (TypeTypeType) → 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:

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.27.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

fromDbChangelog l → Rep (DbChangelog l) x #

toRep (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

showsPrecIntDbChangelog l → ShowS #

showDbChangelog 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

IsLedger l ⇒ GetTip (K (DbChangelog l) ∷ MapKindType) Source # 
Instance details

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

Methods

getTip ∷ ∀ (mk ∷ MapKind). K (DbChangelog l) mk → Point (K (DbChangelog l) ∷ MapKindType) 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.27.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) ∷ MapKindType) Source # 
Instance details

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

Construction

empty ∷ (HasLedgerTables l, GetTip l) ⇒ l EmptyMKDbChangelog 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 EmptyMKKeySetsReader m l → DbChangelog l → LedgerTables l KeysMK → (l ValuesMK → m a) → m a Source #

Read

type KeySetsReader (m ∷ TypeType) (l ∷ (TypeTypeType) → Type) = l EmptyMKLedgerTables l KeysMK → m (UnforwardedReadSets l) Source #

data UnforwardedReadSets (l ∷ LedgerStateKind) Source #

Constructors

UnforwardedReadSets 

Fields

Forward

data RewindReadFwdError Source #

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

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

anchorDbChangelog l → l EmptyMK Source #

The ledger state at the anchor of the Volatile chain (i.e. the immutable tip).

currentGetTip 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(i,n-i)) \)

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.

snapshotsDbChangelog 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

volatileStatesBimapAnchorable (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 DiffMKDbChangelog 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 ]

isSaturated ∷ ∀ (l ∷ LedgerStateKind). GetTip l ⇒ SecurityParamDbChangelog 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 ⇒ LedgerDbPruneDbChangelog l → DbChangelog l Source #

Prune oldest ledger states according to the given LedgerDbPrune strategy.

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

where the state LX is from slot X.

rollbackN ∷ ∀ (l ∷ LedgerStateKind). (GetTip l, HasLedgerTables l) ⇒ Word64DbChangelog 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

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