Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit ac90634

Browse files
committed
Delete sql savepoints from Pact PP, use sql transactions
It seems that since adding the read-only connection pool we get misuse errors from sqlite occasionally when releasing savepoints. I can't tell how exactly savepoints are implemented, and there is no sqlite documentation on the interaction between multiple readers and savepoints for read transactions and the WAL file all together, *but*, the simple policy that transactions are only begun from PactService.hs seems sufficient to give us the same guarantees as we got from savepoints anyway. Change-Id: Id000000058775f60cd5949497a34d0fb06584190
1 parent c925def commit ac90634

File tree

5 files changed

+96
-161
lines changed

5 files changed

+96
-161
lines changed

src/Chainweb/Pact/Backend/ChainwebPactDb.hs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
-- | +v--------------------------------------------+ +v----------------------------------------------+ |
4646
-- | Pact5Db tx Pact5Db tx |
4747
-- +v------------------------------------------------------------------------------------------------------------------------+
48-
-- SQLite tx (withSavepoint)
48+
-- SQLite tx (withTransaction)
4949
-- (in some cases multiple blocks in tx)
5050
--
5151
--
@@ -763,19 +763,18 @@ createVersionedTable tablename db = do
763763

764764
setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT LocatedSQ3Error IO ()
765765
setConsensusState db cs = do
766-
withSavepoint db SetConsensusSavePoint $ do
767-
exec' db
768-
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
769-
\(?, ?, ?, ?);"
770-
(toRow "final" $ _consensusStateFinal cs)
771-
exec' db
772-
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
773-
\(?, ?, ?, ?);"
774-
(toRow "safe" $ _consensusStateSafe cs)
775-
exec' db
776-
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
777-
\(?, ?, ?, ?);"
778-
(toRow "latest" $ _consensusStateLatest cs)
766+
exec' db
767+
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
768+
\(?, ?, ?, ?);"
769+
(toRow "final" $ _consensusStateFinal cs)
770+
exec' db
771+
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
772+
\(?, ?, ?, ?);"
773+
(toRow "safe" $ _consensusStateSafe cs)
774+
exec' db
775+
"INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \
776+
\(?, ?, ?, ?);"
777+
(toRow "latest" $ _consensusStateLatest cs)
779778
where
780779
toRow safety SyncState {..} =
781780
[ SInt $ fromIntegral @BlockHeight @Int64 _syncStateHeight
@@ -816,18 +815,17 @@ getConsensusState db = do
816815
-- | Create all tables that exist pre-genesis
817816
-- TODO: migrate this logic to the checkpointer itself?
818817
initSchema :: SQLiteEnv -> IO ()
819-
initSchema sql =
820-
withSavepoint sql InitSchemaSavePoint $ throwOnDbError $ do
821-
createConsensusStateTable
822-
createBlockHistoryTable
823-
createTableCreationTable
824-
createTableMutationTable
825-
createTransactionIndexTable
826-
create (toUtf8 $ Pact.renderDomain Pact.DKeySets)
827-
create (toUtf8 $ Pact.renderDomain Pact.DModules)
828-
create (toUtf8 $ Pact.renderDomain Pact.DNamespaces)
829-
create (toUtf8 $ Pact.renderDomain Pact.DDefPacts)
830-
create (toUtf8 $ Pact.renderDomain Pact.DModuleSource)
818+
initSchema sql = throwOnDbError $ do
819+
createConsensusStateTable
820+
createBlockHistoryTable
821+
createTableCreationTable
822+
createTableMutationTable
823+
createTransactionIndexTable
824+
create (toUtf8 $ Pact.renderDomain Pact.DKeySets)
825+
create (toUtf8 $ Pact.renderDomain Pact.DModules)
826+
create (toUtf8 $ Pact.renderDomain Pact.DNamespaces)
827+
create (toUtf8 $ Pact.renderDomain Pact.DDefPacts)
828+
create (toUtf8 $ Pact.renderDomain Pact.DModuleSource)
831829
where
832830
create tablename = do
833831
createVersionedTable tablename sql

src/Chainweb/Pact/Backend/Utils.hs

Lines changed: 16 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,12 @@ module Chainweb.Pact.Backend.Utils
3737
, rewindDbToBlock
3838
, rewindDbToGenesis
3939
, getEndTxId
40-
-- * Savepoints
41-
, withSavepoint
42-
, SavepointName(..)
40+
-- * Transactions
41+
, withTransaction
4342
-- * SQLite conversions and assertions
4443
, toUtf8
4544
, fromUtf8
4645
, asStringUtf8
47-
, convSavepointName
4846
-- * SQLite runners
4947
, withSqliteDb
5048
, withReadSqlitePool
@@ -147,88 +145,29 @@ asStringUtf8 = toUtf8 . asString
147145
-- -------------------------------------------------------------------------- --
148146
--
149147

150-
withSavepoint
148+
withTransaction
151149
:: (HasCallStack, MonadMask m, MonadIO m)
152150
=> SQLiteEnv
153-
-> SavepointName
154151
-> m a
155152
-> m a
156-
withSavepoint db name action = fmap fst $ generalBracket
157-
(liftIO $ beginSavepoint db name)
153+
withTransaction db action = fmap fst $ generalBracket
154+
(liftIO $ beginTransaction db)
158155
(\_ -> liftIO . \case
159-
ExitCaseSuccess {} -> commitSavepoint db name
160-
_ -> abortSavepoint db name
156+
ExitCaseSuccess {} -> commitTransaction db
157+
_ -> rollbackTransaction db
161158
) $ \_ -> action
162159

163-
beginSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO ()
164-
beginSavepoint db name =
165-
throwOnDbError $ exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];"
160+
beginTransaction :: HasCallStack => SQLiteEnv -> IO ()
161+
beginTransaction db =
162+
throwOnDbError $ exec_ db $ "BEGIN TRANSACTION;"
166163

167-
commitSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO ()
168-
commitSavepoint db name =
169-
throwOnDbError $ exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];"
164+
commitTransaction :: HasCallStack => SQLiteEnv -> IO ()
165+
commitTransaction db =
166+
throwOnDbError $ exec_ db $ "COMMIT TRANSACTION;"
170167

171-
convSavepointName :: SavepointName -> SQ3.Utf8
172-
convSavepointName = toUtf8 . toText
173-
174-
-- | @rollbackSavepoint n@ rolls back all database updates since the most recent
175-
-- savepoint with the name @n@ and restarts the transaction.
176-
--
177-
-- /NOTE/ that the savepoint is not removed from the savepoint stack. In order to
178-
-- also remove the savepoint @rollbackSavepoint n >> commitSavepoint n@ can be
179-
-- used to release the (empty) transaction.
180-
--
181-
-- Cf. <https://www.sqlite.org/lang_savepoint.html> for details about
182-
-- savepoints.
183-
--
184-
rollbackSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO ()
185-
rollbackSavepoint db name =
186-
throwOnDbError $ exec_ db $ "ROLLBACK TRANSACTION TO SAVEPOINT [" <> convSavepointName name <> "];"
187-
188-
-- | @abortSavepoint n@ rolls back all database updates since the most recent
189-
-- savepoint with the name @n@ and removes it from the savepoint stack.
190-
abortSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO ()
191-
abortSavepoint db name = do
192-
rollbackSavepoint db name
193-
commitSavepoint db name
194-
195-
data SavepointName
196-
= ReadFromSavepoint
197-
| ReadFromNSavepoint
198-
| RestoreAndSaveSavePoint
199-
| RewindSavePoint
200-
| InitSchemaSavePoint
201-
| ValidateBlockSavePoint
202-
| SetConsensusSavePoint
203-
| RunGenesisSavePoint
204-
deriving (Eq, Ord, Enum, Bounded)
205-
206-
instance Show SavepointName where
207-
show = T.unpack . toText
208-
209-
instance HasTextRepresentation SavepointName where
210-
toText ReadFromSavepoint = "read-from"
211-
toText ReadFromNSavepoint = "read-from-n"
212-
toText RestoreAndSaveSavePoint = "restore-and-save"
213-
toText RewindSavePoint = "rewind"
214-
toText InitSchemaSavePoint = "init-schema"
215-
toText ValidateBlockSavePoint = "validate-block"
216-
toText SetConsensusSavePoint = "set-consensus"
217-
toText RunGenesisSavePoint = "run-genesis"
218-
{-# INLINE toText #-}
219-
220-
fromText "read-from" = pure ReadFromSavepoint
221-
fromText "read-from-n" = pure ReadFromNSavepoint
222-
fromText "restore-and-save" = pure RestoreAndSaveSavePoint
223-
fromText "rewind" = pure RewindSavePoint
224-
fromText "init-schema" = pure InitSchemaSavePoint
225-
fromText "validate-block" = pure ValidateBlockSavePoint
226-
fromText "set-consensus" = pure SetConsensusSavePoint
227-
fromText "run-genesis" = pure RunGenesisSavePoint
228-
fromText t = throwM $ TextFormatException
229-
$ "failed to decode SavepointName " <> t
230-
<> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound])
231-
{-# INLINE fromText #-}
168+
rollbackTransaction :: HasCallStack => SQLiteEnv -> IO ()
169+
rollbackTransaction db =
170+
throwOnDbError $ exec_ db $ "ROLLBACK TRANSACTION;"
232171

233172
chainwebPragmas :: [Pact4.Pragma]
234173
chainwebPragmas =

src/Chainweb/Pact/PactService.hs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import Chainweb.Miner.Pact
5252
import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb
5353
import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact
5454
import Chainweb.Pact.Backend.Types
55-
import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..))
55+
import Chainweb.Pact.Backend.Utils (withTransaction)
5656
import Chainweb.Pact.NoCoinbase qualified as Pact
5757
import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer
5858
import Chainweb.Pact.PactService.ExecBlock
@@ -140,7 +140,8 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read
140140
traverse_ cancel refresherThread
141141
)
142142

143-
liftIO $ ChainwebPactDb.initSchema readWriteSqlenv
143+
liftIO $ withTransaction readWriteSqlenv $
144+
ChainwebPactDb.initSchema readWriteSqlenv
144145
candidatePdb <- liftIO MapTable.emptyTable
145146
moduleInitCacheVar <- liftIO $ newMVar mempty
146147

@@ -194,7 +195,7 @@ runGenesisIfNeeded
194195
-> ServiceEnv tbl
195196
-> IO ()
196197
runGenesisIfNeeded logger serviceEnv = do
197-
withSavepoint (_psReadWriteSql serviceEnv) RunGenesisSavePoint $ do
198+
withTransaction (_psReadWriteSql serviceEnv) $ do
198199
latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv)
199200
when (maybe True (isGenesisBlockHeader' cid . Parent . _syncStateBlockHash) latestBlock) $ do
200201
logFunctionText logger Debug "running genesis"
@@ -262,7 +263,7 @@ execNewGenesisBlock
262263
-> ServiceEnv tbl
263264
-> Vector Pact.Transaction
264265
-> IO PayloadWithOutputs
265-
execNewGenesisBlock logger serviceEnv newTrans = do
266+
execNewGenesisBlock logger serviceEnv newTrans = withTransaction (_psReadWriteSql serviceEnv) $ do
266267
let cid = _chainId serviceEnv
267268
let parentCreationTime = Parent (implicitVersion ^?! versionGenesis . genesisTime . atChain cid)
268269
historicalBlock <- Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) parentCreationTime
@@ -328,7 +329,7 @@ execReadOnlyReplay logger serviceEnv blocks = do
328329
let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload)
329330
let isUpgradeBlock = isJust $ implicitVersion ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx)
330331
if isPayloadEmpty && not isUpgradeBlock
331-
then Pool.withResource readSqlPool $ \sql -> do
332+
then Pool.withResource readSqlPool $ \sql -> withTransaction sql $ do
332333
hist <- Checkpointer.readFrom
333334
logger
334335
cid
@@ -372,7 +373,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do
372373
pure $ Historical LocalTimeout
373374
where
374375

375-
doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do
376+
doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> withTransaction sql $ do
376377
fakeNewBlockCtx <- liftIO Checkpointer.mkFakeParentCreationTime
377378
Checkpointer.readFromNthParent logger cid sql fakeNewBlockCtx (fromIntegral rewindDepth)
378379
$ Checkpointer.readPact5 "Pact 4 cannot execute local calls" $ \blockEnv blockHandle -> do
@@ -505,7 +506,7 @@ syncToFork
505506
-> ForkInfo
506507
-> IO ConsensusState
507508
syncToFork logger serviceEnv hints forkInfo = do
508-
(rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do
509+
(rewoundTxs, validatedTxs, newConsensusState) <- withTransaction sql $ do
509510
pactConsensusState <- fromJuste <$> Checkpointer.getConsensusState sql
510511
let atTarget =
511512
_syncStateBlockHash (_consensusStateLatest pactConsensusState) ==
@@ -719,13 +720,15 @@ refreshPayloads logger serviceEnv = do
719720
logFunctionText logger Debug $
720721
"refreshing payloads for " <>
721722
brief (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress)
722-
maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql ->
723-
Checkpointer.readFrom logger cid sql
724-
(_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress)
725-
(_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress)
726-
$ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv _bh -> do
727-
let dbEnv = view psBlockDbEnv blockEnv
728-
continueBlock logger serviceEnv dbEnv blockInProgress
723+
maybeRefreshedBlockInProgress <-
724+
Pool.withResource (view psReadSqlPool serviceEnv) $ \sql ->
725+
withTransaction sql $
726+
Checkpointer.readFrom logger cid sql
727+
(_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress)
728+
(_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress)
729+
$ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv _bh -> do
730+
let dbEnv = view psBlockDbEnv blockEnv
731+
continueBlock logger serviceEnv dbEnv blockInProgress
729732
case maybeRefreshedBlockInProgress of
730733
-- the block's parent was removed
731734
NoHistory -> logOutraced
@@ -788,19 +791,20 @@ execPreInsertCheckReq logger serviceEnv txs = do
788791
let requestKeys = V.map Pact.cmdToRequestKey txs
789792
logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")"
790793
fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime
791-
let act sql = Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ Checkpointer.PactRead
792-
{ pact5Read = \blockEnv bh -> do
793-
forM txs $ \tx ->
794-
fmap (either Just (\_ -> Nothing)) $ runExceptT $ do
795-
-- it's safe to use initialBlockHandle here because it's
796-
-- only used to check for duplicate pending txs in a block
797-
() <- mapExceptT liftIO
798-
$ Pact.validateParsedChainwebTx logger blockEnv tx
799-
evalStateT (attemptBuyGas blockEnv tx) bh
800-
-- pessimistically, if we're catching up and not even past the Pact
801-
-- 5 activation, just badlist everything as in-the-future.
802-
, pact4Read = \_ -> return $ Just InsertErrorTimeInFuture <$ txs
803-
}
794+
let act sql = withTransaction sql $
795+
Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ Checkpointer.PactRead
796+
{ pact5Read = \blockEnv bh -> do
797+
forM txs $ \tx ->
798+
fmap (either Just (\_ -> Nothing)) $ runExceptT $ do
799+
-- it's safe to use initialBlockHandle here because it's
800+
-- only used to check for duplicate pending txs in a block
801+
() <- mapExceptT liftIO
802+
$ Pact.validateParsedChainwebTx logger blockEnv tx
803+
evalStateT (attemptBuyGas blockEnv tx) bh
804+
-- pessimistically, if we're catching up and not even past the Pact
805+
-- 5 activation, just badlist everything as in-the-future.
806+
, pact4Read = \_ -> return $ Just InsertErrorTimeInFuture <$ txs
807+
}
804808
Pool.withResource (view psReadSqlPool serviceEnv) $ \sql ->
805809
timeoutYield timeoutLimit (act sql) >>= \case
806810
Just r -> do
@@ -852,7 +856,7 @@ execLookupPactTxs logger serviceEnv confDepth txs = do
852856
where
853857
depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth
854858
cid = _chainId serviceEnv
855-
go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql ->
859+
go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> withTransaction sql $
856860
Checkpointer.readFromNthParent logger cid sql ctx depth
857861
-- not sure about this, disallows looking up pact txs if we haven't
858862
-- caught up to pact 5

src/Chainweb/Pact/PactService/Checkpointer.hs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -136,24 +136,23 @@ readFromNthParent
136136
-> PactRead a
137137
-> IO (Historical a)
138138
readFromNthParent logger cid sql parentCreationTime n doRead = do
139-
withSavepoint sql ReadFromNSavepoint $ do
140-
latest <-
141-
_consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis")
142-
<$> getConsensusState sql
143-
if genesisHeight cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest
144-
then do
145-
logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to "
146-
<> sshow (int (_syncStateHeight latest) - int n :: Integer)
147-
return NoHistory
148-
else do
149-
let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n
150-
lookupBlockWithHeight sql targetHeight >>= \case
151-
-- this case for shallow nodes without enough history
152-
Nothing -> do
153-
logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks"
154-
return NoHistory
155-
Just nthBlock ->
156-
readFrom logger cid sql parentCreationTime (Parent nthBlock) doRead
139+
latest <-
140+
_consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis")
141+
<$> getConsensusState sql
142+
if genesisHeight cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest
143+
then do
144+
logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to "
145+
<> sshow (int (_syncStateHeight latest) - int n :: Integer)
146+
return NoHistory
147+
else do
148+
let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n
149+
lookupBlockWithHeight sql targetHeight >>= \case
150+
-- this case for shallow nodes without enough history
151+
Nothing -> do
152+
logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks"
153+
return NoHistory
154+
Just nthBlock ->
155+
readFrom logger cid sql parentCreationTime (Parent nthBlock) doRead
157156

158157
-- read-only rewind to a target block.
159158
-- if that target block is missing, return Nothing.
@@ -175,7 +174,7 @@ readFrom logger cid sql parentCreationTime parent pactRead = do
175174
, _bctxMinerReward = blockMinerReward (childBlockHeight cid parent)
176175
, _bctxChainId = cid
177176
}
178-
liftIO $ withSavepoint sql ReadFromSavepoint $ do
177+
liftIO $ do
179178
!latestHeader <- maybe (genesisRankedParentBlockHash cid) (Parent . _syncStateRankedBlockHash . _consensusStateLatest) <$>
180179
ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql)
181180
-- is the parent the latest header, i.e., can we get away without rewinding?
@@ -228,8 +227,7 @@ rewindTo
228227
-> Parent RankedBlockHash
229228
-> IO ()
230229
rewindTo cid sql ancestor = do
231-
withSavepoint sql RewindSavePoint $
232-
void $ PactDb.rewindDbTo cid sql ancestor
230+
void $ PactDb.rewindDbTo cid sql ancestor
233231

234232
data PactRead a
235233
= PactRead
@@ -283,11 +281,10 @@ restoreAndSave
283281
-> NonEmpty (RunnableBlock m q)
284282
-> m q
285283
restoreAndSave logger cid sql parent blocks = do
286-
withSavepoint sql RestoreAndSaveSavePoint $ do
287-
-- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above.
288-
startTxId <- liftIO $ PactDb.rewindDbTo cid sql parent
289-
let startBlockHeight = childBlockHeight cid parent
290-
foldState1 (fmap executeBlock blocks) (T2 startBlockHeight startTxId)
284+
-- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above.
285+
startTxId <- liftIO $ PactDb.rewindDbTo cid sql parent
286+
let startBlockHeight = childBlockHeight cid parent
287+
foldState1 (fmap executeBlock blocks) (T2 startBlockHeight startTxId)
291288
where
292289

293290
executeBlock :: RunnableBlock m q -> T2 BlockHeight Pact.TxId -> m (q, T2 BlockHeight Pact.TxId)

0 commit comments

Comments
 (0)