private BlockNotifier CreateNotifier(ConcurrentChain chain)
        {
            var rpc = new MockRpcClient();

            rpc.OnGetBestBlockHashAsync = () => Task.FromResult(chain.Tip.HashBlock);
            rpc.OnGetBlockAsync         = (blockHash) => Task.FromResult(Block.CreateBlock(chain.GetBlock(blockHash).Header, rpc.Network));
            rpc.OnGetBlockHeaderAsync   = (blockHash) => Task.FromResult(chain.GetBlock(blockHash).Header);

            var notifier = new BlockNotifier(TimeSpan.FromMilliseconds(100), rpc);

            return(notifier);
        }
Example #2
0
        public async Task UnsynchronizedBitcoinNodeAsync()
        {
            var rpc = new MockRpcClient
            {
                OnGetBlockchainInfoAsync = () => Task.FromResult(new BlockchainInfo
                {
                    Headers = 0,
                    Blocks  = 0,
                    InitialBlockDownload = false
                }),
            };
            var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc);
            var indexer       = new IndexBuilderService(rpc, blockNotifier, ".");

            indexer.Synchronize();

            await Task.Delay(TimeSpan.FromSeconds(1));

            //// Assert.False(indexer.IsRunning);     // <------------ ERROR: it should have stopped but there is a bug for RegTest
            Assert.Throws <ArgumentOutOfRangeException>(() => indexer.GetLastFilter());             // There are no filters
        }
Example #3
0
        private volatile bool _disposedValue = false;         // To detect redundant calls

        public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, IRPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network       = Guard.NotNull(nameof(network), network);
            BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
            FolderPath    = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient     = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig   = Guard.NotNull(nameof(roundConfig), roundConfig);

            Rounds = ImmutableList <CoordinatorRound> .Empty;

            LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow;

            Directory.CreateDirectory(FolderPath);

            UtxoReferee = new UtxoReferee(Network, FolderPath, RpcClient, RoundConfig);

            if (File.Exists(CoinJoinsFilePath))
            {
                try
                {
                    var getTxTasks = new List <(Task <Transaction> txTask, string line)>();
                    var batch      = RpcClient.PrepareBatch();

                    var      toRemove = new List <string>();
                    string[] allLines = File.ReadAllLines(CoinJoinsFilePath);
                    foreach (string line in allLines)
                    {
                        try
                        {
                            getTxTasks.Add((batch.GetRawTransactionAsync(uint256.Parse(line)), line));
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line[..20]}\".";
    public IndexBuilderService(IRPCClient rpc, BlockNotifier blockNotifier, string indexFilePath)
    {
        RpcClient     = Guard.NotNull(nameof(rpc), rpc);
        BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
        IndexFilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(indexFilePath), indexFilePath);

        Index     = new List <FilterModel>();
        IndexLock = new AsyncLock();

        StartingHeight = SmartHeader.GetStartingHeader(RpcClient.Network).Height;

        _serviceStatus = NotStarted;

        IoHelpers.EnsureContainingDirectoryExists(IndexFilePath);

        // Testing permissions.
        using (var _ = File.Open(IndexFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
        {
        }

        if (File.Exists(IndexFilePath))
        {
            if (RpcClient.Network == Network.RegTest)
            {
                File.Delete(IndexFilePath);                 // RegTest is not a global ledger, better to delete it.
            }
            else
            {
                foreach (var line in File.ReadAllLines(IndexFilePath))
                {
                    var filter = FilterModel.FromLine(line);
                    Index.Add(filter);
                }
            }
        }

        BlockNotifier.OnBlock += BlockNotifier_OnBlock;
    }
Example #5
0
        public static PeerManager getTestPeerManager()
        {
            var logger       = new TestLogger();
            var broadcaster  = new TestBroadcaster();
            var feeEstiamtor = new TestFeeEstimator();
            var n            = NBitcoin.Network.TestNet;

            var chainWatchInterface = new ChainWatchInterfaceUtil(n);
            var seed               = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
            var keysInterface      = new KeysManager(seed, DateTime.UnixEpoch);
            var blockNotifier      = BlockNotifier.Create(chainWatchInterface);
            var manyChannelMonitor = ManyChannelMonitor.Create(n, chainWatchInterface, broadcaster, logger, feeEstiamtor);
            var channelManager     = ChannelManager.Create(n, TestUserConfig.Default, chainWatchInterface, keysInterface, logger, broadcaster, feeEstiamtor, 400000, manyChannelMonitor);

            blockNotifier.RegisterChannelManager(channelManager);
            blockNotifier.RegisterManyChannelMonitor(manyChannelMonitor);
            var peerManager =
                PeerManager.Create(
                    seed, in TestUserConfig.Default, chainWatchInterface, logger, keysInterface.GetNodeSecret().ToBytes(), channelManager, blockNotifier, 10000
                    );

            return(peerManager);
        }
        public void RegistrationTests()
        {
            var keySeed             = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
            var keysInterface       = new KeysManager(keySeed, DateTime.UnixEpoch);
            var logger              = new TestLogger();
            var broadcaster         = new TestBroadcaster();
            var feeEstiamtor        = new TestFeeEstimator();
            var n                   = NBitcoin.Network.TestNet;
            var chainWatchInterface = new ChainWatchInterfaceUtil(n);

            using var blockNotifier      = BlockNotifier.Create(chainWatchInterface);
            using var manyChannelMonitor =
                      ManyChannelMonitor.Create(n, chainWatchInterface, broadcaster, logger, feeEstiamtor);

            blockNotifier.RegisterManyChannelMonitor(manyChannelMonitor);

            using var channelManager = ChannelManager.Create(n, UserConfig.GetDefault(), chainWatchInterface, keysInterface, logger, broadcaster, feeEstiamtor, 0, manyChannelMonitor);
            blockNotifier.RegisterChannelManager(channelManager);

            // second block in testnet3
            var block = (Block.Parse("0100000006128e87be8b1b4dea47a7247d5528d2702c96826c7a648497e773b800000000e241352e3bec0a95a6217e10c3abb54adfa05abb12c126695595580fb92e222032e7494dffff001d00d235340101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0432e7494d010e062f503253482fffffffff0100f2052a010000002321038a7f6ef1c8ca0c588aa53fa860128077c9e6c11e6830f4d7ee4e763a56b7718fac00000000", n));

            blockNotifier.BlockConnected(block, 1);

            var b = manyChannelMonitor.Serialize(_pool);

            var(manyChannelMonitor2, keyToHeaderHash) = ManyChannelMonitor.Deserialize(new ManyChannelMonitorReadArgs(chainWatchInterface, broadcaster, logger, feeEstiamtor, n), b.AsMemory(), _pool);
            using (manyChannelMonitor2)
            {
                Assert.True(NBitcoin.Utils.ArrayEqual(b, manyChannelMonitor2.Serialize(_pool)));
                // without creating any channel, it will result to empty.
                Assert.Empty(keyToHeaderHash);
            }

            blockNotifier.UnregisterManyChannelMonitor(manyChannelMonitor);
            blockNotifier.UnregisterChannelManager(channelManager);
        }
Example #7
0
        public async Task BlockNotifierTestsAsync()
        {
            using HostedServices services = new();
            var coreNode = await TestNodeBuilder.CreateAsync(services);

            await services.StartAllAsync();

            try
            {
                var rpc        = coreNode.RpcClient;
                var walletName = "wallet.dat";
                await rpc.CreateWalletAsync(walletName);

                BlockNotifier notifier = services.FirstOrDefault <BlockNotifier>();

                // Make sure we get notification for one block.
                EventAwaiter <Block> blockEventAwaiter = new(h => notifier.OnBlock += h, h => notifier.OnBlock -= h);

                var hash  = (await rpc.GenerateAsync(1)).First();
                var block = await blockEventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

                Assert.Equal(hash, block.GetHash());

                // Make sure we get notifications about 10 blocks created at the same time.
                var blockNum = 10;
                EventsAwaiter <Block> blockEventsAwaiter = new(h => notifier.OnBlock += h, h => notifier.OnBlock -= h, blockNum);

                var hashes = (await rpc.GenerateAsync(blockNum)).ToArray();

                var arrivedBlocks = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();

                for (int i = 0; i < hashes.Length; i++)
                {
                    var expected = hashes[i];
                    var actual   = arrivedBlocks[i].GetHash();
                    Assert.Equal(expected, actual);
                }

                // Make sure we get reorg notifications.
                var reorgNum    = 3;
                var newBlockNum = reorgNum + 1;
                EventsAwaiter <uint256> reorgEventsAwaiter = new(h => notifier.OnReorg += h, h => notifier.OnReorg -= h, reorgNum);
                blockEventsAwaiter = new(
                    h => notifier.OnBlock += h,
                    h => notifier.OnBlock -= h,
                    newBlockNum);

                var reorgedHashes = hashes.TakeLast(reorgNum).ToArray();
                await rpc.InvalidateBlockAsync(reorgedHashes[0]);

                var newHashes = (await rpc.GenerateAsync(newBlockNum)).ToArray();

                var reorgedHeaders = (await reorgEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();
                var newBlocks      = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();

                reorgedHashes = reorgedHashes.Reverse().ToArray();
                for (int i = 0; i < reorgedHashes.Length; i++)
                {
                    var expected = reorgedHashes[i];
                    var actual   = reorgedHeaders[i];
                    Assert.Equal(expected, actual);
                }

                for (int i = 0; i < newHashes.Length; i++)
                {
                    var expected = newHashes[i];
                    var actual   = newBlocks[i].GetHash();
                    Assert.Equal(expected, actual);
                }
            }
            finally
            {
                await services.StopAllAsync();

                await coreNode.TryStopAsync();
            }
        }
        private volatile bool _disposedValue = false;         // To detect redundant calls

        public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, IRPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network       = Guard.NotNull(nameof(network), network);
            BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
            FolderPath    = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient     = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig   = Guard.NotNull(nameof(roundConfig), roundConfig);

            Rounds         = new List <CoordinatorRound>();
            RoundsListLock = new AsyncLock();

            CoinJoins                  = new List <uint256>();
            UnconfirmedCoinJoins       = new List <uint256>();
            CoinJoinsLock              = new AsyncLock();
            LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow;

            Directory.CreateDirectory(FolderPath);

            UtxoReferee = new UtxoReferee(Network, FolderPath, RpcClient, RoundConfig);

            if (File.Exists(CoinJoinsFilePath))
            {
                try
                {
                    var getTxTasks = new List <(Task <Transaction> txTask, string line)>();
                    var batch      = RpcClient.PrepareBatch();

                    var      toRemove = new List <string>();
                    string[] allLines = File.ReadAllLines(CoinJoinsFilePath);
                    foreach (string line in allLines)
                    {
                        try
                        {
                            getTxTasks.Add((batch.GetRawTransactionAsync(uint256.Parse(line)), line));
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    batch.SendBatchAsync().GetAwaiter().GetResult();

                    foreach (var(txTask, line) in getTxTasks)
                    {
                        try
                        {
                            var tx = txTask.GetAwaiter().GetResult();
                            CoinJoins.Add(tx.GetHash());
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    if (toRemove.Count != 0)                     // a little performance boost, it'll be empty almost always
                    {
                        var newAllLines = allLines.Where(x => !toRemove.Contains(x));
                        File.WriteAllLines(CoinJoinsFilePath, newAllLines);
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"CoinJoins file got corrupted. Deleting {CoinJoinsFilePath}. {ex.GetType()}: {ex.Message}");
                    File.Delete(CoinJoinsFilePath);
                }

                uint256[] mempoolHashes = RpcClient.GetRawMempoolAsync().GetAwaiter().GetResult();
                UnconfirmedCoinJoins.AddRange(CoinJoins.Intersect(mempoolHashes));
            }

            try
            {
                string roundCountFilePath = Path.Combine(folderPath, "RoundCount.txt");
                if (File.Exists(roundCountFilePath))
                {
                    string roundCount = File.ReadAllText(roundCountFilePath);
                    CoordinatorRound.RoundCount = long.Parse(roundCount);
                }
                else
                {
                    // First time initializes (so the first constructor will increment it and we'll start from 1.)
                    CoordinatorRound.RoundCount = 0;
                }
            }
            catch (Exception ex)
            {
                CoordinatorRound.RoundCount = 0;
                Logger.LogInfo($"{nameof(CoordinatorRound.RoundCount)} file was corrupt. Resetting to 0.");
                Logger.LogDebug(ex);
            }

            BlockNotifier.OnBlock += BlockNotifier_OnBlockAsync;
        }