module Spec.Marconi.ChainIndex.Indexers.Utxo.UtxoIndex (tests) where
import Control.Lens (filtered, folded, toListOf)
+
import Control.Lens.Operators ((^..))
import Control.Monad (forM, forM_, void)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson qualified as Aeson
-
import Data.ByteString (ByteString)
import Data.Functor ((<&>))
import Data.List qualified as List
import Data.List.NonEmpty (nonEmpty)
import Data.Map qualified
-
import Data.Maybe (fromJust, fromMaybe, isJust, isNothing, mapMaybe)
-
import Data.Proxy (Proxy (Proxy))
+
import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe)
import Data.Set qualified as Set
import Cardano.Api qualified as C
import Gen.Marconi.ChainIndex.Indexers.Utxo (genShelleyEraUtxoEvents, genUtxoEvents)
import Gen.Marconi.ChainIndex.Indexers.Utxo qualified as UtxoGen
import Gen.Marconi.ChainIndex.Mockchain (mockBlockTxs)
+
import Gen.Marconi.ChainIndex.Types (genChainPoints)
import Helpers (addressAnyToShelley)
import Marconi.ChainIndex.Indexers.Utxo (StorableEvent (ueInputs, ueUtxos))
import Marconi.ChainIndex.Indexers.Utxo qualified as Utxo
tests = testGroup "Spec.Marconi.ChainIndex.Indexers.Utxo"
-
"filter UtxoEvent for Utxos with address in the TargetAddress"
+
"marconi-chain-index-utxo compute utxos with address in TargetAddresses property"
+
"propComputeEventsAtAddress"
+
propComputeEventsAtAddress
-
"marconi-chain-index-utxo event-to-sqlRows property"
-
"eventsToRowsRoundTripTest"
-
eventsToRowsRoundTripTest
+
"marconi-chain-index-utxo roundtrip event-to-sqlRows conversion property"
+
"propRoundTripEventsToRowConversion"
+
propRoundTripEventsToRowConversion
"getUtxoEvents with target addresses corresponding to all addresses in generated txs should return the same 'UtxoEvent' as if no target addresses were provided"
"propUsingAllAddressesOfTxsAsTargetAddressesShouldReturnUtxosAsIfNoFilterWasApplied "
propUsingAllAddressesOfTxsAsTargetAddressesShouldReturnUtxosAsIfNoFilterWasApplied
-
"marconi-chain-index-utxo storage-roundtrip property"
+
"marconi-chain-index-utxo roundtrip save/retrieve of events from utxo-store property"
+
"propSaveToAndRetrieveFromUtxoStore"
+
propSaveToAndRetrieveFromUtxoStore
-
"marconi-chain-index-utxo insert-query property"
-
"utxoInsertAndQueryTest"
+
"marconi-chain-index-utxo insert and retrieve events by address-query property"
+
"propUtxoQueryByAddress"
-
"marconi-chain-index-utxo query-interval property"
-
"utxoQueryIntervalTest"
+
"marconi-chain-index-utxo insert and retrieve events by address-query and query-interval property"
+
"propUtxoQueryByAddressAndQueryInterval"
+
propUtxoQueryByAddressAndQueryInterval
"The points that indexer can be resumed from should return at least non-genesis point when some data was indexed on disk"
"propJsonRoundtripUtxoRow"
-- | Round trip UtxoEvents to UtxoRow conversion
-
-- The purpose of this test is to show that there is a isomorphism
-
-- between `UtxoRow` and UtxoEent. Indeed UtxoRow is a presentation of the UtxoEvent.
-
-- Note: To prove the isomorphism, we assume we can retrieve the UtxoEvent C.TxIns from the environment.
-
eventsToRowsRoundTripTest :: Property
-
eventsToRowsRoundTripTest = property $ do
+
-- The purpose of this test is to show that there is a isomorphism between `UtxoRow` and UtxoEvent.
+
propRoundTripEventsToRowConversion :: Property
+
propRoundTripEventsToRowConversion = property $ do
events <- forAll UtxoGen.genUtxoEvents
txInsMap :: Map C.SlotNo (Set C.TxIn)
-- we are intetested in testing the in-memory storage only for this test.
-- The semantics of disk storage in regards to `ChainPoint.slotNo` are such that:
-- - database rerieved Spent TxIns is a function of `depth` and `slotNo`, and thus potentially
-
-- A consequence of the above is that the set of databaset fetched `Sptnt TxIns` may not match those of the original evcent
-
utxoStorageTest :: Property
-
utxoStorageTest = property $ do
+
-- A consequence of the above is that the set of databaset fetched `Sptnt TxIns` may not match those of the original event
+
propSaveToAndRetrieveFromUtxoStore :: Property
+
propSaveToAndRetrieveFromUtxoStore = property $ do
let depth = Utxo.Depth 2160 -- we set a high valued for depth, to make sure we only test in-memory buffer
events <- forAll UtxoGen.genUtxoEvents
(storedEvents :: [StorableEvent Utxo.UtxoHandle]) <-
>>= liftIO . Storable.getEvents
List.sort storedEvents === List.sort events
-
-- Insert Utxo events in storage, and retrieve the events by address
-
utxoInsertAndQueryTest :: Property
-
utxoInsertAndQueryTest = property $ do
-
events <- forAll genUtxoEvents
-
depth <- forAll $ Gen.int (Range.linear 1 5)
-
indexer <- liftIO $ Utxo.open ":memory:" (Utxo.Depth depth) False -- don't vacuum sqlite
-
>>= liftIO . Storable.insertMany events
-
qs :: [StorableQuery Utxo.UtxoHandle]
-
qs = fmap (Utxo.UtxoAddress . Utxo._address) . concatMap (Set.toList . Utxo.ueUtxos) $ events
-
results <- liftIO . traverse (Storable.query Storable.QEverything indexer) $ qs
-
let rows = concatMap (\(Utxo.UtxoResult rs) -> rs ) results
-
let postGenesis :: [StorableEvent Utxo.UtxoHandle]= filter (\e -> Utxo.ueChainPoint e /= C.ChainPointAtGenesis) events
-
let postGenesisRows :: [Utxo.UtxoRow] = concatMap Utxo.eventToRows postGenesis
-
-- The semanics of fteched `Spent` that corresponds to the original event
-
-- from database is a function of database `depth` and `slotNo`.
-
-- For this reason, we compare the `row` presentation of the events
-
List.sort rows === List.sort postGenesisRows
-
-- | create m ordered chainpoints
-
mkChainPoints :: C.SlotNo -> [C.ChainPoint]
-
bs::ByteString = "00000000000000000000000000000000"
-
blockhash :: C.Hash C.BlockHeader
-
blockhash = fromJust $ C.deserialiseFromRawBytes(C.proxyToAsType Proxy) bs
-
cps = flip C.ChainPoint blockhash <$> [1 .. m]
-
C.ChainPointAtGenesis : cps
-- Insert Utxo events in storage, and retreive the events by address and query interval
-- Note: The property we are checking is:
-- - Insert many events with at various chainPoints
-- - Fetch for all the evenent addresses in the ChainPoint interval [lowChainPoint, highChainPoint]
-- - There should not be any results with ChainPoint > highChainPoint
-
utxoQueryIntervalTest :: Property
-
utxoQueryIntervalTest = property $ do
-
highSlotNo <- forAll $ Gen.integral $ Range.constantFrom 5 3 10
-
Hedgehog.classify "SlotNo greater than 7" $ highSlotNo > 5
-
Hedgehog.classify "SlotNo equal 3" $ highSlotNo == 3
-
let chainPoints = mkChainPoints . C.SlotNo . fromIntegral $ highSlotNo
-
events::[StorableEvent Utxo.UtxoHandle] <- forAll $ forM chainPoints genEventWithShelleyAddressAtChainPoint <&> concat -- (chainpoints !! 4)
-
depth <- forAll $ Gen.int $ Range.constantFrom 5 1 10
-
Hedgehog.classify "Storage depth of 1" $ depth == 1
-
Hedgehog.classify "Storage depth > 5" $ depth > 1
+
propUtxoQueryByAddressAndQueryInterval :: Property
+
propUtxoQueryByAddressAndQueryInterval = property $ do
+
highSlotNo <- forAll $ Gen.integral $ Range.constantFrom 7 5 20
+
chainPoints :: [C.ChainPoint] <- forAll $ genChainPoints 2 highSlotNo
+
events::[StorableEvent Utxo.UtxoHandle] <- forAll $ forM chainPoints genEventWithShelleyAddressAtChainPoint <&> concat
+
depth <- forAll $ Gen.int $ Range.constantFrom 5 1 20
indexer <- liftIO $ Utxo.open ":memory:" (Utxo.Depth depth) False -- don't vacuum sqlite
>>= liftIO . Storable.insertMany events
-
let (_start, _end) = ((highSlotNo `div` 2) - 1, highSlotNo `div` 2)
-
qInterval = Storable.QInterval (chainPoints !! _start)(chainPoints !! _end)
-
qAddresses :: [StorableQuery Utxo.UtxoHandle]
-
qAddresses = nub . fmap (Utxo.UtxoAddress . Utxo._address)
-
. concatMap (Set.toList . Utxo.ueUtxos) $ events
+
let _start :: C.ChainPoint = head chainPoints -- the generator will alwys provide a non empty list
+
_end :: C.ChainPoint = chainPoints !! (length chainPoints `div` 2)
+
qInterval = Storable.QInterval _start _end
+
= nub -- remove duplicate addresses
+
. fmap (Utxo.UtxoAddress . Utxo._address)
+
. concatMap (Set.toList . Utxo.ueUtxos)
results <- liftIO . traverse (Storable.query qInterval indexer) $ qAddresses
-
-- liftIO . print . length $ qs
let fetchedRows = concatMap (\(Utxo.UtxoResult rs) -> rs ) results
slotNoFromStorage = List.sort . fmap Utxo._urSlotNo $ fetchedRows
-
(C.ChainPoint sn _) = chainPoints !! (highSlotNo `div` 2)
-
last slotNoFromStorage === sn
+
endIntervalSlotNo = case _end of
+
C.ChainPointAtGenesis -> C.SlotNo 0
+
C.ChainPoint sn _ -> sn
+
Hedgehog.classify "SlotNo greater than 15" $ highSlotNo > 15
+
Hedgehog.classify "SlotNo greater than 10" $ highSlotNo > 10
+
Hedgehog.classify "Storage depth of 1" $ depth == 1
+
Hedgehog.classify "Storage depth > 5" $ depth > 10
+
last slotNoFromStorage === endIntervalSlotNo