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);
            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;
        }
Exemplo n.º 2
0
    public void ServerTipHeightTests()
    {
        SmartHeaderChain chain = new();

        Assert.Equal(0u, chain.ServerTipHeight);

        chain.SetServerTipHeight(2);
        Assert.Equal(2, chain.HashesLeft);

        // Add first header.
        SmartHeader header = CreateGenesisHeader();

        chain.AppendTip(header);
        Assert.Equal(2, chain.HashesLeft);

        // Add second header.
        header = CreateSmartHeader(new uint256(1), chain.TipHash !, height: 1);
        chain.AppendTip(header);
        Assert.Equal(1, chain.HashesLeft);

        // Add third header.
        header = CreateSmartHeader(new uint256(2), chain.TipHash !, height: 2);
        chain.AppendTip(header);
        Assert.Equal(0, chain.HashesLeft);

        // Add fourth header. Hashes left should not report negative numbers
        header = CreateSmartHeader(new uint256(3), chain.TipHash !, height: 3);
        chain.AppendTip(header);
        Assert.Equal(0, chain.HashesLeft);
    }
Exemplo n.º 3
0
    public void AddAndRemoveTests()
    {
        SmartHeaderChain chain = new();

        AssertEverythingDefault(chain);

        // Attempt to remove an element when there is none.
        Assert.False(chain.RemoveTip());
        AssertEverythingDefault(chain);

        SmartHeader header = CreateGenesisHeader();

        chain.AppendTip(header);

        for (uint i = 0; i < 5000; i++)
        {
            uint height = chain.TipHeight + 1;
            header = CreateSmartHeader(new uint256(height), chain.TipHash !, height);
            chain.AppendTip(header);
        }

        for (uint i = 0; i < 3000; i++)
        {
            Assert.True(chain.RemoveTip());
        }

        for (uint i = 0; i < 500; i++)
        {
            uint height = chain.TipHeight + 1;
            header = CreateSmartHeader(new uint256(height), chain.TipHash !, height);
            chain.AppendTip(header);
        }

        Assert.Equal(2500u, chain.Tip !.Height);
    }
Exemplo n.º 4
0
    public void HashCountTests()
    {
        SmartHeaderChain chain = new(maxChainSize : 2);

        Assert.Equal(0u, chain.ServerTipHeight);

        // Add 1st header.
        SmartHeader header = CreateGenesisHeader();

        chain.AppendTip(header);
        Assert.Equal(1, chain.HashCount);

        // Add 2nd header.
        header = CreateSmartHeader(new uint256(1), chain.TipHash !, height: 1);
        chain.AppendTip(header);
        Assert.Equal(2, chain.HashCount);

        // Add 3rd header.
        header = CreateSmartHeader(new uint256(2), chain.TipHash !, height: 2);
        chain.AppendTip(header);
        Assert.Equal(3, chain.HashCount);

        // Add 4th header.
        header = CreateSmartHeader(new uint256(3), chain.TipHash !, height: 3);
        chain.AppendTip(header);
        Assert.Equal(4, chain.HashCount);
    }
		public IndexBuilderService(RPCClient rpc, BlockNotifier blockNotifier, string indexFilePath, string bech32UtxoSetFilePath)
		{
			RpcClient = Guard.NotNull(nameof(rpc), rpc);
			BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
			IndexFilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(indexFilePath), indexFilePath);
			Bech32UtxoSetFilePath = Guard.NotNullOrEmptyOrWhitespace(nameof(bech32UtxoSetFilePath), bech32UtxoSetFilePath);

			Bech32UtxoSet = new Dictionary<OutPoint, UtxoEntry>();
			Bech32UtxoSetHistory = new List<ActionHistoryHelper>(capacity: 100);
			Index = new List<FilterModel>();
			IndexLock = new AsyncLock();

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

			_running = 0;

			IoHelpers.EnsureContainingDirectoryExists(IndexFilePath);
			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);
					}
				}
			}

			IoHelpers.EnsureContainingDirectoryExists(bech32UtxoSetFilePath);
			if (File.Exists(bech32UtxoSetFilePath))
			{
				if (RpcClient.Network == Network.RegTest)
				{
					File.Delete(bech32UtxoSetFilePath); // RegTest is not a global ledger, better to delete it.
				}
				else
				{
					foreach (var line in File.ReadAllLines(Bech32UtxoSetFilePath))
					{
						var parts = line.Split(':');

						var txHash = new uint256(parts[0]);
						var nIn = int.Parse(parts[1]);
						var script = new Script(ByteHelpers.FromHex(parts[2]), true);
						OutPoint outPoint = new OutPoint(txHash, nIn);
						var utxoEntry = new UtxoEntry(outPoint, script);
						Bech32UtxoSet.Add(outPoint, utxoEntry);
					}
				}
			}

			BlockNotifier.OnBlock += BlockNotifier_OnBlock;
		}
Exemplo n.º 6
0
    public void InvalidAddTests()
    {
        SmartHeaderChain chain = new();

        AssertEverythingDefault(chain);

        // Attempt to remove an element when there is none.
        Assert.False(chain.RemoveTip());
        AssertEverythingDefault(chain);

        SmartHeader header = CreateGenesisHeader();

        chain.AppendTip(header);

        InvalidOperationException ex = Assert.Throws <InvalidOperationException>(() => chain.AppendTip(header));

        Assert.StartsWith("Header doesn't point to previous header.", ex.Message, StringComparison.Ordinal);
    }
Exemplo n.º 7
0
    private async Task SetInitValuesAsync(bool isBackendAvailable)
    {
        while (isBackendAvailable && Services.Synchronizer.LastResponse is null)
        {
            await Task.Delay(500).ConfigureAwait(false);
        }

        _filtersToDownloadCount = (uint)Services.BitcoinStore.SmartHeaderChain.HashesLeft;

        if (Services.BitcoinStore.SmartHeaderChain.ServerTipHeight is { } serverTipHeight&&
            Services.BitcoinStore.SmartHeaderChain.TipHeight is { } clientTipHeight)
        {
            var tipHeight      = Math.Max(serverTipHeight, clientTipHeight);
            var startingHeight = SmartHeader.GetStartingHeader(_wallet.Network).Height;
            var bestHeight     = (uint)_wallet.KeyManager.GetBestHeight().Value;
            _filterProcessStartingHeight = bestHeight < startingHeight ? startingHeight : bestHeight;

            _filtersToProcessCount = tipHeight - _filterProcessStartingHeight;
        }
    }
Exemplo n.º 8
0
    public void ReplaceTests()
    {
        SmartHeaderChain chain = new();

        SmartHeader header = CreateGenesisHeader();

        chain.AppendTip(header);

        uint height = 1;

        // Add new header.
        header = CreateSmartHeader(new uint256(465465465), chain.TipHash !, height);
        chain.AppendTip(header);

        // Attempt to replace the newly added header.
        header = CreateSmartHeader(new uint256(778797897), chain.TipHash !, height);

        InvalidOperationException ex = Assert.Throws <InvalidOperationException>(() => chain.AppendTip(header));

        Assert.StartsWith("Header height isn't one more than the previous header height.", ex.Message, StringComparison.Ordinal);
    }
Exemplo n.º 9
0
        private void ShowFilterProcessingStatus(CompositeDisposable disposables)
        {
            _stopwatch ??= Stopwatch.StartNew();

            Observable.Interval(TimeSpan.FromSeconds(1))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ =>
            {
                var segwitActivationHeight = SmartHeader.GetStartingHeader(_wallet.Network).Height;
                if (_wallet.LastProcessedFilter?.Header?.Height is { } lastProcessedFilterHeight &&
                    lastProcessedFilterHeight > segwitActivationHeight &&
                    Services.BitcoinStore.SmartHeaderChain.TipHeight is { } tipHeight &&
                    tipHeight > segwitActivationHeight)
                {
                    var allFilters       = tipHeight - segwitActivationHeight;
                    var processedFilters = lastProcessedFilterHeight - segwitActivationHeight;

                    UpdateStatus(allFilters, processedFilters, _stopwatch.ElapsedMilliseconds);
                }
            })
            .DisposeWith(disposables);
        }
Exemplo n.º 10
0
        public static FilterModel GetStartingFilter(Network network)
        {
            var startingHeader = SmartHeader.GetStartingHeader(network);

            if (network == NBitcoin.Altcoins.Litecoin.Instance.Mainnet)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{startingHeader.BlockHash}:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == NBitcoin.Altcoins.Litecoin.Instance.Testnet)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{startingHeader.BlockHash}:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == NBitcoin.Altcoins.Litecoin.Instance.Regtest)
            {
                GolombRiceFilter filter = IndexBuilderService.CreateDummyEmptyFilter(startingHeader.BlockHash);
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{filter}:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }
        }
Exemplo n.º 11
0
        public static FilterModel GetStartingFilter(Network network)
        {
            var startingHeader = SmartHeader.GetStartingHeader(network);

            if (network == Network.Main)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:02832810ec08a0:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == Network.TestNet)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == Network.RegTest)
            {
                GolombRiceFilter filter = IndexBuilderService.CreateDummyEmptyFilter(startingHeader.BlockHash);
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{filter.ToString()}:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }
        }
        public static FilterModel GetStartingFilter(Network network)
        {
            var startingHeader = SmartHeader.GetStartingHeader(network);

            if (network == Network.Main)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:fd37025542ac13565ae0612c2d2c178df07d05402c527fd91942a68300e20853315e278ebc861f7ec7e20ee5c7ea39197ab66ec38c6cacfb7a960e87eafe739fbb58e703811b2ab0cc9ac55c34f3a6d70536776a34679680d5539e3ff089a08d6fed732e3c26e4b5527790cd14a7bd83c8142b5cf4f10fbd09baed653cb4ebf9100bcc5d30878b1aaa01a00230d505004e5d686fd752ec8624f88d640867cab753ca1d3b7829b05a2c20606279242688ba319c2173a5a885399b58c45127bdf976c4d45e79425ff809da944d30405f929c10186e1666bb444cdeece71133049e5e44a265d44c4122e83919d6573220f3b9bdf38ee3037e91b0ae0485707e9a8755dd530fff74338652b55aa547f3c505db5f0a1b92f95fd95852d48167ef857d55500dfb8c7209f05c1669a73f15e7bb26537462a05da4f0d803315dd43630b23019be48a556ebe50752bdd37080460254e4d6d92be80ab0ed8efd882fd0c360fbf00b387b6e5ef4d9a27267b2f51151684fa7cf3064f8e2fc918843215512c838fdd35270da33df3d203e02539f1b030863619a4fdd93b7771de31d35436a50c904225942fda79e2606a21d64dbb110c79f202d10acd94a2ac7cf24cff2dc816ac6828941abec3d9ae721c09f5a2070d3a99aba0fb1e33d7de26c3c2b7f40b002719138826a670c38d56fe5a67e827bf48865f2c34e4eafa68486ecb419118144d212d5a10ee41f26c07b2456c5a0b74fb86cb4eaec27ac2cf80847e57d711754589716c70eea0d1c595b6dd413aa588542acc5ea9d5c102f3b4aac876bb91f57c1eea063d520517bd0c48e2d79ec2164525e1c24a34e8f50e4256790395fcdd5c71b50949738ab92d44476c2073e16bedb4b31517d2264ae4d824b0990fe316919c432edc9f71c1a6a21937786b68e9e11f788fba04ad1253cfcd3148698ec8a4a1054a33aa51adcdb61c922d44ebefb6dc1aab67d07b48bb702606047230e4439bec8d1b371683be41617e4f163509b09f5cd6577956b1857a338764486017949f594c90be92783776ff0ba0d6d517d18d5842c36eee044290df2ca0ad3818f3bcd874ad23e41a77c441da273f20e3472801a4892b635d3d2a0b76ca1c0025b5283850897993410f5c654527ebfc446445ca10d0264c964b86719a108179f303165b3b5f53441d9fe9f6e1a1ae2d2d61c6c34752ca9b02b6c8fb2166dba95370ad1cace915e90cab14797a33be4454fe44dfecb015c59e76b60336192b262169db6461b26da3b03cdcca4c185e7d76bef944ea02a350449e9119aeb82ac094b87cd3fec4bd289c55bf008e3eb614017240c73fb3440de1cbd0f1e634bd997f1c10ccf97598b8d0144bd8608999a6420ff65cfb17f275b33e16d08bd5f01ff2a98b02f3cc84b1931b68b4d47ebab850824d55029e2deaab509bbe09b698675add8a5119630207c41c54a9503ea2ab15ce4446504ce19c52313733a95fed0dea0f9e56c5658d52d43f09b7a246140dac1e49aeb00d7d0b50aac0fab135249f523ee1fc59d8d2c18af69db23fc26a006451e132448bc7b671fa864897a8e23b1bf4b97397d091f8106e12174efd82e7f4479aab210f488cd4cb45a093392ecab407ad3a8836c771ec87d36d41aeeac20c36b3d39c87ea1eaba86411a78ca33f658cceadb903ec7482a395f11b7aa54eecd9da1a9a542695723fff6056ac736b788abd2498488a6737236d70e7aceecdc71e4878c2f1e390866020cf20681230f3858dd7ae44b9bdb0a3726b12db3b42b2863cf03d3e34b733d9c63e92c5b07bf942dc740a2c902621c1f74c4e00e3a36002fb8a24febc89aa5350544566de06ba81aa915e115368aabe7fdedb676082abac78a1b216d7e56c8ed0da8ada71bc2f46ade811233f9568308136700e21b5941286d0d48fe43009b48161837e5255e639d8a4225928a2637d745cbc580c9b14c50860dba2f1be321463e47cb4914c81c7b81206090aa23a73103f139dfca303447cc7140eef9a5f5235675073cdcd5635abad96de169975f2cf06689c9dc9c9d7557d504c7ec1541b493769a2106537d56d489f8800d5d76870c716678ff3672c76b8a179b0a601858b8973ed59a9ecc13f115b44d2a3251e658b95bd5f3f1aa21c57a6eb960dcbf968890:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == Network.TestNet)
            {
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else if (network == Network.RegTest)
            {
                GolombRiceFilter filter = IndexBuilderService.CreateDummyEmptyFilter(startingHeader.BlockHash);
                return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{filter}:{startingHeader.PrevHash}:{startingHeader.BlockTime.ToUnixTimeSeconds()}"));
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }
        }
Exemplo n.º 13
0
        public void StartingHeaderTests()
        {
            var startingMain = SmartHeader.GetStartingHeader(NBitcoin.Altcoins.Groestlcoin.Instance.Mainnet);
            var startingTest = SmartHeader.GetStartingHeader(NBitcoin.Altcoins.Groestlcoin.Instance.Testnet);
            var startingReg  = SmartHeader.GetStartingHeader(NBitcoin.Altcoins.Groestlcoin.Instance.Regtest);

            var  expectedHashMain     = new uint256("0000000019972e461d5c8d1c60108bcb299d274ade2be8194e0f0da6b16c7ead");
            var  expectedPrevHashMain = new uint256("00000000001937123368f42f9843ecf1d6835f449674e00d3c40d158d91f3dbe");
            uint expectedHeightMain   = 481824;
            var  expectedTimeMain     = DateTimeOffset.FromUnixTimeSeconds(1424704328);

            var  expectedHashTest     = new uint256("0000008df6e4f82cc6f1c71d461bf739e0e20a44e19e5535feabcfb79acc6136");
            var  expectedPrevHashTest = new uint256("0000000f8846debf66f5c5df0d8985800ad4ea21b557a5d01dc34b94eaa3ac70");
            uint expectedHeightTest   = 828575;
            var  expectedTimeTest     = DateTimeOffset.FromUnixTimeSeconds(1541082333);

            var  expectedHashReg     = Network.RegTest.GenesisHash;
            var  expectedPrevHashReg = uint256.Zero;
            uint expectedHeightReg   = 0;
            var  expectedTimeReg     = Network.RegTest.GetGenesis().Header.BlockTime;

            Assert.Equal(expectedHashMain, startingMain.BlockHash);
            Assert.Equal(expectedPrevHashMain, startingMain.PrevHash);
            Assert.Equal(expectedHeightMain, startingMain.Height);
            Assert.Equal(expectedTimeMain, startingMain.BlockTime);

            Assert.Equal(expectedHashTest, startingTest.BlockHash);
            Assert.Equal(expectedPrevHashTest, startingTest.PrevHash);
            Assert.Equal(expectedHeightTest, startingTest.Height);
            Assert.Equal(expectedTimeTest, startingTest.BlockTime);

            Assert.Equal(expectedHashReg, startingReg.BlockHash);
            Assert.Equal(expectedPrevHashReg, startingReg.PrevHash);
            Assert.Equal(expectedHeightReg, startingReg.Height);
            Assert.Equal(expectedTimeReg, startingReg.BlockTime);
        }
Exemplo n.º 14
0
        public void StartingHeaderTests()
        {
            var startingMain = SmartHeader.GetStartingHeader(Network.Main);
            var startingTest = SmartHeader.GetStartingHeader(Network.TestNet);
            var startingReg  = SmartHeader.GetStartingHeader(Network.RegTest);

            var  expectedHashMain     = new uint256("0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893");
            var  expectedPrevHashMain = new uint256("000000000000000000cbeff0b533f8e1189cf09dfbebf57a8ebe349362811b80");
            uint expectedHeightMain   = 481824;
            var  expectedTimeMain     = DateTimeOffset.FromUnixTimeSeconds(1503539857);

            var  expectedHashTest     = new uint256("00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a");
            var  expectedPrevHashTest = new uint256("0000000000211a4d54bceb763ea690a4171a734c48d36f7d8e30b51d6df6ea85");
            uint expectedHeightTest   = 828575;
            var  expectedTimeTest     = DateTimeOffset.FromUnixTimeSeconds(1463079943);

            var  expectedHashReg     = Network.RegTest.GenesisHash;
            var  expectedPrevHashReg = uint256.Zero;
            uint expectedHeightReg   = 0;
            var  expectedTimeReg     = Network.RegTest.GetGenesis().Header.BlockTime;

            Assert.Equal(expectedHashMain, startingMain.BlockHash);
            Assert.Equal(expectedPrevHashMain, startingMain.PrevHash);
            Assert.Equal(expectedHeightMain, startingMain.Height);
            Assert.Equal(expectedTimeMain, startingMain.BlockTime);

            Assert.Equal(expectedHashTest, startingTest.BlockHash);
            Assert.Equal(expectedPrevHashTest, startingTest.PrevHash);
            Assert.Equal(expectedHeightTest, startingTest.Height);
            Assert.Equal(expectedTimeTest, startingTest.BlockTime);

            Assert.Equal(expectedHashReg, startingReg.BlockHash);
            Assert.Equal(expectedPrevHashReg, startingReg.PrevHash);
            Assert.Equal(expectedHeightReg, startingReg.Height);
            Assert.Equal(expectedTimeReg, startingReg.BlockTime);
        }
Exemplo n.º 15
0
 public FilterModel(SmartHeader header, GolombRiceFilter filter)
 {
     Header = Guard.NotNull(nameof(header), header);
     Filter = Guard.NotNull(nameof(filter), filter);
 }
Exemplo n.º 16
0
        public void Synchronize()
        {
            // Check permissions.
            using var _ = File.Open(IndexFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);

            Task.Run(async() =>
            {
                try
                {
                    if (Interlocked.Read(ref _workerCount) >= 2)
                    {
                        return;
                    }

                    Interlocked.Increment(ref _workerCount);
                    while (Interlocked.Read(ref _workerCount) != 1)
                    {
                        await Task.Delay(100).ConfigureAwait(false);
                    }

                    if (IsStopping)
                    {
                        return;
                    }

                    try
                    {
                        Interlocked.Exchange(ref _serviceStatus, Running);

                        while (IsRunning)
                        {
                            try
                            {
                                SyncInfo syncInfo = await GetSyncInfoAsync().ConfigureAwait(false);

                                uint currentHeight;
                                uint256?currentHash = null;
                                using (await IndexLock.LockAsync())
                                {
                                    if (Index.Count != 0)
                                    {
                                        var lastIndex = Index[^ 1];
                                        currentHeight = lastIndex.Header.Height;
                                        currentHash   = lastIndex.Header.BlockHash;
                                    }
                                    else
                                    {
                                        currentHash = StartingHeight == 0
                                                                                        ? uint256.Zero
                                                                                        : await RpcClient.GetBlockHashAsync((int)StartingHeight - 1).ConfigureAwait(false);
                                        currentHeight = StartingHeight - 1;
                                    }
                                }

                                var coreNotSynced   = !syncInfo.IsCoreSynchronized;
                                var tipReached      = syncInfo.BlockCount == currentHeight;
                                var isTimeToRefresh = DateTimeOffset.UtcNow - syncInfo.BlockchainInfoUpdated > TimeSpan.FromMinutes(5);
                                if (coreNotSynced || tipReached || isTimeToRefresh)
                                {
                                    syncInfo = await GetSyncInfoAsync().ConfigureAwait(false);
                                }

                                // If wasabi filter height is the same as core we may be done.
                                if (syncInfo.BlockCount == currentHeight)
                                {
                                    // Check that core is fully synced
                                    if (syncInfo.IsCoreSynchronized && !syncInfo.InitialBlockDownload)
                                    {
                                        // Mark the process notstarted, so it can be started again
                                        // and finally block can mark it as stopped.
                                        Interlocked.Exchange(ref _serviceStatus, NotStarted);
                                        return;
                                    }
                                    else
                                    {
                                        // Knots is catching up give it a 10 seconds
                                        await Task.Delay(10000).ConfigureAwait(false);
                                        continue;
                                    }
                                }

                                uint nextHeight        = currentHeight + 1;
                                uint256 blockHash      = await RpcClient.GetBlockHashAsync((int)nextHeight).ConfigureAwait(false);
                                VerboseBlockInfo block = await RpcClient.GetVerboseBlockAsync(blockHash).ConfigureAwait(false);

                                // Check if we are still on the best chain,
                                // if not rewind filters till we find the fork.
                                if (currentHash != block.PrevBlockHash)
                                {
                                    Logger.LogWarning("Reorg observed on the network.");

                                    await ReorgOneAsync().ConfigureAwait(false);

                                    // Skip the current block.
                                    continue;
                                }

                                var filter = BuildFilterForBlock(block);

                                var smartHeader = new SmartHeader(block.Hash, block.PrevBlockHash, nextHeight, block.BlockTime);
                                var filterModel = new FilterModel(smartHeader, filter);

                                await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() }).ConfigureAwait(false);

                                using (await IndexLock.LockAsync())
                                {
                                    Index.Add(filterModel);
                                }

                                // If not close to the tip, just log debug.
                                if (syncInfo.BlockCount - nextHeight <= 3 || nextHeight % 100 == 0)
                                {
                                    Logger.LogInfo($"Created filter for block: {nextHeight}.");
                                }
                                else
                                {
                                    Logger.LogDebug($"Created filter for block: {nextHeight}.");
                                }
                                LastFilterBuildTime = DateTimeOffset.UtcNow;
                            }
Exemplo n.º 17
0
		public void Synchronize()
		{
			Task.Run(async () =>
			{
				try
				{
					if (Interlocked.Read(ref _runner) >= 2)
					{
						return;
					}

					Interlocked.Increment(ref _runner);
					while (Interlocked.Read(ref _runner) != 1)
					{
						await Task.Delay(100);
					}

					if (Interlocked.Read(ref _running) >= 2)
					{
						return;
					}

					try
					{
						Interlocked.Exchange(ref _running, 1);

						var isImmature = false; // The last 100 blocks are reorgable. (Assume it is mature at first.)
						SyncInfo syncInfo = null;
						while (IsRunning)
						{
							try
							{
								// If we did not yet initialized syncInfo, do so.
								if (syncInfo is null)
								{
									syncInfo = await GetSyncInfoAsync();
								}

								uint heightToRequest = StartingHeight;
								uint256 currentHash = null;
								using (await IndexLock.LockAsync())
								{
									if (Index.Count != 0)
									{
										var lastIndex = Index.Last();
										heightToRequest = lastIndex.Header.Height + 1;
										currentHash = lastIndex.Header.BlockHash;
									}
								}

								// If not synchronized or already 5 min passed since last update, get the latest blockchain info.
								if (!syncInfo.IsCoreSynchornized || syncInfo.BlockchainInfoUpdated - DateTimeOffset.UtcNow > TimeSpan.FromMinutes(5))
								{
									syncInfo = await GetSyncInfoAsync();
								}

								if (syncInfo.BlockCount - heightToRequest <= 100)
								{
									// Both Wasabi and our Core node is in sync. Start doing stuff through P2P from now on.
									if (syncInfo.IsCoreSynchornized && syncInfo.BlockCount == heightToRequest - 1)
									{
										syncInfo = await GetSyncInfoAsync();
										// Double it to make sure not to accidentally miss any notification.
										if (syncInfo.IsCoreSynchornized && syncInfo.BlockCount == heightToRequest - 1)
										{
											// Mark the process notstarted, so it can be started again and finally block can mark it is stopped.
											Interlocked.Exchange(ref _running, 0);
											return;
										}
									}

									// Mark the synchronizing process is working with immature blocks from now on.
									isImmature = true;
								}

								Block block = await RpcClient.GetBlockAsync(heightToRequest);

								// Reorg check, except if we're requesting the starting height, because then the "currentHash" wouldn't exist.
								if (heightToRequest != StartingHeight && currentHash != block.Header.HashPrevBlock)
								{
									// Reorg can happen only when immature. (If it'd not be immature, that'd be a huge issue.)
									if (isImmature)
									{
										await ReorgOneAsync();
									}
									else
									{
										Logger.LogCritical("This is something serious! Over 100 block reorg is noticed! We cannot handle that!");
									}

									// Skip the current block.
									continue;
								}

								if (isImmature)
								{
									PrepareBech32UtxoSetHistory();
								}

								var scripts = new HashSet<Script>();

								foreach (var tx in block.Transactions)
								{
									// If stop was requested return.
									// Because this tx iteration can take even minutes
									// It does not need to be accessed with a thread safe fashion with Interlocked through IsRunning, this may have some performance benefit
									if (_running != 1)
									{
										return;
									}

									for (int i = 0; i < tx.Outputs.Count; i++)
									{
										var output = tx.Outputs[i];
										if (output.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
										{
											var outpoint = new OutPoint(tx.GetHash(), i);
											var utxoEntry = new UtxoEntry(outpoint, output.ScriptPubKey);
											Bech32UtxoSet.Add(outpoint, utxoEntry);
											if (isImmature)
											{
												Bech32UtxoSetHistory.Last().StoreAction(Operation.Add, outpoint, output.ScriptPubKey);
											}
											scripts.Add(output.ScriptPubKey);
										}
									}

									foreach (var input in tx.Inputs)
									{
										OutPoint prevOut = input.PrevOut;
										if (Bech32UtxoSet.TryGetValue(prevOut, out UtxoEntry foundUtxoEntry))
										{
											var foundScript = foundUtxoEntry.Script;
											Bech32UtxoSet.Remove(prevOut);
											if (isImmature)
											{
												Bech32UtxoSetHistory.Last().StoreAction(Operation.Remove, prevOut, foundScript);
											}
											scripts.Add(foundScript);
										}
									}
								}

								GolombRiceFilter filter;
								if (scripts.Any())
								{
									filter = new GolombRiceFilterBuilder()
										.SetKey(block.GetHash())
										.SetP(20)
										.SetM(1 << 20)
										.AddEntries(scripts.Select(x => x.ToCompressedBytes()))
										.Build();
								}
								else
								{
									// We cannot have empty filters, because there was a bug in GolombRiceFilterBuilder that evaluates empty filters to true.
									// And this must be fixed in a backwards compatible way, so we create a fake filter with a random scp instead.
									filter = CreateDummyEmptyFilter(block.GetHash());
								}

								var smartHeader = new SmartHeader(block.GetHash(), block.Header.HashPrevBlock, heightToRequest, block.Header.BlockTime);
								var filterModel = new FilterModel(smartHeader, filter);

								await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
								using (await IndexLock.LockAsync())
								{
									Index.Add(filterModel);
								}
								if (File.Exists(Bech32UtxoSetFilePath))
								{
									File.Delete(Bech32UtxoSetFilePath);
								}
								var bech32UtxoSetLines = Bech32UtxoSet.Select(entry => entry.Value.Line);

								// Keep it sync unless you fix the performance issue with async.
								File.WriteAllLines(Bech32UtxoSetFilePath, bech32UtxoSetLines);

								// If not close to the tip, just log debug.
								// Use height.Value instead of simply height, because it cannot be negative height.
								if (syncInfo.BlockCount - heightToRequest <= 3 || heightToRequest % 100 == 0)
								{
									Logger.LogInfo($"Created filter for block: {heightToRequest}.");
								}
								else
								{
									Logger.LogDebug($"Created filter for block: {heightToRequest}.");
								}
							}
							catch (Exception ex)
							{
								Logger.LogDebug(ex);
							}
						}
					}
					finally
					{
						Interlocked.CompareExchange(ref _running, 3, 2); // If IsStopping, make it stopped.
						Interlocked.Decrement(ref _runner);
					}
				}
				catch (Exception ex)
				{
					Logger.LogError($"Synchronization attempt failed to start: {ex}");
				}
			});
		}
Exemplo n.º 18
0
        public void Synchronize()
        {
            Task.Run(async() =>
            {
                try
                {
                    if (Interlocked.Read(ref _workerCount) >= 2)
                    {
                        return;
                    }

                    Interlocked.Increment(ref _workerCount);
                    while (Interlocked.Read(ref _workerCount) != 1)
                    {
                        await Task.Delay(100);
                    }

                    if (IsStopping)
                    {
                        return;
                    }

                    try
                    {
                        Interlocked.Exchange(ref _serviceStatus, Running);

                        SyncInfo syncInfo = null;
                        while (IsRunning)
                        {
                            try
                            {
                                // If we did not yet initialized syncInfo, do so.
                                if (syncInfo is null)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                uint currentHeight  = 0;
                                uint256 currentHash = null;
                                using (await IndexLock.LockAsync())
                                {
                                    if (Index.Count != 0)
                                    {
                                        var lastIndex = Index[^ 1];
                                        currentHeight = lastIndex.Header.Height;
                                        currentHash   = lastIndex.Header.BlockHash;
                                    }
                                    else
                                    {
                                        currentHash = StartingHeight == 0
                                                                                        ? uint256.Zero
                                                                                        : await RpcClient.GetBlockHashAsync((int)StartingHeight - 1);
                                        currentHeight = StartingHeight - 1;
                                    }
                                }

                                var coreNotSynced   = !syncInfo.IsCoreSynchornized;
                                var tipReached      = syncInfo.BlockCount == currentHeight;
                                var isTimeToRefresh = DateTimeOffset.UtcNow - syncInfo.BlockchainInfoUpdated > TimeSpan.FromMinutes(5);
                                if (coreNotSynced || tipReached || isTimeToRefresh)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                // If wasabi filter height is the same as core we may be done.
                                if (syncInfo.BlockCount == currentHeight)
                                {
                                    // Check that core is fully synced
                                    if (syncInfo.IsCoreSynchornized && !syncInfo.InitialBlockDownload)
                                    {
                                        // Mark the process notstarted, so it can be started again
                                        // and finally block can mark it as stopped.
                                        Interlocked.Exchange(ref _serviceStatus, NotStarted);
                                        return;
                                    }
                                    else
                                    {
                                        // Knots is catching up give it a 10 seconds
                                        await Task.Delay(10000);
                                        continue;
                                    }
                                }

                                uint nextHeight        = currentHeight + 1;
                                uint256 blockHash      = await RpcClient.GetBlockHashAsync((int)nextHeight);
                                VerboseBlockInfo block = await RpcClient.GetVerboseBlockAsync(blockHash);

                                // Check if we are still on the best chain,
                                // if not rewind filters till we find the fork.
                                if (currentHash != block.PrevBlockHash)
                                {
                                    Logger.LogWarning("Reorg observed on the network.");

                                    await ReorgOneAsync();

                                    // Skip the current block.
                                    continue;
                                }

                                var scripts = FetchScripts(block);

                                GolombRiceFilter filter;
                                if (scripts.Any())
                                {
                                    filter = new GolombRiceFilterBuilder()
                                             .SetKey(block.Hash)
                                             .SetP(20)
                                             .SetM(1 << 20)
                                             .AddEntries(scripts.Select(x => x.ToCompressedBytes()))
                                             .Build();
                                }
                                else
                                {
                                    // We cannot have empty filters, because there was a bug in GolombRiceFilterBuilder that evaluates empty filters to true.
                                    // And this must be fixed in a backwards compatible way, so we create a fake filter with a random scp instead.
                                    filter = CreateDummyEmptyFilter(block.Hash);
                                }

                                var smartHeader = new SmartHeader(block.Hash, block.PrevBlockHash, nextHeight, block.BlockTime);
                                var filterModel = new FilterModel(smartHeader, filter);

                                await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });

                                using (await IndexLock.LockAsync())
                                {
                                    Index.Add(filterModel);
                                }

                                // If not close to the tip, just log debug.
                                if (syncInfo.BlockCount - nextHeight <= 3 || nextHeight % 100 == 0)
                                {
                                    Logger.LogInfo($"Created filter for block: {nextHeight}.");
                                }
                                else
                                {
                                    Logger.LogDebug($"Created filter for block: {nextHeight}.");
                                }
                            }
Exemplo n.º 19
0
 public FilterModel(SmartHeader header, GolombRiceFilter filter)
 {
     Header  = header;
     _filter = new Lazy <GolombRiceFilter>(filter);
 }
Exemplo n.º 20
0
        public void Initialize(NodesCollection nodes, WasabiSynchronizer synchronizer)
        {
            Nodes          = nodes;
            Synchronizer   = synchronizer;
            HashChain      = synchronizer.BitcoinStore.SmartHeaderChain;
            UseTor         = Global.Config.UseTor;     // Do not make it dynamic, because if you change this config settings only next time will it activate.
            UseBitcoinCore = Global.Config.StartLocalBitcoinCoreOnStartup;
            var hostedServices = Global.HostedServices;

            var updateChecker = hostedServices.FirstOrDefault <UpdateChecker>();

            Guard.NotNull(nameof(updateChecker), updateChecker);
            UpdateStatus = updateChecker.UpdateStatus;

            var rpcMonitor = hostedServices.FirstOrDefault <RpcMonitor>();

            BitcoinCoreStatus = rpcMonitor?.RpcStatus ?? RpcStatus.Unresponsive;

            _status = ActiveStatuses.WhenAnyValue(x => x.CurrentStatus)
                      .Select(x => x.ToString())
                      .ObserveOn(RxApp.MainThreadScheduler)
                      .ToProperty(this, x => x.Status)
                      .DisposeWith(Disposables);

            Observable
            .Merge(Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Added)).Select(x => true)
                   .Merge(Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Removed)).Select(x => true)
                          .Merge(Synchronizer.WhenAnyValue(x => x.TorStatus).Select(x => true))))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => Peers = Synchronizer.TorStatus == TorStatus.NotRunning ? 0 : Nodes.Count)                     // Set peers to 0 if Tor is not running, because we get Tor status from backend answer so it seems to the user that peers are connected over clearnet, while they are not.
            .DisposeWith(Disposables);

            Peers = Tor == TorStatus.NotRunning ? 0 : Nodes.Count;

            Observable.FromEventPattern <bool>(typeof(WalletService), nameof(WalletService.DownloadingBlockChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(x => DownloadingBlock = x.EventArgs)
            .DisposeWith(Disposables);

            IDisposable walletCheckingInterval = null;

            Observable.FromEventPattern <bool>(typeof(WalletService), nameof(WalletService.InitializingChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(x =>
            {
                if (x.EventArgs)
                {
                    TryAddStatus(StatusType.WalletLoading);

                    if (walletCheckingInterval is null)
                    {
                        walletCheckingInterval = Observable.Interval(TimeSpan.FromSeconds(1))
                                                 .ObserveOn(RxApp.MainThreadScheduler)
                                                 .Subscribe(_ =>
                        {
                            var global        = Global;
                            var walletService = global?.WalletService;
                            if (walletService is { })
                            {
                                var segwitActivationHeight = SmartHeader.GetStartingHeader(walletService.Network).Height;
                                if (walletService.LastProcessedFilter?.Header?.Height is uint lastProcessedFilterHeight &&
                                    lastProcessedFilterHeight > segwitActivationHeight &&
                                    global?.BitcoinStore?.SmartHeaderChain?.TipHeight is uint tipHeight &&
                                    tipHeight > segwitActivationHeight)
                                {
                                    var allFilters       = tipHeight - segwitActivationHeight;
                                    var processedFilters = lastProcessedFilterHeight - segwitActivationHeight;
                                    var perc             = allFilters == 0 ?
                                                           100
                                                                                                : ((decimal)processedFilters / allFilters * 100);
                                    TryAddStatus(StatusType.WalletProcessingFilters, (ushort)perc);
                                }

                                var txProcessor = walletService.TransactionProcessor;
                                if (txProcessor is { })
                                {
                                    var perc = txProcessor.QueuedTxCount == 0 ?
                                               100
                                                                                                : ((decimal)txProcessor.QueuedProcessedTxCount / txProcessor.QueuedTxCount * 100);
                                    TryAddStatus(StatusType.WalletProcessingTransactions, (ushort)perc);
                                }
                            }
Exemplo n.º 21
0
 public FilterModel(SmartHeader header, Lazy <GolombRiceFilter> filter)
 {
     Header  = header;
     _filter = filter;
 }
        public StatusViewModel()
            : base(Locator.Current.GetService <IViewStackService>())
        {
            Global         = Locator.Current.GetService <Global>();
            Backend        = BackendStatus.NotConnected;
            UseTor         = false;
            Tor            = TorStatus.NotRunning;
            Peers          = 0;
            BtcPrice       = "$0";
            ActiveStatuses = new StatusSet();

            UseTor = Global.Config.UseTor; // Do not make it dynamic, because if you change this config settings only next time will it activate.

            _status = ActiveStatuses.WhenAnyValue(x => x.CurrentStatus)
                      .Select(x => x.ToString())
                      .ObserveOn(RxApp.MainThreadScheduler)
                      .ToProperty(this, x => x.Status)
                      .DisposeWith(Disposables);

            bool progressReset = true;

            _progressPercent = this.WhenAnyValue(x => x.ActiveStatuses.CurrentStatus, x => x.Peers)
                               .Select(tup =>
            {
                var(status, peers) = tup;
                if (peers == 0 && progressReset)
                {
                    progressReset = false;
                    return(0.01);
                }

                switch (status.Type)
                {
                case StatusType.Ready:
                    progressReset = true;
                    return(1);

                case StatusType.Synchronizing:
                    return(status.Percentage / 200.0 + 0.3);

                case StatusType.Connecting:
                default:
                    return(0.3);
                }
            })
                               .ToProperty(this, x => x.ProgressPercent);

            if (Global.IsInitialized)
            {
                OnInitialized(this, EventArgs.Empty);
            }
            else
            {
                Global.Initialized += OnInitialized;
            }

            Peers = Tor == TorStatus.NotRunning ? 0 : Nodes.Count;

            Observable.FromEventPattern <bool>(typeof(P2pBlockProvider), nameof(P2pBlockProvider.DownloadingBlockChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(x => DownloadingBlock = x.EventArgs)
            .DisposeWith(Disposables);

            IDisposable walletCheckingInterval = null;

            Observable.FromEventPattern <bool>(typeof(Wallet), nameof(Wallet.InitializingChanged))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(x =>
            {
                if (x.EventArgs)
                {
                    TryAddStatus(StatusType.WalletLoading);

                    if (walletCheckingInterval is null)
                    {
                        walletCheckingInterval = Observable.Interval(TimeSpan.FromSeconds(1))
                                                 .ObserveOn(RxApp.MainThreadScheduler)
                                                 .Subscribe(_ =>
                        {
                            var wallet = Global.Wallet;
                            if (wallet is { })
                            {
                                var startingHeight = SmartHeader.GetStartingHeader(wallet.Network).Height;
                                if (wallet.LastProcessedFilter?.Header?.Height is uint lastProcessedFilterHeight &&
                                    lastProcessedFilterHeight > startingHeight &&
                                    Global.BitcoinStore?.SmartHeaderChain?.TipHeight is uint tipHeight &&
                                    tipHeight > startingHeight)
                                {
                                    var allFilters       = tipHeight - startingHeight;
                                    var processedFilters = lastProcessedFilterHeight - startingHeight;
                                    var perc             = allFilters == 0 ?
                                                           100
                                                : ((decimal)processedFilters / allFilters * 100);
                                    TryAddStatus(StatusType.WalletProcessingFilters, (ushort)perc);
                                }

                                var txProcessor = wallet.TransactionProcessor;
                                if (txProcessor is { })
                                {
                                    var perc = txProcessor.QueuedTxCount == 0 ?
                                               100
                                                : ((decimal)txProcessor.QueuedProcessedTxCount / txProcessor.QueuedTxCount * 100);
                                    TryAddStatus(StatusType.WalletProcessingTransactions, (ushort)perc);
                                }
                            }