|
1 | | -{-# LANGUAGE DerivingStrategies #-} |
2 | | -{-# LANGUAGE GADTs #-} |
3 | | -{-# LANGUAGE NamedFieldPuns #-} |
4 | | -{-# LANGUAGE ScopedTypeVariables #-} |
5 | | -{-# LANGUAGE StandaloneDeriving #-} |
| 1 | +{-# LANGUAGE BangPatterns #-} |
| 2 | +{-# LANGUAGE DerivingStrategies #-} |
| 3 | +{-# LANGUAGE DisambiguateRecordFields #-} |
| 4 | +{-# LANGUAGE FlexibleContexts #-} |
| 5 | +{-# LANGUAGE GADTs #-} |
| 6 | +{-# LANGUAGE NamedFieldPuns #-} |
| 7 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 8 | +{-# LANGUAGE StandaloneDeriving #-} |
| 9 | +{-# LANGUAGE TupleSections #-} |
| 10 | +{-# LANGUAGE TypeFamilies #-} |
6 | 11 |
|
7 | 12 | -- | The module should be imported qualified. |
8 | 13 | -- |
9 | 14 | module Ouroboros.Network.TxSubmission.Mempool.Simple |
10 | | - ( Mempool (..) |
| 15 | + ( InvalidTxsError |
| 16 | + , MempoolAddFail |
| 17 | + , Mempool (..) |
11 | 18 | , MempoolSeq (..) |
| 19 | + , MempoolWriter (..) |
12 | 20 | , empty |
13 | 21 | , new |
14 | 22 | , read |
15 | 23 | , getReader |
16 | 24 | , getWriter |
| 25 | + , writerAdapter |
17 | 26 | ) where |
18 | 27 |
|
19 | 28 | import Prelude hiding (read, seq) |
20 | 29 |
|
21 | 30 | import Control.Concurrent.Class.MonadSTM.Strict |
22 | | -import Control.Monad (when) |
| 31 | +import Control.DeepSeq |
| 32 | +import Control.Exception (assert) |
23 | 33 | import Control.Monad.Class.MonadThrow |
24 | | - |
| 34 | +import Control.Monad.Trans.Except |
25 | 35 | import Data.Bifunctor (bimap) |
26 | | -import Data.Either (partitionEithers) |
| 36 | +import Data.Either |
27 | 37 | import Data.Foldable (toList) |
28 | 38 | import Data.Foldable qualified as Foldable |
29 | | -import Data.Function (on) |
30 | | -import Data.List (find, nubBy) |
| 39 | +import Data.List (find) |
31 | 40 | import Data.Maybe (isJust) |
32 | 41 | import Data.Sequence (Seq) |
33 | 42 | import Data.Sequence qualified as Seq |
34 | 43 | import Data.Set (Set) |
35 | 44 | import Data.Set qualified as Set |
36 | | -import Data.Typeable (Typeable) |
37 | 45 |
|
| 46 | +import Ouroboros.Network.Protocol.LocalTxSubmission.Type (SubmitResult (..)) |
38 | 47 | import Ouroboros.Network.SizeInBytes |
39 | 48 | import Ouroboros.Network.TxSubmission.Inbound.V2.Types |
40 | 49 | import Ouroboros.Network.TxSubmission.Mempool.Reader |
@@ -105,69 +114,98 @@ getReader getTxId getTxSize (Mempool mempool) = |
105 | 114 | f :: Int -> tx -> (txid, Int, SizeInBytes) |
106 | 115 | f idx tx = (getTxId tx, idx, getTxSize tx) |
107 | 116 |
|
| 117 | +-- | type of mempool validation errors which are thrown as exceptions |
| 118 | +-- |
| 119 | +data family InvalidTxsError failure |
108 | 120 |
|
109 | | -data InvalidTxsError where |
110 | | - InvalidTxsError :: forall txid failure. |
111 | | - ( Typeable txid |
112 | | - , Typeable failure |
113 | | - , Show txid |
114 | | - , Show failure |
115 | | - ) |
116 | | - => [(txid, failure)] |
117 | | - -> InvalidTxsError |
118 | | - |
119 | | -deriving instance Show InvalidTxsError |
120 | | -instance Exception InvalidTxsError |
121 | | - |
| 121 | +-- | type of mempool validation errors which are non-fatal |
| 122 | +-- |
| 123 | +data family MempoolAddFail tx |
122 | 124 |
|
123 | | --- | A simple mempool writer. |
| 125 | +-- | A mempool writer which generalizes the tx submission mempool writer |
| 126 | +-- TODO: We could replace TxSubmissionMempoolWriter with this at some point |
| 127 | +-- |
| 128 | +data MempoolWriter txid tx failure idx m = |
| 129 | + MempoolWriter { |
| 130 | + |
| 131 | + -- | Compute the transaction id from a transaction. |
| 132 | + -- |
| 133 | + -- This is used in the protocol handler to verify a full transaction |
| 134 | + -- matches a previously given transaction id. |
| 135 | + -- |
| 136 | + txId :: tx -> txid, |
| 137 | + |
| 138 | + -- | Supply a batch of transactions to the mempool. They are either |
| 139 | + -- accepted or rejected individually, but in the order supplied. |
| 140 | + -- |
| 141 | + -- The 'txid's of all transactions that were added successfully are |
| 142 | + -- returned. |
| 143 | + mempoolAddTxs :: [tx] -> m [(txid, SubmitResult (MempoolAddFail tx))] |
| 144 | + } |
| 145 | + |
| 146 | + |
| 147 | +-- | A mempool writer with validation harness |
| 148 | +-- PRECONDITION: no duplicates given to mempoolAddTxs |
124 | 149 | -- |
125 | 150 | getWriter :: forall tx txid ctx failure m. |
126 | 151 | ( MonadSTM m |
| 152 | + , Exception (InvalidTxsError failure) |
127 | 153 | , MonadThrow m |
| 154 | + -- TODO: |
| 155 | + -- , NFData txid |
| 156 | + -- , NFData tx |
| 157 | + -- , NFData (MempoolAddFail tx) |
128 | 158 | , Ord txid |
129 | | - , Typeable txid |
130 | | - , Typeable failure |
131 | | - , Show txid |
132 | | - , Show failure |
133 | 159 | ) |
134 | 160 | => (tx -> txid) |
135 | 161 | -- ^ get txid of a tx |
136 | 162 | -> m ctx |
137 | | - -- ^ monadic validation ctx |
138 | | - -> (ctx -> tx -> Either failure ()) |
139 | | - -- ^ validate a tx, any failing `tx` throws an exception. |
140 | | - -> (failure -> Bool) |
141 | | - -- ^ return `True` when a failure should throw an exception |
| 163 | + -- ^ acquire validation context |
| 164 | + -> ([tx] -> ctx -> Except (InvalidTxsError failure) [(Either (MempoolAddFail tx) ())]) |
| 165 | + -- ^ validation function which should evaluate its result to normal form |
| 166 | + -- esp. if it is 'expensive' |
| 167 | + -> MempoolAddFail tx |
| 168 | + -- ^ replace duplicates |
142 | 169 | -> Mempool m txid tx |
143 | | - -> TxSubmissionMempoolWriter txid tx Int m |
144 | | -getWriter getTxId getValidationCtx validateTx failureFilterFn (Mempool mempool) = |
145 | | - TxSubmissionMempoolWriter { |
146 | | - txId = getTxId, |
147 | | - |
148 | | - mempoolAddTxs = \txs -> do |
149 | | - ctx <- getValidationCtx |
150 | | - (invalidTxIds, validTxs) <- atomically $ do |
151 | | - MempoolSeq { mempoolSet, mempoolSeq } <- readTVar mempool |
152 | | - let (invalidTxIds, validTxs) = |
153 | | - bimap (filter (failureFilterFn . snd)) |
154 | | - (nubBy (on (==) getTxId)) |
155 | | - . partitionEithers |
156 | | - . map (\tx -> case validateTx ctx tx of |
157 | | - Left e -> Left (getTxId tx, e) |
158 | | - Right _ -> Right tx |
159 | | - ) |
160 | | - . filter (\tx -> getTxId tx `Set.notMember` mempoolSet) |
161 | | - $ txs |
162 | | - mempoolTxs' = MempoolSeq { |
163 | | - mempoolSet = Foldable.foldl' (\s tx -> getTxId tx `Set.insert` s) |
164 | | - mempoolSet |
165 | | - validTxs, |
166 | | - mempoolSeq = Foldable.foldl' (Seq.|>) mempoolSeq validTxs |
167 | | - } |
168 | | - writeTVar mempool mempoolTxs' |
169 | | - return (invalidTxIds, map getTxId validTxs) |
170 | | - when (not (null invalidTxIds)) $ |
171 | | - throwIO (InvalidTxsError invalidTxIds) |
172 | | - return validTxs |
173 | | - } |
| 170 | + -> MempoolWriter txid tx failure Int m |
| 171 | +getWriter getTxId acquireCtx validateTxs duplicateFail (Mempool mempool) = |
| 172 | + MempoolWriter { |
| 173 | + txId = getTxId, |
| 174 | + |
| 175 | + mempoolAddTxs = \txs -> assert (not . null $ txs) $ do |
| 176 | + ctx <- acquireCtx |
| 177 | + !vTxs <- case runExcept (validateTxs txs ctx) of |
| 178 | + Left e -> throwIO e |
| 179 | + Right r -> pure {-. force-} $ zipWith3 ((,,) . getTxId) txs txs r |
| 180 | + |
| 181 | + atomically $ do |
| 182 | + MempoolSeq { mempoolSet, mempoolSeq } <- readTVar mempool |
| 183 | + let result = |
| 184 | + [if duplicate then |
| 185 | + Left . (txid,) $ SubmitFail duplicateFail |
| 186 | + else |
| 187 | + bimap ((txid,) . SubmitFail) (const (txid, tx)) eErrTx |
| 188 | + | (txid, tx, eErrTx) <- vTxs |
| 189 | + , let duplicate = txid `Set.member` mempoolSet |
| 190 | + ] |
| 191 | + (validIds, validTxs) = unzip . rights $ result |
| 192 | + mempoolTxs' = MempoolSeq { |
| 193 | + mempoolSet = Set.union mempoolSet (Set.fromList validIds), |
| 194 | + mempoolSeq = Foldable.foldl' (Seq.|>) mempoolSeq validTxs |
| 195 | + } |
| 196 | + writeTVar mempool mempoolTxs' |
| 197 | + return $ either id ((,SubmitSuccess) . fst) <$> result |
| 198 | + } |
| 199 | + |
| 200 | + |
| 201 | +-- | Takes the general mempool writer defined here |
| 202 | +-- and adapts it to the API of the tx submission mempool writer |
| 203 | +-- to avoid more breaking changes for now. |
| 204 | +-- |
| 205 | +writerAdapter :: (Functor m) |
| 206 | + => MempoolWriter txid tx failure idx m |
| 207 | + -> TxSubmissionMempoolWriter txid tx idx m |
| 208 | +writerAdapter MempoolWriter { txId, mempoolAddTxs } = |
| 209 | + TxSubmissionMempoolWriter { txId, mempoolAddTxs = adapter } |
| 210 | + where |
| 211 | + adapter = fmap (fmap fst) . mempoolAddTxs |
0 commit comments