Skip to content

Commit 1f25899

Browse files
authored
Merge pull request IntersectMBO#3909 from input-output-hk/scp-2735-better-rollback-handling
make transaction status return an either
2 parents 73b21ed + 5078d12 commit 1f25899

File tree

6 files changed

+124
-89
lines changed

6 files changed

+124
-89
lines changed

plutus-chain-index/src/Plutus/ChainIndex/TxIdState.hs

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
{-# LANGUAGE ViewPatterns #-}
1313

1414
module Plutus.ChainIndex.TxIdState(
15-
TxIdState(..)
16-
, TxConfirmedState(..)
17-
, isConfirmed
15+
isConfirmed
1816
, increaseDepth
1917
, initialStatus
2018
, transactionStatus
@@ -27,47 +25,16 @@ module Plutus.ChainIndex.TxIdState(
2725
import Control.Lens ((^.))
2826
import Data.FingerTree (Measured (..), (|>))
2927
import qualified Data.FingerTree as FT
30-
import Data.Map (Map)
3128
import qualified Data.Map as Map
3229
import Data.Monoid (Last (..), Sum (..))
33-
import Data.Semigroup.Generic (GenericSemigroupMonoid (..))
34-
import GHC.Generics (Generic)
3530
import Ledger (OnChainTx, TxId, eitherTx)
3631
import Plutus.ChainIndex.Tx (ChainIndexTx (..), ChainIndexTxOutputs (..), citxOutputs, citxTxId)
37-
import Plutus.ChainIndex.Types (BlockNumber (..), Depth (..), Point (..), Tip (..), TxStatus (..),
38-
TxValidity (..), pointsToTip)
32+
import Plutus.ChainIndex.Types (BlockNumber (..), Depth (..), Point (..), Tip (..), TxConfirmedState (..),
33+
TxIdState (..), TxStatus (..), TxStatusFailure (..), TxValidity (..),
34+
pointsToTip)
3935
import Plutus.ChainIndex.UtxoState (RollbackFailed (..), RollbackResult (..), UtxoIndex, UtxoState (..), tip,
4036
viewTip)
4137

42-
data TxIdState = TxIdState
43-
{ txnsConfirmed :: Map TxId TxConfirmedState
44-
-- ^ Number of times this transaction has been added as well as other
45-
-- necessary metadata.
46-
, txnsDeleted :: Map TxId (Sum Int)
47-
-- ^ Number of times this transaction has been deleted.
48-
}
49-
deriving stock (Eq, Generic, Show)
50-
51-
instance Monoid TxIdState where
52-
mappend = (<>)
53-
mempty = TxIdState { txnsConfirmed=mempty, txnsDeleted=mempty }
54-
55-
data TxConfirmedState =
56-
TxConfirmedState
57-
{ timesConfirmed :: Sum Int
58-
, blockAdded :: Last BlockNumber
59-
, validity :: Last TxValidity
60-
}
61-
deriving stock (Eq, Generic, Show)
62-
deriving (Semigroup, Monoid) via (GenericSemigroupMonoid TxConfirmedState)
63-
64-
-- A semigroup instance that merges the two maps, instead of taking the
65-
-- leftmost one.
66-
instance Semigroup TxIdState where
67-
TxIdState{txnsConfirmed=c, txnsDeleted=d} <> TxIdState{txnsConfirmed=c', txnsDeleted=d'}
68-
= TxIdState { txnsConfirmed = Map.unionWith (<>) c c'
69-
, txnsDeleted = Map.unionWith (<>) d d'
70-
}
7138

7239
-- | The 'TxStatus' of a transaction right after it was added to the chain
7340
initialStatus :: OnChainTx -> TxStatus
@@ -95,23 +62,22 @@ chainConstant = Depth 8
9562

9663
-- | Given the current block, compute the status for the given transaction by
9764
-- checking to see if it has been deleted.
98-
transactionStatus :: BlockNumber -> TxIdState -> TxId -> TxStatus
65+
transactionStatus :: BlockNumber -> TxIdState -> TxId -> Either TxStatusFailure TxStatus
9966
transactionStatus currentBlock txIdState txId
10067
= case (confirmed, deleted) of
101-
(Nothing, _) -> Unknown
68+
(Nothing, _) -> Right Unknown
10269

10370
(Just TxConfirmedState{blockAdded=Last (Just block'), validity=Last (Just validity')}, Nothing) ->
10471
if block' + (fromIntegral chainConstant) >= currentBlock
105-
then newStatus block' validity'
106-
else Committed validity'
72+
then Right $ newStatus block' validity'
73+
else Right $ Committed validity'
10774

10875
(Just TxConfirmedState{timesConfirmed=confirms, blockAdded=Last (Just block'), validity=Last (Just validity')}, Just deletes) ->
10976
if confirms >= deletes
110-
then newStatus block' validity'
111-
else Unknown
77+
then Right $ newStatus block' validity'
78+
else Right $ Unknown
11279

113-
-- TODO: Proper error.
114-
_ -> error $ "Unable to determine transactionStatus for TxId: " <> show txId <> " at block: " <> show currentBlock <> "."
80+
_ -> Left $ TxIdStateInvalid currentBlock txId txIdState
11581
where
11682
newStatus block' validity' = TentativelyConfirmed (Depth $ fromIntegral $ currentBlock - block') validity'
11783
confirmed = Map.lookup txId (txnsConfirmed txIdState)

plutus-chain-index/src/Plutus/ChainIndex/Types.hs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ module Plutus.ChainIndex.Types(
2020
, BlockNumber(..)
2121
, Depth(..)
2222
, Diagnostics(..)
23+
, TxConfirmedState(..)
24+
, TxStatusFailure(..)
25+
, TxIdState(..)
2326
) where
2427

2528
import qualified Codec.Serialise as CBOR
@@ -28,6 +31,10 @@ import Data.Aeson (FromJSON, ToJSON)
2831
import qualified Data.ByteArray as BA
2932
import qualified Data.ByteString.Lazy as BSL
3033
import Data.Default (Default (..))
34+
import Data.Map (Map)
35+
import qualified Data.Map as Map
36+
import Data.Monoid (Last (..), Sum (..))
37+
import Data.Semigroup.Generic (GenericSemigroupMonoid (..))
3138
import Data.Set (Set)
3239
import qualified Data.Set as Set
3340
import Data.Text.Prettyprint.Doc.Extras (PrettyShow (..))
@@ -212,3 +219,40 @@ data Diagnostics =
212219
}
213220
deriving stock (Eq, Ord, Show, Generic)
214221
deriving anyclass (ToJSON, FromJSON)
222+
223+
data TxStatusFailure =
224+
-- | We couldn't return the status because the 'TxIdState' was in a ...
225+
-- state ... that we didn't know how to decode in
226+
-- 'Plutus.ChainIndex.TxIdState.transactionStatus'.
227+
TxIdStateInvalid BlockNumber TxId TxIdState
228+
deriving (Show, Eq)
229+
230+
data TxIdState = TxIdState
231+
{ txnsConfirmed :: Map TxId TxConfirmedState
232+
-- ^ Number of times this transaction has been added as well as other
233+
-- necessary metadata.
234+
, txnsDeleted :: Map TxId (Sum Int)
235+
-- ^ Number of times this transaction has been deleted.
236+
}
237+
deriving stock (Eq, Generic, Show)
238+
239+
instance Monoid TxIdState where
240+
mappend = (<>)
241+
mempty = TxIdState { txnsConfirmed=mempty, txnsDeleted=mempty }
242+
243+
data TxConfirmedState =
244+
TxConfirmedState
245+
{ timesConfirmed :: Sum Int
246+
, blockAdded :: Last BlockNumber
247+
, validity :: Last TxValidity
248+
}
249+
deriving stock (Eq, Generic, Show)
250+
deriving (Semigroup, Monoid) via (GenericSemigroupMonoid TxConfirmedState)
251+
252+
-- A semigroup instance that merges the two maps, instead of taking the
253+
-- leftmost one.
254+
instance Semigroup TxIdState where
255+
TxIdState{txnsConfirmed=c, txnsDeleted=d} <> TxIdState{txnsConfirmed=c', txnsDeleted=d'}
256+
= TxIdState { txnsConfirmed = Map.unionWith (<>) c c'
257+
, txnsDeleted = Map.unionWith (<>) d d'
258+
}

plutus-chain-index/test/Generators.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ import Ledger.Tx (Address, TxIn (..), TxOut (..), Tx
4747
import Ledger.TxId (TxId (..))
4848
import Ledger.Value (Value)
4949
import Plutus.ChainIndex.Tx (ChainIndexTx (..), ChainIndexTxOutputs (..))
50-
import Plutus.ChainIndex.TxIdState (TxIdState)
5150
import qualified Plutus.ChainIndex.TxIdState as TxIdState
52-
import Plutus.ChainIndex.Types (BlockId (..), BlockNumber (..), Tip (..))
51+
import Plutus.ChainIndex.Types (BlockId (..), BlockNumber (..), Tip (..), TxIdState)
5352
import Plutus.ChainIndex.UtxoState (TxUtxoBalance (..), fromTx)
5453
import qualified PlutusTx.Prelude as PlutusTx
5554

plutus-chain-index/test/Spec.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ rollbackTxIdState = property $ do
101101
status3 = transactionStatus (BlockNumber 2) (getState f3) (txB ^. citxTxId)
102102
status4 = transactionStatus (BlockNumber 3) (getState f4) (txB ^. citxTxId)
103103

104-
status2 === TentativelyConfirmed (Depth 0) TxValid
105-
status3 === Unknown
106-
status4 === TentativelyConfirmed (Depth 1) TxValid
104+
status2 === (Right $ TentativelyConfirmed (Depth 0) TxValid)
105+
status3 === (Right $ Unknown)
106+
status4 === (Right $ TentativelyConfirmed (Depth 1) TxValid)
107107

108108
transactionDepthIncreases :: Property
109109
transactionDepthIncreases = property $ do
@@ -121,8 +121,8 @@ transactionDepthIncreases = property $ do
121121
status2 = transactionStatus (BlockNumber 1) (UtxoState._usTxUtxoData (measure f2)) (txA ^. citxTxId)
122122
status3 = transactionStatus (BlockNumber (1 + d)) (UtxoState._usTxUtxoData (measure f2)) (txA ^. citxTxId)
123123

124-
status2 === increaseDepth status1
125-
status3 === Committed TxValid
124+
status2 === (increaseDepth <$> status1)
125+
status3 === (Right $ Committed TxValid)
126126

127127
uniqueTransactionIds :: Property
128128
uniqueTransactionIds = property $ do

plutus-pab/src/Plutus/PAB/Core/ContractInstance/BlockchainEnv.hs

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ import Plutus.V1.Ledger.Api (toBuiltin)
3333
import Control.Concurrent.STM (STM)
3434
import qualified Control.Concurrent.STM as STM
3535
import Control.Lens
36-
import Control.Monad (forM_, unless, void, when)
36+
import Control.Monad (forM_, void, when)
3737
import Data.Foldable (foldl')
3838
import Ledger.TimeSlot (SlotConfig)
3939
import Plutus.ChainIndex (BlockNumber (..), ChainIndexTx (..), ChainIndexTxOutputs (..),
40-
InsertUtxoSuccess (..), RollbackResult (..), Tip (..),
40+
InsertUtxoFailed (..), InsertUtxoSuccess (..),
41+
RollbackFailed (..), RollbackResult (..), Tip (..),
4142
TxConfirmedState (..), TxIdState (..), TxValidity (..),
4243
UtxoState (..), blockId, citxTxId, fromOnChainTx, insert)
4344
import Plutus.ChainIndex.Compatibility (fromCardanoBlockHeader, fromCardanoPoint)
@@ -57,13 +58,18 @@ startNodeClient socket mode slotConfig networkId instancesState = do
5758
case mode of
5859
MockNode ->
5960
void $ MockClient.runChainSync socket slotConfig
60-
(\block slot -> STM.atomically $ processMockBlock instancesState env block slot)
61+
(\block slot -> handleSyncAction $ processMockBlock instancesState env block slot)
6162
AlonzoNode -> do
6263
let resumePoints = []
6364
void $ Client.runChainSync socket slotConfig networkId resumePoints
64-
(\block slot -> STM.atomically $ processChainSyncEvent env block slot)
65+
(\block slot -> handleSyncAction $ processChainSyncEvent env block slot)
6566
pure env
6667

68+
-- | Deal with sync action failures from running this STM action. For now, we
69+
-- deal with them by simply calling `error`; i.e. the application exits.
70+
handleSyncAction :: STM (Either SyncActionFailure ()) -> IO ()
71+
handleSyncAction action = STM.atomically action >>= either (error . show) pure
72+
6773
updateInstances :: IndexedBlock -> InstanceClientEnv -> STM ()
6874
updateInstances IndexedBlock{ibUtxoSpent, ibUtxoProduced} InstanceClientEnv{ceUtxoSpentRequests, ceUtxoProducedRequests} = do
6975
forM_ (Map.intersectionWith (,) ibUtxoSpent ceUtxoSpentRequests) $ \(onChainTx, requests) ->
@@ -72,23 +78,29 @@ updateInstances IndexedBlock{ibUtxoSpent, ibUtxoProduced} InstanceClientEnv{ceUt
7278
traverse (\OpenTxOutProducedRequest{otxProducingTxns} -> STM.tryPutTMVar otxProducingTxns txns) requests
7379

7480
-- | Process a chain sync event that we receive from the alonzo node client
75-
processChainSyncEvent :: BlockchainEnv -> ChainSyncEvent -> Slot -> STM ()
76-
processChainSyncEvent blockchainEnv event _slot = case event of
77-
Resume _ -> pure () -- TODO: Handle resume
78-
RollForward (BlockInMode (C.Block header transactions) era) -> processBlock header blockchainEnv transactions era
79-
RollBackward chainPoint -> runRollback blockchainEnv chainPoint
81+
processChainSyncEvent :: BlockchainEnv -> ChainSyncEvent -> Slot -> STM (Either SyncActionFailure ())
82+
processChainSyncEvent blockchainEnv event _slot = do
83+
case event of
84+
Resume _ -> pure $ Right () -- TODO: Handle resume
85+
RollForward (BlockInMode (C.Block header transactions) era) -> processBlock header blockchainEnv transactions era
86+
RollBackward chainPoint -> runRollback blockchainEnv chainPoint
87+
88+
data SyncActionFailure
89+
= RollbackFailure RollbackFailed
90+
| InsertUtxoStateFailure InsertUtxoFailed
91+
deriving (Show)
8092

8193
-- | Roll back the chain to the given ChainPoint and slot.
82-
runRollback :: BlockchainEnv -> ChainPoint -> STM ()
94+
runRollback :: BlockchainEnv -> ChainPoint -> STM (Either SyncActionFailure ())
8395
runRollback BlockchainEnv{beTxChanges} chainPoint = do
8496
txIdStateIndex <- STM.readTVar beTxChanges
8597

8698
let point = fromCardanoPoint chainPoint
8799
rs = rollback point txIdStateIndex
88100

89101
case rs of
90-
Left e -> error $ "Rollback Failed: " <> show e
91-
Right RollbackResult{rolledBackIndex} -> STM.writeTVar beTxChanges rolledBackIndex
102+
Left e -> pure $ Left (RollbackFailure e)
103+
Right RollbackResult{rolledBackIndex} -> Right <$> STM.writeTVar beTxChanges rolledBackIndex
92104

93105
-- | Get transaction ID and validity from a cardano transaction in any era
94106
txEvent :: forall era. C.Tx era -> C.EraInMode era C.CardanoMode -> (TxId, TxValidity)
@@ -108,11 +120,17 @@ txMockEvent tx =
108120

109121
-- | Update the blockchain env. with changes from a new block of cardano
110122
-- transactions in any era
111-
processBlock :: forall era. C.BlockHeader -> BlockchainEnv -> [C.Tx era] -> C.EraInMode era C.CardanoMode -> STM ()
123+
processBlock :: forall era. C.BlockHeader
124+
-> BlockchainEnv
125+
-> [C.Tx era]
126+
-> C.EraInMode era C.CardanoMode
127+
-> STM (Either SyncActionFailure ())
112128
processBlock header env transactions era =
113-
unless (null transactions) $ do
114-
let tip = fromCardanoBlockHeader header
115-
updateTransactionState tip env (flip txEvent era <$> transactions)
129+
if null transactions
130+
then pure $ Right ()
131+
else do
132+
let tip = fromCardanoBlockHeader header
133+
updateTransactionState tip env (flip txEvent era <$> transactions)
116134

117135
-- | For the given transactions, perform the updates in the TxIdState, and
118136
-- also record that a new block has been processed.
@@ -121,19 +139,19 @@ updateTransactionState
121139
=> Tip
122140
-> BlockchainEnv
123141
-> t (TxId, TxValidity)
124-
-> STM ()
142+
-> STM (Either SyncActionFailure ())
125143
updateTransactionState tip BlockchainEnv{beTxChanges, beCurrentBlock} xs = do
126144
txIdStateIndex <- STM.readTVar beTxChanges
127145
let txIdState = _usTxUtxoData $ measure $ txIdStateIndex
128146
blockNumber <- STM.readTVar beCurrentBlock
129147
let txIdState' = foldl' (insertNewTx blockNumber) txIdState xs
130148
is = insert (UtxoState txIdState' tip) txIdStateIndex
131149
case is of
132-
-- TODO: Proper error.
133-
Left e -> error $ "Insert of new TxIdState failed." <> show e
134-
Right InsertUtxoSuccess{newIndex=newTxIdState} -> STM.writeTVar beTxChanges newTxIdState
135-
STM.writeTVar beCurrentBlock (succ blockNumber)
136-
150+
Right InsertUtxoSuccess{newIndex=newTxIdState} -> do
151+
STM.writeTVar beTxChanges newTxIdState
152+
STM.writeTVar beCurrentBlock (succ blockNumber)
153+
pure $ Right ()
154+
Left e -> pure $ Left $ InsertUtxoStateFailure e
137155

138156
insertNewTx :: BlockNumber -> TxIdState -> (TxId, TxValidity) -> TxIdState
139157
insertNewTx blockNumber TxIdState{txnsConfirmed, txnsDeleted} (txi, txValidity) =
@@ -150,26 +168,30 @@ insertNewTx blockNumber TxIdState{txnsConfirmed, txnsDeleted} (txi, txValidity)
150168

151169
-- | Go through the transactions in a block, updating the 'BlockchainEnv'
152170
-- when any interesting addresses or transactions have changed.
153-
processMockBlock :: InstancesState -> BlockchainEnv -> Block -> Slot -> STM ()
171+
processMockBlock :: InstancesState -> BlockchainEnv -> Block -> Slot -> STM (Either SyncActionFailure ())
154172
processMockBlock instancesState env@BlockchainEnv{beAddressMap, beCurrentSlot, beCurrentBlock} transactions slot = do
155173
lastSlot <- STM.readTVar beCurrentSlot
156174
when (slot > lastSlot) $ do
157175
STM.writeTVar beCurrentSlot slot
158-
unless (null transactions) $ do
159-
addressMap <- STM.readTVar beAddressMap
160-
let addressMap' = foldl' (processTx slot) addressMap transactions
161-
STM.writeTVar beAddressMap addressMap'
162-
blockNumber <- STM.readTVar beCurrentBlock
163176

164-
let tip = Tip { tipSlot = slot
165-
, tipBlockId = blockId transactions
166-
, tipBlockNo = blockNumber
167-
}
177+
if null transactions
178+
then pure $ Right ()
179+
else do
180+
addressMap <- STM.readTVar beAddressMap
181+
let addressMap' = foldl' (processTx slot) addressMap transactions
182+
STM.writeTVar beAddressMap addressMap'
183+
blockNumber <- STM.readTVar beCurrentBlock
184+
185+
instEnv <- S.instancesClientEnv instancesState
186+
updateInstances (indexBlock $ fmap fromOnChainTx transactions) instEnv
187+
168188

169-
updateTransactionState tip env (txMockEvent <$> fmap fromOnChainTx transactions)
189+
let tip = Tip { tipSlot = slot
190+
, tipBlockId = blockId transactions
191+
, tipBlockNo = blockNumber
192+
}
170193

171-
instEnv <- S.instancesClientEnv instancesState
172-
updateInstances (indexBlock $ fmap fromOnChainTx transactions) instEnv
194+
updateTransactionState tip env (txMockEvent <$> fmap fromOnChainTx transactions)
173195

174196
processTx :: Slot -> AddressMap -> OnChainTx -> AddressMap
175197
processTx _ addressMap tx = addressMap' where

plutus-pab/src/Plutus/PAB/Core/ContractInstance/STM.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,12 @@ waitForTxStatusChange oldStatus tx BlockchainEnv{beTxChanges, beCurrentBlock} =
424424
txIdState <- _usTxUtxoData . measure <$> STM.readTVar beTxChanges
425425
blockNumber <- STM.readTVar beCurrentBlock
426426
let newStatus = transactionStatus blockNumber txIdState tx
427-
guard $ oldStatus /= newStatus
428-
pure newStatus
427+
-- Succeed only if we _found_ a status and it was different; if
428+
-- the status hasn't changed, _or_ there was an error computing
429+
-- the status, keep retrying.
430+
case newStatus of
431+
Right s | s /= oldStatus -> pure s
432+
_ -> empty
429433

430434
-- | The value at an address
431435
valueAt :: Address -> BlockchainEnv -> STM Value.Value

0 commit comments

Comments
 (0)