Skip to content

Commit dac0447

Browse files
PLT-6165 db sync comparison spent info (#289)
1 parent 96c5bef commit dac0447

16 files changed

+307
-46
lines changed

marconi-cardano-indexers/marconi-cardano-indexers.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ library marconi-cardano-indexers-test-lib
220220
Test.Marconi.Cardano.Chain.Snapshot
221221
Test.Marconi.Cardano.DbSyncComparison.BlockInfoResult
222222
Test.Marconi.Cardano.DbSyncComparison.Common
223+
Test.Marconi.Cardano.DbSyncComparison.SpentInfoResult
223224

224225
--------------------
225226
-- Local components

marconi-cardano-indexers/test-lib/Test/Marconi/Cardano/DbSyncComparison/BlockInfoResult.hs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import Marconi.Cardano.Indexers.BlockInfo qualified as BlockInfo
2121
import Marconi.Core.Indexer.SQLiteIndexer qualified as Core
2222
import System.FilePath ((</>))
2323
import Test.Marconi.Cardano.DbSyncComparison.Common (
24-
Era,
25-
NodeType (Mainnet),
24+
DbSyncComparisonConfig (DbSyncComparisonConfig),
2625
eraToString,
27-
nodeTypeToString,
26+
pathToGoldenFile,
27+
pathToOutFile,
2828
queryIndexerOnSnapshot,
2929
toRuntimeException,
3030
)
@@ -62,8 +62,8 @@ toResult (BlockInfo.BlockInfo (C.BlockNo bn) timestamp (C.EpochNo en)) =
6262
6363
where :slot_no is the argument.
6464
-}
65-
mkBlockInfoQueryBySlotNoTest :: String -> NodeType -> Era -> C.SlotNo -> TestTree
66-
mkBlockInfoQueryBySlotNoTest testName nodeType era slotNo =
65+
mkBlockInfoQueryBySlotNoTest :: String -> DbSyncComparisonConfig -> TestTree
66+
mkBlockInfoQueryBySlotNoTest testName cfg@(DbSyncComparisonConfig nodeType era slotNo dbPath _ _) =
6767
goldenVsFileDiff
6868
testName
6969
(\expected actual -> ["diff", "--color=always", expected, actual])
@@ -73,13 +73,13 @@ mkBlockInfoQueryBySlotNoTest testName nodeType era slotNo =
7373
where
7474
runTest = do
7575
indexer <- toRuntimeException $ BlockInfo.mkBlockInfoIndexer Core.inMemoryDB
76-
queryResult <- queryIndexerOnSnapshot Mainnet subChainPath dbPath blockInfoConfig query indexer
76+
queryResult <-
77+
queryIndexerOnSnapshot nodeType subChainPath dbPath blockInfoConfig Nothing query indexer
7778
let finalResult = List.singleton . toResult <$> queryResult
7879
Aeson.encodeFile outFile finalResult
7980

80-
outFile = pathToOutFile nodeType era slotNo
81-
goldenFile = pathToGoldenFile nodeType era slotNo
82-
dbPath = "test/Spec/Golden/Snapshot/block-info-db/"
81+
outFile = pathToOutFile cfg
82+
goldenFile = pathToGoldenFile cfg
8383
subChainPath = "../../mainnet-snapshots" </> eraToString era
8484
query = BlockInfoBySlotNoQuery slotNo
8585

@@ -88,10 +88,3 @@ blockInfoConfig =
8888
Core.RunIndexerOnSnapshotConfig
8989
(Core.withPreprocessorOnSnapshot BlockInfo.extractBlockInfo (Just . Lens.view BlockInfo.blockNo))
9090
1 -- is this right?
91-
92-
pathToOutFile :: NodeType -> Era -> C.SlotNo -> FilePath
93-
pathToOutFile (nodeTypeToString -> nodeType) (eraToString -> era) (C.unSlotNo -> slotNo) =
94-
"test/Spec/Golden/Snapshot" </> nodeType </> era </> "blockinfo-slot" <> show slotNo <> ".out"
95-
96-
pathToGoldenFile :: NodeType -> Era -> C.SlotNo -> FilePath
97-
pathToGoldenFile nt e s = pathToOutFile nt e s <> ".golden"

marconi-cardano-indexers/test-lib/Test/Marconi/Cardano/DbSyncComparison/Common.hs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE FlexibleContexts #-}
22
{-# LANGUAGE LambdaCase #-}
3+
{-# LANGUAGE ViewPatterns #-}
34

45
module Test.Marconi.Cardano.DbSyncComparison.Common (
56
-- * Runner
@@ -8,8 +9,11 @@ module Test.Marconi.Cardano.DbSyncComparison.Common (
89
-- * Utils
910
toRuntimeException,
1011
getNodeConfigPath,
12+
pathToOutFile,
13+
pathToGoldenFile,
1114

1215
-- * Types
16+
DbSyncComparisonConfig (..),
1317
NodeType (..),
1418
nodeTypeToString,
1519
Era (..),
@@ -27,6 +31,33 @@ import System.Environment (getEnv)
2731
import System.FilePath ((</>))
2832
import Test.Marconi.Cardano.Chain.Snapshot (setupSnapshot)
2933

34+
data DbSyncComparisonConfig = DbSyncComparisonConfig
35+
{ dbSyncComparisonNodeType :: NodeType
36+
, dbSyncComparisonEra :: Era
37+
, dbSyncComparisonSlotNo :: C.SlotNo
38+
, dbSyncComparisonDbPath :: FilePath
39+
, dbSyncComparisonGoldenDir :: String
40+
-- ^ Base directory in which golden files are located.
41+
-- See 'pathToOutFile' and 'pathToGoldenFile'.
42+
, dbSyncComparisonName :: String
43+
-- ^ Identifier for the comparison, e.g. "blockinfo"
44+
}
45+
46+
pathToOutFile :: DbSyncComparisonConfig -> FilePath
47+
pathToOutFile
48+
( DbSyncComparisonConfig
49+
(nodeTypeToString -> nodeType)
50+
(eraToString -> era)
51+
(C.unSlotNo -> slotNo)
52+
_
53+
gdir
54+
nm
55+
) =
56+
gdir </> nodeType </> era </> nm <> "-slot" <> show slotNo <> ".out"
57+
58+
pathToGoldenFile :: DbSyncComparisonConfig -> FilePath
59+
pathToGoldenFile cfg = pathToOutFile cfg <> ".golden"
60+
3061
-- | The Cardano network type used to create the snapshot.
3162
data NodeType = Preview | Preprod | Mainnet
3263

@@ -78,16 +109,19 @@ queryIndexerOnSnapshot
78109
-> FilePath
79110
-- ^ directory to be used as the indexer's DB
80111
-> Core.RunIndexerOnSnapshotConfig BlockEvent event
81-
-> query -- BlockInfoBySlotNoQuery BlockInfo.BlockInfo
112+
-> Maybe C.ChainPoint
113+
-- ^ Optional point to pass to @Core.'query'@. Else, use @Core.'queryLatest'@.
114+
-> query
82115
-> indexer event
83116
-> IO (Core.Result query)
84-
queryIndexerOnSnapshot nodeType subChainPath dbPath config query indexer = do
117+
queryIndexerOnSnapshot nodeType subChainPath dbPath config point query indexer = do
85118
configFile <- getNodeConfigPath nodeType
86119
blockStream <- setupSnapshot configFile subChainPath dbPath
87120
let runIndexer = Core.runIndexerOnSnapshot config indexer blockStream
88121
withAsync runIndexer $ \runner -> do
89122
finalState <- wait runner >>= readMVar
90-
toRuntimeException $ Core.queryLatest query finalState
123+
toRuntimeException $
124+
maybe Core.queryLatest Core.query point query finalState
91125

92126
{- | The path to a data directory containing configuration files
93127
is set to an environment variable. This function retrieves the right
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
-- | Utilities for comparing the @Spent.'SpentInfoEvent'@ indexer to cardano-db-sync results.
2+
module Test.Marconi.Cardano.DbSyncComparison.SpentInfoResult (
3+
-- * Test builder
4+
mkSpentInfoEventAtQueryTest,
5+
6+
-- ** Generating results from cardano-db-sync data
7+
-- $sql-query
8+
) where
9+
10+
import Cardano.Api qualified as C
11+
import Cardano.Api.Extended.Streaming (BlockEvent (BlockEvent))
12+
import Cardano.Api.Shelley qualified as C
13+
import Control.Exception (throwIO)
14+
import Control.Monad.Except (runExceptT)
15+
import Data.Aeson qualified as Aeson
16+
import Data.ByteString.Short qualified as BSS
17+
import Data.List.NonEmpty qualified as NEList
18+
import Marconi.Cardano.Core.Runner qualified as Runner
19+
import Marconi.Cardano.Indexers.Spent qualified as Spent
20+
import Marconi.Core qualified as Core
21+
import Marconi.Core.Indexer.SQLiteIndexer qualified as SQLiteIndexer
22+
import System.FilePath ((</>))
23+
import Test.Marconi.Cardano.DbSyncComparison.Common (
24+
DbSyncComparisonConfig (DbSyncComparisonConfig),
25+
eraToString,
26+
pathToGoldenFile,
27+
pathToOutFile,
28+
queryIndexerOnSnapshot,
29+
)
30+
import Test.Tasty (TestTree)
31+
import Test.Tasty.Golden (goldenVsFileDiff)
32+
33+
{- | Builds a golden test for the @Core.'EventAtQuery'@ for @Spent.'SpentInfoEvent'@
34+
and compares it to manually generated results from `cardano-db-sync`. See documentation
35+
section "Generating results from cardano-db-sync data" for the SQL query run to generate
36+
golden files.
37+
-}
38+
mkSpentInfoEventAtQueryTest :: String -> DbSyncComparisonConfig -> TestTree
39+
mkSpentInfoEventAtQueryTest testName cfg@(DbSyncComparisonConfig nodeType era slotNo dbPath _ _) =
40+
goldenVsFileDiff
41+
testName
42+
(\expected actual -> ["diff", "--color=always", expected, actual])
43+
goldenFile
44+
outFile
45+
runTest
46+
where
47+
runTest = do
48+
indexer <-
49+
either throwIO pure
50+
=<< runExceptT (Spent.mkSpentIndexer SQLiteIndexer.inMemoryDB)
51+
queryResult :: Maybe Spent.SpentInfoEvent <-
52+
queryIndexerOnSnapshot
53+
nodeType
54+
subChainPath
55+
dbPath
56+
spentInfoIndexerConfig
57+
(Just $ chainPointAtSlotNo slotNo)
58+
Core.EventAtQuery
59+
indexer
60+
-- NOTE: See documentation $sql-query for mention of sorting results.
61+
Aeson.encodeFile outFile (NEList.sort <$> queryResult)
62+
63+
outFile = pathToOutFile cfg
64+
goldenFile = pathToGoldenFile cfg
65+
subChainPath = "../../mainnet-snapshots" </> eraToString era
66+
67+
-- | Config used for testing SpentInfo indexer on snapshot with no rollback.
68+
spentInfoIndexerConfig :: Runner.RunIndexerOnSnapshotConfig BlockEvent Spent.SpentInfoEvent
69+
spentInfoIndexerConfig = Runner.RunIndexerOnSnapshotConfig preprocessor 1
70+
where
71+
preprocessor = Runner.RunIndexerEventPreprocessing extractSpent (const Nothing) (const Nothing)
72+
mkIndexedTimedSpent
73+
:: C.ChainPoint -> C.TxBody era -> Core.ProcessedInput C.ChainPoint Spent.SpentInfoEvent
74+
mkIndexedTimedSpent point = Core.Index . Core.Timed point . NEList.nonEmpty . Spent.getInputs
75+
extractSpent :: BlockEvent -> [Core.ProcessedInput C.ChainPoint Spent.SpentInfoEvent]
76+
extractSpent (BlockEvent (C.BlockInMode (C.Block (C.BlockHeader slotNo hash _) txs) _) _ _) =
77+
map (mkIndexedTimedSpent (C.ChainPoint slotNo hash) . C.getTxBody) txs
78+
79+
{- | Create a 'ChainPoint' from a 'SlotNo' with arbitrary hash.
80+
Used solely for testing here, where the hash information of chain point is
81+
known not to be used.
82+
83+
NOTE: This is needed since the only way to query SpentInfo at a given slot number, as of
84+
writing, is to use the 'ChainPoint' provided to 'query' with 'EventAtQuery'.
85+
-}
86+
chainPointAtSlotNo :: C.SlotNo -> C.ChainPoint
87+
chainPointAtSlotNo = flip C.ChainPoint (C.HeaderHash BSS.empty)
88+
89+
{- $sql-query
90+
Results were generated using the following SQL query (PostgreSQL 11.19) on the
91+
database created from 'cardano-db-sync-13.1.1.3', varying the slot number according to the test.
92+
This creates the resulting JSON golden file.
93+
94+
Note the result is sorted according to fields 'spentTxOutRef' then 'spentAtTxId', to avoid
95+
false negatives because of different orderings in the golden test.
96+
97+
@
98+
SET client_encoding = 'UTF8';
99+
100+
\set slot 18728839
101+
102+
CREATE TEMP TABLE out_result AS
103+
WITH tx_at_slot AS (
104+
SELECT
105+
tx.id,
106+
tx.hash,
107+
tx.valid_contract,
108+
tx.block_index,
109+
block.slot_no
110+
FROM block
111+
JOIN tx ON tx.block_id = block.id
112+
WHERE block.slot_no = :slot
113+
-- tx_in_* tables attaches info on spent UTxOs from either the
114+
-- collateral_tx_in or tx_in tables, based on whether the TxIn
115+
-- or collateral TxIns would have been spent based on whether
116+
-- the script was valid or invalid, respectively.
117+
), tx_in_collat AS (
118+
-- Script is invalid, so collateral is spent
119+
SELECT
120+
tx_at_slot.id,
121+
tx_at_slot.hash,
122+
collateral_tx_in.tx_out_id,
123+
collateral_tx_in.tx_out_index
124+
FROM tx_at_slot
125+
JOIN collateral_tx_in ON tx_at_slot.id = collateral_tx_in.tx_in_id
126+
WHERE NOT tx_at_slot.valid_contract
127+
), tx_in_nocollat AS (
128+
-- Script is valid (or there is no script), so usual TxIn is spent
129+
SELECT
130+
tx_at_slot.id,
131+
tx_at_slot.hash,
132+
tx_in.tx_out_id,
133+
tx_in.tx_out_index
134+
FROM tx_at_slot
135+
JOIN tx_in ON tx_at_slot.id = tx_in.tx_in_id
136+
WHERE tx_at_slot.valid_contract
137+
), tx_in_data AS (
138+
SELECT * FROM tx_in_collat
139+
UNION ALL
140+
SELECT * FROM tx_in_nocollat
141+
), result AS (
142+
-- Attach the TxIx hash identifying each TxIn from the tx table
143+
-- based on the tx_out_id.
144+
SELECT
145+
-- Transaction where this TxIn was spent
146+
encode(tx_in_data.hash, 'hex') as spentAtTxId,
147+
-- Identifier for where this TxIn was created (appeared as TxOut)
148+
-- See FromJSON instance for TxIn
149+
-- https://cardano-api.cardano.intersectmbo.org/cardano-api/src/Cardano.Api.TxIn.html#TxIn
150+
concat(encode(tx.hash, 'hex'), '#', tx_in_data.tx_out_index) as spentTxOutRef
151+
FROM
152+
tx_in_data
153+
JOIN tx on tx_in_data.tx_out_id = tx.id
154+
-- Ordering used so that Haskell value can be encoded with the same ordering.
155+
ORDER BY spentTxOutRef, spentAtTxId
156+
)
157+
-- JSON formatting to match To/FromJSON instance in cardano-api
158+
SELECT
159+
json_agg(
160+
json_build_object(
161+
'spentAtTxId',
162+
spentAtTxId,
163+
'spentTxOutRef',
164+
spentTxOutRef
165+
)
166+
)
167+
FROM result;
168+
169+
\copy out_result TO result.out.golden
170+
@
171+
-
172+
-}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"spentAtTxId" : "14bc6b152e3cc20fd40a08a224095ab53d6ee3c9a0e6abb9ce09f82ab5acf2a3", "spentTxOutRef" : "2ec7c4d2557f2498528491c8e7fa2fb2457000002b71d9c7978e32398a8a8211#0"}, {"spentAtTxId" : "1a71cf02a0a1fad32cb7c10a2564e3a5c463b73c40395301f454979aa7442bed", "spentTxOutRef" : "6d2b63ca61449c4ba5783b1c0236d136409b5738875ed1a8fcb02aec7c5d2169#1"}, {"spentAtTxId" : "c3a3877906b10027aec76e47c42a7956c579dc1f26b9edddf047a55770c633e3", "spentTxOutRef" : "7c685f9d47332cee3c54afba19b6e2bb8ab402c4bb29cef2353ec178ced2501b#1"}, {"spentAtTxId" : "fc0fae1c7a6019183e0f71df5a339baf5ccab458a14991caffe31c58df62c283", "spentTxOutRef" : "8483e307c335a843ea3668e567f88ee0be8ff97d2a0a168ffeb93e8d82704d61#1"}, {"spentAtTxId" : "ff796f7fc2a64e7571bd8c32f34c72ebf2db70cc415bdfe9a50aa06ca98e10d7", "spentTxOutRef" : "bb3b06d98e4e70a785a5e4ffc8ad77eb443ddf0c468d2adaa3764e44a072d652#1"}, {"spentAtTxId" : "ff796f7fc2a64e7571bd8c32f34c72ebf2db70cc415bdfe9a50aa06ca98e10d7", "spentTxOutRef" : "c717be8b2dd9ff54e90c96387a6f6722cd5a0d58f34e71a273b0c1c7fb614fc1#1"}, {"spentAtTxId" : "48d161e453c8a9567dda7913fb6430781a12249e25fb14b5f74f1272f88df47e", "spentTxOutRef" : "cc04c34ace0af80631f01aa604ea479f64cf8ee1d26b4d572ed34a971b61d08f#1"}, {"spentAtTxId" : "ff796f7fc2a64e7571bd8c32f34c72ebf2db70cc415bdfe9a50aa06ca98e10d7", "spentTxOutRef" : "dea2bab714e1631fb9f4dc51abea5d40145d417af09b80a14d10875f8bbfd083#0"}, {"spentAtTxId" : "7f4ffbd4512557e82fb2bf1436be747a76aa697be14b3670915c03074b85325d", "spentTxOutRef" : "f8756b99863bb5ed24612daa0f732b045c2f24be6a561d95a9c86a9c70a496bc#0"}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"spentAtTxId" : "4fc1c90b8ee3e2e252d3fd74472f461809aa523e64502d08cd319709ad03826e", "spentTxOutRef" : "10994456e6486f9ebe6bab373129595b397c75fa1883fd79140f95d5abab4b4a#0"}, {"spentAtTxId" : "4fc1c90b8ee3e2e252d3fd74472f461809aa523e64502d08cd319709ad03826e", "spentTxOutRef" : "2351136f7cb6f97f653c9d1b05e8f0633529c91623a211a9347bcba534bd3a0b#0"}, {"spentAtTxId" : "444c4306a69cfe1e3733422b3e0f02eb602de3afecb956885bb4afb01f11d0a8", "spentTxOutRef" : "47503cf310d167b5bfbe119543954d71916204e65169be0f47e718d62a8e8063#0"}, {"spentAtTxId" : "444c4306a69cfe1e3733422b3e0f02eb602de3afecb956885bb4afb01f11d0a8", "spentTxOutRef" : "8399276806d7cd125339ce280ce03ba52457871211a2c46f931ceee821739b40#0"}, {"spentAtTxId" : "5fa6c737500a0473e8878b079378838b67f870c0e9a487270e0008f774badcd8", "spentTxOutRef" : "88600f287af5bb6d2c6b347713025b9829516d85272b36c38dc8594bcc19d898#0"}, {"spentAtTxId" : "5fa6c737500a0473e8878b079378838b67f870c0e9a487270e0008f774badcd8", "spentTxOutRef" : "88600f287af5bb6d2c6b347713025b9829516d85272b36c38dc8594bcc19d898#1"}, {"spentAtTxId" : "e2fb077b3beecdcd79ccbb5a1a9b82b52ff3090de237a908760406b0b4de2a6e", "spentTxOutRef" : "8c135a510f93e6e391735dd252b4943fc248473b0fd475d8c857d624a4e2c002#0"}, {"spentAtTxId" : "e2fb077b3beecdcd79ccbb5a1a9b82b52ff3090de237a908760406b0b4de2a6e", "spentTxOutRef" : "8c135a510f93e6e391735dd252b4943fc248473b0fd475d8c857d624a4e2c002#1"}, {"spentAtTxId" : "4fc1c90b8ee3e2e252d3fd74472f461809aa523e64502d08cd319709ad03826e", "spentTxOutRef" : "90d56028775796f3b8e6d24c11390505b1fe5289e7ae4c957d8d3c97a57a49e7#0"}, {"spentAtTxId" : "2c2ae600c43cff1ba3636aec737a9b3ce2673a77a0658951f44eafddbdb82b56", "spentTxOutRef" : "943d490694518bb3da7ad36d34f027bbc4a9071f0a5f3699c5026679bc59433c#0"}, {"spentAtTxId" : "65e84325f731569113ea3b1e85429bc472d5a81539084e4f552c59e3b58a7e09", "spentTxOutRef" : "962fc6b492f99491decc0634e4a5ed8117cb94f3e9268d32b9727d8566db671a#1"}, {"spentAtTxId" : "444c4306a69cfe1e3733422b3e0f02eb602de3afecb956885bb4afb01f11d0a8", "spentTxOutRef" : "97695c95122f8498dbb6fa89d0501a6a37278b755472fad18fa68313e04234a4#0"}, {"spentAtTxId" : "444c4306a69cfe1e3733422b3e0f02eb602de3afecb956885bb4afb01f11d0a8", "spentTxOutRef" : "97695c95122f8498dbb6fa89d0501a6a37278b755472fad18fa68313e04234a4#1"}]

marconi-cardano-indexers/test/Spec/Golden/Snapshot/mainnet/alonzo1/spentinfo-slot39916975.out.golden

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

marconi-cardano-indexers/test/Spec/Golden/Snapshot/mainnet/alonzo2/spentinfo-slot43372972.out.golden

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

marconi-cardano-indexers/test/Spec/Golden/Snapshot/mainnet/alonzo2/spentinfo-slot59438205.out.golden

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)