Skip to content

Commit 4e04843

Browse files
authored
Merge pull request IntersectMBO#3941 from input-output-hk/scp-2736-more-property-tests
Fix bug in transactionStatus, and TxIdState tracking when rolling-back
2 parents 9879b31 + 9a7189f commit 4e04843

File tree

4 files changed

+66
-30
lines changed

4 files changed

+66
-30
lines changed

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

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

1414
module Plutus.ChainIndex.TxIdState(
15-
isConfirmed
16-
, increaseDepth
15+
increaseDepth
1716
, initialStatus
1817
, transactionStatus
1918
, fromTx
@@ -41,13 +40,6 @@ initialStatus :: OnChainTx -> TxStatus
4140
initialStatus =
4241
TentativelyConfirmed 0 . eitherTx (const TxInvalid) (const TxValid)
4342

44-
-- | Whether a 'TxStatus' counts as confirmed given the minimum depth
45-
isConfirmed :: Depth -> TxStatus -> Bool
46-
isConfirmed minDepth = \case
47-
TentativelyConfirmed d _ | d >= minDepth -> True
48-
Committed{} -> True
49-
_ -> False
50-
5143
-- | Increase the depth of a tentatively confirmed transaction
5244
increaseDepth :: TxStatus -> TxStatus
5345
increaseDepth (TentativelyConfirmed d s)
@@ -68,18 +60,32 @@ transactionStatus currentBlock txIdState txId
6860
(Nothing, _) -> Right Unknown
6961

7062
(Just TxConfirmedState{blockAdded=Last (Just block'), validity=Last (Just validity')}, Nothing) ->
71-
if block' + (fromIntegral chainConstant) >= currentBlock
72-
then Right $ newStatus block' validity'
73-
else Right $ Committed validity'
63+
if isCommitted block'
64+
then Right $ Committed validity'
65+
else Right $ newStatus block' validity'
7466

7567
(Just TxConfirmedState{timesConfirmed=confirms, blockAdded=Last (Just block'), validity=Last (Just validity')}, Just deletes) ->
76-
if confirms >= deletes
68+
if confirms > deletes
69+
-- It's fine, it's confirmed
7770
then Right $ newStatus block' validity'
78-
else Right $ Unknown
71+
-- Otherwise, throw an error if it looks deleted but we're too far
72+
-- into the future.
73+
else if isCommitted block'
74+
-- Illegal - We can't roll this transaction back.
75+
then Left $ InvalidRollbackAttempt currentBlock txId txIdState
76+
else Right $ Unknown
7977

8078
_ -> Left $ TxIdStateInvalid currentBlock txId txIdState
8179
where
82-
newStatus block' validity' = TentativelyConfirmed (Depth $ fromIntegral $ currentBlock - block') validity'
80+
-- A block is committed at least 'chainConstant' number of blocks
81+
-- has elapsed since the block was added.
82+
isCommitted addedInBlock = currentBlock > addedInBlock + fromIntegral chainConstant
83+
84+
newStatus block' validity' =
85+
if isCommitted block'
86+
then Committed validity'
87+
else TentativelyConfirmed (Depth $ fromIntegral $ currentBlock - block') validity'
88+
8389
confirmed = Map.lookup txId (txnsConfirmed txIdState)
8490
deleted = Map.lookup txId (txnsDeleted txIdState)
8591

@@ -104,7 +110,7 @@ fromTx blockAdded tx =
104110
Map.singleton
105111
(tx ^. citxTxId)
106112
(TxConfirmedState { timesConfirmed = Sum 1
107-
, blockAdded = Last (Just blockAdded)
113+
, blockAdded = Last . Just $ blockAdded
108114
, validity = Last . Just $ validityFromChainIndex tx })
109115
, txnsDeleted = mempty
110116
}
@@ -123,12 +129,14 @@ rollback targetPoint idx@(viewTip -> currentTip)
123129
case tip (measure before) of
124130
TipAtGenesis -> Left $ OldPointNotFound targetPoint
125131
oldTip | targetPoint `pointsToTip` oldTip ->
126-
let x = _usTxUtxoData (measure deleted)
132+
let oldTxIdState = _usTxUtxoData (measure deleted)
127133
newTxIdState = TxIdState
128134
{ txnsConfirmed = mempty
129-
, txnsDeleted = const 1 <$> txnsConfirmed x
135+
-- All the transactions that were confirmed in the deleted
136+
-- section are now deleted.
137+
, txnsDeleted = const 1 <$> txnsConfirmed oldTxIdState
130138
}
131-
newUtxoState = UtxoState newTxIdState oldTip
139+
newUtxoState = UtxoState (oldTxIdState <> newTxIdState) oldTip
132140
in Right RollbackResult{newTip=oldTip, rolledBackIndex=before |> newUtxoState }
133141
| otherwise -> Left TipMismatch{foundTip=oldTip, targetPoint}
134142
where

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,12 @@ data Diagnostics =
220220
deriving stock (Eq, Ord, Show, Generic)
221221
deriving anyclass (ToJSON, FromJSON)
222222

223-
data TxStatusFailure =
223+
data TxStatusFailure
224224
-- | We couldn't return the status because the 'TxIdState' was in a ...
225225
-- state ... that we didn't know how to decode in
226226
-- 'Plutus.ChainIndex.TxIdState.transactionStatus'.
227-
TxIdStateInvalid BlockNumber TxId TxIdState
227+
= TxIdStateInvalid BlockNumber TxId TxIdState
228+
| InvalidRollbackAttempt BlockNumber TxId TxIdState
228229
deriving (Show, Eq)
229230

230231
data TxIdState = TxIdState

plutus-chain-index/test/Generators.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ module Generators(
2525
execTxIdGenState,
2626
txgsBlocks,
2727
txgsNumTransactions,
28-
genTxIdStateTipAndTxId
28+
genTxIdStateTipAndTxId,
29+
txIdFromInt
2930
) where
3031

3132
import Codec.Serialise (serialise)

plutus-chain-index/test/Spec.hs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Data.Either (isRight)
1515
import Data.FingerTree (Measured (..))
1616
import Data.Foldable (fold, toList)
1717
import Data.List (nub, sort)
18+
import qualified Data.Map as Map
1819
import qualified Data.Set as Set
1920
import qualified Generators as Gen
2021
import Hedgehog (Property, annotateShow, assert, failure, forAll, property, (===))
@@ -24,7 +25,8 @@ import qualified Plutus.ChainIndex.Emulator.DiskState as DiskState
2425
import Plutus.ChainIndex.Tx (citxTxId, txOutsWithRef)
2526
import Plutus.ChainIndex.TxIdState (increaseDepth, transactionStatus)
2627
import qualified Plutus.ChainIndex.TxIdState as TxIdState
27-
import Plutus.ChainIndex.Types (BlockNumber (..), Depth (..), Tip (..), TxStatus (..),
28+
import Plutus.ChainIndex.Types (BlockNumber (..), Depth (..), Tip (..), TxConfirmedState (..),
29+
TxIdState (..), TxStatus (..), TxStatusFailure (..),
2830
TxValidity (..), tipAsPoint)
2931
import Plutus.ChainIndex.UtxoState (InsertUtxoSuccess (..), RollbackResult (..), TxUtxoBalance (..))
3032
import qualified Plutus.ChainIndex.UtxoState as UtxoState
@@ -97,13 +99,37 @@ rollbackTxIdState = property $ do
9799
Right s4 = UtxoState.insert (UtxoState.UtxoState (TxIdState.fromTx (BlockNumber 2) txB) tipB) f3
98100
f4 = newIndex s4
99101

100-
status2 = transactionStatus (BlockNumber 1) (getState f2) (txB ^. citxTxId)
101-
status3 = transactionStatus (BlockNumber 2) (getState f3) (txB ^. citxTxId)
102-
status4 = transactionStatus (BlockNumber 3) (getState f4) (txB ^. citxTxId)
103-
104-
status2 === (Right $ TentativelyConfirmed (Depth 0) TxValid)
105-
status3 === (Right $ Unknown)
106-
status4 === (Right $ TentativelyConfirmed (Depth 1) TxValid)
102+
let confirmed tx txIdState = (timesConfirmed <$> ( Map.lookup (tx ^. citxTxId) $ txnsConfirmed $ getState txIdState))
103+
deleted tx txIdState = (Map.lookup (tx ^. citxTxId) $ txnsDeleted $ getState txIdState)
104+
status bn tx txIdState = transactionStatus (BlockNumber bn) (getState txIdState) (tx ^. citxTxId)
105+
106+
isInvalidRollback (Left (InvalidRollbackAttempt _ _ _)) = True
107+
isInvalidRollback _ = False
108+
109+
-- It's inserted at f2, and is confirmed once and not deleted, resulting
110+
-- in a tentatively-confirmed status.
111+
confirmed txB f2 === Just 1
112+
deleted txB f2 === Nothing
113+
status 1 txB f2 === (Right $ TentativelyConfirmed (Depth 0) TxValid)
114+
115+
-- At f3, it's deleted once, and confirmed once, resulting in an unknown
116+
-- status.
117+
confirmed txB f3 === Just 1
118+
deleted txB f3 === Just 1
119+
status 2 txB f3 === (Right $ Unknown)
120+
121+
-- If we check the status far into the future, this should be an error, as
122+
-- we're trying to rollback something that is committed.
123+
isInvalidRollback (status 100 txB f3) === True
124+
125+
-- At f4, it's confirmed twice, and deleted once, resulting in a
126+
-- tentatively-confirmed status again.
127+
confirmed txB f4 === Just 2
128+
deleted txB f4 === Just 1
129+
status 3 txB f4 === (Right $ TentativelyConfirmed (Depth 1) TxValid)
130+
131+
-- Much later, it should be committed.
132+
status 100 txB f4 === Right (Committed TxValid)
107133

108134
transactionDepthIncreases :: Property
109135
transactionDepthIncreases = property $ do

0 commit comments

Comments
 (0)