@@ -12,21 +12,23 @@ import BotPlutusInterface.Effects (
1212 logToContract ,
1313 printLog ,
1414 queryChainIndex ,
15+ readFileTextEnvelope ,
1516 threadDelay ,
1617 uploadDir ,
1718 )
1819import BotPlutusInterface.Files (DummyPrivKey (FromSKey , FromVKey ))
1920import BotPlutusInterface.Files qualified as Files
20- import BotPlutusInterface.Types (ContractEnvironment (.. ), LogLevel (Debug , Warn ), Tip (slot ))
21- import Control.Lens ((^.) )
22- import Control.Monad (void )
21+ import BotPlutusInterface.Types (ContractEnvironment (.. ), LogLevel (Debug , Warn ), Tip (block , slot ))
22+ import Cardano.Api (AsType (.. ), EraInMode (.. ))
23+ import Control.Lens (preview , (^.) )
24+ import Control.Monad (join , void , when )
2325import Control.Monad.Freer (Eff , Member , interpret , reinterpret , runM , subsume , type (~> ))
2426import Control.Monad.Freer.Error (runError )
2527import Control.Monad.Freer.Extras.Log (handleLogIgnore )
2628import Control.Monad.Freer.Extras.Modify (raiseEnd )
2729import Control.Monad.Freer.Writer (Writer (Tell ))
2830import Control.Monad.Trans.Class (lift )
29- import Control.Monad.Trans.Either (eitherT , firstEitherT , newEitherT , secondEitherT )
31+ import Control.Monad.Trans.Either (eitherT , firstEitherT , newEitherT )
3032import Data.Aeson (ToJSON , Value )
3133import Data.Aeson.Extras (encodeByteString )
3234import Data.Kind (Type )
@@ -42,13 +44,15 @@ import Ledger.Slot (Slot (Slot))
4244import Ledger.TimeSlot (posixTimeRangeToContainedSlotRange , posixTimeToEnclosingSlot , slotToEndPOSIXTime )
4345import Ledger.Tx (CardanoTx )
4446import Ledger.Tx qualified as Tx
45- import Plutus.ChainIndex.Types (RollbackState (Committed ), TxValidity (.. ))
47+ import Plutus.ChainIndex.Types (RollbackState (Committed ), TxStatus , TxValidity (.. ))
4648import Plutus.Contract.Checkpoint (Checkpoint (.. ))
4749import Plutus.Contract.Effects (
4850 BalanceTxResponse (.. ),
51+ ChainIndexQuery (.. ),
4952 PABReq (.. ),
5053 PABResp (.. ),
5154 WriteBalancedTxResponse (.. ),
55+ _TxIdResponse ,
5256 )
5357import Plutus.Contract.Resumable (Resumable (.. ))
5458import Plutus.Contract.Types (Contract (.. ), ContractEffs )
@@ -148,13 +152,13 @@ handlePABReq contractEnv req = do
148152 PosixTimeRangeToContainedSlotRangeResp $
149153 Right $
150154 posixTimeRangeToContainedSlotRange contractEnv. cePABConfig. pcSlotConfig posixTimeRange
155+ AwaitTxStatusChangeReq txId -> AwaitTxStatusChangeResp txId <$> getTxUpdate @ w contractEnv txId
151156 ------------------------
152157 -- Unhandled requests --
153158 ------------------------
154159 -- AwaitTimeReq t -> pure $ AwaitTimeResp t
155160 -- AwaitUtxoSpentReq txOutRef -> pure $ AwaitUtxoSpentResp ChainIndexTx
156161 -- AwaitUtxoProducedReq Address -> pure $ AwaitUtxoProducedResp (NonEmpty ChainIndexTx)
157- AwaitTxStatusChangeReq txId -> pure $ AwaitTxStatusChangeResp txId (Committed TxValid () )
158162 -- AwaitTxOutStatusChangeReq TxOutRef
159163 -- ExposeEndpointReq ActiveEndpoint -> ExposeEndpointResp EndpointDescription (EndpointValue JSON.Value)
160164 -- YieldUnbalancedTxReq UnbalancedTx
@@ -163,6 +167,33 @@ handlePABReq contractEnv req = do
163167 printLog @ w Debug $ show resp
164168 pure resp
165169
170+ getTxUpdate ::
171+ forall (w :: Type ) (effs :: [Type -> Type ]).
172+ Member (PABEffect w ) effs =>
173+ ContractEnvironment w ->
174+ Ledger. TxId ->
175+ Eff effs TxStatus
176+ getTxUpdate contractEnv txId = do
177+ let minBlockHeight = 3
178+ _ <- findTxByIdOrLoop
179+ printLog @ w Debug $ " Found tx in chain index. Waiting for " ++ show minBlockHeight ++ " blocks"
180+ _ <- awaitNBlocks @ w contractEnv minBlockHeight
181+ printLog @ w Debug " Waited, checking once more for tx rollback"
182+ _ <- findTxByIdOrLoop
183+ printLog @ w Debug " Tx is confirmed"
184+ pure $ Committed TxValid ()
185+ where
186+ findTxByIdOrLoop :: Eff effs ()
187+ findTxByIdOrLoop = do
188+ mTx <- join . preview _TxIdResponse <$> (queryChainIndex @ w $ TxFromTxId txId)
189+ case mTx of
190+ Just _ -> pure ()
191+ Nothing -> do
192+ printLog @ w Debug " Tx not found... looping"
193+ -- Wait for 1 block and try again
194+ _ <- awaitNBlocks @ w contractEnv 1
195+ void $ getTxUpdate @ w contractEnv txId
196+
166197-- | This will FULLY balance a transaction
167198balanceTx ::
168199 forall (w :: Type ) (effs :: [Type -> Type ]).
@@ -194,7 +225,7 @@ writeBalancedTx contractEnv (Right tx) = do
194225 uploadDir @ w pabConf. pcSigningKeyFileDir
195226 createDirectoryIfMissing @ w False (Text. unpack pabConf. pcScriptFileDir)
196227
197- eitherT (pure . WriteBalancedTxFailed . OtherError ) (pure . WriteBalancedTxSuccess . Right ) $ do
228+ eitherT (pure . WriteBalancedTxFailed . OtherError ) (pure . WriteBalancedTxSuccess . Left ) $ do
198229 void $ firstEitherT (Text. pack . show ) $ newEitherT $ Files. writeAll @ w pabConf tx
199230 lift $ uploadDir @ w pabConf. pcScriptFileDir
200231
@@ -215,9 +246,16 @@ writeBalancedTx contractEnv (Right tx) = do
215246 , " Signatories (pkh): " <> Text. unwords (map pkhToText requiredSigners)
216247 ]
217248
218- if not pabConf. pcDryRun && signable
219- then secondEitherT (const tx) $ newEitherT $ CardanoCLI. submitTx @ w pabConf tx
220- else pure tx
249+ let ext = if signable then " signed" else " raw"
250+ path = Text. unpack $ Files. txFilePath pabConf ext tx
251+ -- We read back the tx from file as tx currently has the wrong id (but the one we create with cardano-cli is correct)
252+ alonxoTx <- firstEitherT (Text. pack . show ) $ newEitherT $ readFileTextEnvelope @ w (AsTx AsAlonzoEra ) path
253+ let cardanoTx = Tx. SomeTx alonxoTx AlonzoEraInCardanoMode
254+
255+ when (not pabConf. pcDryRun && signable) $ do
256+ newEitherT $ CardanoCLI. submitTx @ w pabConf tx
257+
258+ pure cardanoTx
221259
222260pkhToText :: Ledger. PubKey -> Text
223261pkhToText = encodeByteString . fromBuiltin . Ledger. getPubKeyHash . Ledger. pubKeyHash
@@ -239,6 +277,26 @@ awaitSlot contractEnv s@(Slot n) = do
239277 | n < tip'. slot -> pure $ Slot tip'. slot
240278 _ -> awaitSlot contractEnv s
241279
280+ -- | Wait for n Blocks.
281+ awaitNBlocks ::
282+ forall (w :: Type ) (effs :: [Type -> Type ]).
283+ Member (PABEffect w ) effs =>
284+ ContractEnvironment w ->
285+ Integer ->
286+ Eff effs ()
287+ awaitNBlocks contractEnv n = do
288+ current <- currentBlock contractEnv
289+ go current
290+ where
291+ go :: Integer -> Eff effs ()
292+ go start = do
293+ threadDelay @ w (fromIntegral contractEnv. cePABConfig. pcTipPollingInterval)
294+ tip <- CardanoCLI. queryTip @ w contractEnv. cePABConfig
295+ case tip of
296+ Right tip'
297+ | start + n >= tip'. block -> pure ()
298+ _ -> go start
299+
242300{- | Wait at least until the given time. Uses the awaitSlot under the hood, so the same constraints
243301 are applying here as well.
244302-}
@@ -261,6 +319,14 @@ currentSlot ::
261319currentSlot contractEnv =
262320 Slot . slot . either (error . Text. unpack) id <$> CardanoCLI. queryTip @ w contractEnv. cePABConfig
263321
322+ currentBlock ::
323+ forall (w :: Type ) (effs :: [Type -> Type ]).
324+ Member (PABEffect w ) effs =>
325+ ContractEnvironment w ->
326+ Eff effs Integer
327+ currentBlock contractEnv =
328+ block . either (error . Text. unpack) id <$> CardanoCLI. queryTip @ w contractEnv. cePABConfig
329+
264330currentTime ::
265331 forall (w :: Type ) (effs :: [Type -> Type ]).
266332 Member (PABEffect w ) effs =>
0 commit comments