Skip to content

Commit 0e84bce

Browse files
authored
Merge pull request #5356 from IntersectMBO/karknu/tx_tests
cardano-diffusion: tx integrity test
2 parents 132a7f8 + 8d13659 commit 0e84bce

10 files changed

Lines changed: 656 additions & 18 deletions

File tree

cardano-diffusion/cardano-diffusion.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ library cardano-diffusion-tests-lib
512512
exposed-modules:
513513
Test.Cardano.Network.Diffusion.Policies
514514
Test.Cardano.Network.Diffusion.Testnet
515+
Test.Cardano.Network.Diffusion.Testnet.ChainedTxs
515516
Test.Cardano.Network.Diffusion.Testnet.MiniProtocols
516517
Test.Cardano.Network.Diffusion.Testnet.Simulation
517518
Test.Cardano.Network.OrphanInstances.Tests
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
### Non-Breaking
2+
3+
- Add test for testing V2 tx-submission when faced with option to download TXs in different
4+
orders from different peers.
5+

cardano-diffusion/tests/lib/Test/Cardano/Network/Diffusion/Testnet.hs

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ import Ouroboros.Network.TxSubmission.Outbound (TxSubmissionProtocolError (..))
9797

9898
import Simulation.Network.Snocket (BearerInfo (..), noAttenuation)
9999

100+
import Test.Cardano.Network.Diffusion.Testnet.ChainedTxs (ChainedPeerTxs (..))
101+
import Test.Cardano.Network.Diffusion.Testnet.ChainedTxs qualified as ChainedTxs
100102
import Test.Cardano.Network.Diffusion.Testnet.Simulation
101103

102104
import Test.Ouroboros.Network.ConnectionManager.Timeouts
@@ -179,6 +181,8 @@ tests =
179181
, testGroup "Tx Submission"
180182
[ nightlyTest $ testProperty "no protocol errors"
181183
prop_no_txSubmission_error_iosimpor
184+
, nightlyTest $ testProperty "tx chain integrity"
185+
prop_txSubmission_chainIntegrity_iosimpor
182186
]
183187
, testGroup "Churn"
184188
[ nightlyTest $ testProperty "no timeouts"
@@ -265,12 +269,15 @@ tests =
265269
prop_no_peershare_unwilling_iosim
266270
]
267271
, testGroup "Tx Submission"
268-
[ testProperty "no protocol errors"
272+
[ ChainedTxs.tests
273+
, testProperty "no protocol errors"
269274
prop_no_txSubmission_error_iosim
270275
, testProperty "all transactions"
271276
prop_txSubmission_allTransactions
272277
, testProperty "inflight coverage"
273278
prop_check_inflight_ratio
279+
, testProperty "tx chain integrity"
280+
prop_txSubmission_chainIntegrity_iosim
274281
]
275282
, testGroup "Churn"
276283
[ testProperty "no timeouts" prop_churn_notimeouts_iosim
@@ -1080,6 +1087,215 @@ prop_txSubmission_allTransactions (ArbTxDecisionPolicy decisionPolicy)
10801087
| otherwise -> counterexample "Didn't found any entry in the map!" False
10811088

10821089

1090+
-- | Three-node diffusion topology used by the Tx Chain Integrity
1091+
-- property: one node (0.0.0.0) downloading txs from two downstream
1092+
-- peers (0.0.0.1 and 0.0.0.2). The node has no outbound txs of its own;
1093+
-- each downstream peer advertises the tx list it was assigned by
1094+
-- 'ChainedPeerTxs'.
1095+
txChainIntegrityDiffScript :: ArbTxDecisionPolicy
1096+
-> ChainedPeerTxs
1097+
-> DiffusionScript
1098+
txChainIntegrityDiffScript (ArbTxDecisionPolicy decisionPolicy)
1099+
(ChainedPeerTxs chainedTxsB chainedTxsC) =
1100+
let localRootConfig = LocalRootConfig
1101+
DoNotAdvertisePeer
1102+
InitiatorAndResponderDiffusionMode
1103+
Outbound
1104+
IsNotTrustable
1105+
1106+
noPeerTargets = PeerSelectionTargets {
1107+
targetNumberOfRootPeers = 0,
1108+
targetNumberOfKnownPeers = 0,
1109+
targetNumberOfEstablishedPeers = 0,
1110+
targetNumberOfActivePeers = 0,
1111+
targetNumberOfKnownBigLedgerPeers = 0,
1112+
targetNumberOfEstablishedBigLedgerPeers = 0,
1113+
targetNumberOfActiveBigLedgerPeers = 0
1114+
}
1115+
1116+
upstreamTargets = PeerSelectionTargets {
1117+
targetNumberOfRootPeers = 1,
1118+
targetNumberOfKnownPeers = 1,
1119+
targetNumberOfEstablishedPeers = 1,
1120+
targetNumberOfActivePeers = 1,
1121+
targetNumberOfKnownBigLedgerPeers = 0,
1122+
targetNumberOfEstablishedBigLedgerPeers = 0,
1123+
targetNumberOfActiveBigLedgerPeers = 0
1124+
}
1125+
1126+
in DiffusionScript
1127+
(SimArgs 1 10 decisionPolicy)
1128+
(singletonTimedScript Map.empty)
1129+
[ ( NodeArgs
1130+
(-1)
1131+
InitiatorAndResponderDiffusionMode
1132+
Map.empty
1133+
PraosMode
1134+
(Script (DontUseBootstrapPeers :| []))
1135+
(TestAddress (IPAddr (read "0.0.0.0") 0))
1136+
PeerSharingDisabled
1137+
[]
1138+
(Script (LedgerPools [] :| []))
1139+
(noPeerTargets, noPeerTargets)
1140+
(Script (DNSTimeout {getDNSTimeout = 10} :| []))
1141+
(Script (DNSLookupDelay {getDNSLookupDelay = 0} :| []))
1142+
Nothing
1143+
False
1144+
(Script (PraosFetchMode FetchModeDeadline :| []))
1145+
[]
1146+
, [JoinNetwork 0] )
1147+
, ( NodeArgs
1148+
(-2)
1149+
InitiatorAndResponderDiffusionMode
1150+
Map.empty
1151+
PraosMode
1152+
(Script (DontUseBootstrapPeers :| []))
1153+
(TestAddress (IPAddr (read "0.0.0.1") 0))
1154+
PeerSharingDisabled
1155+
[(1, 1, Map.fromList
1156+
[(RelayAccessAddress "0.0.0.0" 0, localRootConfig)])]
1157+
(Script (LedgerPools [] :| []))
1158+
(upstreamTargets, upstreamTargets)
1159+
(Script (DNSTimeout {getDNSTimeout = 10} :| []))
1160+
(Script (DNSLookupDelay {getDNSLookupDelay = 0} :| []))
1161+
Nothing
1162+
False
1163+
(Script (PraosFetchMode FetchModeDeadline :| []))
1164+
chainedTxsB
1165+
, [JoinNetwork 0] )
1166+
, ( NodeArgs
1167+
(-3)
1168+
InitiatorAndResponderDiffusionMode
1169+
Map.empty
1170+
PraosMode
1171+
(Script (DontUseBootstrapPeers :| []))
1172+
(TestAddress (IPAddr (read "0.0.0.2") 0))
1173+
PeerSharingDisabled
1174+
[(1, 1, Map.fromList
1175+
[(RelayAccessAddress "0.0.0.0" 0, localRootConfig)])]
1176+
(Script (LedgerPools [] :| []))
1177+
(upstreamTargets, upstreamTargets)
1178+
(Script (DNSTimeout {getDNSTimeout = 10} :| []))
1179+
(Script (DNSLookupDelay {getDNSLookupDelay = 0} :| []))
1180+
Nothing
1181+
False
1182+
(Script (PraosFetchMode FetchModeDeadline :| []))
1183+
chainedTxsC
1184+
, [JoinNetwork 0] )
1185+
]
1186+
1187+
-- | Txs guaranteed to reach the node's mempool: at least one downstream
1188+
-- peer carries the tx together with all of its ancestors, and every
1189+
-- member of that chain is valid. Txs whose chain isn't fully represented
1190+
-- on either downstream peer are legitimately unreachable and correctly
1191+
-- excluded from this lower bound. V2 may deliver additional txs when a
1192+
-- split chain is assembled across downstream peers via favourable
1193+
-- ordering, but those are not guaranteed.
1194+
txChainIntegrityExpected :: ChainedPeerTxs -> Set TxId
1195+
txChainIntegrityExpected (ChainedPeerTxs chainedTxsB chainedTxsC) =
1196+
Set.union
1197+
(perPeerDeliverable chainedTxsB)
1198+
(perPeerDeliverable chainedTxsC)
1199+
where
1200+
perPeerDeliverable :: [Tx TxId] -> Set TxId
1201+
perPeerDeliverable peerTxs =
1202+
let txMap = Map.fromList [(getTxId t, t) | t <- peerTxs]
1203+
complete tid = case Map.lookup tid txMap of
1204+
Nothing -> False
1205+
Just t -> getTxValid t
1206+
&& maybe True complete (getTxParent t) in
1207+
Set.fromList [ getTxId t | t <- peerTxs, complete (getTxId t) ]
1208+
1209+
checkTxChainIntegrity :: forall r.
1210+
ChainedPeerTxs
1211+
-> Set TxId
1212+
-> SimTrace r
1213+
-> Int
1214+
-> Property
1215+
checkTxChainIntegrity (ChainedPeerTxs chainedTxsB chainedTxsC)
1216+
expectedAtReceiver
1217+
ioSimTrace
1218+
traceNumber =
1219+
let trace = Trace.take traceNumber ioSimTrace
1220+
1221+
events = fmap (\(WithTime t (WithName name b)) ->
1222+
WithName name (WithTime t b))
1223+
. withTimeNameTraceEvents
1224+
@DiffusionTestTrace
1225+
@NtNAddr
1226+
$ trace
1227+
1228+
sortedAcceptedTxidsMap :: Map NtNAddr [TxId]
1229+
sortedAcceptedTxidsMap =
1230+
foldr (\l r ->
1231+
List.foldl' (\rr (WithName n (WithTime _ x)) ->
1232+
case x of
1233+
DiffusionTxSubmissionInbound (TraceTxInboundAddedToMempool txids _) ->
1234+
Map.alter (maybe (Just txids) (Just . sort . (txids ++))) n rr
1235+
_ -> rr) r l
1236+
) Map.empty
1237+
. Trace.toList
1238+
. splitWithNameTrace
1239+
$ events
1240+
1241+
receiverAddr = TestAddress (IPAddr (read "0.0.0.0") 0)
1242+
accepted = Map.lookup receiverAddr sortedAcceptedTxidsMap
1243+
actualSet = maybe Set.empty Set.fromList accepted
1244+
missing = expectedAtReceiver `Set.difference` actualSet in
1245+
1246+
counterexample ("accepted txids map: " ++ show sortedAcceptedTxidsMap)
1247+
$ counterexample ("downstream peer B outbound: " ++ show chainedTxsB)
1248+
$ counterexample ("downstream peer C outbound: " ++ show chainedTxsC)
1249+
$ counterexample ("expected (reliably deliverable): "
1250+
++ show (Set.toList expectedAtReceiver))
1251+
$ counterexample ("missing from node: " ++ show (Set.toList missing))
1252+
$ label ("expected count: "
1253+
++ renderRanges 5 (Set.size expectedAtReceiver))
1254+
$ label ("accepted count: "
1255+
++ renderRanges 5 (Set.size actualSet))
1256+
$ counterexample "node (0.0.0.0)"
1257+
$ property (Set.null missing)
1258+
1259+
-- | Tx Chain Integrity property: the node's mempool accumulates every
1260+
-- transaction reachable via a complete, valid ancestor chain on at least
1261+
-- one of its downstream peers.
1262+
--
1263+
-- This exercises V2's cross-peer retry path: if an adversarial downstream
1264+
-- peer delivers a child out-of-order and the mempool rejects with
1265+
-- 'MissingParent', the tx must still reach the node via the well-behaved
1266+
-- downstream peer's re-advertisement.
1267+
prop_txSubmission_chainIntegrity :: ArbTxDecisionPolicy
1268+
-> ChainedPeerTxs
1269+
-> Property
1270+
prop_txSubmission_chainIntegrity argPolicy chainedTxs =
1271+
let diffScript = txChainIntegrityDiffScript argPolicy chainedTxs
1272+
expected = txChainIntegrityExpected chainedTxs in
1273+
checkTxChainIntegrity
1274+
chainedTxs
1275+
expected
1276+
(runSimTrace (diffusionSimulation noAttenuation diffScript))
1277+
long_trace
1278+
1279+
prop_txSubmission_chainIntegrity_iosimpor :: ArbTxDecisionPolicy
1280+
-> ChainedPeerTxs
1281+
-> Property
1282+
prop_txSubmission_chainIntegrity_iosimpor argPolicy chainedTxs =
1283+
let diffScript = txChainIntegrityDiffScript argPolicy chainedTxs
1284+
expected = txChainIntegrityExpected chainedTxs
1285+
sim :: forall s. IOSim s DiffSimResult
1286+
sim = do
1287+
exploreRaces
1288+
diffusionSimulation noAttenuation diffScript
1289+
in labelDiffusionScript diffScript
1290+
$ exploreSimTrace (\a -> a { explorationScheduleBound = 10 }) sim $ \_ trace ->
1291+
checkTxChainIntegrity chainedTxs expected trace long_trace
1292+
1293+
prop_txSubmission_chainIntegrity_iosim :: ArbTxDecisionPolicy
1294+
-> ChainedPeerTxs
1295+
-> Property
1296+
prop_txSubmission_chainIntegrity_iosim = prop_txSubmission_chainIntegrity
1297+
1298+
10831299
-- | This test checks the ratio of the inflight txs against the allowed by the
10841300
-- TxDecisionPolicy.
10851301
--

0 commit comments

Comments
 (0)