public void CanHandshakeWithSeveralTemplateBehaviors() { using (var builder = NodeBuilderEx.Create()) { var node = builder.CreateNode(true); node.Generate(101); AddressManager manager = new AddressManager(); manager.Add(new NetworkAddress(node.NodeEndpoint), IPAddress.Loopback); var chain = new SlimChain(builder.Network.GenesisHash); NodesGroup group = new NodesGroup(builder.Network, new NodeConnectionParameters() { Services = NodeServices.Nothing, IsRelay = true, TemplateBehaviors = { new AddressManagerBehavior(manager) { PeersToDiscover = 1, Mode = AddressManagerBehaviorMode.None }, new SlimChainBehavior(chain), new PingPongBehavior() } }); group.AllowSameGroup = true; group.MaximumNodeConnection = 1; var connecting = WaitConnected(group); try { group.Connect(); connecting.GetAwaiter().GetResult(); Eventually(() => { Assert.Equal(101, chain.Height); }); var ms = new MemoryStream(); chain.Save(ms); var chain2 = new SlimChain(chain.Genesis); ms.Position = 0; chain2.Load(ms); Assert.Equal(chain.Tip, chain2.Tip); using (var fs = new FileStream("test.slim.dat", FileMode.Create, FileAccess.Write, FileShare.None, 1024 * 1024)) { chain.Save(fs); fs.Flush(); } chain.ResetToGenesis(); using (var fs = new FileStream("test.slim.dat", FileMode.Open, FileAccess.Read, FileShare.None, 1024 * 1024)) { chain.Load(fs); } Assert.Equal(101, chain2.Height); chain.ResetToGenesis(); } finally { group.Disconnect(); } } }
public AnnotatedTransactionCollection(ICollection <TrackedTransaction> transactions, Models.TrackedSource trackedSource, SlimChain headerChain, Network network) : base(transactions.Count) { _TxById = new Dictionary <uint256, AnnotatedTransaction>(transactions.Count); foreach (var tx in transactions) { foreach (var keyPathInfo in tx.KnownKeyPathMapping) { _KeyPaths.TryAdd(keyPathInfo.Key, keyPathInfo.Value); } } // Let's remove the dups and let's get the current height of the transactions foreach (var trackedTx in transactions) { int? txHeight = null; bool isMature = true; if (trackedTx.BlockHash != null && headerChain.TryGetHeight(trackedTx.BlockHash, out var height)) { txHeight = height; isMature = trackedTx.IsCoinBase ? headerChain.Height - height >= network.Consensus.CoinbaseMaturity : true; } AnnotatedTransaction annotatedTransaction = new AnnotatedTransaction(txHeight, trackedTx, isMature); if (_TxById.TryGetValue(trackedTx.TransactionHash, out var conflicted)) { if (ShouldReplace(annotatedTransaction, conflicted)) { CleanupTransactions.Add(conflicted); _TxById.Remove(trackedTx.TransactionHash); _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } else { CleanupTransactions.Add(annotatedTransaction); } } else { _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } } // Let's resolve the double spents Dictionary <OutPoint, uint256> spentBy = new Dictionary <OutPoint, uint256>(transactions.SelectMany(t => t.SpentOutpoints).Count()); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is int)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // No way to have double spent in confirmed transactions spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } } var unconfs = _TxById.Where(r => r.Value.Height is null) .ToDictionary(kv => kv.Key, kv => kv.Value); var replaced = new Dictionary <uint256, AnnotatedTransaction>(); removeConflicts: HashSet <uint256> toRemove = new HashSet <uint256>(); foreach (var annotatedTransaction in unconfs.Values) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // All children of a replaced transaction should be replaced if (replaced.TryGetValue(spent.Hash, out var parent) && parent.ReplacedBy is uint256) { annotatedTransaction.Replaceable = false; annotatedTransaction.ReplacedBy = parent.ReplacedBy; replaced.Add(annotatedTransaction.Record.TransactionHash, annotatedTransaction); toRemove.Add(annotatedTransaction.Record.TransactionHash); goto nextTransaction; } // If there is a conflict, let's see who get replaced if (spentBy.TryGetValue(spent, out var conflictHash) && _TxById.TryGetValue(conflictHash, out var conflicted)) { // Conflict with one-self... not a conflict. if (conflicted == annotatedTransaction) { goto nextTransaction; } // We know the conflict is already removed, so this transaction replace it if (toRemove.Contains(conflictHash)) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } else { AnnotatedTransaction toKeep = null, toReplace = null; var shouldReplace = ShouldReplace(annotatedTransaction, conflicted); if (shouldReplace) { toReplace = conflicted; toKeep = annotatedTransaction; } else { toReplace = annotatedTransaction; toKeep = conflicted; } toRemove.Add(toReplace.Record.TransactionHash); if (toKeep == annotatedTransaction) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } if (toKeep.Height is null && toReplace.Height is null) { toReplace.ReplacedBy = toKeep.Record.TransactionHash; toReplace.Replaceable = false; toKeep.Replacing = toReplace.Record.TransactionHash; replaced.TryAdd(toReplace.Record.TransactionHash, toReplace); } else { CleanupTransactions.Add(toReplace); } } }
public void CanBuildSlimChain() { var b0 = RandomUInt256(); SlimChain chain = new SlimChain(b0); var b1 = RandomUInt256(); Assert.Throws <ArgumentException>(() => chain.TrySetTip(b0, b0)); Assert.True(chain.TrySetTip(b1, b0)); var b2 = RandomUInt256(); Assert.True(chain.TrySetTip(b2, b1)); Assert.True(chain.TrySetTip(b2, b1)); Assert.Equal(b0, chain.Genesis); Assert.Equal(b2, chain.Tip); Assert.True(chain.Contains(b2)); Assert.Equal(2, chain.Height); Assert.False(chain.TrySetTip(b1, b0, true)); Assert.True(chain.TrySetTip(b1, b0, false)); Assert.Equal(b1, chain.Tip); Assert.False(chain.TryGetHeight(b2, out int height)); Assert.False(chain.Contains(b2)); Assert.True(chain.TryGetHeight(b1, out height)); Assert.Equal(1, height); Assert.True(chain.TrySetTip(b2, b1)); Assert.Throws <ArgumentException>(() => chain.TrySetTip(b1, b2)); // Incoherent Assert.Throws <ArgumentException>(() => chain.TrySetTip(b0, b1, true)); // Genesis block should not have previosu Assert.Throws <ArgumentException>(() => chain.TrySetTip(b0, b1, false)); Assert.True(chain.TrySetTip(b0, null)); Assert.Equal(0, chain.Height); Assert.True(chain.TrySetTip(b1, b0, true)); Assert.True(chain.TrySetTip(b2, b1)); var b3 = RandomUInt256(); var block = chain.GetBlock(b2); Assert.Equal(b2, block.Hash); Assert.Equal(b1, block.Previous); Assert.Equal(2, block.Height); Assert.Null(chain.GetBlock(b3)); block = chain.GetBlock(2); Assert.Equal(b2, block.Hash); Assert.Equal(b1, block.Previous); Assert.Equal(2, block.Height); Assert.Null(chain.GetBlock(3)); Assert.Null(chain.GetBlock(-1)); block = chain.GetBlock(0); Assert.Equal(b0, block.Hash); Assert.Null(block.Previous); Assert.Equal(0, block.Height); var chain2 = new SlimChain(RandomUInt256()); var ms = new MemoryStream(); chain.Save(ms); ms.Position = 0; // Not good genesis Assert.Throws <InvalidOperationException>(() => chain2.Load(ms)); chain2 = new SlimChain(b0); ms.Position = 0; chain2.Load(ms); Assert.Equal(chain.Tip, chain2.Tip); Assert.Equal(2, chain2.Height); }
private async Task UpdateRepository(DerivationSchemeTrackedSource trackedSource, Repository repo, SlimChain chain, ScanTxoutOutput[] outputs, ScannedItems scannedItems, ScanUTXOProgress progressObj) { var data = outputs .GroupBy(o => o.Coin.Outpoint.Hash) .Select(o => (Coins: o.Select(c => c.Coin).ToList(), BlockId: chain.GetBlock(o.First().Height)?.Hash, TxId: o.Select(c => c.Coin.Outpoint.Hash).FirstOrDefault(), KeyPathInformations: o.Select(c => scannedItems.KeyPathInformations[c.Coin.ScriptPubKey]).ToList())) .Where(o => o.BlockId != null) .Select(o => { foreach (var keyInfo in o.KeyPathInformations) { var index = keyInfo.KeyPath.Indexes.Last(); var highest = progressObj.HighestKeyIndexFound[keyInfo.Feature]; if (highest == null || index > highest.Value) { progressObj.HighestKeyIndexFound[keyInfo.Feature] = (int)index; } } return(o); }).ToList(); await repo.SaveKeyInformations(scannedItems. KeyPathInformations. Select(p => p.Value). Where(p => { var highest = progressObj.HighestKeyIndexFound[p.Feature]; if (highest == null) { return(false); } return(p.KeyPath.Indexes.Last() <= highest.Value); }).ToArray()); await repo.UpdateAddressPool(trackedSource, progressObj.HighestKeyIndexFound); DateTimeOffset now = DateTimeOffset.UtcNow; await repo.SaveMatches(data.Select(o => new TrackedTransaction(new TrackedTransactionKey(o.TxId, o.BlockId, true), trackedSource, o.Coins, o.KeyPathInformations)).ToArray()); }
private async Task UpdateRepository(RPCClient client, DerivationSchemeTrackedSource trackedSource, Repository repo, SlimChain chain, ScanTxoutOutput[] outputs, ScannedItems scannedItems, ScanUTXOProgress progressObj) { var data = outputs .GroupBy(o => o.Coin.Outpoint.Hash) .Select(o => (Coins: o.Select(c => c.Coin).ToList(), BlockId: chain.GetBlock(o.First().Height)?.Hash, TxId: o.Select(c => c.Coin.Outpoint.Hash).FirstOrDefault(), KeyPathInformations: o.Select(c => scannedItems.KeyPathInformations[c.Coin.ScriptPubKey]).ToList())) .Where(o => o.BlockId != null) .Select(o => { foreach (var keyInfo in o.KeyPathInformations) { var index = keyInfo.KeyPath.Indexes.Last(); var highest = progressObj.HighestKeyIndexFound[keyInfo.Feature]; if (highest == null || index > highest.Value) { progressObj.HighestKeyIndexFound[keyInfo.Feature] = (int)index; } } return(o); }).ToList(); var headers = new ConcurrentDictionary <uint256, BlockHeader>(); var clientBatch = client.PrepareBatch(); var gettingBlockHeaders = Task.WhenAll(data.Select(async o => { headers.TryAdd(o.BlockId, await clientBatch.GetBlockHeaderAsync(o.BlockId)); }).Concat(new[] { clientBatch.SendBatchAsync() }).ToArray()); await repo.SaveKeyInformations(scannedItems. KeyPathInformations. Select(p => p.Value). Where(p => { var highest = progressObj.HighestKeyIndexFound[p.Feature]; if (highest == null) { return(false); } return(p.KeyPath.Indexes.Last() <= highest.Value); }).ToArray()); await repo.UpdateAddressPool(trackedSource, progressObj.HighestKeyIndexFound); await gettingBlockHeaders; DateTimeOffset now = DateTimeOffset.UtcNow; await repo.SaveMatches(data.Select(o => new TrackedTransaction(new TrackedTransactionKey(o.TxId, o.BlockId, true), trackedSource, o.Coins, o.KeyPathInformations) { Inserted = now, FirstSeen = headers.TryGetValue(o.BlockId, out var header) && header != null ? header.BlockTime : NBitcoin.Utils.UnixTimeToDateTime(0) }).ToArray());
public AnnotatedTransactionCollection(ICollection <TrackedTransaction> transactions, Models.TrackedSource trackedSource, SlimChain headerChain, Network network) : base(transactions.Count) { _TxById = new Dictionary <uint256, AnnotatedTransaction>(transactions.Count); foreach (var tx in transactions) { foreach (var keyPathInfo in tx.KnownKeyPathMapping) { _KeyPaths.TryAdd(keyPathInfo.Key, keyPathInfo.Value); } } // Let's remove the dups and let's get the current height of the transactions foreach (var trackedTx in transactions) { int? txHeight = null; bool isMature = true; if (trackedTx.BlockHash != null && headerChain.TryGetHeight(trackedTx.BlockHash, out var height)) { txHeight = height; isMature = trackedTx.IsCoinBase ? headerChain.Height - height >= network.Consensus.CoinbaseMaturity : true; } AnnotatedTransaction annotatedTransaction = new AnnotatedTransaction(txHeight, trackedTx, isMature); if (_TxById.TryGetValue(trackedTx.TransactionHash, out var conflicted)) { if (ShouldReplace(annotatedTransaction, conflicted)) { CleanupTransactions.Add(conflicted); _TxById.Remove(trackedTx.TransactionHash); _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } else { CleanupTransactions.Add(annotatedTransaction); } } else { _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } } // Let's resolve the double spents Dictionary <OutPoint, uint256> spentBy = new Dictionary <OutPoint, uint256>(transactions.SelectMany(t => t.SpentOutpoints).Count()); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is int)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // No way to have double spent in confirmed transactions spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } } removeConflicts: HashSet <uint256> toRemove = new HashSet <uint256>(); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is null)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { if (spentBy.TryGetValue(spent, out var conflictHash) && _TxById.TryGetValue(conflictHash, out var conflicted)) { if (conflicted == annotatedTransaction) { goto nextTransaction; } if (toRemove.Contains(conflictHash)) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } else if (ShouldReplace(annotatedTransaction, conflicted)) { toRemove.Add(conflictHash); spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); if (conflicted.Height is null && annotatedTransaction.Height is null) { ReplacedTransactions.Add(conflicted); } else { CleanupTransactions.Add(conflicted); } } else { toRemove.Add(annotatedTransaction.Record.TransactionHash); if (conflicted.Height is null && annotatedTransaction.Height is null) { ReplacedTransactions.Add(annotatedTransaction); }