Skip to content

Commit d8d4b53

Browse files
committed
wip: waitTxStatusChange rework
- tests added - config spec added - bugs fixed - todo: docs, refactoring, testnet tests
1 parent 0474636 commit d8d4b53

File tree

8 files changed

+193
-67
lines changed

8 files changed

+193
-67
lines changed

bot-plutus-interface.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ test-suite bot-plutus-interface-test
177177
Spec.BotPlutusInterface.Contract
178178
Spec.BotPlutusInterface.ContractStats
179179
Spec.BotPlutusInterface.Server
180+
Spec.BotPlutusInterface.TxStatusChange
180181
Spec.BotPlutusInterface.UtxoParser
181182
Spec.MockContract
182183

src/BotPlutusInterface/Config.hs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import BotPlutusInterface.Effects (
1414
ShellArgs (..),
1515
callLocalCommand,
1616
)
17-
import BotPlutusInterface.Types (CLILocation (..), LogLevel (..), PABConfig (..), TxStatusPolling)
17+
import BotPlutusInterface.Types (
18+
CLILocation (..),
19+
LogLevel (..),
20+
PABConfig (..),
21+
TxStatusPolling (TxStatusPolling, spBlocksTimeOut, spInterval),
22+
)
1823

1924
import Cardano.Api (NetworkId (Mainnet, Testnet), unNetworkMagic)
2025
import Config (Section (Section), Value (Atom, Sections, Text))
@@ -24,6 +29,7 @@ import Config.Schema (
2429
atomSpec,
2530
generateDocs,
2631
naturalSpec,
32+
reqSection',
2733
sectionsSpec,
2834
trueOrFalseSpec,
2935
(<!>),
@@ -75,11 +81,27 @@ logLevelSpec =
7581
<!> Debug <$ atomSpec "debug"
7682

7783
instance ToValue TxStatusPolling where
78-
toValue = error "TODO: toValue TxStatusPolling"
84+
toValue (TxStatusPolling interval timeout) =
85+
Sections
86+
()
87+
[ Section () "pollingInterval" $ toValue interval
88+
, Section () "pollingTimeout" $ toValue timeout
89+
]
7990

8091
txStatusPollingSpec :: ValueSpec TxStatusPolling
81-
txStatusPollingSpec = error "TODO: txStatusPollingSpec"
82-
92+
txStatusPollingSpec =
93+
sectionsSpec "TxStatusPolling configuration" $ do
94+
spInterval <-
95+
reqSection'
96+
"milliseconds"
97+
naturalSpec
98+
"Interval between chain-index queries for transacions status change detection"
99+
spBlocksTimeOut <-
100+
reqSection'
101+
"blocks"
102+
naturalSpec
103+
"Timeout (in blocks) after which awating of transaction status change will be cancelled and current staus returned"
104+
pure $ TxStatusPolling {..}
83105

84106
{- ORMOLU_DISABLE -}
85107
instance ToValue PABConfig where
@@ -102,7 +124,7 @@ instance ToValue PABConfig where
102124
pcPort
103125
pcEnableTxEndpoint
104126
pcCollectStats
105-
pcTxStausPolling
127+
pcTxStatusPolling
106128
) =
107129
Sections
108130
()
@@ -124,7 +146,7 @@ instance ToValue PABConfig where
124146
, Section () "port" $ toValue pcPort
125147
, Section () "enableTxEndpoint" $ toValue pcEnableTxEndpoint
126148
, Section () "collectStats" $ toValue pcCollectStats
127-
, Section () "pcTxStausPolling" $ toValue pcTxStausPolling
149+
, Section () "pcTxStatusPolling" $ toValue pcTxStatusPolling
128150
]
129151
{- ORMOLU_ENABLE -}
130152

@@ -215,13 +237,12 @@ pabConfigSpec = sectionsSpec "PABConfig" $ do
215237
trueOrFalseSpec
216238
"Save some stats during contract run (only transactions execution budgets supported atm)"
217239

218-
pcTxStausPolling <-
240+
pcTxStatusPolling <-
219241
sectionWithDefault'
220-
(pcTxStausPolling def)
221-
"pcTxStausPolling"
222-
txStatusPollingSpec
223-
(error "TODO: TxStatusPolling config help")
224-
242+
(pcTxStatusPolling def)
243+
"pcTxStatusPolling"
244+
txStatusPollingSpec
245+
"TODO: TxStatusPolling config help" -- FIXME
225246
pure PABConfig {..}
226247

227248
docPABConfig :: String

src/BotPlutusInterface/Contract.hs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ import BotPlutusInterface.Effects (
2424
saveBudget,
2525
slotToPOSIXTime,
2626
threadDelay,
27-
uploadDir
27+
uploadDir,
2828
)
2929
import BotPlutusInterface.Files (DummyPrivKey (FromSKey, FromVKey))
3030
import BotPlutusInterface.Files qualified as Files
3131
import BotPlutusInterface.Types (
3232
ContractEnvironment (..),
3333
LogLevel (Debug, Warn),
3434
Tip (block, slot),
35-
TxFile (Signed), spInterval, spBlocksTimeOut
35+
TxFile (Signed),
36+
-- spBlocksTimeOut,
37+
-- spInterval,
3638
)
3739
import Cardano.Api (AsType (..), EraInMode (..), Tx (Tx))
3840
import Control.Lens (preview, (^.))
@@ -52,6 +54,7 @@ import Data.Row (Row)
5254
import Data.Text (Text)
5355
import Data.Text qualified as Text
5456
import Data.Vector qualified as V
57+
import Debug.Trace (traceM)
5558
import Ledger (POSIXTime)
5659
import Ledger qualified
5760
import Ledger.Address (PaymentPubKeyHash (PaymentPubKeyHash))
@@ -209,35 +212,43 @@ awaitTxStatusChange ::
209212
Ledger.TxId ->
210213
Eff effs TxStatus
211214
awaitTxStatusChange contractEnv txId = do
212-
let txStatusPolling = contractEnv.cePABConfig.pcTxStausPolling
213-
pollInterval = fromIntegral $ spInterval txStatusPolling
214-
pollTimeOut = fromIntegral $ spBlocksTimeOut txStatusPolling
215-
cutOffBlock <- (pollTimeOut +) <$> currentBlock contractEnv
215+
traceM "@@ await stats change"
216+
checkStartedBlock <- currentBlock contractEnv
216217
printBpiLog @w Debug $ pretty $ "Awaiting status change for " ++ show txId
217-
txStausCheckLoop txId contractEnv pollInterval cutOffBlock
218+
txStausCheckLoop txId contractEnv checkStartedBlock
218219

219220
txStausCheckLoop ::
220221
forall (w :: Type) (effs :: [Type -> Type]).
221222
Member (PABEffect w) effs =>
222223
Ledger.TxId ->
223224
ContractEnvironment w ->
224-
Int ->
225225
Integer ->
226226
Eff effs TxStatus
227-
txStausCheckLoop txId contractEnv pollInterval cutOffBlock = do
227+
txStausCheckLoop txId contractEnv checkStartedBlock = do
228+
let txStatusPolling = contractEnv.cePABConfig.pcTxStatusPolling
229+
pollInterval = fromIntegral $ txStatusPolling.spInterval
230+
pollTimeout = txStatusPolling.spBlocksTimeOut
231+
cutOffBlock = checkStartedBlock + fromIntegral pollTimeout
228232
currBlock <- currentBlock contractEnv
229233
txStatus <- getStatus
230234
case (txStatus, currBlock > cutOffBlock) of
231235
(status, True) -> do
232-
logDebug $ "Awaiting preiod for TxId " ++ show txId
233-
++ " status change is over, current status: " ++ show status
236+
logDebug . mconcat $
237+
[ "Timeout period for waiting `TxId "
238+
, show txId
239+
, "` status cahnge is over"
240+
, " - waited "
241+
, show pollTimeout
242+
, " blocks."
243+
, " Current status: "
244+
, show status
245+
]
234246
return status
235247
(Unknown, _) -> do
236248
threadDelay @w pollInterval
237-
txStausCheckLoop txId contractEnv pollInterval cutOffBlock
249+
retry
238250
(status, _) -> return status
239251
where
240-
-- | get Tx status with extensive debug logging
241252
getStatus = do
242253
mTx <- queryChainIndexForTxState
243254
case mTx of
@@ -251,16 +262,9 @@ txStausCheckLoop txId contractEnv pollInterval cutOffBlock = do
251262
Left e -> do
252263
logDebug $ "Staus check for TxId " ++ show txId ++ " failed with " ++ show e
253264
return Unknown
254-
Right st -> case st of
255-
Unknown -> do
256-
logDebug $ "Staus for TxId " ++ show txId ++ " is Unknown"
257-
return Unknown
258-
other -> do
259-
logDebug $
260-
"Staus for TxId " ++ show txId ++ " is " ++ show other
261-
pure other
262-
263-
logDebug = printBpiLog @w Debug . pretty
265+
Right st -> do
266+
logDebug $ "Status for TxId " ++ show txId ++ " is " ++ show st
267+
return st
264268

265269
queryChainIndexForTxState :: Eff effs (Maybe TxIdState)
266270
queryChainIndexForTxState = do
@@ -271,6 +275,11 @@ txStausCheckLoop txId contractEnv pollInterval cutOffBlock = do
271275
pure . Just $ fromTx blk tx
272276
Nothing -> pure Nothing
273277

278+
logDebug = printBpiLog @w Debug . pretty
279+
-- logDebug = traceM . show . pretty
280+
281+
retry = txStausCheckLoop txId contractEnv checkStartedBlock
282+
274283
-- | This will FULLY balance a transaction
275284
balanceTx ::
276285
forall (w :: Type) (effs :: [Type -> Type]).

src/BotPlutusInterface/Types.hs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module BotPlutusInterface.Types (
2222
SpendBudgets,
2323
MintBudgets,
2424
ContractStats (..),
25-
TxStatusPolling(..),
25+
TxStatusPolling (..),
2626
addBudget,
2727
) where
2828

@@ -84,13 +84,15 @@ data PABConfig = PABConfig
8484
, pcPort :: !Port
8585
, pcEnableTxEndpoint :: !Bool
8686
, pcCollectStats :: !Bool
87-
, pcTxStausPolling :: !TxStatusPolling
87+
, pcTxStatusPolling :: !TxStatusPolling
8888
}
8989
deriving stock (Show, Eq)
9090

9191
data TxStatusPolling = TxStatusPolling
92-
{ spInterval :: !Natural -- ^ mocroseconds
93-
, spBlocksTimeOut :: !Natural -- ^ blocks until timeout, most likely `Unknown` state will be returned
92+
{ -- | mocroseconds
93+
spInterval :: !Natural
94+
, -- | blocks until timeout, most likely `Unknown` state will be returned
95+
spBlocksTimeOut :: !Natural
9496
}
9597
deriving stock (Show, Eq)
9698

@@ -229,7 +231,7 @@ instance Default PABConfig where
229231
, pcPort = 9080
230232
, pcEnableTxEndpoint = False
231233
, pcCollectStats = False
232-
, pcTxStausPolling = TxStatusPolling 1_000 8
234+
, pcTxStatusPolling = TxStatusPolling 1_000 8
233235
}
234236

235237
data RawTx = RawTx

test/Spec.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Spec.BotPlutusInterface.Balance qualified
44
import Spec.BotPlutusInterface.Contract qualified
55
import Spec.BotPlutusInterface.ContractStats qualified
66
import Spec.BotPlutusInterface.Server qualified
7+
import Spec.BotPlutusInterface.TxStatusChange qualified
78
import Spec.BotPlutusInterface.UtxoParser qualified
89
import Test.Tasty (TestTree, defaultMain, testGroup)
910
import Prelude
@@ -25,4 +26,5 @@ tests =
2526
, Spec.BotPlutusInterface.Balance.tests
2627
, Spec.BotPlutusInterface.Server.tests
2728
, Spec.BotPlutusInterface.ContractStats.tests
29+
, Spec.BotPlutusInterface.TxStatusChange.tests
2830
]

test/Spec/BotPlutusInterface/Config.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,5 @@ pabConfigExample =
113113
, pcPort = 1021
114114
, pcEnableTxEndpoint = True
115115
, pcCollectStats = False
116-
, pcTxStausPolling = TxStatusPolling 1_000 8
116+
, pcTxStatusPolling = TxStatusPolling 1_000 8
117117
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module Spec.BotPlutusInterface.TxStatusChange (tests) where
2+
3+
import BotPlutusInterface.Types (
4+
ContractEnvironment (cePABConfig),
5+
PABConfig (pcOwnPubKeyHash),
6+
)
7+
import Control.Lens ((&), (.~))
8+
import Control.Monad (void)
9+
import Data.Default (def)
10+
import Data.Text (Text)
11+
import Data.Text qualified as Text
12+
import Ledger (PaymentPubKeyHash (unPaymentPubKeyHash), getCardanoTxId)
13+
import Ledger.Ada qualified as Ada
14+
import Ledger.Constraints qualified as Constraints
15+
import Ledger.Tx (TxOut (TxOut), TxOutRef (TxOutRef))
16+
import Plutus.ChainIndex (RollbackState (Unknown), TxStatus)
17+
import Plutus.Contract (
18+
Contract (..),
19+
Endpoint,
20+
awaitTxStatusChange,
21+
submitTx,
22+
)
23+
import Spec.MockContract (
24+
contractEnv,
25+
nonExistingTxId,
26+
paymentPkh1,
27+
paymentPkh2,
28+
pkhAddr1,
29+
runContractPure,
30+
utxos,
31+
)
32+
import Test.Tasty (TestTree, testGroup)
33+
import Test.Tasty.HUnit (Assertion, assertFailure, testCase, (@?=))
34+
import Prelude
35+
36+
tests :: TestTree
37+
tests =
38+
testGroup
39+
"Await Tx status change"
40+
[ testCase "Return status if Tx was found as status is not Unknown" testTxFoundAndConfirmed
41+
, testCase "Stop waiting by timeout if Tx could not be found" testStopWaitingByTimeout
42+
]
43+
44+
testTxFoundAndConfirmed :: Assertion
45+
testTxFoundAndConfirmed = do
46+
let txOutRef = TxOutRef "e406b0cf676fc2b1a9edb0617f259ad025c20ea6f0333820aa7cef1bfe7302e5" 0
47+
txOut = TxOut pkhAddr1 (Ada.lovelaceValueOf 1350) Nothing
48+
initState =
49+
def & utxos .~ [(txOutRef, txOut)]
50+
& contractEnv .~ contractEnv'
51+
pabConf = def {pcOwnPubKeyHash = unPaymentPubKeyHash paymentPkh1}
52+
contractEnv' = def {cePABConfig = pabConf}
53+
54+
contract :: Contract () (Endpoint "SendAda" ()) Text ()
55+
contract = do
56+
let constraints =
57+
Constraints.mustPayToPubKey paymentPkh2 (Ada.lovelaceValueOf 1000)
58+
tx <- submitTx constraints
59+
void $ awaitTxStatusChange $ getCardanoTxId tx
60+
61+
case runContractPure contract initState of
62+
(Left err, _) -> assertFailure $ Text.unpack err
63+
(Right _, _) -> pure ()
64+
65+
testStopWaitingByTimeout :: Assertion
66+
testStopWaitingByTimeout = do
67+
let initState =
68+
def & contractEnv .~ contractEnv'
69+
pabConf = def {pcOwnPubKeyHash = unPaymentPubKeyHash paymentPkh1}
70+
contractEnv' = def {cePABConfig = pabConf}
71+
72+
contract :: Contract () (Endpoint "SendAda" ()) Text TxStatus
73+
contract =
74+
awaitTxStatusChange nonExistingTxId
75+
76+
case runContractPure contract initState of
77+
(Left err, _) -> assertFailure $ Text.unpack err
78+
(Right txStatus, _) -> txStatus @?= Unknown

0 commit comments

Comments
 (0)