Skip to content

Commit 6612d48

Browse files
committed
2051 - Era aware pointer addresses
1 parent 2187c9c commit 6612d48

File tree

8 files changed

+123
-23
lines changed

8 files changed

+123
-23
lines changed

cardano-chain-gen/test/Test/Cardano/Db/Mock/Unit/Conway.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ unitTests iom knownMigrations =
156156
, test "stake address pointers" Stake.stakeAddressPtr
157157
, test "stake address pointers deregistration" Stake.stakeAddressPtrDereg
158158
, test "stake address pointers. Use before registering." Stake.stakeAddressPtrUseBefore
159+
, test "stake address pointers have NULL stake_address_id in Conway" Stake.stakeAddressPtrNullInConway
159160
, test "register stake creds" Stake.registerStakeCreds
160161
, test "register stake creds with shelley disabled" Stake.registerStakeCredsNoShelley
161162
]

cardano-chain-gen/test/Test/Cardano/Db/Mock/Unit/Conway/Stake.hs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Test.Cardano.Db.Mock.Unit.Conway.Stake (
1010
stakeAddressPtr,
1111
stakeAddressPtrDereg,
1212
stakeAddressPtrUseBefore,
13+
stakeAddressPtrNullInConway,
1314
registerStakeCreds,
1415
registerStakeCredsNoShelley,
1516

@@ -37,7 +38,7 @@ import Ouroboros.Network.Block (blockSlot)
3738
import Test.Cardano.Db.Mock.Config
3839
import qualified Test.Cardano.Db.Mock.UnifiedApi as Api
3940
import Test.Cardano.Db.Mock.Validate
40-
import Test.Tasty.HUnit (Assertion ())
41+
import Test.Tasty.HUnit (Assertion (), assertBool)
4142
import Prelude ()
4243

4344
registrationTx :: IOManager -> [(Text, Text)] -> Assertion
@@ -229,6 +230,43 @@ stakeAddressPtrUseBefore =
229230
where
230231
testLabel = "conwayStakeAddressPtrUseBefore"
231232

233+
-- | Test that Pointer addresses in Conway era have NULL stake_address_id
234+
-- This verifies the fix for issue #2051: Pointer addresses in Conway era
235+
-- should be treated as Enterprise addresses with no stake key association
236+
stakeAddressPtrNullInConway :: IOManager -> [(Text, Text)] -> Assertion
237+
stakeAddressPtrNullInConway =
238+
withFullConfig conwayConfigDir testLabel $ \interpreter mockServer dbSync -> do
239+
startDBSync dbSync
240+
241+
-- Forge a block with a stake registration cert
242+
blk <-
243+
Api.withConwayFindLeaderAndSubmitTx interpreter mockServer $
244+
Conway.mkSimpleDCertTx [(StakeIndexNew 1, Conway.mkRegTxCert SNothing)]
245+
246+
-- Forge a block with a payment to a Pointer address
247+
-- The pointer references the stake registration certificate we just created
248+
let ptr = Ptr (unsafeToSlotNo32 $ blockSlot blk) (TxIx 0) (CertIx 0)
249+
void $
250+
Api.withConwayFindLeaderAndSubmitTx interpreter mockServer $
251+
Conway.mkPaymentTx (UTxOIndex 0) (UTxOAddressNewWithPtr 0 ptr) 20_000 20_000 0
252+
253+
-- Wait for it to sync
254+
assertBlockNoBackoff dbSync 2
255+
256+
-- Get the tx_out variant type for queries
257+
let txOutVariantType = txOutVariantTypeFromConfig dbSync
258+
259+
-- Query for Pointer address tx_outs with NULL stake_address_id
260+
-- In Conway era, Pointer addresses should NOT have stake_address_id populated
261+
ptrCount <- runQuery dbSync $ DB.queryPtrTxOutNullStake txOutVariantType
262+
263+
-- Assert that we have at least one Pointer address with NULL stake_address_id
264+
assertBool
265+
"Pointer address in Conway should have NULL stake_address_id (treated as Enterprise address)"
266+
(ptrCount > 0)
267+
where
268+
testLabel = "conwayStakeAddressPtrNullInConway"
269+
232270
stakeDistGenesis :: IOManager -> [(Text, Text)] -> Assertion
233271
stakeDistGenesis =
234272
withFullConfigDropDB conwayConfigDir testLabel $ \interpreter mockServer dbSync -> do

cardano-db-sync/src/Cardano/DbSync/Api/Ledger.hs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ storeUTxOFromLedger ::
7878
ExtLedgerState CardanoBlock mk ->
7979
ExceptT SyncNodeError DB.DbM ()
8080
storeUTxOFromLedger env st = case ledgerState st of
81-
LedgerStateBabbage bts -> storeUTxO env (getUTxO bts)
82-
LedgerStateConway stc -> storeUTxO env (getUTxO stc)
83-
LedgerStateDijkstra stc -> storeUTxO env (getUTxO stc)
81+
LedgerStateBabbage bts -> storeUTxO env Babbage (getUTxO bts)
82+
LedgerStateConway stc -> storeUTxO env Conway (getUTxO stc)
83+
LedgerStateDijkstra stc -> storeUTxO env Dijkstra (getUTxO stc)
8484
_otherwise -> liftIO $ logError trce "storeUTxOFromLedger is only supported after Babbage"
8585
where
8686
trce = getTrace env
@@ -96,9 +96,10 @@ storeUTxO ::
9696
, NativeScript era ~ Timelock era
9797
) =>
9898
SyncEnv ->
99+
BlockEra ->
99100
Map TxIn (BabbageTxOut era) ->
100101
ExceptT SyncNodeError DB.DbM ()
101-
storeUTxO env mp = do
102+
storeUTxO env blkEra mp = do
102103
liftIO $
103104
logInfo trce $
104105
mconcat
@@ -107,7 +108,7 @@ storeUTxO env mp = do
107108
, " tx_out as pages of "
108109
, textShow bulkSize
109110
]
110-
mapM_ (storePage env pagePerc) . zip [0 ..] . chunksOf bulkSize . Map.toList $ mp
111+
mapM_ (storePage env blkEra pagePerc) . zip [0 ..] . chunksOf bulkSize . Map.toList $ mp
111112
where
112113
trce = getTrace env
113114
bulkSize = DB.getTxOutBulkSize (getTxOutVariantType env)
@@ -124,12 +125,13 @@ storePage ::
124125
, NativeScript era ~ Timelock era
125126
) =>
126127
SyncEnv ->
128+
BlockEra ->
127129
Float ->
128130
(Int, [(TxIn, BabbageTxOut era)]) ->
129131
ExceptT SyncNodeError DB.DbM ()
130-
storePage syncEnv percQuantum (n, ls) = do
132+
storePage syncEnv blkEra percQuantum (n, ls) = do
131133
when (n `mod` 10 == 0) $ liftIO $ logInfo trce $ "Bootstrap in progress " <> prc <> "%"
132-
txOuts <- mapM (prepareTxOut syncEnv) ls
134+
txOuts <- mapM (prepareTxOut syncEnv blkEra) ls
133135
txOutIds <- lift $ DB.insertBulkTxOut False $ etoTxOut . fst <$> txOuts
134136
let maTxOuts = concatMap (mkmaTxOuts txOutVariantType) $ zip txOutIds (snd <$> txOuts)
135137
void . lift $ DB.insertBulkMaTxOutPiped [maTxOuts]
@@ -147,12 +149,13 @@ prepareTxOut ::
147149
, NativeScript era ~ Timelock era
148150
) =>
149151
SyncEnv ->
152+
BlockEra ->
150153
(TxIn, BabbageTxOut era) ->
151154
ExceptT SyncNodeError DB.DbM (ExtendedTxOut, [MissingMaTxOut])
152-
prepareTxOut syncEnv (TxIn txIntxId (TxIx index), txOut) = do
155+
prepareTxOut syncEnv blkEra (TxIn txIntxId (TxIx index), txOut) = do
153156
let txHashByteString = Generic.safeHashToByteString $ unTxId txIntxId
154157
let genTxOut = fromTxOut (fromIntegral index) txOut
155158
txId <- liftDbLookupEither mkSyncNodeCallStack $ queryTxIdWithCache syncEnv txIntxId
156-
insertTxOut syncEnv iopts (txId, txHashByteString) genTxOut
159+
insertTxOut syncEnv iopts (txId, txHashByteString) genTxOut blkEra
157160
where
158161
iopts = soptInsertOptions $ envOptions syncEnv

cardano-db-sync/src/Cardano/DbSync/Era/Shelley/Genesis.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Cardano.DbSync.Era.Universal.Insert.Certificate (insertDelegation, insert
2626
import Cardano.DbSync.Era.Universal.Insert.Other (insertStakeAddressRefIfMissing)
2727
import Cardano.DbSync.Era.Universal.Insert.Pool (insertPoolRegister)
2828
import Cardano.DbSync.Error
29+
import Cardano.DbSync.Types (BlockEra (..))
2930
import Cardano.DbSync.Util
3031
import Cardano.Ledger.Address (serialiseAddr)
3132
import qualified Cardano.Ledger.Coin as Ledger
@@ -269,7 +270,7 @@ insertTxOuts syncEnv blkId (TxIn txInId _, txOut) = do
269270
}
270271

271272
tryUpdateCacheTx (envCache syncEnv) txInId txId
272-
_ <- insertStakeAddressRefIfMissing (withNoCache syncEnv) (txOut ^. Core.addrTxOutL)
273+
_ <- insertStakeAddressRefIfMissing (withNoCache syncEnv) (txOut ^. Core.addrTxOutL) Shelley
273274
case ioTxOutVariantType . soptInsertOptions $ envOptions syncEnv of
274275
DB.TxOutVariantCore ->
275276
void

cardano-db-sync/src/Cardano/DbSync/Era/Universal/Block.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ insertBlockUniversal syncEnv shouldLog withinTwoMins withinHalfHour blk details
9797

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

102102
minIds <- insertBlockGroupedData syncEnv blockGroupedData
103103

cardano-db-sync/src/Cardano/DbSync/Era/Universal/Insert/Other.hs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE DataKinds #-}
22
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE OverloadedStrings #-}
34
{-# LANGUAGE RankNTypes #-}
45
{-# LANGUAGE ScopedTypeVariables #-}
56
{-# LANGUAGE TypeFamilies #-}
@@ -26,6 +27,7 @@ import qualified Cardano.DbSync.Era.Shelley.Generic as Generic
2627
import Cardano.DbSync.Era.Universal.Insert.Grouped
2728
import Cardano.DbSync.Era.Util (safeDecodeToJson)
2829
import Cardano.DbSync.Error (SyncNodeError)
30+
import Cardano.DbSync.Types (BlockEra (..))
2931
import Cardano.DbSync.Util
3032
import qualified Cardano.Ledger.Address as Ledger
3133
import qualified Cardano.Ledger.BaseTypes as Ledger
@@ -136,16 +138,20 @@ insertWithdrawals syncEnv txId redeemers txWdrl = do
136138
insertStakeAddressRefIfMissing ::
137139
SyncEnv ->
138140
Ledger.Addr ->
141+
BlockEra ->
139142
ExceptT SyncNodeError DB.DbM (Maybe DB.StakeAddressId)
140-
insertStakeAddressRefIfMissing syncEnv addr =
143+
insertStakeAddressRefIfMissing syncEnv addr era =
141144
case addr of
142145
Ledger.AddrBootstrap {} -> pure Nothing
143146
Ledger.Addr nw _pcred sref ->
144147
case sref of
145148
Ledger.StakeRefBase cred -> do
146149
Just <$> queryOrInsertStakeAddress syncEnv UpdateCache nw cred
147-
Ledger.StakeRefPtr ptr -> do
148-
lift $ DB.queryStakeRefPtr ptr
150+
Ledger.StakeRefPtr ptr ->
151+
-- In Conway era onwards, Pointer addresses are treated as Enterprise addresses
152+
case era of
153+
Conway -> pure Nothing
154+
_ -> lift $ DB.queryStakeRefPtr ptr
149155
Ledger.StakeRefNull -> pure Nothing
150156

151157
insertMultiAsset ::

cardano-db-sync/src/Cardano/DbSync/Era/Universal/Insert/Tx.hs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import Cardano.DbSync.Era.Universal.Insert.Pool (IsPoolMember)
5656
import Cardano.DbSync.Era.Util (safeDecodeToJson)
5757
import Cardano.DbSync.Error (SyncNodeError)
5858
import Cardano.DbSync.Ledger.Types (ApplyResult (..), getGovExpiresAt, lookupDepositsMap)
59+
import Cardano.DbSync.Types (BlockEra)
5960
import Cardano.DbSync.Util
6061
import Cardano.DbSync.Util.Cbor (serialiseTxMetadataToCbor)
6162

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

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

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

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

166168
txMetadata <-
167169
whenFalseMempty (ioMetadata iopts) $
@@ -213,9 +215,10 @@ insertTxOut ::
213215
InsertOptions ->
214216
(DB.TxId, ByteString) ->
215217
Generic.TxOut ->
218+
BlockEra ->
216219
ExceptT SyncNodeError DB.DbM (ExtendedTxOut, [MissingMaTxOut])
217-
insertTxOut syncEnv iopts (txId, txHash) (Generic.TxOut index addr value maMap mScript dt) = do
218-
mSaId <- insertStakeAddressRefIfMissing syncEnv addr
220+
insertTxOut syncEnv iopts (txId, txHash) (Generic.TxOut index addr value maMap mScript dt) era = do
221+
mSaId <- insertStakeAddressRefIfMissing syncEnv addr era
219222
mDatumId <-
220223
whenFalseEmpty (ioPlutusExtra iopts) Nothing $
221224
Generic.whenInlineDatum dt $
@@ -387,9 +390,10 @@ insertCollateralTxOut ::
387390
InsertOptions ->
388391
(DB.TxId, ByteString) ->
389392
Generic.TxOut ->
393+
BlockEra ->
390394
ExceptT SyncNodeError DB.DbM ()
391-
insertCollateralTxOut syncEnv iopts (txId, _txHash) (Generic.TxOut index addr value maMap mScript dt) = do
392-
mSaId <- insertStakeAddressRefIfMissing syncEnv addr
395+
insertCollateralTxOut syncEnv iopts (txId, _txHash) (Generic.TxOut index addr value maMap mScript dt) era = do
396+
mSaId <- insertStakeAddressRefIfMissing syncEnv addr era
393397
mDatumId <-
394398
whenFalseEmpty (ioPlutusExtra iopts) Nothing $
395399
Generic.whenInlineDatum dt $

cardano-db/src/Cardano/Db/Statement/Variants/TxOut.hs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,53 @@ setNullTxOutConsumedBatchStmt =
800800
encoder = Id.idEncoder Id.getTxId
801801
decoder = HsqlD.singleRow (HsqlD.column (HsqlD.nonNullable HsqlD.int8))
802802

803+
--------------------------------------------------------------------------------
804+
-- Query to count tx_outs with NULL stake_address_id for Pointer addresses
805+
-- Used to verify that Pointer addresses in Conway era don't have stake keys
806+
--------------------------------------------------------------------------------
807+
queryPtrTxOutNullStakeCoreStmt :: HsqlStmt.Statement () Word64
808+
queryPtrTxOutNullStakeCoreStmt =
809+
HsqlStmt.Statement sql HsqlE.noParams decoder True
810+
where
811+
sql =
812+
TextEnc.encodeUtf8 $
813+
Text.concat
814+
[ "SELECT COUNT(*) FROM tx_out"
815+
, " WHERE stake_address_id IS NULL"
816+
, " AND address LIKE 'addr_test1g%'" -- Pointer address prefix for testnet
817+
]
818+
decoder =
819+
HsqlD.singleRow $
820+
fromIntegral <$> HsqlD.column (HsqlD.nonNullable HsqlD.int8)
821+
822+
queryPtrTxOutNullStakeAddressStmt :: HsqlStmt.Statement () Word64
823+
queryPtrTxOutNullStakeAddressStmt =
824+
HsqlStmt.Statement sql HsqlE.noParams decoder True
825+
where
826+
sql =
827+
TextEnc.encodeUtf8 $
828+
Text.concat
829+
[ "SELECT COUNT(*) FROM tx_out"
830+
, " INNER JOIN address ON tx_out.address_id = address.id"
831+
, " WHERE tx_out.stake_address_id IS NULL"
832+
, " AND address.address LIKE 'addr_test1g%'" -- Pointer address prefix for testnet
833+
]
834+
decoder =
835+
HsqlD.singleRow $
836+
fromIntegral <$> HsqlD.column (HsqlD.nonNullable HsqlD.int8)
837+
838+
-- | Count tx_outs with NULL stake_address_id for Pointer addresses (Conway era check)
839+
queryPtrTxOutNullStake :: TxOutVariantType -> DbM Word64
840+
queryPtrTxOutNullStake txOutVariantType =
841+
case txOutVariantType of
842+
TxOutVariantCore ->
843+
runSession mkDbCallStack $
844+
HsqlSes.statement () queryPtrTxOutNullStakeCoreStmt
845+
TxOutVariantAddress ->
846+
runSession mkDbCallStack $
847+
HsqlSes.statement () queryPtrTxOutNullStakeAddressStmt
848+
849+
--------------------------------------------------------------------------------
803850
-- Main function to set NULL for tx_out consumed_by_tx_id
804851
querySetNullTxOut ::
805852
TxOutVariantType ->

0 commit comments

Comments
 (0)