Skip to content

Commit be9afc6

Browse files
committed
fixup: fix tx submission order
When picking TXs to submit to the mempool stop at gaps. When picking TXs to download use the peer's advertisement order as a guide, not the TxKey order.
1 parent cddef43 commit be9afc6

2 files changed

Lines changed: 65 additions & 28 deletions

File tree

  • ouroboros-network
    • lib/Ouroboros/Network/TxSubmission/Inbound/V2
    • tests/lib/Test/Ouroboros/Network/TxSubmission

ouroboros-network/lib/Ouroboros/Network/TxSubmission/Inbound/V2/State.hs

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -277,18 +277,33 @@ pickSubmitAction PeerActionContext { pacPeerAddr, pacPeerState, pacSharedState }
277277
else Just txsToSubmit
278278
where
279279

280-
-- Filters the unacknowledged txid queue for bodies buffered by this peer
281-
-- that are not currently being submitted by another advertiser.
282-
-- Returns the list of tx keys ready for immediate submission in the order they
283-
-- were originally advertised by the peer.
284-
pickBufferedTxsToSubmit =
285-
[ txKey
286-
| txKey@(TxKey k) <- toList (peerUnacknowledgedTxIds pacPeerState)
287-
, IntMap.member k (peerDownloadedTxs pacPeerState)
288-
, Just txEntry <- [IntMap.lookup k (sharedTxTable pacSharedState)]
289-
, txBufferedByPeer pacPeerAddr txEntry
290-
, not (txSubmittingByOther pacPeerAddr txEntry)
291-
]
280+
-- Walk the unacknowledged txid queue in peer advertisement order, picking
281+
-- bodies buffered by this peer for immediate submission. Stop at the
282+
-- first tx that is unresolved and not available from this peer: later
283+
-- txs in the peer's stream must not run ahead of earlier ones, otherwise
284+
-- a tx may be offered to the mempool before a transaction it depends on
285+
-- is confirmed. Txs already resolved elsewhere (present in
286+
-- 'sharedRetainedTxs') are skipped over since no further action is
287+
-- needed for them.
288+
pickBufferedTxsToSubmit = go [] (toList (peerUnacknowledgedTxIds pacPeerState))
289+
where
290+
go acc [] = reverse acc
291+
go acc (txKey@(TxKey k) : rest) =
292+
case IntMap.lookup k (sharedTxTable pacSharedState) of
293+
Just txEntry
294+
| IntMap.member k (peerDownloadedTxs pacPeerState)
295+
, txBufferedByPeer pacPeerAddr txEntry
296+
, not (txSubmittingByOther pacPeerAddr txEntry) ->
297+
go (txKey : acc) rest
298+
_ | retainedMember k (sharedRetainedTxs pacSharedState) ->
299+
-- already resolved via another peer
300+
go acc rest
301+
_ | not (IntMap.member k (peerAvailableTxIds pacPeerState))
302+
, not (IntMap.member k (peerDownloadedTxs pacPeerState)) ->
303+
-- we have already finished with this tx (previously
304+
-- submitted, or never had a body to submit)
305+
go acc rest
306+
_ -> reverse acc
292307

293308
-- | Select transactions to request from the peer, if within policy limits.
294309
--
@@ -325,18 +340,22 @@ pickRequestTxsAction ctx@PeerActionContext { pacNow, pacPolicy, pacPeerState, pa
325340

326341
leaseUntil = addTime (interTxSpace pacPolicy) pacNow
327342

328-
-- We pick which TXs to download based on TxKey in ascending order.
329-
-- This makes it likely (but not guaranteed) that we end up downloading
330-
-- TXs in the order the peer presented them to us.
343+
-- Iterate the peer's unacknowledged queue, which preserves the peer's
344+
-- advertisement order. Peers are expected to advertise in chain-
345+
-- topological order (parents before children), so walking in that
346+
-- order aligns fetch order with submission-validity order and a
347+
-- child is never requested ahead of its parent when the same peer
348+
-- carries both.
331349
candidates =
332350
[ (k, txSize)
333-
| (k, txSize) <- IntMap.toAscList (peerAvailableTxIds pacPeerState)
351+
| TxKey k <- toList (peerUnacknowledgedTxIds pacPeerState)
334352
, IntSet.notMember k (peerRequestedTxs pacPeerState)
335353
, IntMap.notMember k (peerDownloadedTxs pacPeerState)
354+
, Just txSize <- [IntMap.lookup k (peerAvailableTxIds pacPeerState)]
336355
]
337356

338-
-- Select transactions to request by iterating through candidates in ascending
339-
-- key order until the size budget is consumed.
357+
-- Select transactions to request by iterating through candidates in
358+
-- peer advertisement order until the size budget is consumed.
340359
go selectedRev selectedSize txTable [] = (reverse selectedRev, selectedSize, txTable)
341360
go selectedRev selectedSize txTable ((k, txSize) : rest) =
342361
if exceedsBudget selectedSize txSize

ouroboros-network/tests/lib/Test/Ouroboros/Network/TxSubmission/TxLogic.hs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,7 @@ prop_nextPeerAction_claimsClaimableTx
14381438
-> Property
14391439
prop_nextPeerAction_claimsClaimableTx (Positive peerA0) (Positive peerB0) (Positive peerC0) txid0 txSize0 =
14401440
distinctPeers ==>
1441+
peerTxLocalStateInvariant peerState0 .&&.
14411442
case peerAction of
14421443
PeerRequestTxs txKeys ->
14431444
conjoin
@@ -1474,8 +1475,11 @@ prop_nextPeerAction_claimsClaimableTx (Positive peerA0) (Positive peerB0) (Posit
14741475
, txAttempts = Map.empty
14751476
}
14761477
}
1477-
peerState0 = emptyPeerTxLocalState { peerAvailableTxIds = IntMap.singleton k txSize
1478-
, peerScore = peerAScore }
1478+
peerState0 = emptyPeerTxLocalState
1479+
{ peerUnacknowledgedTxIds = StrictSeq.singleton key
1480+
, peerAvailableTxIds = IntMap.singleton k txSize
1481+
, peerScore = peerAScore
1482+
}
14791483
(peerAction, peerState', sharedState') = nextPeerAction now defaultTxDecisionPolicy peerA peerState0 sharedState0
14801484

14811485
unit_nextPeerAction_claimsAtScoreDelayThreshold :: (String -> IO ()) -> Assertion
@@ -1510,8 +1514,11 @@ unit_nextPeerAction_claimsAtScoreDelayThreshold step = do
15101514
, txAttempts = Map.empty
15111515
}
15121516
}
1513-
peerState0 = emptyPeerTxLocalState { peerAvailableTxIds = IntMap.singleton k txSize
1514-
, peerScore = PeerScore 20 now }
1517+
peerState0 = emptyPeerTxLocalState
1518+
{ peerUnacknowledgedTxIds = StrictSeq.singleton key
1519+
, peerAvailableTxIds = IntMap.singleton k txSize
1520+
, peerScore = PeerScore 20 now
1521+
}
15151522
(peerAction, peerState', sharedState') =
15161523
nextPeerAction now defaultTxDecisionPolicy peeraddr peerState0 sharedState0
15171524

@@ -1600,6 +1607,7 @@ prop_nextPeerAction_claimsExpiredLease
16001607
-> Property
16011608
prop_nextPeerAction_claimsExpiredLease (Positive oldOwner0) (Positive peerA0) (Positive peerB0) txid0 txSize0 =
16021609
distinctPeers ==>
1610+
peerTxLocalStateInvariant peerState0 .&&.
16031611
case peerAction of
16041612
PeerRequestTxs txKeys ->
16051613
conjoin
@@ -1636,8 +1644,11 @@ prop_nextPeerAction_claimsExpiredLease (Positive oldOwner0) (Positive peerA0) (P
16361644
, txAttempts = Map.empty
16371645
}
16381646
}
1639-
peerState0 = emptyPeerTxLocalState { peerAvailableTxIds = IntMap.singleton k txSize
1640-
, peerScore = peerAScore }
1647+
peerState0 = emptyPeerTxLocalState
1648+
{ peerUnacknowledgedTxIds = StrictSeq.singleton key
1649+
, peerAvailableTxIds = IntMap.singleton k txSize
1650+
, peerScore = peerAScore
1651+
}
16411652
(peerAction, peerState', sharedState') = nextPeerAction now defaultTxDecisionPolicy peerA peerState0 sharedState0
16421653

16431654
-- Verifies that nextPeerAction still requests an oversized first tx when it
@@ -1648,6 +1659,7 @@ prop_nextPeerAction_requestsOversizedFirstTx
16481659
-> Positive Int
16491660
-> Property
16501661
prop_nextPeerAction_requestsOversizedFirstTx (Positive peeraddr) txid0 (Positive txSize0) =
1662+
peerTxLocalStateInvariant peerState0 .&&.
16511663
case peerAction of
16521664
PeerRequestTxs [txKey] ->
16531665
conjoin
@@ -1678,7 +1690,8 @@ prop_nextPeerAction_requestsOversizedFirstTx (Positive peeraddr) txid0 (Positive
16781690
, sharedTxTable = IntMap.singleton k (mkTxEntry peeraddr txSize Nothing)
16791691
}
16801692
peerState0 = emptyPeerTxLocalState
1681-
{ peerAvailableTxIds = IntMap.singleton k txSize
1693+
{ peerUnacknowledgedTxIds = StrictSeq.singleton key
1694+
, peerAvailableTxIds = IntMap.singleton k txSize
16821695
, peerRequestedTxIds = maxNumTxIdsToRequest policy
16831696
}
16841697
(peerAction, peerState', sharedState') = nextPeerAction now policy peeraddr peerState0 sharedState0
@@ -1707,7 +1720,8 @@ unit_nextPeerAction_skipsBlockedAvailableTxs step = do
17071720
kBlocked = 1
17081721
kClaimable = 2
17091722
peerState = emptyPeerTxLocalState
1710-
{ peerAvailableTxIds = IntMap.fromList [(kBlocked, 10), (kClaimable, 11)]
1723+
{ peerUnacknowledgedTxIds = StrictSeq.fromList [blockedKey, claimableKey]
1724+
, peerAvailableTxIds = IntMap.fromList [(kBlocked, 10), (kClaimable, 11)]
17111725
}
17121726
sharedState :: SharedTxState PeerAddr TxId
17131727
sharedState = emptySharedTxState
@@ -2117,6 +2131,7 @@ prop_nextPeerActionPipelined_secondBodyBatch
21172131
-> Property
21182132
prop_nextPeerActionPipelined_secondBodyBatch (Positive peeraddr) txidA0 txidB0 txSizeA0 txSizeB0 =
21192133
txidA /= txidB ==>
2134+
peerTxLocalStateInvariant peerState0 .&&.
21202135
case peerAction of
21212136
PeerRequestTxs [txKey] ->
21222137
conjoin
@@ -2141,7 +2156,8 @@ prop_nextPeerActionPipelined_secondBodyBatch (Positive peeraddr) txidA0 txidB0 t
21412156
kA = unTxKey keyA
21422157
kB = unTxKey keyB
21432158
peerState0 = emptyPeerTxLocalState
2144-
{ peerAvailableTxIds = IntMap.singleton kB txSizeB
2159+
{ peerUnacknowledgedTxIds = StrictSeq.fromList [keyA, keyB]
2160+
, peerAvailableTxIds = IntMap.fromList [(kA, txSizeA), (kB, txSizeB)]
21452161
, peerRequestedTxs = IntSet.singleton kA
21462162
, peerRequestedTxBatches = StrictSeq.singleton (mkRequestedTxBatch [keyA] txSizeA)
21472163
, peerRequestedTxsSize = txSizeA
@@ -2177,6 +2193,7 @@ prop_nextPeerActionPipelined_noThirdBodyBatch
21772193
-> Property
21782194
prop_nextPeerActionPipelined_noThirdBodyBatch (Positive peeraddr) txidA0 txidB0 txidC0 txSizeA0 txSizeB0 txSizeC0 =
21792195
distinctTxIds ==>
2196+
peerTxLocalStateInvariant peerState0 .&&.
21802197
case peerAction of
21812198
PeerDoNothing _ _ ->
21822199
conjoin
@@ -2201,7 +2218,8 @@ prop_nextPeerActionPipelined_noThirdBodyBatch (Positive peeraddr) txidA0 txidB0
22012218
kB = unTxKey keyB
22022219
kC = unTxKey keyC
22032220
peerState0 = emptyPeerTxLocalState
2204-
{ peerAvailableTxIds = IntMap.singleton kC txSizeC
2221+
{ peerUnacknowledgedTxIds = StrictSeq.fromList [keyA, keyB, keyC]
2222+
, peerAvailableTxIds = IntMap.fromList [(kA, txSizeA), (kB, txSizeB), (kC, txSizeC)]
22052223
, peerRequestedTxs = IntSet.fromList [kA, kB]
22062224
, peerRequestedTxBatches = StrictSeq.fromList
22072225
[ mkRequestedTxBatch [keyA] txSizeA

0 commit comments

Comments
 (0)