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 @@ -156,6 +156,7 @@ unitTests iom knownMigrations =
, test "stake address pointers" Stake.stakeAddressPtr
, test "stake address pointers deregistration" Stake.stakeAddressPtrDereg
, test "stake address pointers. Use before registering." Stake.stakeAddressPtrUseBefore
, test "stake address pointers have NULL stake_address_id in Conway" Stake.stakeAddressPtrNullInConway
, test "register stake creds" Stake.registerStakeCreds
, test "register stake creds with shelley disabled" Stake.registerStakeCredsNoShelley
]
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.Stake (
stakeAddressPtr,
stakeAddressPtrDereg,
stakeAddressPtrUseBefore,
stakeAddressPtrNullInConway,
registerStakeCreds,
registerStakeCredsNoShelley,

Expand Down Expand Up @@ -37,7 +38,7 @@ import Ouroboros.Network.Block (blockSlot)
import Test.Cardano.Db.Mock.Config
import qualified Test.Cardano.Db.Mock.UnifiedApi as Api
import Test.Cardano.Db.Mock.Validate
import Test.Tasty.HUnit (Assertion ())
import Test.Tasty.HUnit (Assertion (), assertBool)
import Prelude ()

registrationTx :: IOManager -> [(Text, Text)] -> Assertion
Expand Down Expand Up @@ -229,6 +230,43 @@ stakeAddressPtrUseBefore =
where
testLabel = "conwayStakeAddressPtrUseBefore"

-- | Test that Pointer addresses in Conway era have NULL stake_address_id
-- This verifies the fix for issue #2051: Pointer addresses in Conway era
-- should be treated as Enterprise addresses with no stake key association
stakeAddressPtrNullInConway :: IOManager -> [(Text, Text)] -> Assertion
stakeAddressPtrNullInConway =
withFullConfig conwayConfigDir testLabel $ \interpreter mockServer dbSync -> do
startDBSync dbSync

-- Forge a block with a stake registration cert
blk <-
Api.withConwayFindLeaderAndSubmitTx interpreter mockServer $
Conway.mkSimpleDCertTx [(StakeIndexNew 1, Conway.mkRegTxCert SNothing)]

-- Forge a block with a payment to a Pointer address
-- The pointer references the stake registration certificate we just created
let ptr = Ptr (unsafeToSlotNo32 $ blockSlot blk) (TxIx 0) (CertIx 0)
void $
Api.withConwayFindLeaderAndSubmitTx interpreter mockServer $
Conway.mkPaymentTx (UTxOIndex 0) (UTxOAddressNewWithPtr 0 ptr) 20_000 20_000 0

-- Wait for it to sync
assertBlockNoBackoff dbSync 2

-- Get the tx_out variant type for queries
let txOutVariantType = txOutVariantTypeFromConfig dbSync

-- Query for Pointer address tx_outs with NULL stake_address_id
-- In Conway era, Pointer addresses should NOT have stake_address_id populated
ptrCount <- runQuery dbSync $ DB.queryPtrTxOutNullStake txOutVariantType

-- Assert that we have at least one Pointer address with NULL stake_address_id
assertBool
"Pointer address in Conway should have NULL stake_address_id (treated as Enterprise address)"
(ptrCount > 0)
where
testLabel = "conwayStakeAddressPtrNullInConway"

stakeDistGenesis :: IOManager -> [(Text, Text)] -> Assertion
stakeDistGenesis =
withFullConfigDropDB conwayConfigDir testLabel $ \interpreter mockServer dbSync -> do
Expand Down
21 changes: 12 additions & 9 deletions cardano-db-sync/src/Cardano/DbSync/Api/Ledger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ storeUTxOFromLedger ::
ExtLedgerState CardanoBlock mk ->
ExceptT SyncNodeError DB.DbM ()
storeUTxOFromLedger env st = case ledgerState st of
LedgerStateBabbage bts -> storeUTxO env (getUTxO bts)
LedgerStateConway stc -> storeUTxO env (getUTxO stc)
LedgerStateDijkstra stc -> storeUTxO env (getUTxO stc)
LedgerStateBabbage bts -> storeUTxO env Babbage (getUTxO bts)
LedgerStateConway stc -> storeUTxO env Conway (getUTxO stc)
LedgerStateDijkstra stc -> storeUTxO env Dijkstra (getUTxO stc)
_otherwise -> liftIO $ logError trce "storeUTxOFromLedger is only supported after Babbage"
where
trce = getTrace env
Expand All @@ -96,9 +96,10 @@ storeUTxO ::
, NativeScript era ~ Timelock era
) =>
SyncEnv ->
BlockEra ->
Map TxIn (BabbageTxOut era) ->
ExceptT SyncNodeError DB.DbM ()
storeUTxO env mp = do
storeUTxO env blkEra mp = do
liftIO $
logInfo trce $
mconcat
Expand All @@ -107,7 +108,7 @@ storeUTxO env mp = do
, " tx_out as pages of "
, textShow bulkSize
]
mapM_ (storePage env pagePerc) . zip [0 ..] . chunksOf bulkSize . Map.toList $ mp
mapM_ (storePage env blkEra pagePerc) . zip [0 ..] . chunksOf bulkSize . Map.toList $ mp
where
trce = getTrace env
bulkSize = DB.getTxOutBulkSize (getTxOutVariantType env)
Expand All @@ -124,12 +125,13 @@ storePage ::
, NativeScript era ~ Timelock era
) =>
SyncEnv ->
BlockEra ->
Float ->
(Int, [(TxIn, BabbageTxOut era)]) ->
ExceptT SyncNodeError DB.DbM ()
storePage syncEnv percQuantum (n, ls) = do
storePage syncEnv blkEra percQuantum (n, ls) = do
when (n `mod` 10 == 0) $ liftIO $ logInfo trce $ "Bootstrap in progress " <> prc <> "%"
txOuts <- mapM (prepareTxOut syncEnv) ls
txOuts <- mapM (prepareTxOut syncEnv blkEra) ls
txOutIds <- lift $ DB.insertBulkTxOut False $ etoTxOut . fst <$> txOuts
let maTxOuts = concatMap (mkmaTxOuts txOutVariantType) $ zip txOutIds (snd <$> txOuts)
void . lift $ DB.insertBulkMaTxOutPiped [maTxOuts]
Expand All @@ -147,12 +149,13 @@ prepareTxOut ::
, NativeScript era ~ Timelock era
) =>
SyncEnv ->
BlockEra ->
(TxIn, BabbageTxOut era) ->
ExceptT SyncNodeError DB.DbM (ExtendedTxOut, [MissingMaTxOut])
prepareTxOut syncEnv (TxIn txIntxId (TxIx index), txOut) = do
prepareTxOut syncEnv blkEra (TxIn txIntxId (TxIx index), txOut) = do
let txHashByteString = Generic.safeHashToByteString $ unTxId txIntxId
let genTxOut = fromTxOut (fromIntegral index) txOut
txId <- liftDbLookupEither mkSyncNodeCallStack $ queryTxIdWithCache syncEnv txIntxId
insertTxOut syncEnv iopts (txId, txHashByteString) genTxOut
insertTxOut syncEnv iopts (txId, txHashByteString) genTxOut blkEra
where
iopts = soptInsertOptions $ envOptions syncEnv
3 changes: 2 additions & 1 deletion cardano-db-sync/src/Cardano/DbSync/Era/Shelley/Genesis.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Cardano.DbSync.Era.Universal.Insert.Certificate (insertDelegation, insert
import Cardano.DbSync.Era.Universal.Insert.Other (insertStakeAddressRefIfMissing)
import Cardano.DbSync.Era.Universal.Insert.Pool (insertPoolRegister)
import Cardano.DbSync.Error
import Cardano.DbSync.Types (BlockEra (..))
import Cardano.DbSync.Util
import Cardano.Ledger.Address (serialiseAddr)
import qualified Cardano.Ledger.Coin as Ledger
Expand Down Expand Up @@ -269,7 +270,7 @@ insertTxOuts syncEnv blkId (TxIn txInId _, txOut) = do
}

tryUpdateCacheTx (envCache syncEnv) txInId txId
_ <- insertStakeAddressRefIfMissing (withNoCache syncEnv) (txOut ^. Core.addrTxOutL)
_ <- insertStakeAddressRefIfMissing (withNoCache syncEnv) (txOut ^. Core.addrTxOutL) Shelley
case ioTxOutVariantType . soptInsertOptions $ envOptions syncEnv of
DB.TxOutVariantCore ->
void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ insertBlockUniversal syncEnv shouldLog withinTwoMins withinHalfHour blk details

let zippedTx = zip [0 ..] (Generic.blkTxs blk)
let txInserter = insertTx syncEnv isMember blkId (sdEpochNo details) (Generic.blkSlotNo blk) applyResult
blockGroupedData <- foldM (\gp (idx, tx) -> txInserter idx tx gp) mempty zippedTx
blockGroupedData <- foldM (\gp (idx, tx) -> txInserter idx tx gp (Generic.blkEra blk)) mempty zippedTx

minIds <- insertBlockGroupedData syncEnv blockGroupedData

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
Expand All @@ -26,6 +27,7 @@ import qualified Cardano.DbSync.Era.Shelley.Generic as Generic
import Cardano.DbSync.Era.Universal.Insert.Grouped
import Cardano.DbSync.Era.Util (safeDecodeToJson)
import Cardano.DbSync.Error (SyncNodeError)
import Cardano.DbSync.Types (BlockEra (..))
import Cardano.DbSync.Util
import qualified Cardano.Ledger.Address as Ledger
import qualified Cardano.Ledger.BaseTypes as Ledger
Expand Down Expand Up @@ -136,16 +138,20 @@ insertWithdrawals syncEnv txId redeemers txWdrl = do
insertStakeAddressRefIfMissing ::
SyncEnv ->
Ledger.Addr ->
BlockEra ->
ExceptT SyncNodeError DB.DbM (Maybe DB.StakeAddressId)
insertStakeAddressRefIfMissing syncEnv addr =
insertStakeAddressRefIfMissing syncEnv addr era =
case addr of
Ledger.AddrBootstrap {} -> pure Nothing
Ledger.Addr nw _pcred sref ->
case sref of
Ledger.StakeRefBase cred -> do
Just <$> queryOrInsertStakeAddress syncEnv UpdateCache nw cred
Ledger.StakeRefPtr ptr -> do
lift $ DB.queryStakeRefPtr ptr
Ledger.StakeRefPtr ptr ->
-- In Conway era onwards, Pointer addresses are treated as Enterprise addresses
case era of
Conway -> pure Nothing
_ -> lift $ DB.queryStakeRefPtr ptr
Ledger.StakeRefNull -> pure Nothing

insertMultiAsset ::
Expand Down
20 changes: 12 additions & 8 deletions cardano-db-sync/src/Cardano/DbSync/Era/Universal/Insert/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import Cardano.DbSync.Era.Universal.Insert.Pool (IsPoolMember)
import Cardano.DbSync.Era.Util (safeDecodeToJson)
import Cardano.DbSync.Error (SyncNodeError)
import Cardano.DbSync.Ledger.Types (ApplyResult (..), getGovExpiresAt, lookupDepositsMap)
import Cardano.DbSync.Types (BlockEra)
import Cardano.DbSync.Util
import Cardano.DbSync.Util.Cbor (serialiseTxMetadataToCbor)

Expand All @@ -72,8 +73,9 @@ insertTx ::
Word64 ->
Generic.Tx ->
BlockGroupedData ->
BlockEra ->
ExceptT SyncNodeError DB.DbM BlockGroupedData
insertTx syncEnv isMember blkId epochNo slotNo applyResult blockIndex tx grouped = do
insertTx syncEnv isMember blkId epochNo slotNo applyResult blockIndex tx grouped era = do
let !txHash = Generic.txHash tx
let !mdeposits = if not (Generic.txValidContract tx) then Just (Coin 0) else lookupDepositsMap txHash (apDepositsMap applyResult)
let !outSum = fromIntegral $ unCoin $ Generic.txOutSum tx
Expand Down Expand Up @@ -140,7 +142,7 @@ insertTx syncEnv isMember blkId epochNo slotNo applyResult blockIndex tx grouped

if not (Generic.txValidContract tx)
then do
!txOutsGrouped <- mapM (insertTxOut syncEnv iopts (txId, txHash)) (Generic.txOutputs tx)
!txOutsGrouped <- mapM (\txOut -> insertTxOut syncEnv iopts (txId, txHash) txOut era) (Generic.txOutputs tx)

let !txIns = map (prepareTxIn txId Map.empty) resolvedInputs
-- There is a custom semigroup instance for BlockGroupedData which uses addition for the values `fees` and `outSum`.
Expand All @@ -149,7 +151,7 @@ insertTx syncEnv isMember blkId epochNo slotNo applyResult blockIndex tx grouped
else do
-- The following operations only happen if the script passes stage 2 validation (or the tx has
-- no script).
!txOutsGrouped <- mapM (insertTxOut syncEnv iopts (txId, txHash)) (Generic.txOutputs tx)
!txOutsGrouped <- mapM (\txOut -> insertTxOut syncEnv iopts (txId, txHash) txOut era) (Generic.txOutputs tx)

!redeemers <-
Map.fromList
Expand All @@ -161,7 +163,7 @@ insertTx syncEnv isMember blkId epochNo slotNo applyResult blockIndex tx grouped
mapM_ (insertDatum syncEnv txId) (Generic.txData tx)
mapM_ (insertCollateralTxIn syncEnv tracer txId) (Generic.txCollateralInputs tx)
mapM_ (insertReferenceTxIn syncEnv tracer txId) (Generic.txReferenceInputs tx)
mapM_ (insertCollateralTxOut syncEnv iopts (txId, txHash)) (Generic.txCollateralOutputs tx)
mapM_ (\txOut -> insertCollateralTxOut syncEnv iopts (txId, txHash) txOut era) (Generic.txCollateralOutputs tx)

txMetadata <-
whenFalseMempty (ioMetadata iopts) $
Expand Down Expand Up @@ -213,9 +215,10 @@ insertTxOut ::
InsertOptions ->
(DB.TxId, ByteString) ->
Generic.TxOut ->
BlockEra ->
ExceptT SyncNodeError DB.DbM (ExtendedTxOut, [MissingMaTxOut])
insertTxOut syncEnv iopts (txId, txHash) (Generic.TxOut index addr value maMap mScript dt) = do
mSaId <- insertStakeAddressRefIfMissing syncEnv addr
insertTxOut syncEnv iopts (txId, txHash) (Generic.TxOut index addr value maMap mScript dt) era = do
mSaId <- insertStakeAddressRefIfMissing syncEnv addr era
mDatumId <-
whenFalseEmpty (ioPlutusExtra iopts) Nothing $
Generic.whenInlineDatum dt $
Expand Down Expand Up @@ -387,9 +390,10 @@ insertCollateralTxOut ::
InsertOptions ->
(DB.TxId, ByteString) ->
Generic.TxOut ->
BlockEra ->
ExceptT SyncNodeError DB.DbM ()
insertCollateralTxOut syncEnv iopts (txId, _txHash) (Generic.TxOut index addr value maMap mScript dt) = do
mSaId <- insertStakeAddressRefIfMissing syncEnv addr
insertCollateralTxOut syncEnv iopts (txId, _txHash) (Generic.TxOut index addr value maMap mScript dt) era = do
mSaId <- insertStakeAddressRefIfMissing syncEnv addr era
mDatumId <-
whenFalseEmpty (ioPlutusExtra iopts) Nothing $
Generic.whenInlineDatum dt $
Expand Down
47 changes: 47 additions & 0 deletions cardano-db/src/Cardano/Db/Statement/Variants/TxOut.hs
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,53 @@ setNullTxOutConsumedBatchStmt =
encoder = Id.idEncoder Id.getTxId
decoder = HsqlD.singleRow (HsqlD.column (HsqlD.nonNullable HsqlD.int8))

--------------------------------------------------------------------------------
-- Query to count tx_outs with NULL stake_address_id for Pointer addresses
-- Used to verify that Pointer addresses in Conway era don't have stake keys
--------------------------------------------------------------------------------
queryPtrTxOutNullStakeCoreStmt :: HsqlStmt.Statement () Word64
queryPtrTxOutNullStakeCoreStmt =
HsqlStmt.Statement sql HsqlE.noParams decoder True
where
sql =
TextEnc.encodeUtf8 $
Text.concat
[ "SELECT COUNT(*) FROM tx_out"
, " WHERE stake_address_id IS NULL"
, " AND address LIKE 'addr_test1g%'" -- Pointer address prefix for testnet
]
decoder =
HsqlD.singleRow $
fromIntegral <$> HsqlD.column (HsqlD.nonNullable HsqlD.int8)

queryPtrTxOutNullStakeAddressStmt :: HsqlStmt.Statement () Word64
queryPtrTxOutNullStakeAddressStmt =
HsqlStmt.Statement sql HsqlE.noParams decoder True
where
sql =
TextEnc.encodeUtf8 $
Text.concat
[ "SELECT COUNT(*) FROM tx_out"
, " INNER JOIN address ON tx_out.address_id = address.id"
, " WHERE tx_out.stake_address_id IS NULL"
, " AND address.address LIKE 'addr_test1g%'" -- Pointer address prefix for testnet
]
decoder =
HsqlD.singleRow $
fromIntegral <$> HsqlD.column (HsqlD.nonNullable HsqlD.int8)

-- | Count tx_outs with NULL stake_address_id for Pointer addresses (Conway era check)
queryPtrTxOutNullStake :: TxOutVariantType -> DbM Word64
queryPtrTxOutNullStake txOutVariantType =
case txOutVariantType of
TxOutVariantCore ->
runSession mkDbCallStack $
HsqlSes.statement () queryPtrTxOutNullStakeCoreStmt
TxOutVariantAddress ->
runSession mkDbCallStack $
HsqlSes.statement () queryPtrTxOutNullStakeAddressStmt

--------------------------------------------------------------------------------
-- Main function to set NULL for tx_out consumed_by_tx_id
querySetNullTxOut ::
TxOutVariantType ->
Expand Down
Loading