diff --git a/chainweb.cabal b/chainweb.cabal index ae95dec72..72b0cb96d 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -278,6 +278,7 @@ library , Chainweb.Version.Guards , Chainweb.Version.Mainnet , Chainweb.Version.Pact5Development + , Chainweb.Version.Pact5Retro , Chainweb.Version.RecapDevelopment , Chainweb.Version.Registry , Chainweb.Version.Testnet04 @@ -380,6 +381,7 @@ library build-depends: , Decimal >= 0.4.2 , aeson >= 2.2 + , aeson-pretty >= 0.8 , asn1-encoding >=0.9 , asn1-types >=0.3 , async >= 2.2 diff --git a/node/ChainwebNode.hs b/node/ChainwebNode.hs index 24ba26d12..aec64a9b9 100644 --- a/node/ChainwebNode.hs +++ b/node/ChainwebNode.hs @@ -104,6 +104,7 @@ import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version import Chainweb.Version.Mainnet +import Chainweb.Version.Pact5Retro (pact5Retro) import Chainweb.Version.Testnet04 (testnet04) import Chainweb.Version.Registry @@ -558,10 +559,14 @@ main = do installFatalSignalHandlers [ sigHUP, sigTERM, sigXCPU, sigXFSZ ] checkRLimits runWithPkgInfoConfiguration mainInfo pkgInfo $ \conf -> do - let v = _configChainwebVersion $ _nodeConfigChainweb conf + let nodeConf = _nodeConfigChainweb conf + let v = if _configPact5Retro nodeConf + then pact5Retro (_configChainwebVersion nodeConf) + else _configChainwebVersion nodeConf + let conf' = conf & nodeConfigChainweb . configChainwebVersion .~ v registerVersion v hSetBuffering stderr LineBuffering - withNodeLogger (_nodeConfigLog conf) (_nodeConfigChainweb conf) v $ \logger -> do + withNodeLogger (_nodeConfigLog conf') (_nodeConfigChainweb conf') v $ \logger -> do logFunctionJson logger Info ProcessStarted handles [ Handler $ \(e :: SomeAsyncException) -> @@ -570,8 +575,8 @@ main = do logFunctionJson logger Error (ProcessDied $ show e) >> throwIO e ] $ do kt <- mapM iso8601ParseM (_versionServiceDate v) - withServiceDate (_configChainwebVersion (_nodeConfigChainweb conf)) (logFunctionText logger) kt $ void $ - race (node conf logger) (gcRunner (logFunctionText logger)) + withServiceDate (_configChainwebVersion (_nodeConfigChainweb conf')) (logFunctionText logger) kt $ void $ + race (node conf' logger) (gcRunner (logFunctionText logger)) where gcRunner lf = runForever lf "GarbageCollect" $ do performMajorGC diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index fd13e8db4..98bb1f628 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -87,6 +87,7 @@ import Numeric.Additive data Configuration = Configuration { _configRoot :: !BlockHeader , _configRocksDb :: !RocksDb + , _configReadOnly :: !Bool } -- -------------------------------------------------------------------------- -- @@ -239,7 +240,8 @@ dbAddChecked db e = unlessM (tableMember (_chainDbCas db) ek) dbAddCheckedIntern -- initBlockHeaderDb :: Configuration -> IO BlockHeaderDb initBlockHeaderDb config = do - dbAddChecked db rootEntry + unless (_configReadOnly config) $ do + dbAddChecked db rootEntry return db where rootEntry = _configRoot config @@ -271,14 +273,16 @@ closeBlockHeaderDb _ = return () withBlockHeaderDb :: RocksDb -> ChainwebVersion + -> Bool -> ChainId -> (BlockHeaderDb -> IO b) -> IO b -withBlockHeaderDb db v cid = bracket start closeBlockHeaderDb +withBlockHeaderDb db v readOnly cid = bracket start closeBlockHeaderDb where start = initBlockHeaderDb Configuration { _configRoot = genesisBlockHeader v cid , _configRocksDb = db + , _configReadOnly = readOnly } -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index e09e9ba27..8f5a6815f 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -364,8 +364,8 @@ withChainwebInternal -> (StartedChainweb logger -> IO ()) -> IO () withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir resetDb inner = do - - unless (_configOnlySyncPact conf || _configReadOnlyReplay conf) $ + let isReadOnly = _configReadOnlyReplay conf + unless (_configOnlySyncPact conf || isReadOnly) $ initializePayloadDb v payloadDb -- Garbage Collection @@ -412,6 +412,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re v cid rocksDb + isReadOnly (chainLogger cid) mcfg payloadDb @@ -421,33 +422,34 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re x ) - -- initialize global resources after all chain resources are initialized - (\cs -> do - logg Debug "finished initializing chain resources" - global (HM.fromList $ zip cidsList cs) - ) - cidsList + -- initialize global resources after all chain resources are initialized + (\cs -> do + logg Debug "finished initializing chain resources" + global (HM.fromList $ zip cidsList cs) + ) + + cidsList where pactConfig maxGasLimit = PactServiceConfig - { _pactReorgLimit = _configReorgLimit conf - , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf - , _pactQueueSize = _configPactQueueSize conf - , _pactResetDb = resetDb - , _pactAllowReadsInLocal = _configAllowReadsInLocal conf - , _pactUnlimitedInitialRewind = - isJust (_cutDbParamsInitialHeightLimit cutConfig) || - isJust (_cutDbParamsInitialCutFile cutConfig) - , _pactBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) - , _pactLogGas = _configLogGas conf - , _pactModuleCacheLimit = _configModuleCacheLimit conf - , _pactEnableLocalTimeout = _configEnableLocalTimeout conf - , _pactFullHistoryRequired = _configFullHistoricPactState conf - , _pactPersistIntraBlockWrites = - if _configFullHistoricPactState conf - then PersistIntraBlockWrites - else DoNotPersistIntraBlockWrites - , _pactTxTimeLimit = Nothing - } + { _pactReorgLimit = _configReorgLimit conf + , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf + , _pactQueueSize = _configPactQueueSize conf + , _pactResetDb = resetDb + , _pactAllowReadsInLocal = _configAllowReadsInLocal conf + , _pactUnlimitedInitialRewind = + isJust (_cutDbParamsInitialHeightLimit cutConfig) || + isJust (_cutDbParamsInitialCutFile cutConfig) + , _pactBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) + , _pactLogGas = _configLogGas conf + , _pactModuleCacheLimit = _configModuleCacheLimit conf + , _pactEnableLocalTimeout = _configEnableLocalTimeout conf + , _pactFullHistoryRequired = _configFullHistoricPactState conf + , _pactPersistIntraBlockWrites = + if _configFullHistoricPactState conf + then PersistIntraBlockWrites + else DoNotPersistIntraBlockWrites + , _pactTxTimeLimit = Nothing + } pruningLogger :: T.Text -> logger pruningLogger l = addLabel ("sub-component", l) @@ -506,8 +508,8 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re let pactSyncChains = case _configSyncPactChains conf of - Just syncChains | _configOnlySyncPact conf || _configReadOnlyReplay conf -> HM.filterWithKey (\k _ -> elem k syncChains) cs - _ -> cs + Just syncChains | _configOnlySyncPact conf || _configReadOnlyReplay conf -> HM.filterWithKey (\k _ -> elem k syncChains) cs + _ -> cs if _configReadOnlyReplay conf then do diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 02573d1b1..48ba7d330 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} @@ -84,6 +85,8 @@ withChainResources => ChainwebVersion -> ChainId -> RocksDb + -> Bool + -- ^ are we in readonly mode? -> logger -> (MVar PactExecutionService -> Mempool.InMemConfig Pact4.UnparsedTransaction) -> PayloadDb tbl @@ -93,25 +96,23 @@ withChainResources -> Counter "txFailures" -> (ChainResources logger -> IO a) -> IO a -withChainResources - v cid rdb logger mempoolCfg0 payloadDb pactDbDir pactConfig txFailuresCounter inner = - withBlockHeaderDb rdb v cid $ \cdb -> do - pexMv <- newEmptyMVar - let mempoolCfg = mempoolCfg0 pexMv - Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolCfg v $ \mempool -> do - mpc <- MPCon.mkMempoolConsensus mempool cdb $ Just payloadDb - withPactService v cid logger (Just txFailuresCounter) mpc cdb - payloadDb pactDbDir pactConfig $ \requestQ -> do - let pex = pes requestQ - putMVar pexMv pex - - -- run inner - inner $ ChainResources - { _chainResBlockHeaderDb = cdb - , _chainResLogger = logger - , _chainResMempool = mempool - , _chainResPact = pex - } +withChainResources v cid rdb readOnly logger mempoolCfg0 payloadDb pactDbDir pactConfig txFailuresCounter inner = do + withBlockHeaderDb rdb v readOnly cid $ \cdb -> do + pexMv <- newEmptyMVar + let mempoolCfg = mempoolCfg0 pexMv + Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolCfg v $ \mempool -> do + mpc <- MPCon.mkMempoolConsensus mempool cdb $ Just payloadDb + withPactService v cid logger (Just txFailuresCounter) mpc cdb payloadDb pactDbDir pactConfig $ \requestQ -> do + let pex = pes requestQ + putMVar pexMv pex + + -- run inner + inner $ ChainResources + { _chainResBlockHeaderDb = cdb + , _chainResLogger = logger + , _chainResMempool = mempool + , _chainResPact = pex + } where pes requestQ | v ^. versionCheats . disablePact = emptyPactExecutionService diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index c802d375c..eb2625b16 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -126,7 +126,6 @@ import Chainweb.Time import P2P.Node.Configuration import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) -import Chainweb.Version.Testnet04 -- -------------------------------------------------------------------------- -- -- Throttling Configuration @@ -407,7 +406,9 @@ data ChainwebConfiguration = ChainwebConfiguration , _configReadOnlyReplay :: !Bool -- ^ do a read-only replay using the cut db params for the block heights , _configPactParityReplay :: !Bool - -- ^ perform replay in a way that compares pact4 and pact5 outputs. + -- ^ dump transaction outputs for comparison. + , _configPact5Retro :: !Bool + -- ^ Enable pact5 retroactively, as far back as Chainweb 2.17, inclusive , _configSyncPactChains :: !(Maybe [ChainId]) -- ^ the only chains to be synchronized on startup to the latest cut. -- if unset, all chains will be synchronized. @@ -468,12 +469,12 @@ validateChainwebVersion v = do , sshow (_versionName v) ] -- FIXME Pact5: disable - when (v == mainnet || v == testnet04) $ + {-when (v == mainnet || v == testnet04) $ throwError $ T.unwords [ "This node version is a technical preview of Pact 5, and" , "cannot be used with Pact 4 chainweb versions (testnet04, mainnet)" , "just yet." - ] + ]-} where isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet]] @@ -509,6 +510,7 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configOnlySyncPact = False , _configReadOnlyReplay = False , _configPactParityReplay = False + , _configPact5Retro = False , _configSyncPactChains = Nothing , _configBackup = defaultBackupConfig , _configModuleCacheLimit = defaultModuleCacheLimit @@ -538,6 +540,8 @@ instance ToJSON ChainwebConfiguration where , "serviceApi" .= _configServiceApi o , "onlySyncPact" .= _configOnlySyncPact o , "readOnlyReplay" .= _configReadOnlyReplay o + , "pactParityReplay" .= _configPactParityReplay o + , "pact5Retro" .= _configPact5Retro o , "syncPactChains" .= _configSyncPactChains o , "backup" .= _configBackup o , "moduleCacheLimit" .= _configModuleCacheLimit o @@ -572,6 +576,7 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configOnlySyncPact ..: "onlySyncPact" % o <*< configReadOnlyReplay ..: "readOnlyReplay" % o <*< configPactParityReplay ..: "pactParityReplay" % o + <*< configPact5Retro ..: "pact5Retro" % o <*< configSyncPactChains ..: "syncPactChains" % o <*< configBackup %.: "backup" % o <*< configModuleCacheLimit ..: "moduleCacheLimit" % o @@ -633,6 +638,9 @@ pChainwebConfiguration = id <*< configPactParityReplay .:: boolOption_ % long "pact-parity-replay" <> help "Replay the block history in a way that compares Pact 4 and Pact 5 outputs" + <*< configPact5Retro .:: boolOption_ + % long "pact-5-retro" + <> help "Enable pact5 retroactively, as far back as Chainweb 2.17, inclusive" <*< configSyncPactChains .:: fmap Just % jsonOption % long "sync-pact-chains" <> help "The only Pact databases to synchronize. If empty or unset, all chains will be synchronized." diff --git a/src/Chainweb/Chainweb/PeerResources.hs b/src/Chainweb/Chainweb/PeerResources.hs index e823821fc..5004bcddd 100644 --- a/src/Chainweb/Chainweb/PeerResources.hs +++ b/src/Chainweb/Chainweb/PeerResources.hs @@ -206,8 +206,11 @@ getHost -> [PeerInfo] -> IO (Either T.Text Hostname) getHost mgr ver logger peers = do + let vName = case _versionName ver of + ChainwebVersionName n -> ChainwebVersionName (fromMaybe n (T.stripPrefix "pact5-retro-" n)) + nis <- forConcurrently peers $ \p -> - tryAllSynchronous (requestRemoteNodeInfo mgr (_versionName ver) (_peerAddr p) Nothing) >>= \case + tryAllSynchronous (requestRemoteNodeInfo mgr vName (_peerAddr p) Nothing) >>= \case Right x -> Just x <$ do logFunctionText logger Info $ "got remote info from " <> toText (_peerAddr p) diff --git a/src/Chainweb/Chainweb/PruneChainDatabase.hs b/src/Chainweb/Chainweb/PruneChainDatabase.hs index 08963bef0..6852db147 100644 --- a/src/Chainweb/Chainweb/PruneChainDatabase.hs +++ b/src/Chainweb/Chainweb/PruneChainDatabase.hs @@ -178,7 +178,7 @@ pruneAllChains -> IO () pruneAllChains logger rdb v checks = do now <- getCurrentTimeIntegral - wdb <- initWebBlockHeaderDb rdb v + wdb <- initWebBlockHeaderDb rdb v False forConcurrently_ (toList $ chainIds v) (pruneChain now wdb) where diam = diameter $ chainGraphAt v (maxBound @BlockHeight) @@ -311,7 +311,7 @@ fullGc logger rdb v = do -- Prune a single chain and return the sets of marked payloads -- - pruneChain cid = withBlockHeaderDb rdb v cid $ \cdb -> do + pruneChain cid = withBlockHeaderDb rdb v False cid $ \cdb -> do let chainLogger = addLabel ("chain", toText cid) logger chainLogg = logFunctionText chainLogger diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 91b4250f4..02e30536c 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -734,8 +734,8 @@ compactRocksDb logger cwVersion cids minBlockHeight srcDb targetDb = do log LL.Info "Initializing payload db" initializePayloadDb cwVersion targetPayloads - srcWbhdb <- initWebBlockHeaderDb srcDb cwVersion - targetWbhdb <- initWebBlockHeaderDb targetDb cwVersion + srcWbhdb <- initWebBlockHeaderDb srcDb cwVersion True + targetWbhdb <- initWebBlockHeaderDb targetDb cwVersion False forM_ cids $ \cid -> do let log' = logFunctionText (addChainIdLabel cid logger) log' LL.Info $ "Starting chain " <> chainIdToText cid diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs index 1dfaa4806..b82e8cf0b 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs @@ -105,7 +105,7 @@ getLatestCutHeaders :: () -> RocksDb -> IO (WebBlockHeaderDb, HashMap ChainId BlockHeader) getLatestCutHeaders v rocksDb = do - wbhdb <- initWebBlockHeaderDb rocksDb v + wbhdb <- initWebBlockHeaderDb rocksDb v True let cutHashes = cutHashesTable rocksDb latestCutHeaders <- readHighestCutHeaders v (\_ _ -> pure ()) wbhdb cutHashes pure (wbhdb, latestCutHeaders) diff --git a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs b/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs index 1378658cd..3776968ef 100644 --- a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs +++ b/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs @@ -177,64 +177,66 @@ doReadFrom logger v cid sql moduleCacheVar maybeParent pactVersion doRead = do latestHeader <- doGetLatestBlock sql h <- case pactVersion of Pact4T - | pact5 v cid currentHeight -> internalError $ - "Pact 4 readFrom executed on block height after Pact 5 fork, height: " <> sshow currentHeight - | otherwise -> PactDb.getEndTxId "doReadFrom" sql maybeParent >>= traverse \startTxId -> do - newDbEnv <- newMVar $ PactDb.BlockEnv - (PactDb.mkBlockHandlerEnv v cid currentHeight sql DoNotPersistIntraBlockWrites logger) - (PactDb.initBlockState defaultModuleCacheLimit startTxId) - { PactDb._bsModuleCache = sharedModuleCache } - let - -- is the parent the latest header, i.e., can we get away without rewinding? - parentIsLatestHeader = case (latestHeader, maybeParent) of - (Nothing, Nothing) -> True - (Just (_, latestHash), Just (ParentHeader ph)) -> - view blockHash ph == latestHash - _ -> False - mkBlockDbEnv db = PactDb.CurrentBlockDbEnv - { PactDb._cpPactDbEnv = PactDbEnv db newDbEnv - , PactDb._cpRegisterProcessedTx = \hash -> - PactDb.runBlockEnv newDbEnv (PactDb.indexPactTransaction $ BS.fromShort $ coerce hash) - , PactDb._cpLookupProcessedTx = \hs -> - HashMap.mapKeys coerce <$> doLookupSuccessful sql currentHeight (coerce hs) - } - pactDb - | parentIsLatestHeader = PactDb.chainwebPactDb - | otherwise = PactDb.rewoundPactDb currentHeight startTxId - r <- doRead (mkBlockDbEnv pactDb) (emptyBlockHandle startTxId) - finalCache <- PactDb._bsModuleCache . PactDb._benvBlockState <$> readMVar newDbEnv - return (r, finalCache) + | pact5 v cid currentHeight -> do + internalError $ + "Pact 4 readFrom executed on block height after Pact 5 fork, height: " <> sshow currentHeight + | otherwise -> do + PactDb.getEndTxId "doReadFrom" sql maybeParent >>= traverse \startTxId -> do + newDbEnv <- newMVar $ PactDb.BlockEnv + (PactDb.mkBlockHandlerEnv v cid currentHeight sql DoNotPersistIntraBlockWrites logger) + (PactDb.initBlockState defaultModuleCacheLimit startTxId) + { PactDb._bsModuleCache = sharedModuleCache } + let + -- is the parent the latest header, i.e., can we get away without rewinding? + parentIsLatestHeader = case (latestHeader, maybeParent) of + (Nothing, Nothing) -> True + (Just (_, latestHash), Just (ParentHeader ph)) -> + view blockHash ph == latestHash + _ -> False + mkBlockDbEnv db = PactDb.CurrentBlockDbEnv + { PactDb._cpPactDbEnv = PactDbEnv db newDbEnv + , PactDb._cpRegisterProcessedTx = \hash -> + PactDb.runBlockEnv newDbEnv (PactDb.indexPactTransaction $ BS.fromShort $ coerce hash) + , PactDb._cpLookupProcessedTx = \hs -> + HashMap.mapKeys coerce <$> doLookupSuccessful sql currentHeight (coerce hs) + } + pactDb + | parentIsLatestHeader = PactDb.chainwebPactDb + | otherwise = PactDb.rewoundPactDb currentHeight startTxId + r <- doRead (mkBlockDbEnv pactDb) (emptyBlockHandle startTxId) + finalCache <- PactDb._bsModuleCache . PactDb._benvBlockState <$> readMVar newDbEnv + return (r, finalCache) Pact5T - | pact5 v cid currentHeight -> - Pact5.getEndTxId "doReadFrom" sql maybeParent >>= traverse \startTxId -> do - let - -- is the parent the latest header, i.e., can we get away without rewinding? - -- TODO: just do this inside of the chainwebPactCoreBlockDb function? - parentIsLatestHeader = case (latestHeader, maybeParent) of - (Nothing, Nothing) -> True - (Just (_, latestHash), Just (ParentHeader ph)) -> - view blockHash ph == latestHash - _ -> False - blockHandlerEnv = Pact5.BlockHandlerEnv - { Pact5._blockHandlerDb = sql - , Pact5._blockHandlerLogger = logger - , Pact5._blockHandlerVersion = v - , Pact5._blockHandlerChainId = cid - , Pact5._blockHandlerBlockHeight = currentHeight - , Pact5._blockHandlerMode = Pact5.Transactional - , Pact5._blockHandlerPersistIntraBlockWrites = DoNotPersistIntraBlockWrites - } - let upperBound - | parentIsLatestHeader = Nothing - | otherwise = Just (currentHeight, startTxId) - let pactDb - = Pact5.chainwebPactCoreBlockDb upperBound blockHandlerEnv - r <- doRead pactDb (emptyBlockHandle (coerce @Pact5.TxId @Pact4.TxId startTxId)) - return (r, sharedModuleCache) - | otherwise -> - internalError $ - "Pact 5 readFrom executed on block height before Pact 5 fork, height: " <> sshow currentHeight + | pact5 v cid currentHeight -> do + Pact5.getEndTxId "doReadFrom" sql maybeParent >>= traverse \startTxId -> do + let + -- is the parent the latest header, i.e., can we get away without rewinding? + -- TODO: just do this inside of the chainwebPactCoreBlockDb function? + parentIsLatestHeader = case (latestHeader, maybeParent) of + (Nothing, Nothing) -> True + (Just (_, latestHash), Just (ParentHeader ph)) -> + view blockHash ph == latestHash + _ -> False + blockHandlerEnv = Pact5.BlockHandlerEnv + { Pact5._blockHandlerDb = sql + , Pact5._blockHandlerLogger = logger + , Pact5._blockHandlerVersion = v + , Pact5._blockHandlerChainId = cid + , Pact5._blockHandlerBlockHeight = currentHeight + , Pact5._blockHandlerMode = Pact5.Transactional + , Pact5._blockHandlerPersistIntraBlockWrites = DoNotPersistIntraBlockWrites + } + let upperBound + | parentIsLatestHeader = Nothing + | otherwise = Just (currentHeight, startTxId) + let pactDb + = Pact5.chainwebPactCoreBlockDb upperBound blockHandlerEnv + r <- doRead pactDb (emptyBlockHandle (coerce @Pact5.TxId @Pact4.TxId startTxId)) + return (r, sharedModuleCache) + | otherwise -> do + internalError $ + "Pact 5 readFrom executed on block height before Pact 5 fork, height: " <> sshow currentHeight case h of NoHistory -> return (sharedModuleCache, NoHistory) Historical (r, finalCache) -> return (finalCache, Historical r) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index d2e92ce41..7a65df48b 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -3,6 +3,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} @@ -45,6 +46,7 @@ module Chainweb.Pact.PactService import Chainweb.BlockHash import Chainweb.BlockHeader +import Pact.Core.StableEncoding (StableEncoding(..)) import Chainweb.BlockHeaderDB import Chainweb.BlockHeight import Chainweb.ChainId @@ -91,7 +93,6 @@ import Control.Monad.Except import Control.Monad.Reader import Control.Monad.State.Strict import Control.Parallel.Strategies qualified as Strategies -import Data.Aeson qualified as Aeson import Data.ByteString qualified as BS import Data.ByteString.Short qualified as SB import Data.Coerce (coerce) @@ -102,15 +103,13 @@ import Data.Functor.Product import Data.HashMap.Strict qualified as HM import Data.IORef import Data.LogMessage -import Data.Map (Map) -import Data.Map.Strict qualified as Map import Data.Maybe import Data.Monoid import Data.Set (Set) import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as Text -import Data.Text.Encoding qualified as T +import Data.Text.Encoding qualified as Text import Data.Time.Clock import Data.Time.Format.ISO8601 import Data.UUID qualified as UUID @@ -133,15 +132,18 @@ import Pact.Core.Persistence qualified as Pact5 import Pact.Gas qualified as Pact4 import Pact.Interpreter(PactDbEnv(..)) import Pact.JSON.Encode qualified as J +import Data.Aeson qualified as Aeson +import Data.Aeson.Encode.Pretty qualified as Aeson import Pact.Parse qualified as Pact4 import Pact.Types.Command qualified as Pact4 import Pact.Types.Hash qualified as Pact4 import Pact.Types.Pretty qualified as Pact4 import Pact.Types.Runtime qualified as Pact4 hiding (catchesPactError) -import Patience.Map qualified as Patience import Prelude hiding (lookup) import Streaming qualified as Stream import Streaming.Prelude qualified as Stream +import System.Directory (createDirectoryIfMissing) +import System.FilePath (()) import System.LogLevel import Text.Printf import Utils.Logging.Trace @@ -278,7 +280,7 @@ initializeCoinContract v cid pwo = do if currentBlockHeader /= ParentHeader genesisHeader then unless (pact5 v cid (view blockHeight genesisHeader)) $ do - !mc <- readFrom (Just currentBlockHeader) (SomeBlockM $ Pair Pact4.readInitModules (error "pact5")) >>= \case + !mc <- readFrom (Just currentBlockHeader) (SomeBlockM $ Pair Pact4.readInitModules (pure mempty)) >>= \case NoHistory -> throwM $ BlockHeaderLookupFailure $ "initializeCoinContract: internal error: latest block not found: " <> sshow currentBlockHeader Historical mc -> return mc @@ -635,22 +637,39 @@ execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" Historical block -> return block - -data CommandResultDiffable = CommandResutlDiffable +data CommandResultDiffable = CommandResultDiffable { _crdTxId :: Maybe Pact5.TxId , _crdRequestKey :: Pact5.RequestKey - -- , _crdMetadata :: Aeson.Value -- maybe ? , _crdResult :: Pact5.PactResult ErrorDiffable - , _crdEvents :: Set (Pact5.PactEvent Pact5.PactValue) + , _crdEvents :: OrderedEvents } deriving stock (Eq, Show) +instance J.Encode CommandResultDiffable where + build CommandResultDiffable{..} = J.object + [ "txId" J..?= fmap (J.Aeson . Pact5._txId) _crdTxId + , "requestKey" J..= _crdRequestKey + -- , "result" J..= _crdResult + , "events" J..= _crdEvents + ] + +newtype OrderedEvents + = OrderedEvents { getOrderedEvents :: (Set (StableEncoding (Pact5.PactEvent Pact5.PactValue))) } + deriving stock (Show) + deriving newtype (Eq) + +instance J.Encode OrderedEvents where + build (OrderedEvents s) = J.array s + newtype ErrorDiffable - = ErrorDiffable (Pact5.PactErrorCompat ()) - deriving Show + = ErrorDiffable (Pact5.PactErrorCompat ()) + deriving stock (Show) + +instance J.Encode ErrorDiffable where + build (ErrorDiffable e) = J.build (fmap J.Array e) instance Eq ErrorDiffable where - (ErrorDiffable ler) == (ErrorDiffable rer) = diffErr ler rer + ErrorDiffable ler == ErrorDiffable rer = diffErr ler rer where diffErr (Pact5.PELegacyError l) (Pact5.PELegacyError r) = Pact5._leType l == Pact5._leType r @@ -682,14 +701,12 @@ instance Eq ErrorDiffable where "PEVerifierError" -> Pact5.LegacyEvalError _ -> error "impossible: Pact 5 error code generated an illegal error code. This should never happen" - - commandResultToDiffable :: Pact5.CommandResult log (Pact5.PactErrorCompat info) -> CommandResultDiffable -commandResultToDiffable cr = CommandResutlDiffable +commandResultToDiffable cr = CommandResultDiffable { _crdTxId = Pact5._crTxId cr , _crdRequestKey = Pact5._crReqKey cr , _crdResult = Pact5._crResult (ErrorDiffable . void <$> cr) - , _crdEvents = Set.fromList (Pact5._crEvents cr) + , _crdEvents = OrderedEvents $ Set.fromList $ fmap StableEncoding (Pact5._crEvents cr) } execReadOnlyReplay @@ -776,13 +793,18 @@ execReadOnlyReplay isParity lowerBound maybeUpperBound = pactLabel "execReadOnly NoHistory -> throwM $ BlockHeaderLookupFailure $ "execReadOnlyReplay: missing block: " <> sshow bh Historical x -> return x + let prettyJson :: J.Encode a => a -> BS.ByteString + prettyJson a = case Aeson.eitherDecode @Aeson.Value (J.encode a) of + Left err -> error $ "execReadOnlyReplay: failed to decode json: " <> sshow err + Right json -> BS.toStrict $ Aeson.encodePretty json + let height = view blockHeight bh if isParity && chainweb217Pact v cid height then do eOutputs <- catchValidationError $ (handleMissingBlock =<<) $ runPact - $ readFromBoth (Just $ ParentHeader bhParent) $ do + $ readFrom (Just $ ParentHeader bhParent) $ do liftIO $ writeIORef heightRef height payload <- liftIO $ fromJuste <$> lookupPayloadDataWithHeight pdb (Just height) (view blockPayloadHash bh) @@ -792,53 +814,49 @@ execReadOnlyReplay isParity lowerBound maybeUpperBound = pactLabel "execReadOnly case eOutputs of Left () -> do return () - Right (pact4Outputs, pact5Outputs) -> do + Right outputs -> do -- TODO: only having the hash of the logs might be an issue if we want to compare logs - pact4Transactions :: Vector (Pact5.Command (Pact5.PayloadWithText Pact5.PublicMeta Text), Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))) - <- forM (_payloadWithOutputsTransactions pact4Outputs) $ \(Transaction txBytes, TransactionOutput txOutBytes) -> do - let cmdText :: Pact4.Command Text - cmdText = fromJuste $ decodeStrictOrThrow' txBytes - - let cmdResult :: Pact4.CommandResult Pact4.Hash - cmdResult = fromJuste $ decodeStrictOrThrow' txOutBytes - - cmdPayload <- case Pact4.decodePayload (pact4ParserVersion v cid height) (T.encodeUtf8 $ Pact4._cmdPayload cmdText) of - Left err -> internalError $ "execReadOnlyReplay parity: failed to decode pact4 command: " <> sshow err - Right cmdPayload -> return $ fmap Pact4._pcCode cmdPayload - - -- TODO: Converting to and from JSON here is bad for perf. - cmdResult5 <- decodeStrictOrThrow' (BS.toStrict $ J.encode cmdResult) - - return (Pact5.fromPact4Command (cmdPayload <$ cmdText), cmdResult5) - - pact5Transactions :: Vector (Pact5.Command (Pact5.PayloadWithText Pact5.PublicMeta Text), Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info))) - <- forM (_payloadWithOutputsTransactions pact5Outputs) $ \(Transaction txBytes, TransactionOutput txOutBytes) -> do - let cmdText :: Pact5.Command Text - cmdText = fromJuste $ decodeStrictOrThrow' txBytes + if pact5 v cid height + then do + forM_ (_payloadWithOutputsTransactions outputs) $ \(Transaction txBytes, TransactionOutput txOutBytes) -> do + cmd <- case Pact5.parseCommand (fromJuste $ decodeStrictOrThrow' txBytes) of + Left err -> internalError $ "execReadOnlyReplay parity: failed to parse pact5 command: " <> sshow err + Right cmd -> return (fmap (fmap Pact5._pcCode) cmd) let cmdResult :: Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info)) cmdResult = fromJuste $ decodeStrictOrThrow' txOutBytes - cmd <- case Pact5.parseCommand cmdText of - Left err -> internalError $ "execReadOnlyReplay parity: failed to parse pact5 command: " <> sshow err - Right cmd -> return cmd - - return (fmap Pact5._pcCode <$> cmd, cmdResult) + let dir = "parity-replay-pact5" + createDirectoryIfMissing True dir + let filename = dir Text.unpack (Text.decodeUtf8 (SB.fromShort (Pact5.unHash (Pact5._cmdHash cmd)))) <> ".json" + BS.writeFile filename $ prettyJson $ commandResultToDiffable cmdResult - let pact4Map :: Map Pact5.Hash CommandResultDiffable - pact4Map = Map.fromList $ fmap (\(cmd, cmdResult) -> (Pact5._cmdHash cmd, commandResultToDiffable cmdResult)) $ V.toList pact4Transactions + else do + forM_ (_payloadWithOutputsTransactions outputs) $ \(Transaction txBytes, TransactionOutput txOutBytes) -> do + cmd <- do + let cmdText :: Pact4.Command Text + cmdText = fromJuste $ decodeStrictOrThrow' txBytes - let pact5Map :: Map Pact5.Hash CommandResultDiffable - pact5Map = Map.fromList $ fmap (\(cmd, cmdResult) -> (Pact5._cmdHash cmd, commandResultToDiffable cmdResult)) $ V.toList pact5Transactions + {- + cmdPayload <- case Pact4.decodePayload (pact4ParserVersion v cid height) (T.encodeUtf8 $ Pact4._cmdPayload cmdText) of + Left err -> internalError $ "execReadOnlyReplay parity: failed to decode pact4 command: " <> sshow err + Right cmdPayload -> return $ fmap Pact4._pcCode cmdPayload - let outputsDiff :: Map Pact5.Hash (Patience.Delta CommandResultDiffable) - outputsDiff = Map.filter (not . Patience.isSame) $ Patience.diff pact4Map pact5Map + return $ Pact5.fromPact4Command (cmdPayload <$ cmdText) + -} - when (not $ Map.null outputsDiff) $ do - writeIORef parityFailedRef True - logFunctionText logger Error $ "execReadOnlyReplay parity: differences in command results: " <> sshow outputsDiff + return cmdText - return () + -- TODO: Converting to and from JSON here is bad for perf. + cmdResult :: Pact5.CommandResult Pact5.Hash (Pact5.PactErrorCompat (Pact5.LocatedErrorInfo Pact5.Info)) <- do + let cmdResult4 :: Pact4.CommandResult Pact4.Hash + cmdResult4 = fromJuste $ decodeStrictOrThrow' txOutBytes + decodeStrictOrThrow' (BS.toStrict $ J.encode cmdResult4) + + let dir = "parity-replay-pact4" + createDirectoryIfMissing True dir + let filename = dir Text.unpack (Pact4.hashToText (Pact4.toUntypedHash (Pact4._cmdHash cmd))) <> ".json" + BS.writeFile filename $ prettyJson $ commandResultToDiffable cmdResult else do handle printValidationError diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index b18f1527d..977a8daaa 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -29,7 +29,6 @@ module Chainweb.Pact.PactService.Checkpointer ( readFromLatest , readFromNthParent , readFrom - , readFromBoth , restoreAndSave , findLatestValidBlockHeader' , findLatestValidBlockHeader @@ -185,31 +184,6 @@ readFrom ph doRead = do | pact5 v cid currentHeight -> execPact5 forPact5 | otherwise -> execPact4 forPact4 --- | Run both read actions, not just one. Used for parity replay. -readFromBoth - :: (Logger logger) - => Maybe ParentHeader - -> SomeBlockM logger tbl a - -> PactServiceM logger tbl (Historical (a, a)) -readFromBoth ph doRead = do - cp <- view psCheckpointer - pactParent <- getPactParent ph - s <- get - e <- ask - let execPact4 act = - liftIO $ _cpReadFrom (_cpReadCp cp) ph Pact4T $ \dbenv _ -> - evalPactServiceM s e $ - Pact4.runPactBlockM pactParent (isNothing ph) dbenv act - let execPact5 act = - liftIO $ _cpReadFrom (_cpReadCp cp) ph Pact5T $ \dbenv blockHandle -> - evalPactServiceM s e $ do - fst <$> Pact5.runPactBlockM pactParent (isNothing ph) dbenv blockHandle act - case doRead of - SomeBlockM (Pair forPact4 forPact5) -> do - pact4Result <- execPact4 forPact4 - pact5Result <- execPact5 forPact5 - pure $ (,) <$> pact4Result <*> pact5Result - -- here we cheat, making the genesis block header's parent the genesis -- block header, only for Pact's information, *not* for the checkpointer; -- the checkpointer knows the difference between rewinding to before the diff --git a/src/Chainweb/Version/Pact5Retro.hs b/src/Chainweb/Version/Pact5Retro.hs new file mode 100644 index 000000000..99db37da1 --- /dev/null +++ b/src/Chainweb/Version/Pact5Retro.hs @@ -0,0 +1,78 @@ +{-# language LambdaCase #-} +{-# language ImportQualifiedPost #-} +{-# language MultiWayIf #-} +{-# language RecordWildCards #-} +{-# language ScopedTypeVariables #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language TypeApplications #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.Pact5Retro (pact5Retro) where + +import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact5.Transaction qualified as Pact5 +import Chainweb.Version +import Control.Lens +import Data.Aeson qualified as Aeson +import Data.ByteString.Short qualified as SBS +import Data.HashMap.Strict qualified as HM +import Data.List qualified as List +import Data.Text (Text) +import Data.Text.Encoding qualified as Text +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Pretty qualified as Pact5 +import Pact.JSON.Encode qualified as J +import Data.Bits (complement) + +pact5Retro :: ChainwebVersion -> ChainwebVersion +pact5Retro v = v + & versionName .~ pact5VersionName + & versionCode .~ pact5VersionCode + & versionForks .~ pact5Forks + & versionUpgrades .~ pact5Upgrades + where + + pact5VersionCode = case _versionCode v of + ChainwebVersionCode n -> ChainwebVersionCode $ complement n + + pact5VersionName = case _versionName v of + ChainwebVersionName n -> ChainwebVersionName $ "pact5-retro-" <> n + + -- take everything after Chainweb217Pact and throw it out. + -- then insert the Pact5 at the same height as Chainweb217Pact + pact5Forks = flip HM.mapWithKey (_versionForks v) $ \fork height -> + if | fork < Chainweb217Pact -> height + | fork == Pact5Fork -> case HM.lookup Chainweb217Pact (_versionForks v) of + Nothing -> error "pact5Retro: Chainweb 2.17 fork missing." + -- When it's available, use the same height as Chainweb217. Pact5 + -- is only guaranteed to replay from Chainweb 2.17 onward. + Just h -> h + | otherwise -> AllChains ForkNever + + -- Migrate all upgrades to Pact5 + pact5Upgrades = _versionUpgrades v + <&> HM.map turnIntoPact5Upgrade + +pactTxFrom4To5 :: Pact4.Transaction -> Pact5.Transaction +pactTxFrom4To5 tx = + let + e = do + let json = J.encode (fmap (Text.decodeUtf8 . SBS.fromShort . Pact4.payloadBytes) tx) + cmdWithPayload <- Aeson.eitherDecode @(Pact5.Command Text) json + over _Left Pact5.renderCompactString $ Pact5.parseCommand cmdWithPayload + in + case e of + Left err -> error err + Right cmds -> cmds + +turnIntoPact5Upgrade :: ForSomePactVersion PactUpgrade -> ForSomePactVersion PactUpgrade +turnIntoPact5Upgrade = + forAnyPactVersion (\case + Pact4Upgrade{..} -> ForPact5 $ Pact5Upgrade + { _pact5UpgradeTransactions = List.map pactTxFrom4To5 _pact4UpgradeTransactions + } + Pact5Upgrade{..} -> ForPact5 $ Pact5Upgrade{..} + ) \ No newline at end of file diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 121a22433..72c440524 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -125,12 +125,11 @@ instance (k ~ CasKeyType (ChainValue BlockHeader)) => ReadableTable WebBlockHead initWebBlockHeaderDb :: RocksDb -> ChainwebVersion + -> Bool -> IO WebBlockHeaderDb -initWebBlockHeaderDb db v = WebBlockHeaderDb - <$!> itraverse (\cid _ -> initBlockHeaderDb (conf cid db)) (HS.toMap $ chainIds v) +initWebBlockHeaderDb db v readOnly = WebBlockHeaderDb + <$!> itraverse (\cid _ -> initBlockHeaderDb (Configuration (genesisBlockHeader v cid) db readOnly)) (HS.toMap $ chainIds v) <*> pure v - where - conf cid = Configuration (genesisBlockHeader v cid) -- | FIXME: this needs some consistency checks --