Ejemplo n.º 1
0
        public void OutPointCacheCanRetrieveExisting()
        {
            const string CollectionName = "DummyCollection";
            var          dataFolder     = new DataFolder(TestBase.CreateTestDir(this));
            string       dbPath         = Path.Combine(dataFolder.RootPath, CollectionName);
            FileMode     fileMode       = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? FileMode.Exclusive : FileMode.Shared;

            var database = new LiteDatabase(new ConnectionString()
            {
                Filename = dbPath, Mode = fileMode
            });
            var cache = new AddressIndexerOutpointsRepository(database, new ExtendedLoggerFactory());

            var outPoint = new OutPoint(uint256.Parse("0000af9ab2c8660481328d0444cf167dfd31f24ca2dbba8e5e963a2434cffa93"), 0);

            var data = new OutPointData()
            {
                Outpoint = outPoint.ToString(), ScriptPubKeyBytes = new byte[] { 0, 0, 0, 0 }, Money = Money.Coins(1)
            };

            cache.AddOutPointData(data);

            Assert.True(cache.TryGetOutPointData(outPoint, out OutPointData retrieved));

            Assert.NotNull(retrieved);
            Assert.Equal(outPoint.ToString(), retrieved.Outpoint);
        }
Ejemplo n.º 2
0
        public async Task CanUseCoinSelection()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                var userId  = s.RegisterNewUser(true);
                var storeId = s.CreateNewStore().storeId;
                s.GenerateWallet("BTC", "", false, true);
                var walletId = new WalletId(storeId, "BTC");
                s.GoToWalletReceive(walletId);
                s.Driver.FindElement(By.Id("generateButton")).Click();
                var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
                var address    = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
                await s.Server.ExplorerNode.GenerateAsync(1);

                for (int i = 0; i < 6; i++)
                {
                    await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
                }
                var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));

                var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);

                var spentOutpoint = new OutPoint(targetTx, tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
                await s.Server.ExplorerNode.GenerateAsync(1);

                s.GoToWalletSend(walletId);
                s.Driver.FindElement(By.Id("advancedSettings")).Click();
                s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
                s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
                var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
                s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
                var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
                SetTransactionOutput(s, 0, bob, 0.3m);
                s.Driver.FindElement(By.Id("SendMenu")).Click();
                s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
                s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                var happyElement = s.AssertHappyMessage();
                var happyText    = happyElement.Text;
                var txid         = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;

                tx = await s.Server.ExplorerNode.GetRawTransactionAsync(new uint256(txid));

                Assert.Single(tx.Inputs);
                Assert.Equal(spentOutpoint, tx.Inputs[0].PrevOut);
            }
        }
Ejemplo n.º 3
0
        public void OutPointCacheEvicts()
        {
            const string CollectionName = "OutputsData";
            var          dataFolder     = new DataFolder(TestBase.CreateTestDir(this));
            string       dbPath         = Path.Combine(dataFolder.RootPath, CollectionName);

            var database = new LiteDatabase(new ConnectionString()
            {
                Filename = dbPath, Upgrade = true
            });
            var cache = new AddressIndexerOutpointsRepository(database, new ExtendedLoggerFactory(), 2);

            Assert.Equal(0, cache.Count);
            Assert.Equal(0, database.GetCollection <OutPointData>(CollectionName).Count());

            var outPoint1 = new OutPoint(uint256.Parse("0000af9ab2c8660481328d0444cf167dfd31f24ca2dbba8e5e963a2434cffa93"), 1);;
            var pair1     = new OutPointData()
            {
                Outpoint = outPoint1.ToString(), ScriptPubKeyBytes = new byte[] { 0, 0, 0, 0 }, Money = Money.Coins(1)
            };

            cache.AddOutPointData(pair1);

            Assert.Equal(1, cache.Count);
            Assert.Equal(0, database.GetCollection <OutPointData>(CollectionName).Count());

            var outPoint2 = new OutPoint(uint256.Parse("cf8ce1419bbc4870b7d4f1c084534d91126dd3283b51ec379e0a20e27bd23633"), 2);;
            var pair2     = new OutPointData()
            {
                Outpoint = outPoint2.ToString(), ScriptPubKeyBytes = new byte[] { 1, 1, 1, 1 }, Money = Money.Coins(2)
            };

            cache.AddOutPointData(pair2);

            Assert.Equal(2, cache.Count);
            Assert.Equal(0, database.GetCollection <OutPointData>(CollectionName).Count());

            var outPoint3 = new OutPoint(uint256.Parse("126dd3283b51ec379e0a20e27bd23633cf8ce1419bbc4870b7d4f1c084534d91"), 3);;
            var pair3     = new OutPointData()
            {
                Outpoint = outPoint3.ToString(), ScriptPubKeyBytes = new byte[] { 2, 2, 2, 2 }, Money = Money.Coins(3)
            };

            cache.AddOutPointData(pair3);

            Assert.Equal(2, cache.Count);

            // One of the cache items should have been evicted, and will therefore be persisted on disk.
            Assert.Equal(1, database.GetCollection <OutPointData>(CollectionName).Count());

            // The evicted item should be pair1.
            Assert.Equal(pair1.ScriptPubKeyBytes, database.GetCollection <OutPointData>(CollectionName).FindAll().First().ScriptPubKeyBytes);

            // It should still be possible to retrieve pair1 from the cache (it will pull it from disk).
            Assert.True(cache.TryGetOutPointData(outPoint1, out OutPointData pair1AfterEviction));

            Assert.NotNull(pair1AfterEviction);
            Assert.Equal(pair1.ScriptPubKeyBytes, pair1AfterEviction.ScriptPubKeyBytes);
            Assert.Equal(pair1.Money, pair1AfterEviction.Money);
        }
Ejemplo n.º 4
0
        public void RemoveOutPointData(OutPoint outPoint)
        {
            lock (this.LockObject)
            {
                if (this.Cache.TryGetValue(outPoint.ToString(), out LinkedListNode <CacheItem> node))
                {
                    this.Cache.Remove(node.Value.Key);
                    this.Keys.Remove(node);
                    this.totalSize -= 1;
                }

                if (!node.Value.Dirty)
                {
                    this.addressIndexerOutPointData.Delete(outPoint.ToString());
                }
            }
        }
Ejemplo n.º 5
0
        public bool TryGetOutPointData(OutPoint outPoint, out OutPointData outPointData)
        {
            if (this.TryGetValue(outPoint.ToString(), out outPointData))
            {
                this.logger.LogTrace("(-)[FOUND_IN_CACHE]:true");
                return(true);
            }

            // Not found in cache - try find it in database.
            outPointData = this.addressIndexerOutPointData.FindById(outPoint.ToString());

            if (outPointData != null)
            {
                this.AddOutPointData(outPointData);
                this.logger.LogTrace("(-)[FOUND_IN_DATABASE]:true");
                return(true);
            }

            return(false);
        }
 public async Task <bool> TryLock(OutPoint outpoint)
 {
     using var ctx = _dbContextFactory.CreateContext();
     ctx.PayjoinLocks.Add(new PayjoinLock()
     {
         Id = outpoint.ToString()
     });
     try
     {
         return(await ctx.SaveChangesAsync() == 1);
     }
     catch (DbUpdateException)
     {
         return(false);
     }
 }
Ejemplo n.º 7
0
        public void CanRewind()
        {
            var rewindDataBlockHash = new uint256(RandomUtils.GetUInt64());

            var outPoint = new OutPoint(new uint256(RandomUtils.GetUInt64()), 1);
            var data     = new OutPointData()
            {
                Outpoint = outPoint.ToString(), Money = 1, ScriptPubKeyBytes = RandomUtils.GetBytes(20)
            };

            var rewindData = new AddressIndexerRewindData()
            {
                BlockHash    = rewindDataBlockHash.ToString(),
                BlockHeight  = 100,
                SpentOutputs = new List <OutPointData>()
                {
                    data
                }
            };

            this.repository.RecordRewindData(rewindData);

            Assert.False(this.repository.TryGetOutPointData(outPoint, out OutPointData dataOut));

            this.repository.Rewind(rewindDataBlockHash);

            Assert.True(this.repository.TryGetOutPointData(outPoint, out dataOut));

            // Now record and purge rewind data.
            this.repository.RecordRewindData(rewindData);

            this.repository.RemoveOutPointData(outPoint);
            Assert.False(this.repository.TryGetOutPointData(outPoint, out dataOut));

            this.repository.PurgeOldRewindData(rewindData.BlockHeight + 1);

            Assert.Throws <Exception>(() => this.repository.Rewind(rewindDataBlockHash));

            Assert.False(this.repository.TryGetOutPointData(outPoint, out dataOut));
        }
        public void CanAddAndRemoveOutpointData()
        {
            var outPoint = new OutPoint(new uint256(RandomUtils.GetUInt64()), 1);

            var data = new OutPointData()
            {
                Outpoint = outPoint.ToString(), Money = 1, ScriptPubKeyBytes = RandomUtils.GetBytes(20)
            };

            this.repository.AddOutPointData(data);

            // Add more to trigger eviction.
            for (int i = 0; i < this.maxItems * 2; i++)
            {
                this.repository.AddOutPointData(new OutPointData()
                {
                    Outpoint = this.RandomString(20)
                });
            }

            Assert.True(this.repository.TryGetOutPointData(outPoint, out OutPointData dataOut));
            Assert.True(data.ScriptPubKeyBytes.SequenceEqual(dataOut.ScriptPubKeyBytes));
        }
Ejemplo n.º 9
0
        public async Task CanUseCoinSelection()
        {
            using (var s = SeleniumTester.Create())
            {
                await s.StartAsync();

                var userId  = s.RegisterNewUser(true);
                var storeId = s.CreateNewStore().storeId;
                s.GenerateWallet("BTC", "", false, true);
                var walletId = new WalletId(storeId, "BTC");
                s.GoToWallet(walletId, WalletsNavPages.Receive);
                s.Driver.FindElement(By.Id("generateButton")).Click();
                var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
                var address    = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
                await s.Server.ExplorerNode.GenerateAsync(1);

                for (int i = 0; i < 6; i++)
                {
                    await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.0m));
                }
                var targetTx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(1.2m));

                var tx = await s.Server.ExplorerNode.GetRawTransactionAsync(targetTx);

                var spentOutpoint = new OutPoint(targetTx, tx.Outputs.FindIndex(txout => txout.Value == Money.Coins(1.2m)));
                await TestUtils.EventuallyAsync(async() =>
                {
                    var store = await s.Server.PayTester.StoreRepository.FindStore(storeId);
                    var x     = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
                                .OfType <DerivationSchemeSettings>()
                                .Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
                    Assert.Contains(
                        await s.Server.PayTester.GetService <BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
                        .GetUnspentCoins(x.AccountDerivation),
                        coin => coin.OutPoint == spentOutpoint);
                });

                await s.Server.ExplorerNode.GenerateAsync(1);

                s.GoToWallet(walletId, WalletsNavPages.Send);
                s.Driver.FindElement(By.Id("advancedSettings")).Click();
                s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
                s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
                Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
                var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
                s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
                var inputSelectionSelect = s.Driver.FindElement(By.Name("SelectedInputs"));
                Assert.Single(inputSelectionSelect.FindElements(By.CssSelector("[selected]")));

                var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
                SetTransactionOutput(s, 0, bob, 0.3m);
                s.Driver.FindElement(By.Id("SendMenu")).Click();
                s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
                s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
                var happyElement = s.AssertHappyMessage();
                var happyText    = happyElement.Text;
                var txid         = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;

                tx = await s.Server.ExplorerNode.GetRawTransactionAsync(new uint256(txid));

                Assert.Single(tx.Inputs);
                Assert.Equal(spentOutpoint, tx.Inputs[0].PrevOut);
            }
        }
        /// <summary>Processes block that was added or removed from consensus chain.</summary>
        /// <returns><c>true</c> if block was processed.</returns>
        private bool ProcessBlock(Block block, ChainedHeader header)
        {
            lock (this.lockObject)
            {
                // Record outpoints.
                foreach (Transaction tx in block.Transactions)
                {
                    for (int i = 0; i < tx.Outputs.Count; i++)
                    {
                        // OP_RETURN outputs and empty outputs cannot be spent and therefore do not need to be put into the cache.
                        if (tx.Outputs[i].IsEmpty || tx.Outputs[i].ScriptPubKey.IsUnspendable)
                        {
                            continue;
                        }

                        var outPoint = new OutPoint(tx, i);

                        var outPointData = new OutPointData()
                        {
                            Outpoint          = outPoint.ToString(),
                            ScriptPubKeyBytes = tx.Outputs[i].ScriptPubKey.ToBytes(),
                            Money             = tx.Outputs[i].Value
                        };

                        // TODO: When the outpoint cache is full, adding outpoints singly causes overhead writing evicted entries out to the repository
                        this.outpointsRepository.AddOutPointData(outPointData);
                    }
                }
            }

            // Process inputs.
            var inputs = new List <TxIn>();

            // Collect all inputs excluding coinbases.
            foreach (TxInList inputsCollection in block.Transactions.Where(x => !x.IsCoinBase).Select(x => x.Inputs))
            {
                inputs.AddRange(inputsCollection);
            }

            lock (this.lockObject)
            {
                var rewindData = new AddressIndexerRewindData()
                {
                    BlockHash = header.HashBlock.ToString(), BlockHeight = header.Height, SpentOutputs = new List <OutPointData>()
                };

                foreach (TxIn input in inputs)
                {
                    OutPoint consumedOutput = input.PrevOut;

                    if (!this.outpointsRepository.TryGetOutPointData(consumedOutput, out OutPointData consumedOutputData))
                    {
                        this.logger.LogError("Missing outpoint data for {0}.", consumedOutput);
                        this.logger.LogTrace("(-)[MISSING_OUTPOINTS_DATA]");
                        throw new Exception($"Missing outpoint data for {consumedOutput}");
                    }

                    Money amountSpent = consumedOutputData.Money;

                    rewindData.SpentOutputs.Add(consumedOutputData);
                    this.outpointsRepository.RemoveOutPointData(consumedOutput);

                    // Transactions that don't actually change the balance just bloat the database.
                    if (amountSpent == 0)
                    {
                        continue;
                    }

                    string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, new Script(consumedOutputData.ScriptPubKeyBytes));

                    if (string.IsNullOrEmpty(address))
                    {
                        // This condition need not be logged, as the address reader should be aware of all possible address formats already.
                        continue;
                    }

                    this.ProcessBalanceChangeLocked(header.Height, address, amountSpent, false);
                }

                // Process outputs.
                foreach (Transaction tx in block.Transactions)
                {
                    foreach (TxOut txOut in tx.Outputs)
                    {
                        Money amountReceived = txOut.Value;

                        // Transactions that don't actually change the balance just bloat the database.
                        if (amountReceived == 0 || txOut.IsEmpty || txOut.ScriptPubKey.IsUnspendable)
                        {
                            continue;
                        }

                        string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, txOut.ScriptPubKey);

                        if (string.IsNullOrEmpty(address))
                        {
                            // This condition need not be logged, as the address reader should be aware of all
                            // possible address formats already.
                            continue;
                        }

                        this.ProcessBalanceChangeLocked(header.Height, address, amountReceived, true);
                    }
                }

                this.outpointsRepository.RecordRewindData(rewindData);
                this.outpointsRepository.PurgeOldRewindData(this.consensusManager.Tip.Height - this.compactionTriggerDistance);
            }

            return(true);
        }
Ejemplo n.º 11
0
 public bool Remove(OutPoint outPoint)
 {
     return(this.trxCol.Delete(outPoint.ToString()));
 }
Ejemplo n.º 12
0
        public TransactionOutputData GetForOutput(OutPoint outPoint)
        {
            var trx = this.trxCol.FindById(outPoint.ToString());

            return(trx);
        }
Ejemplo n.º 13
0
        /// <summary>Processes block that was added or removed from consensus chain.</summary>
        /// <returns><c>true</c> if block was processed.</returns>
        private bool ProcessBlock(Block block, ChainedHeader header)
        {
            lock (this.lockObject)
            {
                // Record outpoints.
                foreach (Transaction tx in block.Transactions)
                {
                    for (int i = 0; i < tx.Outputs.Count; i++)
                    {
                        if (tx.Outputs[i].Value == Money.Zero)
                        {
                            continue;
                        }

                        var outPoint = new OutPoint(tx, i);

                        var outPointData = new OutPointData()
                        {
                            Outpoint          = outPoint.ToString(),
                            ScriptPubKeyBytes = tx.Outputs[i].ScriptPubKey.ToBytes(),
                            Money             = tx.Outputs[i].Value
                        };

                        this.outpointsRepository.AddOutPointData(outPointData);
                    }
                }
            }

            // Process inputs.
            var inputs = new List <TxIn>();

            // Collect all inputs excluding coinbases.
            foreach (TxInList inputsCollection in block.Transactions.Where(x => !x.IsCoinBase).Select(x => x.Inputs))
            {
                inputs.AddRange(inputsCollection);
            }

            lock (this.lockObject)
            {
                foreach (TxIn input in inputs)
                {
                    OutPoint consumedOutput = input.PrevOut;

                    if (!this.outpointsRepository.TryGetOutPointData(consumedOutput, out OutPointData consumedOutputData))
                    {
                        this.logger.LogError("Missing outpoint data for {0}.", consumedOutput);
                        this.logger.LogTrace("(-)[MISSING OUTPOINTS_DATA]");
                        throw new Exception($"Missing outpoint data for {consumedOutput}");
                    }

                    Money amountSpent = consumedOutputData.Money;

                    this.outpointsRepository.RemoveOutPointData(consumedOutput);

                    // Transactions that don't actually change the balance just bloat the database.
                    if (amountSpent == 0)
                    {
                        continue;
                    }

                    string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, new Script(consumedOutputData.ScriptPubKeyBytes));

                    if (string.IsNullOrEmpty(address))
                    {
                        // This condition need not be logged, as the address reader should be aware of all possible address formats already.
                        continue;
                    }

                    this.ProcessBalanceChangeLocked(header.Height, address, amountSpent, false);
                }

                // Process outputs.
                foreach (Transaction tx in block.Transactions)
                {
                    foreach (TxOut txOut in tx.Outputs)
                    {
                        Money amountReceived = txOut.Value;

                        // Transactions that don't actually change the balance just bloat the database.
                        if (amountReceived == 0)
                        {
                            continue;
                        }

                        string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, txOut.ScriptPubKey);

                        if (string.IsNullOrEmpty(address))
                        {
                            // This condition need not be logged, as the address reader should be aware of all
                            // possible address formats already.
                            continue;
                        }

                        this.ProcessBalanceChangeLocked(header.Height, address, amountReceived, true);
                    }
                }
            }

            return(true);
        }