Skip to content

Commit a0eb3e7

Browse files
committed
Add cost model key to the schema and use it for caching
1 parent 4d3572b commit a0eb3e7

File tree

6 files changed

+142
-34
lines changed

6 files changed

+142
-34
lines changed

database/db.dbm

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
CAUTION: Do not modify this file unless you know what you are doing.
44
Unexpected results may occur if the code is changed deliberately.
55
-->
6-
<dbmodel pgmodeler-ver="1.1.5" use-changelog="false" max-obj-count="18"
7-
last-position="147,221" last-zoom="1" scene-rect="0,0,1570.8,1076.8"
6+
<dbmodel pgmodeler-ver="1.1.6" use-changelog="false" max-obj-count="18"
7+
last-position="0,0" last-zoom="1" scene-rect="0,0,1570.8,1076.8"
88
default-schema="public" default-owner="postgres"
99
layers="Default layer"
1010
active-layers="0"
@@ -219,6 +219,7 @@ ORDER BY
219219
SEE.EVALUATED_SUCCESSFULLY,
220220
SEE.EXEC_BUDGET_CPU,
221221
SEE.EXEC_BUDGET_MEM,
222+
CMP.PK AS COST_MODEL_KEY,
222223
CMP.PARAM_VALUES AS COST_MODEL_PARAM_VALUES,
223224
SEE.DATUM,
224225
SEE.REDEEMER,

database/db.sql

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- Database generated with pgModeler (PostgreSQL Database Modeler).
2-
-- pgModeler version: 1.1.4
2+
-- pgModeler version: 1.1.6
33
-- PostgreSQL version: 16.0
44
-- Project Site: pgmodeler.io
55
-- Model Author: ---
@@ -191,6 +191,32 @@ COMMENT ON MATERIALIZED VIEW public.builtin_version_num_scripts IS E'For each co
191191
ALTER MATERIALIZED VIEW public.builtin_version_num_scripts OWNER TO "plutus-indexer";
192192
-- ddl-end --
193193

194+
-- object: public.script_evaluations | type: VIEW --
195+
-- DROP VIEW IF EXISTS public.script_evaluations CASCADE;
196+
CREATE VIEW public.script_evaluations
197+
AS
198+
SELECT
199+
SEE.SLOT,
200+
SEE.BLOCK,
201+
SEE.EVALUATED_SUCCESSFULLY,
202+
SEE.EXEC_BUDGET_CPU,
203+
SEE.EXEC_BUDGET_MEM,
204+
CMP.PK AS COST_MODEL_KEY,
205+
CMP.PARAM_VALUES AS COST_MODEL_PARAM_VALUES,
206+
SEE.DATUM,
207+
SEE.REDEEMER,
208+
SEE.SCRIPT_CONTEXT,
209+
SS.LEDGER_LANGUAGE,
210+
SS.MAJOR_PROTOCOL_VER,
211+
SS.SERIALISED
212+
FROM
213+
SCRIPT_EVALUATION_EVENTS AS SEE
214+
JOIN SERIALISED_SCRIPTS SS ON SEE.SCRIPT_HASH = SS.HASH
215+
JOIN COST_MODEL_PARAMS CMP ON SEE.COST_MODEL_PARAMS = CMP.PK;
216+
-- ddl-end --
217+
ALTER VIEW public.script_evaluations OWNER TO "plutus-admin";
218+
-- ddl-end --
219+
194220
-- object: cost_model_params_fk | type: CONSTRAINT --
195221
-- ALTER TABLE public.script_evaluation_events DROP CONSTRAINT IF EXISTS cost_model_params_fk CASCADE;
196222
ALTER TABLE public.script_evaluation_events ADD CONSTRAINT cost_model_params_fk FOREIGN KEY (cost_model_params)
@@ -290,4 +316,16 @@ GRANT SELECT
290316
TO "plutus-indexer";
291317
-- ddl-end --
292318

319+
-- object: grant_r_9fddb7f795 | type: PERMISSION --
320+
GRANT SELECT
321+
ON TABLE public.script_evaluations
322+
TO "plutus-reader";
323+
-- ddl-end --
324+
325+
-- object: grant_r_e6b216ebea | type: PERMISSION --
326+
GRANT SELECT
327+
ON TABLE public.script_evaluations
328+
TO "plutus-admin";
329+
-- ddl-end --
330+
293331

plutus-script-evaluation/evaluate-scripts/Main.hs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
{- | This module contains the main entry point
2+
into the program which CEK-evaluates scripts using
3+
the information recorded in the database in a
4+
streaming fashion.
5+
-}
16
module Main where
27

38
import Control.Exception (bracket, catch)

plutus-script-evaluation/lib/Database/Query.hs

+2-3
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,8 @@ withScriptEvaluationEvents
106106
withScriptEvaluationEvents conn blockNo a f = do
107107
let startBlock = pgFromInteger (fromIntegral (unBlockNo blockNo))
108108
select = orderBy (asc seBlockNo) do
109-
res@(MkScriptEvaluationRecord' _slot block _ _ _ _ _ _ _ _ _ _) <-
110-
selectTable scriptEvaluations
111-
where_ (block .>= startBlock)
109+
res <- selectTable scriptEvaluations
110+
where_ (seBlockNo res .>= startBlock)
112111
pure res
113112
withRunInIO \runInIO ->
114113
runSelectFold conn select a \accum record ->

plutus-script-evaluation/lib/Database/Schema.hs

+7-2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ data
169169
datum
170170
redeemer
171171
scriptContext
172+
costModelKey
172173
costModel
173174
majorProtoVer
174175
ledgerLang
@@ -182,6 +183,7 @@ data
182183
, seDatum :: datum
183184
, seRedeemer :: redeemer
184185
, seScriptContext :: scriptContext
186+
, seCostModelKey :: costModelKey
185187
, seCostModelParams :: costModel
186188
, seMajorProtocolVersion :: majorProtoVer
187189
, seLedgerLanguage :: ledgerLang
@@ -199,6 +201,7 @@ type ScriptEvaluationRecord =
199201
(Maybe ByteString) -- datum
200202
(Maybe ByteString) -- redeemer
201203
ByteString -- script_context
204+
Hash64 -- cost_model_key
202205
[Int64] -- cost_model_params
203206
MajorProtocolVersion -- major_protocol_version
204207
PlutusLedgerLanguage -- ledger_language
@@ -210,10 +213,11 @@ type ScriptEvaluationRecordFields =
210213
(Field SqlBool) -- evaluated_successfully
211214
(Field SqlInt4) -- exec_budget_cpu
212215
(Field SqlInt4) -- exec_budget_mem
213-
(Field SqlBytea) -- script_hash
216+
(Field SqlBytea) -- script
214217
(FieldNullable SqlBytea) -- datum
215218
(FieldNullable SqlBytea) -- redeemer
216219
(Field SqlBytea) -- script_context
220+
(Field SqlInt8) -- cost_model_params
217221
(Field (SqlArray SqlInt8)) -- cost_model_params
218222
(Field SqlInt2) -- major_protocol_version
219223
(Field SqlInt2) -- ledger_language
@@ -259,10 +263,11 @@ scriptEvaluations =
259263
, seEvaluatedSuccessfully = tableField "evaluated_successfully"
260264
, seExecBudgetCpu = tableField "exec_budget_cpu"
261265
, seExecBudgetMem = tableField "exec_budget_mem"
262-
, seScript = tableField "serialised"
266+
, seScript = tableField "script_serialised"
263267
, seDatum = tableField "datum"
264268
, seRedeemer = tableField "redeemer"
265269
, seScriptContext = tableField "script_context"
270+
, seCostModelKey = tableField "cost_model_key"
266271
, seCostModelParams = tableField "cost_model_param_values"
267272
, seMajorProtocolVersion = tableField "major_protocol_ver"
268273
, seLedgerLanguage = tableField "ledger_language"

plutus-script-evaluation/lib/Evaluate.hs

+86-26
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import Control.Monad.Trans.Except (runExceptT)
99
import Control.Monad.Trans.Writer (WriterT (runWriterT))
1010
import Data.ByteString qualified as BSL
1111
import Data.ByteString.Short qualified as BSS
12+
import Data.Digest.Murmur64 (Hash64)
1213
import Data.Either (isRight)
14+
import Data.Map (Map)
15+
import Data.Map qualified as Map
1316
import Data.Text qualified as Text
1417
import Data.Text.IO qualified as TIO
15-
import Data.Time.Clock (getCurrentTime)
18+
import Data.Time.Clock (NominalDiffTime, diffUTCTime, getCurrentTime, nominalDiffTimeToSeconds)
19+
import Data.Word (Word32)
1620
import Database qualified as Db
1721
import Database.PostgreSQL.Simple qualified as Postgres
1822
import Database.Schema (ScriptEvaluationRecord' (..))
@@ -33,19 +37,20 @@ import PlutusLedgerApi.Common (
3337
import PlutusLedgerApi.V1 qualified as V1
3438
import PlutusLedgerApi.V2 qualified as V2
3539
import PlutusLedgerApi.V3 qualified as V3
40+
import System.Exit (ExitCode (..), exitWith)
3641
import Text.PrettyBy qualified as Pretty
37-
import UnliftIO (IORef, atomicModifyIORef', liftIO, newIORef, readIORef)
42+
import UnliftIO (IORef, MonadIO, atomicModifyIORef', liftIO, newIORef, readIORef, writeIORef)
3843
import UnliftIO.Concurrent (forkFinally, threadDelay)
3944

4045
data ScriptEvaluationInput = MkScriptEvaluationInput
41-
{ seiPlutusLedgerLanguage :: PlutusLedgerLanguage
42-
, seiMajorProtocolVersion :: MajorProtocolVersion
43-
, seiEvaluationContext :: EvaluationContext
46+
{ seiPlutusLedgerLanguage :: !PlutusLedgerLanguage
47+
, seiMajorProtocolVersion :: !MajorProtocolVersion
48+
, seiEvaluationContext :: !EvaluationContext
4449
, seiData :: [Data]
45-
, seiScript :: ScriptForEvaluation
46-
, seiExBudget :: ExBudget
47-
, seiEvaluationSuccess :: Bool
48-
, seiBlock :: BlockNo
50+
, seiScript :: !ScriptForEvaluation
51+
, seiExBudget :: !ExBudget
52+
, seiEvaluationSuccess :: !Bool
53+
, seiBlock :: !BlockNo
4954
}
5055

5156
renderScriptEvaluationInput :: ScriptEvaluationInput -> String
@@ -79,9 +84,10 @@ accumulateScripts
7984
-> (ScriptEvaluationInput -> a -> m a)
8085
-- ^ Accumulation function
8186
-> m a
82-
accumulateScripts conn startBlock initialAccum accumulate =
87+
accumulateScripts conn startBlock initialAccum accumulate = do
88+
evaluationContexts <- newIORef Map.empty
8389
Db.withScriptEvaluationEvents conn startBlock initialAccum \accum record -> do
84-
scriptInput <- inputFromRecord record
90+
scriptInput <- inputFromRecord evaluationContexts record
8591
accumulate scriptInput accum
8692

8793
evaluateScripts
@@ -96,42 +102,96 @@ evaluateScripts
96102
-> m ()
97103
evaluateScripts conn startBlock callback = do
98104
maxThreads <- liftIO getNumCapabilities
99-
threadCounter <- newIORef 0
105+
st <-
106+
newIORef
107+
( 0 -- current number of threads
108+
, 0 -- number of evaluated scripts
109+
, 0 -- average processing time (millis)
110+
, 0 -- average evaluation time (millis)
111+
)
112+
evalContexts <- newIORef Map.empty -- cashed evaluation contexts
100113
Db.withScriptEvaluationEvents conn startBlock () \_unit record -> do
101-
waitForAFreeThread maxThreads threadCounter
102-
atomicModifyIORef' threadCounter \n -> (n + 1, ())
103-
_threadId <- forkFinally (callback =<< inputFromRecord record) \_ ->
104-
atomicModifyIORef' threadCounter \n -> (n - 1, ())
114+
startProcessing <- liftIO getCurrentTime
115+
waitForAFreeThread maxThreads st
116+
atomicModifyIORef' st \(threads, n, a, s) ->
117+
((threads + 1, n, a, s), ())
118+
let work = do
119+
input <- inputFromRecord evalContexts record
120+
startEvaluation <- liftIO getCurrentTime
121+
callback input
122+
end <- liftIO getCurrentTime
123+
pure
124+
( nominalDiffTimeToMillis (end `diffUTCTime` startProcessing)
125+
, nominalDiffTimeToMillis (end `diffUTCTime` startEvaluation)
126+
)
127+
_threadId <- forkFinally work \case
128+
Left err -> liftIO do
129+
putStrLn $ "Failed to evaluate script: " <> show err
130+
exitWith (ExitFailure 1)
131+
Right (!dtp, !dte) -> do
132+
atomicModifyIORef' st \(threads, n, pt, et) ->
133+
let pt' =
134+
if pt == 0
135+
then dtp
136+
else
137+
round @Double @Word32 $
138+
fromIntegral (pt * (n - 1) + dtp) / fromIntegral n
139+
et' =
140+
if et == 0
141+
then dte
142+
else
143+
round @Double @Word32 $
144+
fromIntegral (et * (n - 1) + dte) / fromIntegral n
145+
in ((threads - 1, n + 1, pt', et'), ())
105146
pure ()
106147
where
107-
waitForAFreeThread :: Int -> IORef Int -> m ()
148+
{-
149+
(_, n, pt, et) <- readIORef st
150+
when (n `mod` 100 == 0) $ liftIO do
151+
putStrLn $ "Average time: processing " <> show pt <> "ms, "
152+
<> "evaluation " <> show et <> "ms"
153+
-}
154+
155+
waitForAFreeThread :: Int -> IORef (Int, Word32, Word32, Word32) -> m ()
108156
waitForAFreeThread maxThreads counter = do
109-
threadCount <- readIORef counter
157+
(threadCount, _, _, _) <- readIORef counter
110158
when (threadCount >= maxThreads) do
111159
threadDelay 1_000 -- wait for 1ms
112160
waitForAFreeThread maxThreads counter
113161

162+
nominalDiffTimeToMillis :: NominalDiffTime -> Word32
163+
nominalDiffTimeToMillis dt = round (1000 * nominalDiffTimeToSeconds dt)
164+
114165
inputFromRecord
115-
:: (MonadFail m)
116-
=> Db.ScriptEvaluationRecord
166+
:: (MonadFail m, MonadIO m)
167+
=> IORef (Map Hash64 EvaluationContext)
168+
-> Db.ScriptEvaluationRecord
117169
-> m ScriptEvaluationInput
118-
inputFromRecord MkScriptEvaluationRecord'{..} = do
170+
inputFromRecord evalCtxRef MkScriptEvaluationRecord'{..} = do
119171
let mkEvalCtx f =
120172
runExceptT (runWriterT f) >>= \case
121173
Left e -> fail $ "Failed to create evaluation context: " <> show e
122174
Right (ctx, _warnings) -> pure ctx
123-
seiEvaluationContext <-
124-
mkEvalCtx case seLedgerLanguage of
125-
PlutusV1 -> V1.mkEvaluationContext seCostModelParams
126-
PlutusV2 -> V2.mkEvaluationContext seCostModelParams
127-
PlutusV3 -> V3.mkEvaluationContext seCostModelParams
175+
seiEvaluationContext <- do
176+
keyedEvalCtxs <- liftIO $ readIORef evalCtxRef
177+
case Map.lookup seCostModelKey keyedEvalCtxs of
178+
Just ctx -> pure ctx
179+
Nothing -> do
180+
ctx <- mkEvalCtx case seLedgerLanguage of
181+
PlutusV1 -> V1.mkEvaluationContext seCostModelParams
182+
PlutusV2 -> V2.mkEvaluationContext seCostModelParams
183+
PlutusV3 -> V3.mkEvaluationContext seCostModelParams
184+
let keyedEvalCtxs' = Map.insert seCostModelKey ctx keyedEvalCtxs
185+
liftIO $ writeIORef evalCtxRef keyedEvalCtxs'
186+
pure ctx
128187
seiScript <-
129188
case deserialiseScript
130189
seLedgerLanguage
131190
seMajorProtocolVersion
132191
(BSS.toShort seScript) of
133192
Left err -> fail $ "Failed to deserialise script: " <> show err
134193
Right script -> pure script
194+
135195
pure
136196
MkScriptEvaluationInput
137197
{ seiPlutusLedgerLanguage = seLedgerLanguage

0 commit comments

Comments
 (0)