diff --git a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs b/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs index a426d243cc..fe05ca1440 100644 --- a/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs +++ b/src/Chainweb/Pact/Backend/RelationalCheckpointer.hs @@ -124,6 +124,7 @@ initRelationalCheckpointer' bstate sqlenv loggr v cid = do _cpRestore = doRestore v cid db , _cpSave = doSave db , _cpDiscard = doDiscard db + , _cpGetEarliestBlock = doGetEarliest db , _cpGetLatestBlock = doGetLatest db , _cpBeginCheckpointerBatch = doBeginBatch db , _cpCommitCheckpointerBatch = doCommitBatch db @@ -231,6 +232,22 @@ doDiscard dbenv = runBlockEnv dbenv $ do -- commitSavepoint Block +doGetEarliest :: Db logger -> IO (Maybe (BlockHeight, BlockHash)) +doGetEarliest dbenv = + runBlockEnv dbenv $ callDb "getEarliestBlock" $ \db -> do + r <- qry_ db qtext [RInt, RBlob] >>= mapM go + case r of + [] -> return Nothing + (!o:_) -> return (Just o) + where + qtext = "SELECT blockheight, hash FROM BlockHistory \ + \ ORDER BY blockheight ASC LIMIT 1" + + go [SInt hgt, SBlob blob] = + let hash = either error id $ runGetEitherS decodeBlockHash blob + in return (fromIntegral hgt, hash) + go _ = fail "impossible" + doGetLatest :: Db logger -> IO (Maybe (BlockHeight, BlockHash)) doGetLatest dbenv = runBlockEnv dbenv $ callDb "getLatestBlock" $ \db -> do diff --git a/src/Chainweb/Pact/Backend/Types.hs b/src/Chainweb/Pact/Backend/Types.hs index 4aa790d836..b69b277222 100644 --- a/src/Chainweb/Pact/Backend/Types.hs +++ b/src/Chainweb/Pact/Backend/Types.hs @@ -297,6 +297,9 @@ data Checkpointer logger = Checkpointer -- ^ commits pending modifications to block, with the given blockhash , _cpDiscard :: !(IO ()) -- ^ discard pending block changes + , _cpGetEarliestBlock :: !(IO (Maybe (BlockHeight, BlockHash))) + -- ^ get the checkpointer's idea of the earliest block. The block height + -- is the height of the block of the block hash. , _cpGetLatestBlock :: !(IO (Maybe (BlockHeight, BlockHash))) -- ^ get the checkpointer's idea of the latest block. The block height is -- is the height of the block of the block hash. diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 5ce2773802..c34aec214b 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -41,10 +41,10 @@ module Chainweb.Pact.PactService.Checkpointer -- There are two function for restoring the checkpointer for evaluation of back -- code: -- - -- * 'withCheckPointerRewind' and + -- * 'withCheckpointerRewind' and -- * 'withCurrentCheckpointer'. -- - -- 'withCheckPointerRewind' rewinds the checkpointer to the provided parent + -- 'withCheckpointerRewind' rewinds the checkpointer to the provided parent -- header. 'withCurrentCheckpointer' evaluates the pact transaction within the -- context of the current checkpointer state. Both functions update the value of -- '_psParentHeader' at the beginning and the end of each call. @@ -171,7 +171,7 @@ data WithCheckpointerResult a -- -- This function assumes that '_psParentHeader' has been updated to match the -- latest block in the checkpointers. This is guaranteed to be the case after --- calling any of 'rewindTo', 'syncParentHeader', 'withCheckPointerRewind', +-- calling any of 'rewindTo', 'syncParentHeader', 'withCheckpointerRewind', -- 'withCheckPointerWithoutRewind', or 'withCurrentCheckpointer'. -- -- /NOTE:/ @@ -273,7 +273,7 @@ withCheckpointerRewind rewindLimit p caller act = do -- | Run a batch of checkpointer operations, possibly involving the evaluation -- transactions accross several blocks using more than a single call of --- 'withCheckPointerRewind' or 'withCurrentCheckpointer', and persist the final +-- 'withCheckpointerRewind' or 'withCurrentCheckpointer', and persist the final -- state. In case of an failure, the checkpointer is reverted to the initial -- state. -- @@ -308,7 +308,7 @@ withBatchIO runPact act = mask $ \umask -> do -- | Run a batch of checkpointer operations, possibly involving the evaluation -- transactions accross several blocks using more than a single call of --- 'withCheckPointerRewind' or 'withCurrentCheckpointer', and discard the final +-- 'withCheckpointerRewind' or 'withCurrentCheckpointer', and discard the final -- state at the end. -- withDiscardedBatch :: PactServiceM logger tbl a -> PactServiceM logger tbl a @@ -645,11 +645,19 @@ failOnTooLowRequestedHeight -> Maybe RewindLimit -> BlockHeader -> PactServiceM logger tbl () -failOnTooLowRequestedHeight parent (Just limit) lastHeader - | parentHeight + 1 + limitHeight < lastHeight = -- need to stick with addition because Word64 - throwM $ RewindLimitExceeded limit parentHeight lastHeight parent - where - limitHeight = BlockHeight $ _rewindLimit limit - parentHeight = _blockHeight parent - lastHeight = _blockHeight lastHeader +failOnTooLowRequestedHeight parent (Just limit) lastHeader = do + let limitHeight = BlockHeight $ _rewindLimit limit + let parentHeight = _blockHeight parent + let lastHeight = _blockHeight lastHeader + when (parentHeight + 1 + limitHeight < lastHeight) $ do -- need to stick with addition because Word64 + throwM $ RewindLimitExceeded limit parentHeight lastHeight parent + + cp <- getCheckpointer + mEarliestBlock <- liftIO $ _cpGetEarliestBlock cp + case mEarliestBlock of + Nothing -> error "" + Just (minBlockHeight, _) -> do + when (parentHeight < minBlockHeight) $ do + throwM $ RewindPastMinBlockHeight parentHeight parent minBlockHeight + failOnTooLowRequestedHeight _ _ _ = return () diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index e3600ca427..72cdb41cfc 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -102,8 +102,8 @@ setParentHeader msg ph@(ParentHeader bh) = do -- /NOTE:/ -- -- Any call of this function must occur within a dedicated call to --- 'withChwithCheckpointerRewind', 'withCurrentCheckpointer' or --- 'withCheckPointerWithoutRewind'. +-- 'withCheckpointerRewind', 'withCurrentCheckpointer' or +-- 'withCheckpointerWithoutRewind'. -- execBlock :: (CanReadablePayloadCas tbl, Logger logger) diff --git a/src/Chainweb/Pact/Service/Types.hs b/src/Chainweb/Pact/Service/Types.hs index 9763143fa2..5176083037 100644 --- a/src/Chainweb/Pact/Service/Types.hs +++ b/src/Chainweb/Pact/Service/Types.hs @@ -202,6 +202,14 @@ data PactException , _rewindExceededTarget :: !BlockHeader -- ^ target header } + | RewindPastMinBlockHeight + { _rewindPastMinParentHeight :: !BlockHeight + -- ^ parent block height + , _rewindPastMinParentHeader :: !BlockHeader + -- ^ parent block header + , _rewindPastMinMinBlockHeight :: !BlockHeight + -- ^ min block height @SELECT MIN(blockheight) FROM BlockHistory@ + } | BlockHeaderLookupFailure !Text | BuyGasFailure !GasPurchaseFailure | MempoolFillFailure !Text @@ -230,6 +238,11 @@ instance J.Encode PactException where , "_rewindExceededForkHeight" J..= J.Aeson @Int (fromIntegral $ _rewindExceededForkHeight o) , "_rewindExceededTarget" J..= J.encodeWithAeson (_rewindExceededTarget o) ] + build o@(RewindPastMinBlockHeight{}) = tagged "RewindPastMinBlockHeight" $ J.object + [ "_rewindPastMinParentHeight" J..= J.Aeson @Int (fromIntegral $ _rewindPastMinParentHeight o) + , "_rewindPastMinParentHeader" J..= J.encodeWithAeson (_rewindPastMinParentHeader o) + , "_rewindPastMinMinBlockHeight" J..= J.Aeson @Int (fromIntegral $ _rewindPastMinMinBlockHeight o) + ] build (BlockHeaderLookupFailure msg) = tagged "BlockHeaderLookupFailure" msg build (BuyGasFailure failure) = tagged "BuyGasFailure" failure build (MempoolFillFailure msg) = tagged "MempoolFillFailure" msg diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 072ea5ba28..f30b5ac0d5 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -668,7 +668,6 @@ execPactServiceM execPactServiceM st env act = execStateT (runReaderT (_unPactServiceM act) env) st - getCheckpointer :: PactServiceM logger tbl (Checkpointer logger) getCheckpointer = view psCheckpointer