Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ unitTests iom knownMigrations =
, testGroup
"rollbacks"
[ test "simple rollback" Rollback.simpleRollback
, test "drepDistr rollback" Rollback.drepDistrRollback
, test "sync bigger chain" Rollback.bigChain
, test "rollback while db-sync is off" Rollback.restartAndRollback
, test "big rollback executed lazily" Rollback.lazyRollback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Test.Cardano.Db.Mock.Unit.Conway.Rollback (
stakeAddressRollback,
rollbackChangeTxOrder,
rollbackFullTx,
drepDistrRollback,
) where

import Cardano.Ledger.Coin (Coin (..))
Expand All @@ -27,7 +28,7 @@ import Test.Cardano.Db.Mock.Examples (mockBlock0, mockBlock1, mockBlock2)
import Test.Cardano.Db.Mock.UnifiedApi
import Test.Cardano.Db.Mock.Validate (assertBlockNoBackoff, assertTxCount)
import Test.Tasty.HUnit (Assertion ())
import Prelude (last)
import Prelude (last, (!!))

simpleRollback :: IOManager -> [(Text, Text)] -> Assertion
simpleRollback =
Expand Down Expand Up @@ -291,3 +292,40 @@ rollbackFullTx =
assertTxCount dbSync 14
where
testLabel = "conwayRollbackFullTx"

-- | Test for DrepDistr rollback bug: epoch boundary insertion → mid-epoch rollback → replay
-- This test verifies that DrepDistr records are properly deleted during rollback when
-- rolling back to a point within the same epoch where they were inserted.
drepDistrRollback :: IOManager -> [(Text, Text)] -> Assertion
drepDistrRollback =
withFullConfig conwayConfigDir testLabel $ \interpreter mockServer dbSync -> do
startDBSync dbSync

-- Forge initial blocks to get to a stable state
void $ forgeAndSubmitBlocks interpreter mockServer 10

-- Fill up epoch 0 completely and cross into epoch 1
-- This triggers insertDrepDistr for epoch 1 at the epoch boundary
epochs <- fillEpochs interpreter mockServer 1
assertBlockNoBackoff dbSync (10 + length epochs)

-- Continue a bit into epoch 1 (after DrepDistr was inserted)
blks <- forgeAndSubmitBlocks interpreter mockServer 20
let rollbackPoint = blockPoint $ blks !! 9 -- Rollback to 10th block (0-indexed), keeping 10 blocks
assertBlockNoBackoff dbSync (10 + length epochs + 20)

-- Rollback to mid-epoch 1 (after the epoch boundary where DrepDistr was inserted)
-- With the bug (using ">" for deletion), DrepDistr for epoch 1 would NOT be deleted
-- because epoch_no > 1 is false for the epoch 1 records
rollbackTo interpreter mockServer rollbackPoint

-- Continue syncing forward - this will replay through the epoch 1 boundary again
-- With the fix (using ">="), DrepDistr for epoch 1 was deleted, so re-insertion succeeds
-- Without the fix, this would fail with duplicate key constraint violation
void $ forgeAndSubmitBlocks interpreter mockServer 30

-- Verify we can continue syncing without errors
-- After rollback we have: 10 initial + epochs + 10 kept blocks, then + 30 new = 10 + epochs + 40
assertBlockNoBackoff dbSync (10 + length epochs + 10 + 30)
where
testLabel = "conwayDrepDistrRollback"
1 change: 1 addition & 0 deletions cardano-chain-gen/test/testfiles/pgpass-testing-macos
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/tmp:5432:testing:*:*
4 changes: 2 additions & 2 deletions cardano-db/src/Cardano/Db/Statement/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ deleteUsingEpochNo trce epochN = do
ec <- runSession mkDbCallStack $ HsqlSes.statement epochN (parameterisedCountWhere @SC.Epoch "no" ">= $1" epochEncoder)

liftIO $ updateProgress (Just trce) progressRef 1 "Counting DrepDistr records..."
dc <- runSession mkDbCallStack $ HsqlSes.statement epochN (parameterisedCountWhere @SC.DrepDistr "epoch_no" "> $1" epochEncoder)
dc <- runSession mkDbCallStack $ HsqlSes.statement epochN (parameterisedCountWhere @SC.DrepDistr "epoch_no" ">= $1" epochEncoder)

liftIO $ updateProgress (Just trce) progressRef 2 "Counting RewardRest records..."
rrc <- runSession mkDbCallStack $ HsqlSes.statement epochN (parameterisedCountWhere @SC.RewardRest "spendable_epoch" "> $1" epochEncoder)
Expand All @@ -809,7 +809,7 @@ deleteUsingEpochNo trce epochN = do
epochDeletedCount <- runSession mkDbCallStack $ HsqlSes.statement epochN (deleteWhereCount @SC.Epoch "no" "=" epochEncoder)

liftIO $ updateProgress (Just trce) progressRef 2 $ "Deleting " <> textShow drepCount <> " DrepDistr records..."
drepDeletedCount <- runSession mkDbCallStack $ HsqlSes.statement epochN (deleteWhereCount @SC.DrepDistr "epoch_no" ">" epochEncoder)
drepDeletedCount <- runSession mkDbCallStack $ HsqlSes.statement epochN (deleteWhereCount @SC.DrepDistr "epoch_no" ">=" epochEncoder)

liftIO $ updateProgress (Just trce) progressRef 3 $ "Deleting " <> textShow rewardRestCount <> " RewardRest records..."
rewardRestDeletedCount <- runSession mkDbCallStack $ HsqlSes.statement epochN (deleteWhereCount @SC.RewardRest "spendable_epoch" ">" epochEncoder)
Expand Down
1 change: 1 addition & 0 deletions config/pgpass-mainnet-macos
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/tmp:5432:cexplorer:*:*
4 changes: 2 additions & 2 deletions scripts/run-everything-tmux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ tmux send-keys -t 0 "cardano-node run --config $TESTNET_DIR/config.json --databa

# Cardano DB-Sync
tmux send-keys -t 1 "cd $CARDANO_DB_SYNC_DIR/" 'C-m'; sleep 3
tmux send-keys -t 1 "export PGPASSFILE=$CARDANO_DB_SYNC_DIR/config/pgpass-mainnet" 'C-m'; sleep 2
tmux send-keys -t 1 "PGPASSFILE=$CARDANO_DB_SYNC_DIR/config/pgpass-mainnet $dbsync --config $TESTNET_DIR/db-sync-config.json --socket-path $TESTNET_DIR/db/node.socket --state-dir $TESTNET_DIR/ledger-state --schema-dir $CARDANO_DB_SYNC_DIR/schema/" 'C-m'
tmux send-keys -t 1 "export PGPASSFILE=$CARDANO_DB_SYNC_DIR/config/pgpass-mainnet-macos" 'C-m'; sleep 2
tmux send-keys -t 1 "PGPASSFILE=$CARDANO_DB_SYNC_DIR/config/pgpass-mainnet-macos $dbsync --config $TESTNET_DIR/db-sync-config.json --socket-path $TESTNET_DIR/db/node.socket --state-dir $TESTNET_DIR/ledger-state --schema-dir $CARDANO_DB_SYNC_DIR/schema/" 'C-m'
# tmux send-keys -t 1 "$dbsync --config $TESTNET_DIR/db-sync-config.json --socket-path $TESTNET_DIR/db/node.socket --state-dir $TESTNET_DIR/ledger-state --schema-dir $CARDANO_DB_SYNC_DIR/schema/ +RTS -p -hc -L200 -RTS" 'C-m'

tmux -CC attach-session -t $session
Expand Down
Loading