forked from kadena-io/chainweb-node
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPactService.hs
More file actions
1284 lines (1210 loc) · 63.1 KB
/
PactService.hs
File metadata and controls
1284 lines (1210 loc) · 63.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DataKinds #-}
-- |
-- Module: Chainweb.Pact.PactService
-- Copyright: Copyright © 2018,2019,2020 Kadena LLC.
-- License: See LICENSE file
-- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy
-- Stability: experimental
--
-- Pact service for Chainweb
--
module Chainweb.Pact.PactService
( initialPayloadState
, execNewBlock
, execContinueBlock
, execValidateBlock
, execTransactions
, execLocal
, execLookupPactTxs
, execPreInsertCheckReq
, execBlockTxHistory
, execHistoricalLookup
, execReadOnlyReplay
, execSyncToBlock
, runPactService
, withPactService
, execNewGenesisBlock
) where
import Control.Concurrent hiding (throwTo)
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Exception (AsyncException(ThreadKilled))
import Control.Exception.Safe
import Control.Lens hiding ((:>))
import Control.Monad
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Either
import Data.Foldable (toList)
import Data.IORef
import qualified Data.HashMap.Strict as HM
import Data.LogMessage
import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Vector (Vector)
import qualified Data.Vector as V
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID
import System.IO
import System.LogLevel
import Prelude hiding (lookup)
import qualified Streaming as Stream
import qualified Streaming.Prelude as Stream
import qualified Pact.Gas as Pact4
import Pact.Interpreter(PactDbEnv(..))
import qualified Pact.JSON.Encode as J
import qualified Pact.Types.Command as Pact4
import qualified Pact.Types.Hash as Pact4
import qualified Pact.Types.Runtime as Pact4 hiding (catchesPactError)
import qualified Pact.Types.Pretty as Pact4
import qualified Pact.Core.Builtin as Pact5
import qualified Pact.Core.Persistence as Pact5
import qualified Pact.Core.Gas as Pact5
import qualified Pact.Core.Info as Pact5
import qualified Pact.Core.Command.Types as Pact5
import qualified Pact.Core.Command.RPC as Pact5
import qualified Pact.Core.Hash as Pact5
import qualified Chainweb.Pact4.TransactionExec as Pact4
import qualified Chainweb.Pact4.Validations as Pact4
import Chainweb.BlockHash
import Chainweb.BlockHeader
import Chainweb.BlockHeaderDB
import Chainweb.BlockHeight
import Chainweb.ChainId
import Chainweb.Logger
import Chainweb.Mempool.Mempool as Mempool
import Chainweb.Miner.Pact
import Chainweb.Pact.PactService.Pact4.ExecBlock
import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4
import Chainweb.Pact.Service.PactQueue (PactQueue, getNextRequest)
import Chainweb.Pact.Types
import Chainweb.Pact4.SPV qualified as Pact4
import Chainweb.Pact5.SPV qualified as Pact5
import Chainweb.Payload
import Chainweb.Payload.PayloadStore
import Chainweb.Time
import qualified Chainweb.Pact4.Transaction as Pact4
import Chainweb.TreeDB
import Chainweb.Utils hiding (check)
import Chainweb.Version
import Chainweb.Version.Guards
import Utils.Logging.Trace
import Chainweb.Counter
import Data.Time.Clock
import Text.Printf
import Data.Time.Format.ISO8601
import qualified Chainweb.Pact.PactService.Pact4.ExecBlock as Pact4
import qualified Chainweb.Pact4.Types as Pact4
import qualified Chainweb.Pact5.Backend.ChainwebPactDb as Pact5
import qualified Data.ByteString.Short as SB
import Data.Coerce (coerce)
import Data.Void
import qualified Chainweb.Pact5.Types as Pact5
import qualified Chainweb.Pact.PactService.Pact5.ExecBlock as Pact5
import qualified Pact.Core.Evaluate as Pact5
import qualified Pact.Core.Names as Pact5
import Data.Functor.Product
import qualified Chainweb.Pact5.TransactionExec as Pact5
import qualified Chainweb.Pact5.Transaction as Pact5
import Control.Monad.Except
import qualified Chainweb.Pact5.NoCoinbase as Pact5
import qualified Pact.Parse as Pact4
import qualified Control.Parallel.Strategies as Strategies
import qualified Chainweb.Pact5.Validations as Pact5
import qualified Pact.Core.Errors as Pact5
import Chainweb.Pact.Backend.Types
import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer
import Chainweb.Pact.PactService.Checkpointer (SomeBlockM(..))
import qualified Pact.Core.StableEncoding as Pact5
import Control.Monad.Cont (evalContT)
import qualified Data.List.NonEmpty as NonEmpty
runPactService
:: Logger logger
=> CanReadablePayloadCas tbl
=> ChainwebVersion
-> ChainId
-> logger
-> Maybe (Counter "txFailures")
-> PactQueue
-> MemPoolAccess
-> BlockHeaderDb
-> PayloadDb tbl
-> SQLiteEnv
-> PactServiceConfig
-> IO ()
runPactService ver cid chainwebLogger txFailuresCounter reqQ mempoolAccess bhDb pdb sqlenv config =
void $ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config $ do
initialPayloadState ver cid
serviceRequests mempoolAccess reqQ
withPactService
:: (Logger logger, CanReadablePayloadCas tbl)
=> ChainwebVersion
-> ChainId
-> logger
-> Maybe (Counter "txFailures")
-> BlockHeaderDb
-> PayloadDb tbl
-> SQLiteEnv
-> PactServiceConfig
-> PactServiceM logger tbl a
-> IO (T2 a PactServiceState)
withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config act = do
Checkpointer.withCheckpointerResources checkpointerLogger (_pactModuleCacheLimit config) sqlenv (_pactPersistIntraBlockWrites config) ver cid $ \checkpointer -> do
let !rs = readRewards
let !pse = PactServiceEnv
{ _psMempoolAccess = Nothing
, _psCheckpointer = checkpointer
, _psPdb = pdb
, _psBlockHeaderDb = bhDb
, _psMinerRewards = rs
, _psReorgLimit = _pactReorgLimit config
, _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config
, _psOnFatalError = defaultOnFatalError (logFunctionText chainwebLogger)
, _psVersion = ver
, _psAllowReadsInLocal = _pactAllowReadsInLocal config
, _psLogger = pactServiceLogger
, _psGasLogger = gasLogger <$ guard (_pactLogGas config)
, _psBlockGasLimit = _pactNewBlockGasLimit config
, _psEnableLocalTimeout = _pactEnableLocalTimeout config
, _psTxFailuresCounter = txFailuresCounter
, _psTxTimeLimit = _pactTxTimeLimit config
}
!pst = PactServiceState mempty
runPactServiceM pst pse $ do
when (_pactFullHistoryRequired config) $ do
mEarliestBlock <- Checkpointer.getEarliestBlock
case mEarliestBlock of
Nothing -> do
pure ()
Just (earliestBlockHeight, _) -> do
let gHeight = genesisHeight ver cid
when (gHeight /= earliestBlockHeight) $ do
let msg = J.object
[ "details" J..= J.object
[ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight)
, "genesis-height" J..= J.number (fromIntegral gHeight)
]
, "message" J..= J.text "Your node has been configured\
\ to require the full Pact history; however, the full\
\ history is not available. Perhaps you have compacted\
\ your Pact state?"
]
logError_ chainwebLogger (J.encodeText msg)
throwM FullHistoryRequired
{ _earliestBlockHeight = earliestBlockHeight
, _genesisHeight = gHeight
}
-- If the latest header that is stored in the checkpointer was on an
-- orphaned fork, there is no way to recover it in the call of
-- 'initalPayloadState.readContracts'. We therefore rewind to the latest
-- avaliable header in the block header database.
--
Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config)
act
where
pactServiceLogger = setComponent "pact" chainwebLogger
checkpointerLogger = addLabel ("sub-component", "checkpointer") pactServiceLogger
gasLogger = addLabel ("transaction", "GasLogs") pactServiceLogger
initializeLatestBlock :: (Logger logger) => CanReadablePayloadCas tbl => Bool -> PactServiceM logger tbl ()
initializeLatestBlock unlimitedRewind = Checkpointer.findLatestValidBlockHeader' >>= \case
Nothing -> return ()
Just b -> Checkpointer.rewindToIncremental initialRewindLimit (ParentHeader b)
where
initialRewindLimit = RewindLimit 1000 <$ guard (not unlimitedRewind)
initialPayloadState
:: Logger logger
=> CanReadablePayloadCas tbl
=> ChainwebVersion
-> ChainId
-> PactServiceM logger tbl ()
initialPayloadState v cid
| v ^. versionCheats . disablePact = pure ()
| otherwise = initializeCoinContract v cid $
v ^?! versionGenesis . genesisBlockPayload . atChain cid
initializeCoinContract
:: forall tbl logger. (CanReadablePayloadCas tbl, Logger logger)
=> ChainwebVersion
-> ChainId
-> PayloadWithOutputs
-> PactServiceM logger tbl ()
initializeCoinContract v cid pwo = do
latestBlock <- Checkpointer.getLatestBlock >>= \case
Nothing -> return Nothing
Just (_, latestHash) -> do
latestHeader <- ParentHeader
<$!> lookupBlockHeader latestHash "initializeCoinContract.findLatestValidBlockHeader"
return $ Just latestHeader
case latestBlock of
Nothing -> do
logWarnPact "initializeCoinContract: Checkpointer returned no latest block. Starting from genesis."
validateGenesis
Just currentBlockHeader ->
if currentBlockHeader /= ParentHeader genesisHeader
then
unless (pact5 v cid (view (parentHeader . blockHeight . to succ) currentBlockHeader)) $ do
!mc <- Checkpointer.readFrom (Just currentBlockHeader)
(SomeBlockM $ Pair Pact4.readInitModules (error "pact5")) >>= \case
NoHistory -> throwM $ BlockHeaderLookupFailure
$ "initializeCoinContract: internal error: latest block not found: " <> sshow currentBlockHeader
Historical mc -> return mc
Pact4.updateInitCache mc currentBlockHeader
else do
logWarnPact "initializeCoinContract: Starting from genesis."
validateGenesis
where
validateGenesis = void $!
execValidateBlock mempty genesisHeader (CheckablePayloadWithOutputs pwo)
genesisHeader :: BlockHeader
genesisHeader = genesisBlockHeader v cid
-- | Lookup a block header.
--
-- The block header is expected to be either in the block header database or to
-- be the the currently stored '_psParentHeader'. The latter addresses the case
-- when a block has already been validate with 'execValidateBlock' but isn't (yet)
-- available in the block header database. If that's the case two things can
-- happen:
--
-- 1. the header becomes available before the next 'execValidateBlock' call, or
-- 2. the header gets orphaned and the next 'execValidateBlock' call would cause
-- a rewind to an ancestor, which is available in the db.
--
lookupBlockHeader :: BlockHash -> Text -> PactServiceM logger tbl BlockHeader
lookupBlockHeader bhash ctx = do
bhdb <- view psBlockHeaderDb
liftIO $! lookupM bhdb bhash `catchAllSynchronous` \e ->
throwM $ BlockHeaderLookupFailure $
"failed lookup of parent header in " <> ctx <> ": " <> sshow e
-- | Loop forever, serving Pact execution requests and reponses from the queues
serviceRequests
:: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl)
=> MemPoolAccess
-> PactQueue
-> PactServiceM logger tbl ()
serviceRequests memPoolAccess reqQ = go
where
go :: PactServiceM logger tbl ()
go = do
PactServiceEnv{_psLogger} <- ask
logDebugPact "serviceRequests: wait"
SubmittedRequestMsg msg statusRef <- liftIO $ getNextRequest reqQ
requestId <- liftIO $ UUID.toText <$> UUID.nextRandom
let
logFn :: LogFunction
logFn = logFunction $ addLabel ("pact-request-id", requestId) _psLogger
logDebugPact $ "serviceRequests: " <> sshow msg
case msg of
CloseMsg ->
tryOne "execClose" statusRef $ return ()
LocalMsg (LocalReq localRequest preflight sigVerify rewindDepth) -> do
trace logFn "Chainweb.Pact.PactService.execLocal" () 0 $
tryOne "execLocal" statusRef $
execLocal localRequest preflight sigVerify rewindDepth
go
NewBlockMsg NewBlockReq {..} -> do
trace logFn "Chainweb.Pact.PactService.execNewBlock"
() 1 $
tryOne "execNewBlock" statusRef $
execNewBlock memPoolAccess _newBlockMiner _newBlockFill _newBlockParent
go
ContinueBlockMsg (ContinueBlockReq bip) -> do
trace logFn "Chainweb.Pact.PactService.execContinueBlock"
() 1 $
tryOne "execContinueBlock" statusRef $
execContinueBlock memPoolAccess bip
go
ValidateBlockMsg ValidateBlockReq {..} -> do
tryOne "execValidateBlock" statusRef $
fmap fst $ trace' logFn "Chainweb.Pact.PactService.execValidateBlock"
(\_ -> _valBlockHeader)
(\(_, g) -> fromIntegral g)
(execValidateBlock memPoolAccess _valBlockHeader _valCheckablePayload)
go
LookupPactTxsMsg (LookupPactTxsReq confDepth txHashes) -> do
trace logFn "Chainweb.Pact.PactService.execLookupPactTxs" ()
(length txHashes) $
tryOne "execLookupPactTxs" statusRef $
execLookupPactTxs confDepth txHashes
go
PreInsertCheckMsg (PreInsertCheckReq txs) -> do
trace logFn "Chainweb.Pact.PactService.execPreInsertCheckReq" ()
(length txs) $
tryOne "execPreInsertCheckReq" statusRef $
execPreInsertCheckReq txs
go
BlockTxHistoryMsg (BlockTxHistoryReq bh d) -> do
trace logFn "Chainweb.Pact.PactService.execBlockTxHistory" bh 1 $
tryOne "execBlockTxHistory" statusRef $
execBlockTxHistory bh d
go
HistoricalLookupMsg (HistoricalLookupReq bh d k) -> do
trace logFn "Chainweb.Pact.PactService.execHistoricalLookup" bh 1 $
tryOne "execHistoricalLookup" statusRef $
execHistoricalLookup bh d k
go
SyncToBlockMsg SyncToBlockReq {..} -> do
trace logFn "Chainweb.Pact.PactService.execSyncToBlock" _syncToBlockHeader 1 $
tryOne "syncToBlockBlock" statusRef $
execSyncToBlock _syncToBlockHeader
go
ReadOnlyReplayMsg ReadOnlyReplayReq {..} -> do
trace logFn "Chainweb.Pact.PactService.execReadOnlyReplay" (_readOnlyReplayLowerBound, _readOnlyReplayUpperBound) 1 $
tryOne "readOnlyReplayBlock" statusRef $
execReadOnlyReplay _readOnlyReplayLowerBound _readOnlyReplayUpperBound
go
tryOne
:: forall a. Text
-> TVar (RequestStatus a)
-> PactServiceM logger tbl a
-> PactServiceM logger tbl ()
tryOne which statusRef act =
evalPactOnThread
`catches`
[ Handler $ \(e :: SomeException) -> do
logErrorPact $ mconcat
[ "Received exception running pact service ("
, which
, "): "
, sshow e
]
liftIO $ throwIO e
]
where
-- here we start a thread to service the request
evalPactOnThread :: PactServiceM logger tbl ()
evalPactOnThread = do
maybeException <- withPactState $ \run -> do
goLock <- newEmptyMVar
finishedLock <- newEmptyMVar
-- fork a thread to service the request
bracket
(mask_ $ forkIOWithUnmask $ \restore ->
-- We wrap this whole block in `tryAsync` because we
-- want to ignore `RequestCancelled` exceptions that
-- occur while we are waiting on `takeMVar goLock`.
--
-- Otherwise we get logs like `chainweb-node:
-- RequestCancelled`.
--
-- We don't actually care about whether or not
-- `RequestCancelled` was encountered, so we just `void`
-- it.
void $ tryAsync @_ @RequestCancelled $ flip finally (tryPutMVar finishedLock ()) $ do
-- wait until we've been told to start.
-- we don't want to start if the request was cancelled
-- already
takeMVar goLock
-- run and report the answer.
restore (tryAny (run act)) >>= \case
Left ex -> atomically $ writeTVar statusRef (RequestFailed ex)
Right r -> atomically $ writeTVar statusRef (RequestDone r)
)
-- if Pact itself is killed, kill the request thread too.
(\tid -> throwTo tid RequestCancelled >> takeMVar finishedLock)
(\_tid -> do
-- check first if the request has been cancelled before
-- starting work on it
beforeStarting <- atomically $ do
readTVar statusRef >>= \case
RequestInProgress -> internalError "request in progress before starting"
RequestDone _ -> internalError "request finished before starting"
RequestFailed e -> return (Left e)
RequestNotStarted -> do
writeTVar statusRef RequestInProgress
return (Right ())
case beforeStarting of
-- the request has already been cancelled, don't
-- start work on it.
Left ex -> return (Left ex)
Right () -> do
-- let the request thread start working
putMVar goLock ()
-- wait until the request thread has finished
atomically $ readTVar statusRef >>= \case
RequestInProgress -> retry
RequestDone _ -> return (Right ())
RequestFailed e -> return (Left e)
RequestNotStarted -> internalError "request not started after starting"
)
case maybeException of
Left (fromException -> Just AsyncCancelled) -> do
logDebugPact "Pact action was cancelled"
Left (fromException -> Just ThreadKilled) -> do
logWarnPact "Pact action thread was killed"
Left (exn :: SomeException) -> do
logErrorPact $ mconcat
[ "Received exception running pact service ("
, which
, "): "
, sshow exn
]
Right () -> return ()
execNewBlock
:: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl)
=> MemPoolAccess
-> Miner
-> NewBlockFill
-> ParentHeader
-> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress))
execNewBlock mpAccess miner fill newBlockParent = pactLabel "execNewBlock" $ do
let pHeight = view blockHeight $ _parentHeader newBlockParent
let pHash = view blockHash $ _parentHeader newBlockParent
logInfoPact $ "(parent height = " <> sshow pHeight <> ")"
<> " (parent hash = " <> sshow pHash <> ")"
blockGasLimit <- view psBlockGasLimit
v <- view chainwebVersion
cid <- view chainId
Checkpointer.readFrom (Just newBlockParent) $
-- TODO: after the Pact 5 fork is complete, the Pact 4 case below will
-- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'.
SomeBlockM $ Pair
(do
blockDbEnv <- view psBlockDbEnv
initCache <- initModuleCacheForBlock
coinbaseOutput <- Pact4.runCoinbase
miner
(Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True)
initCache
let pactDb = Pact4._cpPactDbEnv blockDbEnv
finalBlockState <- fmap Pact4._benvBlockState
$ liftIO
$ readMVar
$ pdPactDbVar
$ pactDb
let blockInProgress = BlockInProgress
{ _blockInProgressModuleCache = Pact4ModuleCache initCache
-- ^ we do not use the module cache populated by coinbase in
-- subsequent transactions
, _blockInProgressHandle = BlockHandle (Pact4._bsTxId finalBlockState) (Pact4._bsPendingBlock finalBlockState)
, _blockInProgressParentHeader = Just newBlockParent
, _blockInProgressRemainingGasLimit = blockGasLimit
, _blockInProgressTransactions = Transactions
{ _transactionCoinbase = coinbaseOutput
, _transactionPairs = mempty
}
, _blockInProgressMiner = miner
, _blockInProgressPactVersion = Pact4T
, _blockInProgressChainwebVersion = v
, _blockInProgressChainId = cid
}
case fill of
NewBlockFill -> ForPact4 <$> Pact4.continueBlock mpAccess blockInProgress
NewBlockEmpty -> return (ForPact4 blockInProgress)
)
(do
coinbaseOutput <- Pact5.runCoinbase miner >>= \case
Left coinbaseError -> internalError $ "Error during coinbase: " <> sshow coinbaseError
Right coinbaseOutput ->
-- pretend that coinbase can throw an error, when we know it can't.
-- perhaps we can make the Transactions express this, may not be worth it.
return $ coinbaseOutput & Pact5.crResult . Pact5._PactResultErr %~ absurd
hndl <- use Pact5.pbBlockHandle
let blockInProgress = BlockInProgress
{ _blockInProgressModuleCache = Pact5NoModuleCache
, _blockInProgressHandle = hndl
, _blockInProgressParentHeader = Just newBlockParent
, _blockInProgressRemainingGasLimit = blockGasLimit
, _blockInProgressTransactions = Transactions
{ _transactionCoinbase = coinbaseOutput
, _transactionPairs = mempty
}
, _blockInProgressMiner = miner
, _blockInProgressPactVersion = Pact5T
, _blockInProgressChainwebVersion = v
, _blockInProgressChainId = cid
}
case fill of
NewBlockFill -> ForPact5 <$> Pact5.continueBlock mpAccess blockInProgress
NewBlockEmpty -> return (ForPact5 blockInProgress)
)
execContinueBlock
:: forall logger tbl pv. (Logger logger, CanReadablePayloadCas tbl)
=> MemPoolAccess
-> BlockInProgress pv
-> PactServiceM logger tbl (Historical (BlockInProgress pv))
execContinueBlock mpAccess blockInProgress = pactLabel "execNewBlock" $ do
Checkpointer.readFrom newBlockParent $
case _blockInProgressPactVersion blockInProgress of
-- TODO: after the Pact 5 fork is complete, the Pact 4 case below will
-- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'.
Pact4T -> SomeBlockM $ Pair (Pact4.continueBlock mpAccess blockInProgress) (error "pact5")
Pact5T -> SomeBlockM $ Pair (error "pact4") (Pact5.continueBlock mpAccess blockInProgress)
where
newBlockParent = _blockInProgressParentHeader blockInProgress
-- | only for use in generating genesis blocks in tools.
--
execNewGenesisBlock
:: (Logger logger, CanReadablePayloadCas tbl)
=> Miner
-> Vector Pact4.UnparsedTransaction
-> PactServiceM logger tbl PayloadWithOutputs
execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do
historicalBlock <- Checkpointer.readFrom Nothing $ SomeBlockM $ Pair
(do
logger <- view (psServiceEnv . psLogger)
v <- view chainwebVersion
cid <- view chainId
txs <- liftIO $ traverse (runExceptT . Pact4.checkParse logger v cid (genesisBlockHeight v cid)) newTrans
parsedTxs <- case partitionEithers (V.toList txs) of
([], validTxs) -> return (V.fromList validTxs)
(errs, _) -> internalError $ "Invalid genesis txs: " <> sshow errs
-- NEW GENESIS COINBASE: Reject bad coinbase, use date rule for precompilation
results <-
Pact4.execTransactions miner parsedTxs
(Pact4.EnforceCoinbaseFailure True)
(Pact4.CoinbaseUsePrecompiled False) Nothing Nothing
>>= throwCommandInvalidError
return $! toPayloadWithOutputs Pact4T miner results
)
(do
v <- view chainwebVersion
cid <- view chainId
let mempoolAccess = mempty
{ mpaGetBlock = \bf pbc bh bhash _bheader -> do
if _bfCount bf == 0
then do
maybeInvalidTxs <- pbc bh bhash newTrans
validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of
([], validTxs) -> return validTxs
(errs, _) -> throwM $ Pact5GenesisCommandsInvalid errs
V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq
else do
return V.empty
}
startHandle <- use Pact5.pbBlockHandle
let bipStart = BlockInProgress
{ _blockInProgressHandle = startHandle
, _blockInProgressMiner = miner
, _blockInProgressModuleCache = Pact5NoModuleCache
, _blockInProgressPactVersion = Pact5T
, _blockInProgressParentHeader = Nothing
, _blockInProgressChainwebVersion = v
, _blockInProgressChainId = cid
-- fake gas limit, gas is free for genesis
, _blockInProgressRemainingGasLimit = GasLimit (Pact4.ParsedInteger 999_999_999)
, _blockInProgressTransactions = Transactions
{ _transactionCoinbase = absurd <$> Pact5.noCoinbase
, _transactionPairs = mempty
}
}
results <- Pact5.continueBlock mempoolAccess bipStart
return $! finalizeBlock results
)
case historicalBlock of
NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis"
Historical block -> return block
execReadOnlyReplay
:: forall logger tbl
. (Logger logger, CanReadablePayloadCas tbl)
=> BlockHeader
-> Maybe BlockHeader
-> PactServiceM logger tbl ()
execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ do
ParentHeader cur <- Checkpointer.findLatestValidBlockHeader
logger <- view psLogger
bhdb <- view psBlockHeaderDb
pdb <- view psPdb
v <- view chainwebVersion
cid <- view chainId
-- lower bound must be an ancestor of upper.
upperBound <- case maybeUpperBound of
Just upperBound -> do
liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash upperBound)) >>=
flip unless (internalError "lower bound is not an ancestor of upper bound")
-- upper bound must be an ancestor of latest header.
liftIO (ancestorOf bhdb (view blockHash upperBound) (view blockHash cur)) >>=
flip unless (internalError "upper bound is not an ancestor of latest header")
return upperBound
Nothing -> do
liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash cur)) >>=
flip unless (internalError "lower bound is not an ancestor of latest header")
return cur
liftIO $ logFunctionText logger Info $ "pact db replaying between blocks "
<> sshow (view blockHeight lowerBound, view blockHash lowerBound) <> " and "
<> sshow (view blockHeight upperBound, view blockHash upperBound)
let genHeight = genesisHeight v cid
-- we don't want to replay the genesis header in here.
let lowerHeight = max (succ genHeight) (view blockHeight lowerBound)
withPactState $ \runPact ->
liftIO $ getBranchIncreasing bhdb upperBound (int lowerHeight) $ \blocks -> do
heightRef <- newIORef lowerHeight
withAsync (heightProgress lowerHeight (view blockHeight upperBound) heightRef (logInfo_ logger)) $ \_ -> do
blocks
& Stream.hoist liftIO
& play bhdb pdb heightRef runPact
where
play
:: CanReadablePayloadCas tbl
=> BlockHeaderDb
-> PayloadDb tbl
-> IORef BlockHeight
-> (forall a. PactServiceM logger tbl a -> IO a)
-> Stream.Stream (Stream.Of BlockHeader) IO r
-> IO r
play bhdb pdb heightRef runPact blocks = do
logger <- runPact $ view psLogger
validationFailedRef <- newIORef False
r <- blocks & Stream.mapM_ (\bh -> do
bhParent <- liftIO $ lookupParentM GenesisParentThrow bhdb bh
let
printValidationError (BlockValidationFailure (BlockValidationFailureMsg m)) = do
writeIORef validationFailedRef True
logFunctionText logger Error m
printValidationError e = throwM e
handleMissingBlock NoHistory = throwM $ BlockHeaderLookupFailure $
"execReadOnlyReplay: missing block: " <> sshow bh
handleMissingBlock (Historical ()) = return ()
payload <- liftIO $ fromJuste <$>
lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh)
let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload)
let isUpgradeBlock = isJust $ _chainwebVersion bhdb ^? versionUpgrades . atChain (_chainId bhdb) . ix (view blockHeight bh)
liftIO $ writeIORef heightRef (view blockHeight bh)
unless (isPayloadEmpty && not isUpgradeBlock)
$ handle printValidationError
$ (handleMissingBlock =<<)
$ runPact
$ Checkpointer.readFrom (Just $ ParentHeader bhParent) $
SomeBlockM $ Pair
(void $ Pact4.execBlock bh (CheckablePayloadWithOutputs payload))
(void $ Pact5.execExistingBlock bh (CheckablePayloadWithOutputs payload))
)
validationFailed <- readIORef validationFailedRef
when validationFailed $
throwM $ BlockValidationFailure $ BlockValidationFailureMsg "Prior block validation errors"
return r
heightProgress :: BlockHeight -> BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO ()
heightProgress initialHeight endHeight ref logFun = do
heightAndRateRef <- newIORef (initialHeight, 0.0 :: Double)
let delayMicros = 20_000_000
liftIO $ threadDelay (delayMicros `div` 2)
forever $ do
liftIO $ threadDelay delayMicros
(lastHeight, oldRate) <- readIORef heightAndRateRef
now' <- getCurrentTime
currentHeight <- readIORef ref
let blocksPerSecond
= 0.8
* oldRate
+ 0.2
* fromIntegral (currentHeight - lastHeight)
/ (fromIntegral delayMicros / 1_000_000)
writeIORef heightAndRateRef (currentHeight, blocksPerSecond)
let est =
flip addUTCTime now'
$ realToFrac @Double @NominalDiffTime
$ fromIntegral @BlockHeight @Double
(endHeight - initialHeight)
/ blocksPerSecond
logFun
$ Text.pack $ printf "height: %d | rate: %.1f blocks/sec | est. %s"
(fromIntegral @BlockHeight @Int $ currentHeight - initialHeight)
blocksPerSecond
(formatShow iso8601Format est)
execLocal
:: (Logger logger, CanReadablePayloadCas tbl)
=> Pact4.UnparsedTransaction
-> Maybe LocalPreflightSimulation
-- ^ preflight flag
-> Maybe LocalSignatureVerification
-- ^ turn off signature verification checks?
-> Maybe RewindDepth
-- ^ rewind depth
-> PactServiceM logger tbl LocalResult
execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do
e@PactServiceEnv{..} <- ask
let !cmd = Pact4.payloadObj <$> cwtx
!pm = Pact4.publicMetaOf cmd
!v = _chainwebVersion e
!cid = _chainId e
bhdb <- view psBlockHeaderDb
-- when no depth is defined, treat
-- withCheckpointerRewind as readFrom
-- (i.e. setting rewind to 0).
let rewindDepth = maybe 0 _rewindDepth rdepth
let timeoutLimit
| _psEnableLocalTimeout = Just (2 * 1_000_000)
| otherwise = Nothing
let localPact4 = do
pc <- view psParentHeader
let spv = Pact4.pactSPV bhdb (_parentHeader pc)
ctx <- Pact4.getTxContext noMiner pm
let bh = Pact4.ctxCurrentBlockHeight ctx
let gasModel = Pact4.getGasModel ctx
mc <- Pact4.getInitCache
dbEnv <- Pact4._cpPactDbEnv <$> view psBlockDbEnv
logger <- view (psServiceEnv . psLogger)
evalContT $ withEarlyReturn $ \earlyReturn -> do
pact4Cwtx <- liftIO (runExceptT (Pact4.checkParse logger v cid bh cwtx)) >>= \case
Left err -> earlyReturn $
let
parseError = Pact4.CommandResult
{ _crReqKey = Pact4.cmdToRequestKey cmd
, _crTxId = Nothing
, _crResult = Pact4.PactResult (Left (Pact4.PactError Pact4.SyntaxError Pact4.noInfo [] (sshow err)))
, _crGas = cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit . to int
, _crLogs = Nothing
, _crContinuation = Nothing
, _crMetaData = Nothing
, _crEvents = []
}
in case preflight of
Just PreflightSimulation -> Pact4LocalResultWithWarns parseError []
_ -> Pact4LocalResultLegacy parseError
Right pact4Cwtx -> return pact4Cwtx
case (preflight, sigVerify) of
(_, Just NoVerify) -> do
let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx)
let validated = Pact4.verifyHash @'Pact4.Blake2b_256 (Pact4._cmdHash cmd) payloadBS
case validated of
Left err -> earlyReturn $ review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err
Right _ -> return ()
_ -> do
let validated = Pact4.assertCommand pact4Cwtx (validPPKSchemes v cid bh) (isWebAuthnPrefixLegal v cid bh)
case validated of
Left err -> earlyReturn $ review _MetadataValidationFailure (pure $ displayAssertCommandError err)
Right () -> return ()
--
-- if the ?preflight query parameter is set to True, we run the `applyCmd` workflow
-- otherwise, we prefer the old (default) behavior. When no preflight flag is
-- specified, we run the old behavior. When it is set to true, we also do metadata
-- validations.
--
case preflight of
Just PreflightSimulation -> do
lift (Pact4.liftPactServiceM (Pact4.assertPreflightMetadata cmd ctx sigVerify)) >>= \case
Left err -> earlyReturn $ review _MetadataValidationFailure err
Right () -> return ()
let initialGas = Pact4.initialGasOf $ Pact4._cmdPayload pact4Cwtx
T3 cr _mc warns <- liftIO $ Pact4.applyCmd
_psVersion _psLogger _psGasLogger Nothing dbEnv
noMiner gasModel ctx (TxBlockIdx 0) spv (Pact4.payloadObj <$> pact4Cwtx)
initialGas mc ApplyLocal
let cr' = hashPact4TxLogs cr
warns' = Pact4.renderCompactText <$> toList warns
pure $ Pact4LocalResultWithWarns cr' warns'
_ -> liftIO $ do
let execConfig = Pact4.mkExecutionConfig $
[ Pact4.FlagAllowReadInLocal | _psAllowReadsInLocal ] ++
Pact4.enablePactEvents' v cid bh ++
Pact4.enforceKeysetFormats' v cid bh ++
Pact4.disableReturnRTC v cid bh
cr <- Pact4.applyLocal
_psLogger _psGasLogger dbEnv
gasModel ctx spv
pact4Cwtx mc execConfig
let cr' = hashPact4TxLogs cr
pure $ Pact4LocalResultLegacy cr'
let localPact5 = do
ph <- view psParentHeader
let pact5RequestKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx)
evalContT $ withEarlyReturn $ \earlyReturn -> do
pact5Cmd <- case Pact5.parsePact4Command cwtx of
Left (Left errText) -> do
earlyReturn $ Pact5LocalResultLegacy Pact5.CommandResult
{ _crReqKey = pact5RequestKey
, _crTxId = Nothing
, _crResult = Pact5.PactResultErr $
Pact5.pactErrorToOnChainError $ Pact5.PEParseError
(Pact5.ParsingError $ "pact 4/5 parsing compatibility mismatch: " <> errText)
(Pact5.LineInfo 0)
, _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit
, _crLogs = Nothing
, _crContinuation = Nothing
, _crMetaData = Nothing
, _crEvents = []
}
Left (Right (fmap Pact5.spanInfoToLineInfo -> parseError)) ->
earlyReturn $ Pact5LocalResultLegacy Pact5.CommandResult
{ _crReqKey = pact5RequestKey
, _crTxId = Nothing
, _crResult = Pact5.PactResultErr $
Pact5.pactErrorToOnChainError parseError
, _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit
, _crLogs = Nothing
, _crContinuation = Nothing
, _crMetaData = Nothing
, _crEvents = []
}
Right pact5Cmd -> return pact5Cmd
-- this is just one of our metadata validation passes.
-- in preflight, we do another one, which replicates some of this work;
-- TODO: unify preflight, newblock, and validateblock tx metadata validation
case (preflight, sigVerify) of
(_, Just NoVerify) -> do
let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx)
let validated = Pact5.verifyHash (Pact5._cmdHash pact5Cmd) payloadBS
case validated of
Left err -> earlyReturn $
review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err
Right _ -> return ()
_ -> do
let validated = Pact5.assertCommand pact5Cmd
case validated of
Left err -> earlyReturn $
review _MetadataValidationFailure (pure $ displayAssertCommandError err)
Right () -> return ()
let txCtx = Pact5.TxContext ph noMiner
let spvSupport = Pact5.pactSPV bhdb (_parentHeader ph)
case preflight of
Just PreflightSimulation -> do
-- preflight needs to do additional checks on the metadata
-- to match on-chain tx validation
lift (Pact5.liftPactServiceM (Pact5.assertPreflightMetadata (view Pact5.payloadObj <$> pact5Cmd) txCtx sigVerify)) >>= \case
Left err -> earlyReturn $ review _MetadataValidationFailure err
Right () -> return ()
let initialGas = Pact5.initialGasOf v cid (Pact5.ctxCurrentBlockHeight txCtx) (Pact5.ctxParentForkNumber txCtx) pact5Cmd
applyCmdResult <- lift $ Pact5.pactTransaction Nothing (\dbEnv ->
Pact5.applyCmd
_psLogger _psGasLogger dbEnv
txCtx (TxBlockIdx 0) spvSupport initialGas (view Pact5.payloadObj <$> pact5Cmd)
)
commandResult <- case applyCmdResult of
Left err ->
earlyReturn $ Pact5LocalResultWithWarns Pact5.CommandResult
{ _crReqKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx)
, _crTxId = Nothing
, _crResult = Pact5.PactResultErr $
Pact5.PactOnChainError
-- the only legal error type, once chainweaver is really gone, we
-- can use a real error type
(Pact5.ErrorType "EvalError")
(Pact5.mkBoundedText $ prettyPact5GasPurchaseFailure err)
(Pact5.LocatedErrorInfo Pact5.TopLevelErrorOrigin Pact5.noInfo)
, _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit
, _crLogs = Nothing
, _crContinuation = Nothing
, _crMetaData = Nothing
, _crEvents = []
}
[]
Right commandResult -> return commandResult
let pact5Pm = pact5Cmd ^. Pact5.cmdPayload . Pact5.payloadObj . Pact5.pMeta
let metadata = J.toJsonViaEncode $ Pact5.StableEncoding $ Pact5.ctxToPublicData pact5Pm txCtx
let commandResult' = hashPact5TxLogs $ set Pact5.crMetaData (Just metadata) commandResult
-- TODO: once Pact 5 has warnings, include them here.
pure $ Pact5LocalResultWithWarns
(Pact5.pactErrorToOnChainError <$> commandResult')
[]
_ -> lift $ do
-- default is legacy mode: use applyLocal, don't buy gas, don't do any
-- metadata checks beyond signature and hash checking
cr <- Pact5.pactTransaction Nothing $ \dbEnv -> do
fmap Pact5.pactErrorToOnChainError <$> Pact5.applyLocal _psLogger _psGasLogger dbEnv txCtx spvSupport (view Pact5.payloadObj <$> pact5Cmd)
pure $ Pact5LocalResultLegacy (hashPact5TxLogs cr)
let doLocal = Checkpointer.readFromNthParent (fromIntegral rewindDepth)
$ SomeBlockM $ Pair localPact4 localPact5
case timeoutLimit of
Nothing -> doLocal
Just limit -> withPactState $ \run -> timeoutYield limit (run doLocal) >>= \case
Just r -> pure r
Nothing -> do
logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx
pure $ review _LocalTimeout ()
execSyncToBlock
:: (CanReadablePayloadCas tbl, Logger logger)
=> BlockHeader
-> PactServiceM logger tbl ()
execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do
latestHeader <- Checkpointer.findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return
if latestHeader == targetHeader
then do
logInfoPact $ "checkpointer at checkpointer target"
<> ". target height: " <> sshow (view blockHeight latestHeader)
<> "; target hash: " <> blockHashToText (view blockHash latestHeader)
else do
logInfoPact $ "rewind to checkpointer target"
<> ". current height: " <> sshow (view blockHeight latestHeader)
<> "; current hash: " <> blockHashToText (view blockHash latestHeader)
<> "; target height: " <> sshow targetHeight
<> "; target hash: " <> blockHashToText targetHash
Checkpointer.rewindToIncremental Nothing (ParentHeader targetHeader)
where
targetHeight = view blockHeight targetHeader
targetHash = view blockHash targetHeader
failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB"
-- | Validate a mined block `(headerToValidate, payloadToValidate).
-- Note: The BlockHeader here is the header of the block being validated.
-- To do this, we atomically:
-- - determine if the block is on a different fork from the checkpointer's
-- current latest block, and execute all of the blocks on that fork if so,
-- all the way to the parent of the block we're validating.