public void IndexOrderedBalance(Transaction tx) { var table = Configuration.GetBalanceTable(); var entities = OrderedBalanceChange.ExtractScriptBalances(tx).Select(t => t.ToEntity()).AsEnumerable(); Index(entities, table); }
public void IndexOrderedBalances(ChainBase chain) { IndexBalances(chain, "balances", (txid, tx, blockid, header, height) => { return(OrderedBalanceChange.ExtractScriptBalances(txid, tx, blockid, header, height)); }); }
public static IEnumerable <OrderedBalanceChange> ExtractWalletBalances( uint256 txId, Transaction tx, uint256 blockId, BlockHeader blockHeader, int height, WalletRuleEntryCollection walletCollection) { Dictionary <string, OrderedBalanceChange> entitiesByWallet = new Dictionary <string, OrderedBalanceChange>(); var scriptBalances = ExtractScriptBalances(txId, tx, blockId, blockHeader, height); foreach (var scriptBalance in scriptBalances) { foreach (var walletRuleEntry in walletCollection.GetRulesFor(scriptBalance.ScriptPubKey)) { OrderedBalanceChange walletEntity = null; if (!entitiesByWallet.TryGetValue(walletRuleEntry.WalletId, out walletEntity)) { walletEntity = new OrderedBalanceChange(walletRuleEntry.WalletId, scriptBalance); entitiesByWallet.Add(walletRuleEntry.WalletId, walletEntity); } walletEntity.Merge(scriptBalance, walletRuleEntry.Rule); } } foreach (var b in entitiesByWallet.Values) { b.UpdateToScriptCoins(); } return(entitiesByWallet.Values); }
private bool ShouldReplace(OrderedBalanceChange annotatedTransaction, OrderedBalanceChange conflicted) { if (annotatedTransaction.BlockId == null) { if (conflicted.BlockId == null) { return(annotatedTransaction.SeenUtc > conflicted.SeenUtc); // The is a replaced tx, we want the youngest } else { return(false); } } else { if (conflicted.BlockId == null) { return(true); } else { return(annotatedTransaction.Height < conflicted.Height); // The most buried block win (should never happen though) } } }
public ColoredBalanceChangeEntry(OrderedBalanceChange balanceChange, ColoredTransaction coloredTransaction) { _Colored = coloredTransaction; for (var i = 0; i < balanceChange.SpentIndices.Count; i++) { var spentIndex = balanceChange.SpentIndices[i]; var entry = coloredTransaction.Inputs.FirstOrDefault(o => o.Index == (uint)spentIndex); if (entry != null) { AddSpentCoin(entry.Asset, balanceChange.SpentCoins[(int)i]); } else { AddSpentCoin(null, balanceChange.SpentCoins[(int)i]); } } foreach (var coin in balanceChange.ReceivedCoins) { var entry = coloredTransaction.GetColoredEntry(coin.Outpoint.N); if (entry != null) { AddReceivedCoin(entry.Asset, coin, !coloredTransaction.Issuances.Contains(entry)); } else { AddReceivedCoin(null, coin, false); } } }
public Task IndexOrderedBalanceAsync(Transaction tx) { var table = Configuration.GetBalanceTable(); var entities = OrderedBalanceChange.ExtractScriptBalances(tx).Select(t => t.ToEntity()).AsEnumerable(); return(IndexAsync(entities, table)); }
internal OrderedBalanceChange(string walletId, OrderedBalanceChange source) : this(source.TransactionId, walletId, source.ScriptPubKey, source.BlockId, null, source.Height) { SeenUtc = source.SeenUtc; IsCoinbase = source.IsCoinbase; HasOpReturn = source.HasOpReturn; }
public async Task <bool> EnsurePreviousLoadedAsync(OrderedBalanceChange change) { if (!NeedLoading(change)) { return(true); } var parentIds = change.SpentOutpoints.Select(s => s.Hash).ToArray(); var parents = await GetTransactionsAsync(false, ColoredBalance, parentIds).ConfigureAwait(false); if (change.SpentCoins == null) { var success = await change.EnsureSpentCoinsLoadedAsync(parentIds, parents.Select(t => t == null ? null : t.Transaction).ToArray()).ConfigureAwait(false); if (!success) { return(false); } } if (ColoredBalance && change.ColoredTransaction == null) { var success = await change.EnsureColoredTransactionLoadedAsync(new IndexerColoredTransactionRepository(Configuration)).ConfigureAwait(false); if (!success) { return(false); } } var entity = change.ToEntity(); await Configuration.GetBalanceTable().ExecuteAsync(TableOperation.Merge(entity)).ConfigureAwait(false); return(true); }
public void CleanUnconfirmedChanges(Script scriptPubKey, TimeSpan olderThan) { var table = Configuration.GetBalanceTable(); List <DynamicTableEntity> unconfirmed = new List <DynamicTableEntity>(); foreach (var c in table.ExecuteQueryAsync(new BalanceQuery().CreateTableQuery(new BalanceId(scriptPubKey))).GetAwaiter().GetResult()) { var change = new OrderedBalanceChange(c); if (change.BlockId != null) { break; } if (DateTime.UtcNow - change.SeenUtc < olderThan) { continue; } unconfirmed.Add(c); } Parallel.ForEach(unconfirmed, c => { var t = Configuration.GetBalanceTable(); c.ETag = "*"; t.ExecuteAsync(TableOperation.Delete(c)).GetAwaiter().GetResult(); }); }
public async Task <bool> EnsurePreviousLoadedAsync(OrderedBalanceChange change) { if (!NeedLoading(change)) { return(true); } var parentIds = change.SpentOutpoints.Select(s => s.Hash).ToArray(); var parents = await GetTransactionsAsync(false, ColoredBalance, parentIds).ConfigureAwait(false); var cache = new NoSqlTransactionRepository(); foreach (var parent in parents.Where(p => p != null)) { cache.Put(parent.TransactionId, parent.Transaction); } if (change.SpentCoins == null) { var success = await change.EnsureSpentCoinsLoadedAsync(cache).ConfigureAwait(false); if (!success) { return(false); } } if (ColoredBalance && change.ColoredTransaction == null) { var indexerRepo = new IndexerColoredTransactionRepository(Configuration); indexerRepo.Transactions = new CompositeTransactionRepository(new[] { new ReadOnlyTransactionRepository(cache), indexerRepo.Transactions }); var success = await change.EnsureColoredTransactionLoadedAsync(indexerRepo).ConfigureAwait(false); if (!success) { return(false); } } var entity = change.ToEntity(ConsensusFactory); if (!change.IsEmpty) { await Configuration.GetBalanceTable().ExecuteAsync(TableOperation.Merge(entity)).ConfigureAwait(false); } else { try { await Configuration.GetTransactionTable().ExecuteAsync(TableOperation.Delete(entity)).ConfigureAwait(false); } catch (StorageException ex) { if (ex.RequestInformation == null || ex.RequestInformation.HttpStatusCode != 404) { throw; } } } return(true); }
public void IndexOrderedBalance(Transaction tx) { var table = Configuration.GetBalanceTable(); foreach (var group in OrderedBalanceChange.ExtractScriptBalances(tx).GroupBy(c => c.BalanceId, c => c.ToEntity())) { Index(group, table); } }
public async Task <bool> EnsurePreviousLoadedAsync(OrderedBalanceChange change) { if (!NeedLoading(change)) { return(true); } var transactions = await GetTransactionsAsync(false, ColoredBalance, change.SpentOutpoints.Select(s => s.Hash).ToArray()).ConfigureAwait(false); CoinCollection result = new CoinCollection(); for (int i = 0; i < transactions.Length; i++) { var outpoint = change.SpentOutpoints[i]; if (outpoint.IsNull) { continue; } var prev = transactions[i]; if (prev == null) { return(false); } if (ColoredBalance && prev.ColoredTransaction == null) { return(false); } result.Add(new Coin(outpoint, prev.Transaction.Outputs[change.SpentOutpoints[i].N])); } change.SpentCoins = result; if (ColoredBalance && change.ColoredBalanceChangeEntry == null) { var thisTransaction = await GetTransactionAsync(false, ColoredBalance, change.TransactionId).ConfigureAwait(false); if (thisTransaction.ColoredTransaction == null) { return(false); } change.ColoredBalanceChangeEntry = new ColoredBalanceChangeEntry(change, thisTransaction.ColoredTransaction); } var entity = change.ToEntity(); var spentCoins = Helper.GetEntityProperty(entity, "b"); var coloredTx = ColoredBalance ? entity.Properties["g"].BinaryValue : null; entity.Properties.Clear(); if (coloredTx != null) { entity.Properties.Add("g", new EntityProperty(coloredTx)); } Helper.SetEntityProperty(entity, "b", spentCoins); Configuration.GetBalanceTable().Execute(TableOperation.Merge(entity)); change.AddRedeemInfo(); return(true); }
public void IndexWalletBalances(ChainBase chain) { Configuration.GetWalletBalanceTable().CreateIfNotExists(); Configuration.GetWalletRulesTable().CreateIfNotExists(); var walletRules = Configuration.CreateIndexerClient().GetAllWalletRules(); IndexBalances(chain, "wallets", (txid, tx, blockid, header, height) => { return(OrderedBalanceChange.ExtractWalletBalances(txid, tx, blockid, header, height, walletRules)); }); }
public bool NeedLoading(OrderedBalanceChange change) { if (change.SpentCoins != null) { if (change.ColoredTransaction != null || !ColoredBalance) { return(false); } } return(true); }
private bool NeedLoading(OrderedBalanceChange change) { if (change.SpentCoins != null) { if (change.ColoredBalanceChangeEntry != null || !ColoredBalance) { change.AddRedeemInfo(); return(false); } } return(true); }
public Task IndexWalletOrderedBalanceAsync(int height, Block block, WalletRuleEntryCollection walletRules) { var table = Configuration.GetBalanceTable(); var blockId = block == null ? null : block.GetHash(); var entities = block .Transactions .SelectMany(t => OrderedBalanceChange.ExtractWalletBalances(null, t, blockId, block.Header, height, walletRules)) .Select(t => t.ToEntity(ConsensusFactory)) .AsEnumerable(); return(IndexAsync(entities, table)); }
public void IndexOrderedBalance(int height, Block block) { var table = Configuration.GetBalanceTable(); var blockId = block == null ? null : block.GetHash(); var header = block == null ? null : block.Header; var entities = block .Transactions .SelectMany(t => OrderedBalanceChange.ExtractScriptBalances(t.GetHash(), t, blockId, header, height)) .Select(_ => _.ToEntity(ConsensusFactory)) .AsEnumerable(); Index(entities, table); }
public void IndexWalletOrderedBalance(int height, Block block, WalletRuleEntryCollection walletRules) { var table = Configuration.GetBalanceTable(); var blockId = block.GetHash(); foreach (var transaction in block.Transactions) { var txId = transaction.GetHash(); var changes = OrderedBalanceChange.ExtractWalletBalances(txId, transaction, blockId, block.Header, height, walletRules).Select(c => c.ToEntity()); foreach (var group in changes.GroupBy(c => c.PartitionKey)) { Index(group, table); } } }
private static bool IsMinConf(OrderedBalanceChange e, int minConfirmation, ChainBase chain) { if (e.BlockId == null) { return(minConfirmation == 0); } var b = chain.GetBlock(e.BlockId); if (b == null) { return(false); } return((chain.Height - b.Height) + 1 >= minConfirmation); }
internal void Merge(OrderedBalanceChange other, WalletRule walletRule) { if (other.ReceivedCoins.Count != 0) { ReceivedCoins.AddRange(other.ReceivedCoins); ReceivedCoins = new CoinCollection(ReceivedCoins.Distinct <ICoin, OutPoint>(c => c.Outpoint)); if (walletRule != null) { foreach (var c in other.ReceivedCoins) { this.MatchedRules.Add(new MatchedRule() { Index = c.Outpoint.N, Rule = walletRule, MatchType = MatchLocation.Output }); } } } if (other.SpentIndices.Count != 0) { SpentIndices.AddRange(other.SpentIndices); SpentIndices = SpentIndices.Distinct().ToList(); SpentOutpoints.AddRange(other.SpentOutpoints); SpentOutpoints = SpentOutpoints.Distinct().ToList(); //Remove cached value, no longer correct UpdateToUncoloredCoins(); SpentCoins = null; if (walletRule != null) { foreach (var c in other.SpentIndices) { this.MatchedRules.Add(new MatchedRule() { Index = c, Rule = walletRule, MatchType = MatchLocation.Input }); } } } }
private bool Prepare(OrderedBalanceChange change) { change.UpdateToScriptCoins(); if (change.SpentCoins == null && ExcludeIncompleteBalanceChange) { return(false); } if (ColoredBalance) { if (change.ColoredTransaction == null) { return(false); } change.UpdateToColoredCoins(); } return(true); }
private bool MergeIntoWalletCore(string walletId, BalanceId balanceId, WalletRule rule, CancellationToken cancel) { var indexer = Configuration.CreateIndexer(); var query = new BalanceQuery() { From = new UnconfirmedBalanceLocator().Floor(), RawOrdering = true }; var sourcesByKey = GetOrderedBalanceCore(balanceId, query, cancel) .ToDictionary(i => GetKey(i)); if (sourcesByKey.Count == 0) { return(false); } var destByKey = GetOrderedBalance(walletId, query, cancel) .ToDictionary(i => GetKey(i)); List <OrderedBalanceChange> entities = new List <OrderedBalanceChange>(); foreach (var kv in sourcesByKey) { var source = kv.Value; var existing = destByKey.TryGet(kv.Key); if (existing == null) { existing = new OrderedBalanceChange(walletId, source); } existing.Merge(kv.Value, rule); entities.Add(existing); if (entities.Count == 100) { indexer.Index(entities); } } if (entities.Count != 0) { indexer.Index(entities); } return(true); }
public void IndexOrderedBalance(int height, Block block) { var table = Configuration.GetBalanceTable(); var blockId = block.GetHash(); foreach (var group in block .Transactions .SelectMany(t => OrderedBalanceChange.ExtractScriptBalances(t.GetHash(), t, blockId, block.Header, height)) .Select(_ => _.ToEntity()) .GroupBy(c => c.PartitionKey) ) { foreach (var batch in group.Partition(100)) { Index(batch, table); } } }
private bool Prepare(OrderedBalanceChange change) { change.UpdateToScriptCoins(); if (change.SpentCoins == null || change.ReceivedCoins == null) { return(false); } if (change.IsEmpty) { return(false); } if (ColoredBalance) { if (change.ColoredTransaction == null) { return(false); } change.UpdateToColoredCoins(); } return(true); }
private bool Prepare(OrderedBalanceChange change) { change.UpdateToScriptCoins(); if(change.SpentCoins == null || change.ReceivedCoins == null) return false; if(change.IsEmpty) return false; if(ColoredBalance) { if(change.ColoredTransaction == null) return false; change.UpdateToColoredCoins(); } return true; }
public ConfirmedBalanceLocator(OrderedBalanceChange change) : this(change.Height, change.BlockId, change.TransactionId) { }
private bool MergeIntoWalletCore(string walletId, BalanceId balanceId, WalletRule rule, CancellationToken cancel) { var indexer = Configuration.CreateIndexer(); var query = new BalanceQuery() { From = new UnconfirmedBalanceLocator().Floor(), RawOrdering = true }; var sourcesByKey = GetOrderedBalanceCore(balanceId, query, cancel) .ToDictionary(i => GetKey(i)); if(sourcesByKey.Count == 0) return false; var destByKey = GetOrderedBalance(walletId, query, cancel) .ToDictionary(i => GetKey(i)); List<OrderedBalanceChange> entities = new List<OrderedBalanceChange>(); foreach(var kv in sourcesByKey) { var source = kv.Value; var existing = destByKey.TryGet(kv.Key); if(existing == null) { existing = new OrderedBalanceChange(walletId, source); } existing.Merge(kv.Value, rule); entities.Add(existing); if(entities.Count == 100) indexer.Index(entities); } if(entities.Count != 0) indexer.Index(entities); return true; }
internal void Merge(OrderedBalanceChange other, WalletRule walletRule) { if(other.ReceivedCoins.Count != 0) { ReceivedCoins.AddRange(other.ReceivedCoins); ReceivedCoins = new CoinCollection(ReceivedCoins.Distinct<ICoin, OutPoint>(c => c.Outpoint)); if(walletRule != null) foreach(var c in other.ReceivedCoins) { this.MatchedRules.Add(new MatchedRule() { Index = c.Outpoint.N, Rule = walletRule, MatchType = MatchLocation.Output }); } } if(other.SpentIndices.Count != 0) { SpentIndices.AddRange(other.SpentIndices); SpentIndices = SpentIndices.Distinct().ToList(); SpentOutpoints.AddRange(other.SpentOutpoints); SpentOutpoints = SpentOutpoints.Distinct().ToList(); //Remove cached value, no longer correct UpdateToUncoloredCoins(); SpentCoins = null; if(walletRule != null) foreach(var c in other.SpentIndices) { this.MatchedRules.Add(new MatchedRule() { Index = c, Rule = walletRule, MatchType = MatchLocation.Input }); } } }
public static IEnumerable <OrderedBalanceChange> ExtractScriptBalances(uint256 txId, Transaction transaction, uint256 blockId, BlockHeader blockHeader, int height) { if (transaction == null) { throw new ArgumentNullException("transaction"); } if (txId == null) { txId = transaction.GetHash(); } if (blockId == null && blockHeader != null) { blockId = blockHeader.GetHash(); } Dictionary <Script, OrderedBalanceChange> changeByScriptPubKey = new Dictionary <Script, OrderedBalanceChange>(); uint i = 0; foreach (var input in transaction.Inputs) { if (transaction.IsCoinBase) { i++; break; } TxDestination signer = null; if (input.ScriptSig.Length != 0) { signer = input.ScriptSig.GetSigner(); } else { signer = GetSigner(input.WitScript); } if (signer != null) { OrderedBalanceChange entry = null; if (!changeByScriptPubKey.TryGetValue(signer.ScriptPubKey, out entry)) { entry = new OrderedBalanceChange(txId, signer.ScriptPubKey, blockId, blockHeader, height); changeByScriptPubKey.Add(signer.ScriptPubKey, entry); } entry.SpentOutpoints.Add(input.PrevOut); entry.SpentIndices.Add(i); } i++; } i = 0; bool hasOpReturn = false; foreach (var output in transaction.Outputs) { var bytes = output.ScriptPubKey.ToBytes(true); if (bytes.Length != 0 && bytes[0] == (byte)OpcodeType.OP_RETURN) { hasOpReturn = true; i++; continue; } OrderedBalanceChange entry = null; if (!changeByScriptPubKey.TryGetValue(output.ScriptPubKey, out entry)) { entry = new OrderedBalanceChange(txId, output.ScriptPubKey, blockId, blockHeader, height); changeByScriptPubKey.Add(output.ScriptPubKey, entry); } entry.ReceivedCoins.Add(new Coin() { Outpoint = new OutPoint(txId, i), TxOut = output }); i++; } foreach (var entity in changeByScriptPubKey) { entity.Value.HasOpReturn = hasOpReturn; entity.Value.IsCoinbase = transaction.IsCoinBase; } return(changeByScriptPubKey.Values); }
private string GetKey(OrderedBalanceChange change) { return(change.Height + "-" + (change.BlockId == null ? new uint256(0) : change.BlockId) + "-" + change.TransactionId + "-" + change.SeenUtc.Ticks); }
public bool NeedLoading(OrderedBalanceChange change) { if(change.SpentCoins != null) { if(change.ColoredTransaction != null || !ColoredBalance) { return false; } } return true; }
public void CleanUnconfirmedChanges(Script scriptPubKey, TimeSpan olderThan) { var table = Configuration.GetBalanceTable(); List<DynamicTableEntity> unconfirmed = new List<DynamicTableEntity>(); foreach(var c in table.ExecuteQuery(new BalanceQuery().CreateTableQuery(new BalanceId(scriptPubKey)))) { var change = new OrderedBalanceChange(c); if(change.BlockId != null) break; if(DateTime.UtcNow - change.SeenUtc < olderThan) continue; unconfirmed.Add(c); } Parallel.ForEach(unconfirmed, c => { var t = Configuration.GetBalanceTable(); c.ETag = "*"; t.Execute(TableOperation.Delete(c)); }); }
private string GetKey(OrderedBalanceChange change) { return($"{change.Height}-{(change.BlockId == null ? new uint256(0) : change.BlockId)}-{change.TransactionId}-{change.SeenUtc.Ticks}"); }
public async Task<bool> EnsurePreviousLoadedAsync(OrderedBalanceChange change) { if(!NeedLoading(change)) return true; var parentIds = change.SpentOutpoints.Select(s => s.Hash).ToArray(); var parents = await GetTransactionsAsync(false, ColoredBalance, parentIds).ConfigureAwait(false); var cache = new NoSqlTransactionRepository(); foreach(var parent in parents.Where(p => p != null)) cache.Put(parent.TransactionId, parent.Transaction); if(change.SpentCoins == null) { var success = await change.EnsureSpentCoinsLoadedAsync(cache).ConfigureAwait(false); if(!success) return false; } if(ColoredBalance && change.ColoredTransaction == null) { var indexerRepo = new IndexerColoredTransactionRepository(Configuration); indexerRepo.Transactions = new CompositeTransactionRepository(new[] { new ReadOnlyTransactionRepository(cache), indexerRepo.Transactions }); var success = await change.EnsureColoredTransactionLoadedAsync(indexerRepo).ConfigureAwait(false); if(!success) return false; } var entity = change.ToEntity(); if(!change.IsEmpty) { await Configuration.GetBalanceTable().ExecuteAsync(TableOperation.Merge(entity)).ConfigureAwait(false); } else { try { await Configuration.GetTransactionTable().ExecuteAsync(TableOperation.Delete(entity)).ConfigureAwait(false); } catch(StorageException ex) { if(ex.RequestInformation == null || ex.RequestInformation.HttpStatusCode != 404) throw; } } return true; }
public static IEnumerable<OrderedBalanceChange> ExtractWalletBalances( uint256 txId, Transaction tx, uint256 blockId, BlockHeader blockHeader, int height, WalletRuleEntryCollection walletCollection) { Dictionary<string, OrderedBalanceChange> entitiesByWallet = new Dictionary<string, OrderedBalanceChange>(); var scriptBalances = ExtractScriptBalances(txId, tx, blockId, blockHeader, height); foreach(var scriptBalance in scriptBalances) { foreach(var walletRuleEntry in walletCollection.GetRulesFor(scriptBalance.ScriptPubKey)) { OrderedBalanceChange walletEntity = null; if(!entitiesByWallet.TryGetValue(walletRuleEntry.WalletId, out walletEntity)) { walletEntity = new OrderedBalanceChange(walletRuleEntry.WalletId, scriptBalance); entitiesByWallet.Add(walletRuleEntry.WalletId, walletEntity); } walletEntity.Merge(scriptBalance, walletRuleEntry.Rule); } } foreach(var b in entitiesByWallet.Values) b.UpdateToScriptCoins(); return entitiesByWallet.Values; }
private string GetKey(OrderedBalanceChange change) { return change.Height + "-" + (change.BlockId == null ? new uint256(0) : change.BlockId) + "-" + change.TransactionId + "-" + change.SeenUtc.Ticks; }
public static IEnumerable<OrderedBalanceChange> ExtractScriptBalances(uint256 txId, Transaction transaction, uint256 blockId, BlockHeader blockHeader, int height) { if(transaction == null) throw new ArgumentNullException("transaction"); if(txId == null) txId = transaction.GetHash(); if(blockId == null && blockHeader != null) blockId = blockHeader.GetHash(); Dictionary<Script, OrderedBalanceChange> changeByScriptPubKey = new Dictionary<Script, OrderedBalanceChange>(); uint i = 0; foreach(var input in transaction.Inputs) { if(transaction.IsCoinBase) { i++; break; } TxDestination signer = null; if(input.ScriptSig.Length != 0) { signer = input.ScriptSig.GetSigner(); } else { signer = GetSigner(input.WitScript); } if(signer != null) { OrderedBalanceChange entry = null; if(!changeByScriptPubKey.TryGetValue(signer.ScriptPubKey, out entry)) { entry = new OrderedBalanceChange(txId, signer.ScriptPubKey, blockId, blockHeader, height); changeByScriptPubKey.Add(signer.ScriptPubKey, entry); } entry.SpentOutpoints.Add(input.PrevOut); entry.SpentIndices.Add(i); } i++; } i = 0; bool hasOpReturn = false; foreach(var output in transaction.Outputs) { var bytes = output.ScriptPubKey.ToBytes(true); if(bytes.Length != 0 && bytes[0] == (byte)OpcodeType.OP_RETURN) { hasOpReturn = true; i++; continue; } OrderedBalanceChange entry = null; if(!changeByScriptPubKey.TryGetValue(output.ScriptPubKey, out entry)) { entry = new OrderedBalanceChange(txId, output.ScriptPubKey, blockId, blockHeader, height); changeByScriptPubKey.Add(output.ScriptPubKey, entry); } entry.ReceivedCoins.Add(new Coin() { Outpoint = new OutPoint(txId, i), TxOut = output }); i++; } foreach(var entity in changeByScriptPubKey) { entity.Value.HasOpReturn = hasOpReturn; entity.Value.IsCoinbase = transaction.IsCoinBase; } return changeByScriptPubKey.Values; }