Skip to content

Commit 690dfb0

Browse files
committed
saving execution budgets
- option to save budgets to contract environment
1 parent 5e504bd commit 690dfb0

File tree

12 files changed

+196
-97
lines changed

12 files changed

+196
-97
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ main = do
9999
, -- Protocol params file location relative to the cardano-cli working directory (needed for the cli)
100100
, pcProtocolParamsFile = "./protocol.json"
101101
, pcEnableTxEndpoint = True
102+
-- Save some stats during contract run (only transactions execution budgets supported atm)
103+
, pcCollectStats = False
102104
}
103105
BotPlutusInterface.runPAB @MyContracts pabConf
104106
```

bot-plutus-interface.cabal

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ source-repository head
2525
common common-lang
2626
ghc-options:
2727
-Wall -Wcompat -Wincomplete-record-updates
28-
-Wincomplete-uni-patterns -Wredundant-constraints
29-
-- -Werror
28+
-Wincomplete-uni-patterns -Wredundant-constraints -Werror
3029
-fobject-code -fno-ignore-interface-pragmas
3130
-fno-omit-interface-pragmas -fplugin=RecordDotPreprocessor
3231

@@ -150,10 +149,11 @@ test-suite bot-plutus-interface-test
150149
main-is: Spec.hs
151150
ghc-options: -fplugin-opt PlutusTx.Plugin:defer-errors
152151
other-modules:
153-
Spec.BotPlutusInterface.Contract
154152
Spec.BotPlutusInterface.Balance
155-
Spec.BotPlutusInterface.UtxoParser
153+
Spec.BotPlutusInterface.Contract
154+
Spec.BotPlutusInterface.ContractStats
156155
Spec.BotPlutusInterface.Server
156+
Spec.BotPlutusInterface.UtxoParser
157157
Spec.MockContract
158158

159159
build-depends:

examples/plutus-game/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ main = do
6969
, pcLogLevel = Debug
7070
, pcProtocolParamsFile = "./protocol.json"
7171
, pcEnableTxEndpoint = True
72+
, pcCollectStats = False
7273
}
7374
BotPlutusInterface.runPAB @GameContracts pabConf

examples/plutus-nft/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ main = do
6969
, pcLogLevel = Debug
7070
, pcProtocolParamsFile = "./protocol.json"
7171
, pcEnableTxEndpoint = True
72+
, pcCollectStats = False
7273
}
7374
BotPlutusInterface.runPAB @MintNFTContracts pabConf

examples/plutus-transfer/app/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ main = do
6868
, pcLogLevel = Debug
6969
, pcProtocolParamsFile = "./protocol.json"
7070
, pcEnableTxEndpoint = True
71+
, pcCollectStats = False
7172
}
7273
BotPlutusInterface.runPAB @TransferContracts pabConf

src/BotPlutusInterface/Contract.hs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{-# LANGUAGE AllowAmbiguousTypes #-}
22
{-# LANGUAGE RankNTypes #-}
33

4-
module BotPlutusInterface.Contract (runContract, runContract', handleContract) where
4+
module BotPlutusInterface.Contract (runContract, handleContract) where
55

66
import BotPlutusInterface.Balance qualified as PreBalance
77
import BotPlutusInterface.BodyBuilder qualified as BodyBuilder
@@ -28,11 +28,8 @@ import BotPlutusInterface.Types (
2828
LogLevel (Debug, Warn),
2929
Tip (block, slot),
3030
TxFile (Signed),
31-
TxStats,
32-
emptyStats,
3331
)
3432
import Cardano.Api (AsType (..), EraInMode (..), Tx (Tx))
35-
import Control.Concurrent.STM (newTVarIO, readTVarIO)
3633
import Control.Lens (preview, (^.))
3734
import Control.Monad (join, void, when)
3835
import Control.Monad.Freer (Eff, Member, interpret, reinterpret, runM, subsume, type (~>))
@@ -81,20 +78,8 @@ runContract ::
8178
ContractEnvironment w ->
8279
Contract w s e a ->
8380
IO (Either e a)
84-
runContract contractEnv contract =
85-
fst <$> runContract' contractEnv contract
86-
87-
runContract' ::
88-
forall (w :: Type) (s :: Row Type) (e :: Type) (a :: Type).
89-
(ToJSON w, Monoid w) =>
90-
ContractEnvironment w ->
91-
Contract w s e a ->
92-
IO (Either e a, TxStats)
93-
runContract' contractEnv (Contract effs) = do
94-
emptyBudgets <- newTVarIO emptyStats
95-
res <- runM $ handlePABEffect @w contractEnv emptyBudgets $ raiseEnd $ handleContract contractEnv effs
96-
budgets <- readTVarIO emptyBudgets
97-
return (res, budgets)
81+
runContract contractEnv (Contract effs) = do
82+
runM $ handlePABEffect @w contractEnv $ raiseEnd $ handleContract contractEnv effs
9883

9984
handleContract ::
10085
forall (w :: Type) (e :: Type) (a :: Type).
@@ -292,14 +277,14 @@ writeBalancedTx contractEnv (Right tx) = do
292277
newEitherT $ CardanoCLI.submitTx @w pabConf tx
293278

294279
-- We need to replace the outfile we created at the previous step, as it currently still has the old (incorrect) id
295-
mvFiles (Files.txFilePath pabConf "raw" (Tx.txId tx)) (Files.txFilePath pabConf "raw" (Ledger.getCardanoTxId $ Left cardanoTx))
296-
when signable $ mvFiles (Files.txFilePath pabConf "signed" (Tx.txId tx)) (Files.txFilePath pabConf "signed" (Ledger.getCardanoTxId $ Left cardanoTx))
297-
298-
let txId = Ledger.getCardanoTxId $ Left cardanoTx
299-
path = Text.unpack $ Files.txFilePath pabConf "signed" txId
300-
b <- firstEitherT (Text.pack . show) $ newEitherT $ estimateBudget @w (Signed path)
280+
let cardanoTxId = Ledger.getCardanoTxId $ Left cardanoTx
281+
signedSrcPath = Files.txFilePath pabConf "signed" (Tx.txId tx)
282+
signedDstPath = Files.txFilePath pabConf "signed" cardanoTxId
283+
mvFiles (Files.txFilePath pabConf "raw" (Tx.txId tx)) (Files.txFilePath pabConf "raw" cardanoTxId)
284+
when signable $ mvFiles signedSrcPath signedDstPath
301285

302-
_ <- newEitherT (Right <$> saveBudget @w txId b)
286+
when contractEnv.cePABConfig.pcCollectStats $
287+
collectBudgetStats cardanoTxId signedDstPath
303288

304289
pure cardanoTx
305290
where
@@ -313,6 +298,11 @@ writeBalancedTx contractEnv (Right tx) = do
313298
, cmdOutParser = const ()
314299
}
315300

301+
collectBudgetStats txId txPath = do
302+
let path = Text.unpack txPath
303+
b <- firstEitherT (Text.pack . show) $ newEitherT $ estimateBudget @w (Signed path)
304+
void $ newEitherT (Right <$> saveBudget @w txId b)
305+
316306
pkhToText :: Ledger.PubKey -> Text
317307
pkhToText = encodeByteString . fromBuiltin . Ledger.getPubKeyHash . Ledger.pubKeyHash
318308

src/BotPlutusInterface/Effects.hs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@ import BotPlutusInterface.Types (
3333
LogLevel (..),
3434
TxBudget,
3535
TxFile,
36-
TxStats,
3736
addBudget,
3837
)
3938
import Cardano.Api (AsType, FileError, HasTextEnvelope, TextEnvelopeDescr, TextEnvelopeError)
4039
import Cardano.Api qualified
4140
import Control.Concurrent qualified as Concurrent
42-
import Control.Concurrent.STM (TVar, atomically, modifyTVar, modifyTVar')
41+
import Control.Concurrent.STM (atomically, modifyTVar, modifyTVar')
4342
import Control.Monad (void, when)
4443
import Control.Monad.Freer (Eff, LastMember, Member, interpretM, send, type (~>))
4544
import Data.Aeson (ToJSON)
@@ -99,9 +98,8 @@ handlePABEffect ::
9998
LastMember IO effs =>
10099
(Monoid w) =>
101100
ContractEnvironment w ->
102-
TVar TxStats ->
103101
Eff (PABEffect w ': effs) ~> Eff effs
104-
handlePABEffect contractEnv txStatsVar =
102+
handlePABEffect contractEnv =
105103
interpretM
106104
( \case
107105
CallCommand shellArgs ->
@@ -138,13 +136,9 @@ handlePABEffect contractEnv txStatsVar =
138136
handleChainIndexReq contractEnv.cePABConfig query
139137
EstimateBudget txPath ->
140138
Estimate.estimateBudget contractEnv.cePABConfig txPath
141-
SaveBudget txId exBudget -> saveBudgetImpl txStatsVar txId exBudget
139+
SaveBudget txId exBudget -> saveBudgetImpl contractEnv txId exBudget
142140
)
143141

144-
saveBudgetImpl :: TVar TxStats -> Ledger.TxId -> TxBudget -> IO ()
145-
saveBudgetImpl txStatsVar txId budget =
146-
atomically $ modifyTVar' txStatsVar (addBudget txId budget)
147-
148142
printLog' :: LogLevel -> LogLevel -> String -> IO ()
149143
printLog' logLevelSetting msgLogLvl msg =
150144
when (logLevelSetting >= msgLogLvl) $ putStrLn msg
@@ -179,6 +173,11 @@ readProcessEither path args =
179173
mapToEither (ExitFailure exitCode, _, stderr) =
180174
Left $ "ExitCode " <> Text.pack (show exitCode) <> ": " <> Text.pack stderr
181175

176+
saveBudgetImpl :: ContractEnvironment w -> Ledger.TxId -> TxBudget -> IO ()
177+
saveBudgetImpl contractEnv txId budget =
178+
atomically $
179+
modifyTVar' contractEnv.ceContractStats (addBudget txId budget)
180+
182181
-- Couldn't use the template haskell makeEffect here, because it caused an OverlappingInstances problem.
183182
-- For some reason, we need to manually propagate the @w@ type variable to @send@
184183

src/BotPlutusInterface/Server.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ handleContract ::
256256
handleContract pabConf state@(AppState st) contract = liftIO $ do
257257
contractInstanceID <- liftIO $ ContractInstanceId <$> UUID.nextRandom
258258
contractState <- newTVarIO (ContractState Active mempty)
259+
contractStats <- newTVarIO mempty
259260

260261
atomically $ modifyTVar st (Map.insert contractInstanceID (SomeContractState contractState))
261262

@@ -264,6 +265,7 @@ handleContract pabConf state@(AppState st) contract = liftIO $ do
264265
{ cePABConfig = pabConf
265266
, ceContractState = contractState
266267
, ceContractInstanceId = contractInstanceID
268+
, ceContractStats = contractStats
267269
}
268270
void $
269271
forkIO $ do

src/BotPlutusInterface/Types.hs

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module BotPlutusInterface.Types (
2020
BudgetEstimationError (..),
2121
SpendBudgets,
2222
MintBudgets,
23-
TxStats(..),
23+
ContractStats (..),
2424
emptyStats,
2525
addBudget,
2626
) where
@@ -82,13 +82,76 @@ data PABConfig = PABConfig
8282
, pcTipPollingInterval :: !Natural
8383
, pcPort :: !Port
8484
, pcEnableTxEndpoint :: !Bool
85+
, pcCollectStats :: !Bool
8586
}
8687
deriving stock (Show, Eq)
8788

89+
-- Budget estimation types
90+
91+
{- | Error returned in case any error happened during budget estimation
92+
(wraps whatever received in `Text`)
93+
-}
94+
data BudgetEstimationError
95+
= -- | general error for `Cardano.Api` errors
96+
BudgetEstimationError !Text
97+
| -- | script evaluation failed during budget estimation
98+
ScriptFailure ScriptExecutionError
99+
| {- budget for input or policy was not found after estimation
100+
(arguably should not happen at all) -}
101+
BudgetNotFound ScriptWitnessIndex
102+
deriving stock (Show)
103+
104+
-- | Type of transaction file used for budget estimation
105+
data TxFile
106+
= -- | for using with ".raw" files
107+
Raw !FilePath
108+
| -- | for using with ".signed" files
109+
Signed !FilePath
110+
111+
-- TODO; maybe, Monoid instance could be handy later
112+
emptyStats :: ContractStats
113+
emptyStats = ContractStats mempty
114+
115+
addBudget :: TxId -> TxBudget -> ContractStats -> ContractStats
116+
addBudget txId budget stats =
117+
stats {estimatedBudgets = Map.insert txId budget (estimatedBudgets stats)}
118+
119+
-- | Result of budget estimation
120+
data TxBudget = TxBudget
121+
{ -- | budgets for spending inputs
122+
spendBudgets :: !SpendBudgets
123+
, -- | budgets for minting policies
124+
mintBudgets :: !MintBudgets
125+
}
126+
deriving stock (Show)
127+
128+
instance Semigroup TxBudget where
129+
TxBudget s m <> TxBudget s' m' = TxBudget (s <> s') (m <> m')
130+
131+
instance Monoid TxBudget where
132+
mempty = TxBudget mempty mempty
133+
134+
type SpendBudgets = Map TxOutRef ExBudget
135+
136+
type MintBudgets = Map MintingPolicyHash ExBudget
137+
138+
{- | Collection of stats that could be collected py `bpi`
139+
about contract it runs
140+
-}
141+
newtype ContractStats = ContractStats
142+
{ estimatedBudgets :: Map TxId TxBudget
143+
}
144+
deriving stock (Show)
145+
deriving newtype (Semigroup, Monoid)
146+
147+
instance Show (TVar ContractStats) where
148+
show _ = "<ContractStats>"
149+
88150
data ContractEnvironment w = ContractEnvironment
89151
{ cePABConfig :: PABConfig
90152
, ceContractInstanceId :: ContractInstanceId
91153
, ceContractState :: TVar (ContractState w)
154+
, ceContractStats :: TVar ContractStats
92155
}
93156
deriving stock (Show)
94157

@@ -145,6 +208,7 @@ instance Default PABConfig where
145208
, pcOwnStakePubKeyHash = Nothing
146209
, pcPort = 9080
147210
, pcEnableTxEndpoint = False
211+
, pcCollectStats = False
148212
}
149213

150214
data RawTx = RawTx
@@ -157,60 +221,3 @@ data RawTx = RawTx
157221
-- type is a reserved keyword in haskell and can not be used as a field name
158222
-- when converting this to JSON we drop the _ prefix from each field
159223
deriveJSON defaultOptions {fieldLabelModifier = drop 1} ''RawTx
160-
161-
-- Budget estimation types
162-
163-
{- | Error returned in case any error happened during budget estimation
164-
(wraps whatever received in `Text`)
165-
-}
166-
data BudgetEstimationError
167-
= -- | general error for `Cardano.Api` errors
168-
BudgetEstimationError !Text
169-
| -- | script evaluation failed during budget estimation
170-
ScriptFailure ScriptExecutionError
171-
| {- budget for input or policy was not found after estimation
172-
(arguably should not happen at all) -}
173-
BudgetNotFound ScriptWitnessIndex
174-
deriving stock (Show)
175-
176-
-- | Type of transaction file used for budget estimation
177-
data TxFile
178-
= -- | for using with ".raw" files
179-
Raw !FilePath
180-
| -- | for using with ".signed" files
181-
Signed !FilePath
182-
183-
{- | WIP: Collection of various stats that could be collected py `bpi`
184-
about transactions it performs
185-
-}
186-
data TxStats = TxStats
187-
{ estimatedBudgets :: !(Map TxId TxBudget)
188-
}
189-
deriving stock Show
190-
191-
-- TODO; maybe, Monoid instance could be handy later
192-
emptyStats :: TxStats
193-
emptyStats = TxStats mempty
194-
195-
addBudget :: TxId -> TxBudget -> TxStats -> TxStats
196-
addBudget txId budget stats =
197-
stats {estimatedBudgets = Map.insert txId budget (estimatedBudgets stats)}
198-
199-
-- | Result of budget estimation
200-
data TxBudget = TxBudget
201-
{ -- | budgets for spending inputs
202-
spendBudgets :: !SpendBudgets
203-
, -- | budgets for minting policies
204-
mintBudgets :: !MintBudgets
205-
}
206-
deriving stock (Show)
207-
208-
instance Semigroup TxBudget where
209-
TxBudget s m <> TxBudget s' m' = TxBudget (s <> s') (m <> m')
210-
211-
instance Monoid TxBudget where
212-
mempty = TxBudget mempty mempty
213-
214-
type SpendBudgets = Map TxOutRef ExBudget
215-
216-
type MintBudgets = Map MintingPolicyHash ExBudget

test/Spec.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Main (main) where
22

33
import Spec.BotPlutusInterface.Balance qualified
44
import Spec.BotPlutusInterface.Contract qualified
5+
import Spec.BotPlutusInterface.ContractStats qualified
56
import Spec.BotPlutusInterface.Server qualified
67
import Spec.BotPlutusInterface.UtxoParser qualified
78
import Test.Tasty (TestTree, defaultMain, testGroup)
@@ -23,4 +24,5 @@ tests =
2324
, Spec.BotPlutusInterface.UtxoParser.tests
2425
, Spec.BotPlutusInterface.Balance.tests
2526
, Spec.BotPlutusInterface.Server.tests
27+
, Spec.BotPlutusInterface.ContractStats.tests
2628
]

0 commit comments

Comments
 (0)