1588 commits this week Jul 09, 2019 - Jul 16, 2019

Make block production race-free w.r.t. the Mempool state

In Ouroboros.Consensus.Node we take a snapshot of the Mempool to obtain transactions that we can put in the block we’re producing. Before obtaining that snapshot, the Mempool state should be synched with the current ledger state, so that transactions that are no longer valid are removed from the Mempool, because we don’t want to put invalid transactions in the block we’re going to produce.

In #696, we made Mempool.syncState live in STM instead of m, mainly to be able to trace the removed transactions. However, we forgot to correctly update the block-producing code in Ouroboros.Consensus.Node: we weren’t syncing the Mempool state anymore before obtaining a snapshot.

Since syncState no longer lives in STM, we can no longer sync the Mempool state and produce a block using a in-sync Mempool snapshot /in a single STM transaction/. This means that after we have synced the Mempool state and obtained a snapshot from it, transactions in that snapshot might become invalid before they even end up in the block we’re producing. This will be caught later on, but avoiding it would be cleaner.

This commit offers a solution that combines both: tracing /and/ syncing the mempool + producing a block in a single STM transaction. To accomplish this, the Mempool provides:

withSyncState :: forall a.
                 (MempoolSnapshot (GenTxId blk) (GenTx blk) idx -> STM m a)
              -> m a

This synchronises the Mempool state and calls the given function with an in-sync Mempool snapshot. This is all executed in a single STM transaction. Afterwards, the transactions removed during syncing are traced and the result of the STM transaction is returned. This function can be used instead of atomically.

Note that there is still another race condition: after producing a block with valid transactions (in single STM transaction), the current ledger state might still change (and invalidate one or more transactions in the block) before we are able to add the block to the ChainDB.