public IActionResult GetBalancesOverAmount(int blockHeight, decimal amount) { Money thresholdBalance = Money.Coins(amount); CoinviewContext coinView = GetCoinviewAtHeight(blockHeight); Dictionary <Script, Money> balances = GetBalances(coinView); List <Script> addressesWithThreshold = balances .Where(x => x.Value >= thresholdBalance) .Select(x => x.Key) .ToList(); List <string> base58Addresses = new List <string>(); foreach (Script script in addressesWithThreshold) { GetSenderResult senderResult = this.GetAddressFromScript(script); if (senderResult.Success) { base58Addresses.Add(senderResult.Sender.ToBase58Address(this.network)); } else { this.logger.LogWarning($"{script} has a balance of {balances[script]} but is not a P2PK or P2PKH."); } } return(Json(base58Addresses)); }
private void AdjustCoinviewForBlock(Block block, CoinviewContext coinView) { foreach (Transaction tx in block.Transactions) { // Add outputs for (int i = 0; i < tx.Outputs.Count; i++) { TxOut output = tx.Outputs[i]; if (output.Value > 0) { coinView.UnspentOutputs.Add(new OutPoint(tx, i)); coinView.Transactions[tx.GetHash()] = tx; } } // Spend inputs if (!tx.IsCoinBase) { foreach (TxIn txIn in tx.Inputs) { if (!coinView.UnspentOutputs.Remove(txIn.PrevOut)) { throw new Exception("Spending PrevOut that isn't in Coinview at this time."); } } } } }
private CoinviewContext GetCoinviewAtHeight(int blockHeight) { var coinView = new CoinviewContext(); const int batchSize = 1000; IEnumerable <ChainedHeader> allBlockHeaders = this.chainIndexer.EnumerateToTip(this.chainIndexer.Genesis); int totalBlocksCounted = 0; int i = 0; while (true) { List <ChainedHeader> headers = allBlockHeaders.Skip(i * batchSize).Take(batchSize).ToList(); List <Block> blocks = this.blockStore.GetBlocks(headers.Select(x => x.HashBlock).ToList()); foreach (Block block in blocks) { this.AdjustCoinviewForBlock(block, coinView); totalBlocksCounted += 1; // We have reached the blockheight asked for if (totalBlocksCounted >= blockHeight) { break; } } // We have seen every block up to the tip or our blockheight if (headers.Count < batchSize || totalBlocksCounted >= blockHeight) { break; } // Give the user some feedback every 10k blocks if (i % 10 == 9) { this.logger.LogInformation($"Counted {totalBlocksCounted} blocks for balance calculation."); } i++; } return(coinView); }
private Dictionary <Script, Money> GetBalances(CoinviewContext coinView) { return(coinView.UnspentOutputs.Select(x => coinView.Transactions[x.Hash].Outputs[x.N]) // Get the full output data for each known unspent output. .GroupBy(x => x.ScriptPubKey) // Group them by Script aka address. .ToDictionary(x => x.Key, x => (Money)x.Sum(y => y.Value))); // Find the total value owned by this address. }