Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions issues/issue-1-pagination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

**Title:** Fix missing pagination in FetchAddressHistoryAsync

**Body:**
Hey, I noticed a TODO in `MempoolSpaceIndexerApi.cs` regarding the default paging limit for the mempool api.

Right now `FetchAddressHistoryAsync` only grabs the first page (50 txs) and doesn't loop to get the rest. If an address has more than 50 transactions, we're missing the older history.

I can fix this by adding a loop to fetch subsequent pages using the `after_txid` param until we get a set with fewer than 50 items.

Unassigning myself if anyone else wants to grab this, otherwise I'll push a PR shortly.
12 changes: 12 additions & 0 deletions issues/pr-1-pagination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

**Title:** fix: handle pagination in mempool api history

**Body:**
Closes #1

Implemented the loop to fetch all pages of transactions for an address.
Previously it would stop after the first 50 results (header limit).

Tested locally with an address having >50 txs and verified it pulls the full history now.

Let me know if the while loop logic looks good.
39 changes: 27 additions & 12 deletions src/Angor/Shared/Services/MempoolSpaceIndexerApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,39 @@ public async Task<AddressBalance[]> GetAdressBalancesAsync(List<AddressInfo> dat
return utxoDataList;
}

public async Task<List<QueryTransaction>?> FetchAddressHistoryAsync(string address, string? afterTrxId = null) //TODO check the paging (I think it is 50 by default
public async Task<List<QueryTransaction>?> FetchAddressHistoryAsync(string address, string? afterTrxId = null)
{
var txsUrl = $"{MempoolApiRoute}/address/{address}/txs";
var allTransactions = new List<QueryTransaction>();
string? currentAfterTxId = afterTrxId;

if (!string.IsNullOrEmpty(afterTrxId))
txsUrl += $"?after_txid={afterTrxId}";
while (true)
{
var txsUrl = $"{MempoolApiRoute}/address/{address}/txs";

var response = await GetIndexerClient()
.GetAsync(txsUrl);
_networkService.CheckAndHandleError(response);
if (!string.IsNullOrEmpty(currentAfterTxId))
txsUrl += $"?after_txid={currentAfterTxId}";

if (!response.IsSuccessStatusCode)
throw new InvalidOperationException(response.ReasonPhrase);
var response = await GetIndexerClient().GetAsync(txsUrl);
_networkService.CheckAndHandleError(response);

var trx = await response.Content.ReadFromJsonAsync<List<MempoolTransaction>>(new JsonSerializerOptions()
{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
if (!response.IsSuccessStatusCode)
throw new InvalidOperationException(response.ReasonPhrase);

var trx = await response.Content.ReadFromJsonAsync<List<MempoolTransaction>>(new JsonSerializerOptions()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonSerializerOptions re-allocated every iteration. It should be a static readonly

{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });

if (trx == null || !trx.Any())
break;

allTransactions.AddRange(trx.Select(t => MapToQueryTransaction(t)));

if (trx.Count < 50)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No safety guard against infinite loops.
Make this happen max 10 times

break; // We've reached the last page

currentAfterTxId = trx.Last().Txid;
}

return trx?.Select(t => MapToQueryTransaction(t)).ToList() ?? new List<QueryTransaction>();
return allTransactions;
}

public async Task<FeeEstimations?> GetFeeEstimationAsync(int[] confirmations)
Expand Down