diff --git a/src/Blockcore.Indexer.Angor/Controllers/MempoolSpaceController.cs b/src/Blockcore.Indexer.Angor/Controllers/MempoolSpaceController.cs index aa6525a..0a8a9c3 100644 --- a/src/Blockcore.Indexer.Angor/Controllers/MempoolSpaceController.cs +++ b/src/Blockcore.Indexer.Angor/Controllers/MempoolSpaceController.cs @@ -40,9 +40,9 @@ public IActionResult GetAddress([MinLength(4)][MaxLength(100)] string address) [HttpGet] [Route("address/{address}/txs")] - public async Task GetAddressTransactions(string address) + public async Task GetAddressTransactions(string address, string? after_txid = null) { - var transactions = storage.AddressHistory(address, null, 50).Items.Select(t => t.TransactionHash).ToList(); + List transactions = storage.GetAddressHistory(address,25, 50, after_txid).Items.Select(t => t.TransactionHash).ToList(); List txns = await storage.GetMempoolTransactionListAsync(transactions); return Ok(JsonSerializer.Serialize(txns, serializeOption)); } diff --git a/src/Blockcore.Indexer.Core/Storage/IStorage.cs b/src/Blockcore.Indexer.Core/Storage/IStorage.cs index 99d0297..937bcb9 100644 --- a/src/Blockcore.Indexer.Core/Storage/IStorage.cs +++ b/src/Blockcore.Indexer.Core/Storage/IStorage.cs @@ -22,6 +22,8 @@ Task> QuickBalancesLookupForAddressesWithHistoryCheckA QueryResult AddressHistory(string address, int? offset, int limit); + QueryResult GetAddressHistory(string address, int limit, int limitMempool, string after_txid = null); + Task> GetMempoolTransactionListAsync(List txids); QueryResult GetMemoryTransactionsSlim(int offset, int limit); diff --git a/src/Blockcore.Indexer.Core/Storage/Mongo/MongoData.cs b/src/Blockcore.Indexer.Core/Storage/Mongo/MongoData.cs index 801ad97..7109a9a 100644 --- a/src/Blockcore.Indexer.Core/Storage/Mongo/MongoData.cs +++ b/src/Blockcore.Indexer.Core/Storage/Mongo/MongoData.cs @@ -766,10 +766,87 @@ public QueryResult AddressHistory(string address, int? offset, Total = total }; } + public QueryResult GetAddressHistory(string address, int limit, int limitMempool, string after_txid = null) + { + AddressComputedTable addressComputedTable = ComputeAddressBalance(address); + var transaction = after_txid != null ? GetTransaction(after_txid) : new() + { + BlockIndex = 0 + }; + if (transaction == null) + { + return new QueryResult + { + Items = Enumerable.Empty(), + Offset = -1, + Limit = limit, + Total = 0 + }; + } + IQueryable filter = mongoDb.AddressHistoryComputedTable.AsQueryable() + .Where(t => t.Address == address); + + SyncBlockInfo storeTip = globalState.StoreTip; + if (storeTip == null) + { + // this can happen if node is in the middle of reorg + + return new QueryResult + { + Items = Enumerable.Empty(), + Offset = 0, + Limit = limit, + Total = 0 + }; + } + ; + + filter = filter.OrderBy(s => s.BlockIndex); + var list = filter.Where(w => w.BlockIndex >= transaction.BlockIndex).Take(limit).ToList(); + + // Loop all transaction IDs and get the transaction object. + IEnumerable transactions = list.Select(item => new QueryAddressItem + { + BlockIndex = item.BlockIndex, + Value = item.AmountInOutputs - item.AmountInInputs, + EntryType = item.EntryType, + TransactionHash = item.TransactionId, + Confirmations = storeTip.BlockIndex + 1 - item.BlockIndex + }); + + IEnumerable mempoolTransactions = null; + + List mempoolAddressBag = MempoolBalance(address); + + mempoolTransactions = mempoolAddressBag.Select(item => new QueryAddressItem + { + TransactionHash = item.Mempool.TransactionId, + BlockIndex = 0, + Value = item.AmountInOutputs - item.AmountInInputs, + EntryType = item.AmountInOutputs > item.AmountInInputs ? "receive" : "send", + }).Take(limitMempool); + List allTransactions = new(); + if (mempoolTransactions != null) + allTransactions.AddRange(mempoolTransactions); + + allTransactions.AddRange(transactions); + + return new QueryResult + { + Items = allTransactions, + Offset = -1, + Limit = limit, + Total = list.Count(), + }; + } public async Task> GetMempoolTransactionListAsync(List txids) { FilterDefinition filter = Builders.Filter.In(info => info.TransactionId, txids); + FilterDefinition filter_mempool = Builders.Filter.In(t => t.TransactionId, txids); + var mempool_trxsCursor = await mongoDb.Mempool.FindAsync(filter_mempool); + var mempool_trxs = await mempool_trxsCursor.ToListAsync(); + var trxsCursor = await mongoDb.TransactionBlockTable.FindAsync(filter); var trxs = await trxsCursor.ToListAsync(); @@ -793,7 +870,10 @@ public async Task> GetMempoolTransactionListAsync(List< var tasks = await Task.WhenAll(transactions.Select(async (transaction, index) => { var blk = blks[index]; - var outputsTasks = transactionItemsList[index].Inputs.Select(async input => await GetTransactionOutputAsync(input.PreviousTransactionHash, input.PreviousIndex)); + var outputsTasks = transactionItemsList[index].Inputs.Select(async input => + CheckCoinbaseInput(input.PreviousTransactionHash) + ? await GetTransactionOutputAsync(input.PreviousTransactionHash, input.PreviousIndex) + : new OutputTable()); var outputs = await Task.WhenAll(outputsTasks); return new MempoolTransaction @@ -814,10 +894,11 @@ public async Task> GetMempoolTransactionListAsync(List< Vin = transactionItemsList[index].Inputs.Select((input, inputIndex) => { OutputTable output = outputs[inputIndex]; + var coinbaseCheck = CheckCoinbaseInput(input.PreviousTransactionHash); return new Vin() { - IsCoinbase = input.InputCoinBase != null, - Prevout = new PrevOut() + IsCoinbase = coinbaseCheck, + Prevout = coinbaseCheck ? null : new PrevOut() { Value = output.Value, Scriptpubkey = output.ScriptHex, @@ -843,11 +924,79 @@ public async Task> GetMempoolTransactionListAsync(List< ScriptpubkeyAsm = null, }).ToList(), }; + })); + var transactionList = tasks.ToList(); + var mempoolList = await MapMempoolTableToMempoolTransaction(mempool_trxs); + transactionList.AddRange(mempoolList); + return transactionList; + } + private async Task> MapMempoolTableToMempoolTransaction(List transactions) + { + var tasks = await Task.WhenAll(transactions.Select(async (transaction, index) => + { + var outputsTasks = transaction.Inputs.Select(async input => + CheckCoinbaseInput(input.Outpoint.TransactionId) + ? await GetTransactionOutputAsync(input.Outpoint.TransactionId, input.Outpoint.OutputIndex) + : new OutputTable()); + var outputs = await Task.WhenAll(outputsTasks); + // The unavailable fields are set to -1 temporarily + return new MempoolTransaction + { + Txid = transaction.TransactionId, + Version = -1, + Locktime = -1, + Size = -1, + Weight = -1, + Fee = -1, + Status = new() + { + Confirmed = false, + BlockHeight = -1, + BlockHash = null, + BlockTime = -1, + }, + Vin = transaction.Inputs.Select((input, inputIndex) => + { + OutputTable output = outputs[inputIndex]; + var coinbaseCheck = CheckCoinbaseInput(input.Outpoint.TransactionId); + return new Vin() + { + IsCoinbase = coinbaseCheck, + Prevout = coinbaseCheck ? null : new PrevOut() + { + Value = output.Value, + Scriptpubkey = output.ScriptHex, + ScriptpubkeyAddress = output.Address, + ScriptpubkeyAsm = null, + ScriptpubkeyType = null + }, + Scriptsig = null, + Asm = null, + Sequence = -1, + Txid = input.Outpoint.TransactionId, + Vout = input.Outpoint.OutputIndex, + Witness = null, + InnserRedeemscriptAsm = null, + InnerWitnessscriptAsm = null + }; + }).ToList(), + Vout = transaction.Outputs.Select(output => new PrevOut() + { + Value = output.Value, + Scriptpubkey = output.ScriptHex, + ScriptpubkeyAddress = output.Address, + ScriptpubkeyAsm = null, + }).ToList(), + }; } )); return tasks.ToList(); } + private bool CheckCoinbaseInput(string inputPrevHash) + { + return inputPrevHash == "0000000000000000000000000000000000000000000000000000000000000000"; + } private long TryParseSequenceLock(string sequenceLock) { if (long.TryParse(sequenceLock, out long result))