Skip to content

Commit fb1be50

Browse files
authored
fix: Infinite loop (#464)
1 parent d61cdc2 commit fb1be50

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

  • src/services/blockchain/clients/stellar
  • tests/integration/blockchain/clients/stellar

src/services/blockchain/clients/stellar/client.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ impl<T: Send + Sync + Clone + BlockchainTransport> BlockChainClient for StellarC
516516
let mut blocks = Vec::new();
517517
let target_block = end_block.unwrap_or(start_block);
518518
let mut cursor: Option<String> = None;
519+
let mut previous_cursor: Option<String> = None;
519520
let mut current_iteration = 0;
520521

521522
while cursor.is_some() || current_iteration <= 0 {
@@ -606,10 +607,11 @@ impl<T: Send + Sync + Clone + BlockchainTransport> BlockChainClient for StellarC
606607
.as_str()
607608
.map(|s| s.to_string());
608609

609-
// If the cursor is the same as the start block, we have reached the end of the range
610-
if cursor == Some(start_block.to_string()) {
610+
// Break if the cursor did not advance (stalled pagination) or is absent
611+
if cursor == previous_cursor {
611612
break;
612613
}
614+
previous_cursor = cursor.clone();
613615

614616
if cursor.is_none() {
615617
break;

tests/integration/blockchain/clients/stellar/client.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,73 @@ async fn test_get_blocks_pagination() {
335335
second_mock.assert_async().await;
336336
}
337337

338+
/// Regression test: cursor equal to start_block must NOT terminate pagination early.
339+
#[tokio::test]
340+
async fn test_get_blocks_cursor_equal_start_block_still_paginates() {
341+
let mut server = Server::new_async().await;
342+
let mock = create_stellar_valid_server_mock_network_response(&mut server);
343+
let network = create_stellar_test_network_with_urls(vec![&server.url()]);
344+
345+
// Page 1: returns one ledger at sequence 10, cursor equals start_block ("10")
346+
let first_response = json!({
347+
"result": {
348+
"ledgers": [{
349+
"hash": "aaa",
350+
"sequence": 10,
351+
"ledgerCloseTime": "1734715051",
352+
"headerXdr": "AAA",
353+
"metadataXdr": "BBB"
354+
}],
355+
"cursor": "10"
356+
}
357+
});
358+
359+
// Page 2: returns the next ledger, pagination ends (null cursor)
360+
let second_response = json!({
361+
"result": {
362+
"ledgers": [{
363+
"hash": "bbb",
364+
"sequence": 11,
365+
"ledgerCloseTime": "1734715052",
366+
"headerXdr": "AAA",
367+
"metadataXdr": "BBB"
368+
}],
369+
"cursor": null
370+
}
371+
});
372+
373+
let first_mock = server
374+
.mock("POST", "/")
375+
.with_status(200)
376+
.with_body(first_response.to_string())
377+
.create_async()
378+
.await;
379+
380+
let second_mock = server
381+
.mock("POST", "/")
382+
.with_status(200)
383+
.with_body(second_response.to_string())
384+
.create_async()
385+
.await;
386+
387+
let client = StellarClient::new(&network).await.unwrap();
388+
let result = client.get_blocks(10, Some(11)).await.unwrap();
389+
390+
assert_eq!(result.len(), 2, "expected both pages to be fetched");
391+
match &result[0] {
392+
BlockType::Stellar(block) => assert_eq!(block.sequence, 10),
393+
_ => panic!("Expected Stellar block"),
394+
}
395+
match &result[1] {
396+
BlockType::Stellar(block) => assert_eq!(block.sequence, 11),
397+
_ => panic!("Expected Stellar block"),
398+
}
399+
400+
mock.assert_async().await;
401+
first_mock.assert_async().await;
402+
second_mock.assert_async().await;
403+
}
404+
338405
#[tokio::test]
339406
async fn test_get_contract_spec() {
340407
let mut server = Server::new_async().await;

0 commit comments

Comments
 (0)