Esempio n. 1
0
        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();
                }
            }
        }
Esempio n. 2
0
        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);
                            }
                        }
                    }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        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());
        }
Esempio n. 5
0
        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);
                            }