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); }
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); } }
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); }
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()); } } }
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); } }
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)); }
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); }
public bool Remove(OutPoint outPoint) { return(this.trxCol.Delete(outPoint.ToString())); }
public TransactionOutputData GetForOutput(OutPoint outPoint) { var trx = this.trxCol.FindById(outPoint.ToString()); return(trx); }
/// <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); }