Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions marconi-chain-index/bench/BenchQueries.hs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ runIndexerSyncing trace databaseDir nodeSocketPath indexerTVar = do
networkId
C.ChainPointAtGenesis
nodeSocketPath
(MinIndexingDepth 0)
(ShouldFailIfResync True)
)
(MinIndexingDepth 0)
(ShouldFailIfResync True)
indexers

tests
Expand Down
159 changes: 76 additions & 83 deletions marconi-chain-index/src/Marconi/ChainIndex/Indexers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import Control.Monad.Trans.Except (
ExceptT,
runExceptT,
)
import Control.Monad.Trans.Reader (ReaderT (runReaderT))
import Data.Functor (($>))
import Data.List.NonEmpty (NonEmpty)
import Data.Map (Map)
Expand All @@ -110,9 +111,10 @@ import Marconi.ChainIndex.Indexers.EpochState qualified as EpochState
import Marconi.ChainIndex.Indexers.MintBurn qualified as MintBurn
import Marconi.ChainIndex.Indexers.ScriptTx qualified as ScriptTx
import Marconi.ChainIndex.Indexers.Utxo qualified as Utxo
import Marconi.ChainIndex.Logging (chainSyncEventStreamLogging)
import Marconi.ChainIndex.Logging (chainSyncEventStreamLogging, logMInfo)
import Marconi.ChainIndex.Node.Client.Retry (withNodeConnectRetry)
import Marconi.ChainIndex.Types (
ChainIndexerT (ChainIndexerT),
IndexingDepth (MaxIndexingDepth, MinIndexingDepth),
RunIndexerConfig (RunIndexerConfig),
SecurityParam (SecurityParam),
Expand Down Expand Up @@ -642,90 +644,81 @@ mkIndexerStream
-> IO ()
mkIndexerStream = mkIndexerStream' (CE.bimSlotNo . blockInMode)

-- TODO: placeholder, see definition of 'withNodeConnectRetry'
withNodeConnectRetryNEW = undefined

runIndexers
:: RunIndexerConfig
-> IndexingDepth
-> ShouldFailIfResync
-> [(Worker, Maybe FilePath)]
-> IO ()
runIndexers
( RunIndexerConfig
stdoutTrace
retryConfig
securityParam
networkId
cliChainPoint
socketPath
)
indexingDepth
(ShouldFailIfResync shouldFailIfResync)
indexerList = do
withNodeConnectRetry stdoutTrace retryConfig socketPath $ do
currentNodeBlockNo <- Utils.toException $ Utils.queryCurrentNodeBlockNo @Void networkId socketPath
let indexers = mapMaybe sequenceA indexerList
coordinator <- initializeCoordinatorFromIndexers securityParam indexingDepth indexers
let indexerDepth =
let SecurityParam s = securityParam
in case indexingDepth of
MinIndexingDepth d -> SecurityParam $ s - d + 1
MaxIndexingDepth -> SecurityParam 1
resumablePoints <-
getStartingPointsFromIndexers indexerDepth currentNodeBlockNo indexers coordinator
let oldestCommonChainPoint = minimum resumablePoints
resumePoint = case cliChainPoint of
C.ChainPointAtGenesis -> oldestCommonChainPoint -- User didn't specify a chain point, use oldest common chain point,
cliCp -> cliCp -- otherwise use what was provided on CLI.
logInfo stdoutTrace $
"Resumable points for each indexer:"
<> line
<> indent 4 (align (list (fmap pretty resumablePoints)))

-- Possible runtime failure if an indexer with a non-genesis resumable point will resume from
-- genesis.
if shouldFailIfResync
&& elem C.ChainPointAtGenesis resumablePoints
&& any (\case ChainPoint{} -> True; _ -> False) resumablePoints
then do
logError stdoutTrace $
nest
4
( "At least one indexer has a non-genesis resumable point, while the oldest common resumable point between indexers is genesis."
<> line
<> "Are you sure you want to restart syncing that indexer from genesis?"
<> line
<> "If so, remove the '--fail-if-resyncing-from-genesis' flag."
)
else do
let stream =
mkIndexerStream coordinator
. chainSyncEventStreamLogging stdoutTrace
. updateProcessedBlocksMetric
runChainSyncStream = withChainSyncBlockEventStream socketPath networkId [resumePoint] stream
whenNoIntersectionFound NoIntersectionFound = do
logError stdoutTrace $
"No intersection found when looking for the chain point"
<+> pretty resumePoint
:: [(Worker, Maybe FilePath)]
-> ChainIndexerT IO ()
runIndexers indexerList = do
withNodeConnectRetryNEW $ do
-- TODO: get values of config items from Reader, refactor impure functions to run in ChainIndexerT IO
--
-- currentNodeBlockNo <- Utils.toException $ Utils.queryCurrentNodeBlockNo @Void networkId socketPath
-- let indexers = mapMaybe sequenceA indexerList
-- coordinator <- initializeCoordinatorFromIndexers securityParam indexingDepth indexers
-- let indexerDepth =
-- let SecurityParam s = securityParam
-- in case indexingDepth of
-- MinIndexingDepth d -> SecurityParam $ s - d + 1
-- MaxIndexingDepth -> SecurityParam 1
-- resumablePoints <-
-- getStartingPointsFromIndexers indexerDepth currentNodeBlockNo indexers coordinator
-- let oldestCommonChainPoint = minimum resumablePoints
-- resumePoint = case cliChainPoint of
-- C.ChainPointAtGenesis -> oldestCommonChainPoint -- User didn't specify a chain point, use oldest common chain point,
-- cliCp -> cliCp -- otherwise use what was provided on CLI.
logMInfo $
"Resumable points for each indexer:"
<> line
<> indent 4 (align (list (fmap pretty resumablePoints)))

-- Possible runtime failure if an indexer with a non-genesis resumable point will resume from
-- genesis.
if shouldFailIfResync
&& elem C.ChainPointAtGenesis resumablePoints
&& any (\case ChainPoint{} -> True; _ -> False) resumablePoints
then do
logError stdoutTrace $
nest
4
( "At least one indexer has a non-genesis resumable point, while the oldest common resumable point between indexers is genesis."
<> line
<> "Are you sure you want to restart syncing that indexer from genesis?"
<> line
<> "If so, remove the '--fail-if-resyncing-from-genesis' flag."
)
else do
let stream =
mkIndexerStream coordinator
. chainSyncEventStreamLogging stdoutTrace
. updateProcessedBlocksMetric
runChainSyncStream = withChainSyncBlockEventStream socketPath networkId [resumePoint] stream
whenNoIntersectionFound NoIntersectionFound = do
logError stdoutTrace $
"No intersection found when looking for the chain point"
<+> pretty resumePoint
<> "."
<+> "Please check the slot number and the block hash do belong to the chain."
signalQSemN (coordinator ^. barrier) (coordinator ^. indexerCount)
in finally
(runChainSyncStream `catch` whenNoIntersectionFound)
(waitForIndexersToFinishProcessingLastEvent coordinator)
where
waitForIndexersToFinishProcessingLastEvent :: Coordinator' a -> IO ()
waitForIndexersToFinishProcessingLastEvent coordinator = do
let secondsBeforeTimeout = 180
logInfo stdoutTrace $
"Stopping indexing. Waiting for indexers to finish their work (timeout after "
<> pretty secondsBeforeTimeout
<> "s) ..."
res <-
timeout (secondsBeforeTimeout * 1_000_000) $
waitQSemN (coordinator ^. barrier) (coordinator ^. indexerCount)
case res of
Just _ -> logInfo stdoutTrace "Done!"
-- TODO: When it's possible, let's put some useful information in this exception
Nothing -> throwIO (Timeout @Void "Timed out.")
<+> "Please check the slot number and the block hash do belong to the chain."
signalQSemN (coordinator ^. barrier) (coordinator ^. indexerCount)
in finally
(runChainSyncStream `catch` whenNoIntersectionFound)
(waitForIndexersToFinishProcessingLastEvent coordinator)
where
waitForIndexersToFinishProcessingLastEvent :: Coordinator' a -> IO ()
waitForIndexersToFinishProcessingLastEvent coordinator = do
let secondsBeforeTimeout = 180
logInfo stdoutTrace $
"Stopping indexing. Waiting for indexers to finish their work (timeout after "
<> pretty secondsBeforeTimeout
<> "s) ..."
res <-
timeout (secondsBeforeTimeout * 1_000_000) $
waitQSemN (coordinator ^. barrier) (coordinator ^. indexerCount)
case res of
Just _ -> logInfo stdoutTrace "Done!"
-- TODO: When it's possible, let's put some useful information in this exception
Nothing -> throwIO (Timeout @Void "Timed out.")

updateProcessedBlocksMetric
:: S.Stream (S.Of (ChainSyncEvent BlockEvent)) IO r
Expand Down
25 changes: 19 additions & 6 deletions marconi-chain-index/src/Marconi/ChainIndex/Logging.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Marconi.ChainIndex.Logging (
chainSyncEventStreamLogging,
MarconiTrace,
mkMarconiTrace,
logMInfo,

-- * Exported for testing purposes
marconiFormatting,
Expand All @@ -22,7 +23,11 @@ import Cardano.Api.Extended.Streaming (
)
import Cardano.BM.Trace (Trace, logInfo)
import Cardano.BM.Tracing (contramap)
import Control.Lens.Getter qualified as Lens
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Reader (asks)
import Control.Monad.Trans (lift)
import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
import Data.Text (Text)
import Data.Time (
Expand All @@ -36,7 +41,7 @@ import Data.Time (
import Data.Word (Word64)
import GHC.Generics (Generic)
import Marconi.ChainIndex.Orphans ()
import Marconi.ChainIndex.Types (MarconiTrace)
import Marconi.ChainIndex.Types (ChainIndexerT, MarconiTrace, runIndexerConfigTrace)
import Prettyprinter (Pretty (pretty), (<+>))
import Prettyprinter qualified as Pretty
import Prettyprinter.Render.Text qualified as Pretty
Expand All @@ -54,6 +59,14 @@ marconiFormatting =
Pretty.renderStrict
. Pretty.layoutPretty Pretty.defaultLayoutOptions

logMInfo :: MonadIO m => Pretty.Doc () -> ChainIndexerT m ()
logMInfo msg = do
trace <- Lens.view runIndexerConfigTrace
lift $ logInfo trace msg
Comment thread
ana-pantilie marked this conversation as resolved.
Outdated

-- TODO: this should also throw an exception
logMError = undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like log functions shouldn't be throwing exceptions, despite what we said earlier today about having a unified framework for both.

Perhaps it would be OK to have a function that does both, but have that reflected in the name. Something like logThrowError.

In another system I worked on, we also included a "context" parameter which was just a string that indicated where the error was thrown from. This helped a lot when looking at logs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I think we need to have the concrete requirements regarding errors and exceptions in order to make any changes here.


-- | Chain synchronisation statistics measured starting from previously measured 'LastSyncStats'.
data LastSyncStats = LastSyncStats
{ syncStatsNumBlocks :: !Word64
Expand Down Expand Up @@ -87,7 +100,7 @@ instance Pretty LastSyncLog where
<+> pretty cp
<+> "and current node tip is"
<+> pretty nt
<> "."
<> "."

processingSummaryMsg timeSinceLastMsg =
"Processed"
Expand All @@ -96,12 +109,12 @@ instance Pretty LastSyncLog where
<+> pretty numRollBackwards
<+> "rollbacks in the last"
<+> pretty (formatTime defaultTimeLocale "%s" timeSinceLastMsg)
<> "s"
<> "s"
in case (timeSinceLastMsgM, cp, nt) of
(Nothing, _, _) ->
"Starting from"
<+> pretty cp
<> "."
<> "."
<+> currentTipMsg timeSinceLastMsgM
(Just _, _, C.ChainTipAtGenesis) ->
"Not syncing. Node tip is at Genesis"
Expand All @@ -110,7 +123,7 @@ instance Pretty LastSyncLog where
"Synchronising (0%)."
<+> currentTipMsg timeSinceLastMsgM
<+> processingSummaryMsg timeSinceLastMsg
<> "."
<> "."
( Just timeSinceLastMsg
, C.ChainPoint (C.SlotNo chainSyncSlot) _
, C.ChainTip (C.SlotNo nodeTipSlot) _ _
Expand All @@ -119,7 +132,7 @@ instance Pretty LastSyncLog where
"Fully synchronised."
<+> currentTipMsg timeSinceLastMsgM
<+> processingSummaryMsg timeSinceLastMsg
<> "."
<> "."
( Just timeSinceLastMsg
, C.ChainPoint (C.SlotNo chainSyncSlot) _
, C.ChainTip (C.SlotNo nodeTipSlot) _ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ data RetryState = RetryState
, secondsBeforeNextRetry :: !Word64
}

-- TODO: this function is problematic because it is used both inside the
-- ChainIndexT context and outside to initialize the config
-- see Marconi.ChainIndex.Run.run

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make the monad be a type parameter m and give it constraints (MonadIO m, MonadUnliftIO m)?

Or have two versions of the function, one of which wraps the other and lives in ChainIndexT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about using MonadUnliftIO. If I understand correctly, it looks like we'd be restricting our ChainIndexT monad stack to just having reader capabilites, and I don't have enough knowledge about the domain if we need to worry about this or not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, still, for the second solution I believe you end up explicitly implementing the ChainIndexT -> IO transformation. Unfortunately this isn't the only place in the code this transformation is needed I think, not 100% sure yet but wanted to add to my previous comment.

withNodeConnectRetry :: forall a. MarconiTrace IO -> RetryConfig -> FilePath -> IO a -> IO a
withNodeConnectRetry stdoutTrace retryConfig socketPath action = do
let initialRetryState = RetryState 0 (baseTimeBeforeNextRetry retryConfig)
Expand Down Expand Up @@ -66,7 +69,7 @@ withNodeConnectRetry stdoutTrace retryConfig socketPath action = do
<+> pretty socketPath
<+> "does not exist. Retrying in"
<+> pretty (secondsBeforeNextRetry retryState)
<> "s ..."
<> "s ..."

threadDelay $ fromIntegral $ secondsBeforeNextRetry retryState * 1_000_000
runActionWithRetries
Expand Down
9 changes: 5 additions & 4 deletions marconi-chain-index/src/Marconi/ChainIndex/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Marconi.ChainIndex.Node.Client.Retry (withNodeConnectRetry)
import Marconi.ChainIndex.Types (
RunIndexerConfig (RunIndexerConfig),
UtxoIndexerConfig (UtxoIndexerConfig),
runChainIndexerT,
)
import Marconi.ChainIndex.Utils qualified as Utils
import System.Directory (createDirectoryIfMissing)
Expand Down Expand Up @@ -62,15 +63,15 @@ run = do
securityParam <- withNodeConnectRetry marconiTrace retryConfig socketPath $ do
Utils.toException $ Utils.querySecurityParam @Void networkId socketPath

Indexers.runIndexers
runChainIndexerT
(Indexers.runIndexers indexers)
( RunIndexerConfig
marconiTrace
retryConfig
securityParam
networkId
(Cli.optionsChainPoint $ Cli.commonOptions o)
socketPath
(Cli.optionsMinIndexingDepth $ Cli.commonOptions o)
(Cli.optionsFailsIfResync o)
)
(Cli.optionsMinIndexingDepth $ Cli.commonOptions o)
(Cli.optionsFailsIfResync o)
indexers
25 changes: 23 additions & 2 deletions marconi-chain-index/src/Marconi/ChainIndex/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

-- | This module provides several type aliases and utility functions to deal with them.
module Marconi.ChainIndex.Types (
ChainIndexerT (..),
runChainIndexerT,

-- * Config for retrying
RetryConfig (..),

Expand Down Expand Up @@ -50,12 +53,17 @@ module Marconi.ChainIndex.Types (
runIndexerConfigNetworkId,
runIndexerConfigChainPoint,
runIndexerConfigSocketPath,
runIndexerConfigIndexingDepth,
runIndexerConfigShouldFailIfResync,
) where

import Cardano.Api qualified as C
import Cardano.Api.Extended.Streaming (BlockEvent (BlockEvent, blockInMode, blockTime, epochNo))
import Cardano.BM.Data.Trace (Trace)
import Control.Lens.TH qualified as Lens
import Control.Monad.Reader (MonadReader)
import Control.Monad.Trans (MonadTrans (lift))
import Control.Monad.Trans.Reader (ReaderT (runReaderT))
import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson qualified as Aeson
import Data.Set.NonEmpty (NESet)
Expand All @@ -65,6 +73,17 @@ import Database.SQLite.Simple.ToField qualified as SQL
import GHC.Generics (Generic)
import Prettyprinter (Doc)

newtype ChainIndexerT m a = ChainIndexerT
{getChainIndexerT :: ReaderT (RunIndexerConfig m) m a}
deriving newtype (Functor, Applicative, Monad, MonadReader (RunIndexerConfig m))

instance MonadTrans ChainIndexerT where
lift = ChainIndexerT . lift
Comment thread
ana-pantilie marked this conversation as resolved.

runChainIndexerT :: ChainIndexerT m a -> RunIndexerConfig m -> m a
runChainIndexerT (ChainIndexerT chainIndexerT) config =
runReaderT chainIndexerT config

-- | Config type for node retries
data RetryConfig = RetryConfig
{ baseTimeBeforeNextRetry :: !Word64
Expand Down Expand Up @@ -142,13 +161,15 @@ newtype TxIndexInBlock = TxIndexInBlock Word64
)

-- | Common configuration required to run indexers
data RunIndexerConfig = RunIndexerConfig
{ _runIndexerConfigTrace :: MarconiTrace IO
data RunIndexerConfig m = RunIndexerConfig
{ _runIndexerConfigTrace :: MarconiTrace m
Comment thread
ana-pantilie marked this conversation as resolved.
Outdated
, _runIndexerConfigRetryConfig :: RetryConfig
, _runIndexerConfigSecurityParam :: SecurityParam
, _runIndexerConfigNetworkId :: C.NetworkId
, _runIndexerConfigChainPoint :: C.ChainPoint
, _runIndexerConfigSocketPath :: FilePath
, _runIndexerConfigIndexingDepth :: IndexingDepth
, _runIndexerConfigShouldFailIfResync :: ShouldFailIfResync
}

Lens.makeLenses ''RunIndexerConfig
Expand Down
Loading