public void Given_GetMaximumSpendableAmountIsCalled_When_ThereAreNoConfirmedSpendableFound_Then_MaxAmountReturnsAsTheSumOfUnconfirmedTxs() { DataFolder dataFolder = CreateDataFolder(this); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())).Returns(new FeeRate(20000)); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.PurpleMain, new ConcurrentChain(Network.PurpleMain), NodeSettings.Default(), dataFolder, new Mock <IWalletFeePolicy>().Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, Network.PurpleMain); HdAccount account = WalletTestsHelpers.CreateAccount("account 1"); HdAddress accountAddress1 = WalletTestsHelpers.CreateAddress(); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(1), new Money(15000), null, null, null, new Key().ScriptPubKey)); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(2), new Money(10000), null, null, null, new Key().ScriptPubKey)); HdAddress accountAddress2 = WalletTestsHelpers.CreateAddress(); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(3), new Money(20000), null, null, null, new Key().ScriptPubKey)); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(4), new Money(120000), null, null, null, new Key().ScriptPubKey)); account.ExternalAddresses.Add(accountAddress1); account.InternalAddresses.Add(accountAddress2); var wallet = WalletTestsHelpers.CreateWallet("wallet1"); wallet.AccountsRoot.Add(new AccountRoot() { Accounts = new List <HdAccount> { account } }); walletManager.Wallets.Add(wallet); (Money max, Money fee)result = walletTransactionHandler.GetMaximumSpendableAmount(new WalletAccountReference("wallet1", "account 1"), FeeType.Low, true); Assert.Equal(new Money(165000), result.max + result.fee); }
public void ProcessBlock_NewBlock_PreviousHashSameAsWalletTip_PassesBlockToManagerWithoutReorg() { var result = WalletTestsHelpers.GenerateChainAndBlocksWithHeight(5, Network.PurpleMain); this.chain = result.Chain; var blocks = result.Blocks; var walletSyncManager = new WalletSyncManagerOverride(this.LoggerFactory.Object, this.walletManager.Object, this.chain, Network.PurpleMain, this.blockStoreCache.Object, this.storeSettings, this.nodeLifetime.Object); walletSyncManager.SetWalletTip(this.chain.GetBlock(3)); var blockToProcess = blocks[3]; walletSyncManager.ProcessBlock(blockToProcess); //4th block in the list has same prevhash as which is loaded var expectedBlockHash = this.chain.GetBlock(4).Header.GetHash(); Assert.Equal(expectedBlockHash, walletSyncManager.WalletTip.Header.GetHash()); this.walletManager.Verify(w => w.ProcessBlock(It.Is <Block>(b => b.GetHash() == blockToProcess.GetHash()), It.Is <ChainedBlock>(c => c.Header.GetHash() == expectedBlockHash))); }
public void ProcessBlock_NewBlock_BlockNotOnBestChain_ReOrgWalletManagerUsingBlockStoreCache() { var result = WalletTestsHelpers.GenerateForkedChainAndBlocksWithHeight(5, Network.PurpleMain, 2); // left side chain containing the 'old' fork. var leftChain = result.LeftChain; // right side chain containing the 'new' fork. Work on this. this.chain = result.RightChain; var walletSyncManager = new WalletSyncManagerOverride(this.LoggerFactory.Object, this.walletManager.Object, this.chain, Network.PurpleMain, this.blockStoreCache.Object, this.storeSettings, this.nodeLifetime.Object); // setup blockstorecache to return blocks on the chain. this.blockStoreCache.Setup(b => b.GetBlockAsync(It.IsAny <uint256>())) .ReturnsAsync((uint256 hashblock) => { return(result.LeftForkBlocks.Union(result.RightForkBlocks).Single(b => b.GetHash() == hashblock)); }); // set 4th block of the old chain as tip. 2 ahead of the fork thus not being on the right chain. walletSyncManager.SetWalletTip(leftChain.GetBlock(result.LeftForkBlocks[3].Header.GetHash())); //process 5th block from the right side of the fork in the list does not have same prevhash as which is loaded. var blockToProcess = result.RightForkBlocks[4]; walletSyncManager.ProcessBlock(blockToProcess); // walletmanager removes all blocks up to the fork. this.walletManager.Verify(w => w.RemoveBlocks(ExpectChainedBlock(this.chain.GetBlock(2)))); var expectedBlockHash = this.chain.GetBlock(5).Header.GetHash(); Assert.Equal(expectedBlockHash, walletSyncManager.WalletTip.Header.GetHash()); //verify manager processes each missing block until caught up. // height 3 this.walletManager.Verify(w => w.ProcessBlock(ExpectBlock(result.RightForkBlocks[2]), ExpectChainedBlock(this.chain.GetBlock(3)))); // height 4 this.walletManager.Verify(w => w.ProcessBlock(ExpectBlock(result.RightForkBlocks[3]), ExpectChainedBlock(this.chain.GetBlock(4)))); // height 5 this.walletManager.Verify(w => w.ProcessBlock(ExpectBlock(result.RightForkBlocks[4]), ExpectChainedBlock(this.chain.GetBlock(5))), Times.Exactly(2)); }
public void BuildTransactionFeeTooLowThrowsWalletException() { Assert.Throws <WalletException>(() => { var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())) .Returns(new FeeRate(0)); var wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password"); var accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); var spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); var destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1"); var changeKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0"); var address = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, Transactions = new List <TransactionData>() }; var chain = new ConcurrentChain(wallet.Network); WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account1", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { address }, InternalAddresses = new List <HdAddress> { new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/1/0", Address = changeKeys.Address.ToString(), Pubkey = changeKeys.PubKey.ScriptPubKey, ScriptPubKey = changeKeys.Address.ScriptPubKey, Transactions = new List <TransactionData>() } } }); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.PurpleMain, chain, NodeSettings.Default(), new DataFolder(new NodeSettings { DataDir = "TestData/WalletTransactionHandlerTest/BuildTransactionFeeTooLowThrowsWalletException" }), walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, Network.PurpleMain); walletManager.Wallets.Add(wallet); var walletReference = new WalletAccountReference { AccountName = "account1", WalletName = "myWallet1" }; walletTransactionHandler.BuildTransaction(CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0)); }); }
public void EstimateFeeWithLowFeeMatchesBuildTxLowFee() { var dataFolder = CreateDataFolder(this); Wallet wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password"); var accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); var spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); var destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1"); HdAddress address = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, Transactions = new List <TransactionData>() }; ConcurrentChain chain = new ConcurrentChain(wallet.Network); WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address); TransactionData addressTransaction = address.Transactions.First(); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account1", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { address }, InternalAddresses = new List <HdAddress>() }); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())) .Returns(new FeeRate(20000)); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.PurpleMain, chain, NodeSettings.Default(), dataFolder, walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); WalletTransactionHandler walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, Network.PurpleMain); walletManager.Wallets.Add(wallet); WalletAccountReference walletReference = new WalletAccountReference { AccountName = "account1", WalletName = "myWallet1" }; // Context to build requires password in order to sign transaction. TransactionBuildContext buildContext = CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); walletTransactionHandler.BuildTransaction(buildContext); // Context for estimate does not need password. TransactionBuildContext estimateContext = CreateContext(walletReference, null, destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); Money fee = walletTransactionHandler.EstimateFee(estimateContext); Assert.Equal(fee, buildContext.TransactionFee); }
public void FundTransaction_Given__a_wallet_has_enough_inputs__When__adding_inputs_to_an_existing_transaction__Then__the_transaction_is_funded_successfully() { DataFolder dataFolder = CreateDataFolder(this); var wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password"); var accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); var spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); var destinationKeys1 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1"); var destinationKeys2 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/2"); var destinationKeys3 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/3"); var address = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, Transactions = new List <TransactionData>() }; // wallet with 4 coinbase outputs of 50 = 200 Bitcoin var chain = new ConcurrentChain(wallet.Network); WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address, 4); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account1", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { address }, InternalAddresses = new List <HdAddress>() }); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())).Returns(new FeeRate(20000)); var overrideFeeRate = new FeeRate(20000); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.PurpleMain, chain, NodeSettings.Default(), dataFolder, walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, Network.PurpleMain); walletManager.Wallets.Add(wallet); var walletReference = new WalletAccountReference { AccountName = "account1", WalletName = "myWallet1" }; // create a trx with 3 outputs 50 + 50 + 49 = 149 BTC var context = new TransactionBuildContext(walletReference, new[] { new Recipient { Amount = new Money(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys1.PubKey.ScriptPubKey }, new Recipient { Amount = new Money(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys2.PubKey.ScriptPubKey }, new Recipient { Amount = new Money(49, MoneyUnit.BTC), ScriptPubKey = destinationKeys3.PubKey.ScriptPubKey } } .ToList(), "password") { MinConfirmations = 0, FeeType = FeeType.Low }; var fundTransaction = walletTransactionHandler.BuildTransaction(context); Assert.Equal(3, fundTransaction.Inputs.Count); // 3 inputs Assert.Equal(4, fundTransaction.Outputs.Count); // 3 outputs with change // remove the change output fundTransaction.Outputs.Remove(fundTransaction.Outputs.First(f => f.ScriptPubKey == context.ChangeAddress.ScriptPubKey)); // remove 2 inputs they will be added back by fund transaction fundTransaction.Inputs.RemoveAt(2); fundTransaction.Inputs.RemoveAt(1); Assert.Single(fundTransaction.Inputs); // 3 inputs var fundTransactionClone = fundTransaction.Clone(); var fundContext = new TransactionBuildContext(walletReference, new List <Recipient>(), "password") { MinConfirmations = 0, FeeType = FeeType.Low }; fundContext.OverrideFeeRate = overrideFeeRate; walletTransactionHandler.FundTransaction(fundContext, fundTransaction); foreach (var input in fundTransactionClone.Inputs) // all original inputs are still in the trx { Assert.Contains(fundTransaction.Inputs, a => a.PrevOut == input.PrevOut); } Assert.Equal(3, fundTransaction.Inputs.Count); // we expect 3 inputs Assert.Equal(4, fundTransaction.Outputs.Count); // we expect 4 outputs Assert.Equal(new Money(150, MoneyUnit.BTC) - fundContext.TransactionFee, fundTransaction.TotalOut); Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys1.PubKey.ScriptPubKey); Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys2.PubKey.ScriptPubKey); Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys3.PubKey.ScriptPubKey); }
public void BuildTransactionNoChangeAdressesLeftCreatesNewChangeAddress() { DataFolder dataFolder = CreateDataFolder(this); var wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password"); var accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); var spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); var destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1"); var address = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, Transactions = new List <TransactionData>() }; var chain = new ConcurrentChain(wallet.Network); WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address); var addressTransaction = address.Transactions.First(); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account1", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { address }, InternalAddresses = new List <HdAddress>() }); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())) .Returns(new FeeRate(20000)); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.PurpleMain, chain, NodeSettings.Default(), dataFolder, walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, Network.PurpleMain); walletManager.Wallets.Add(wallet); var walletReference = new WalletAccountReference { AccountName = "account1", WalletName = "myWallet1" }; var context = CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); var transactionResult = walletTransactionHandler.BuildTransaction(context); var result = new Transaction(transactionResult.ToHex()); var expectedChangeAddressKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0"); Assert.Single(result.Inputs); Assert.Equal(addressTransaction.Id, result.Inputs[0].PrevOut.Hash); Assert.Equal(2, result.Outputs.Count); var output = result.Outputs[0]; Assert.Equal((addressTransaction.Amount - context.TransactionFee - 7500), output.Value); Assert.Equal(expectedChangeAddressKeys.Address.ScriptPubKey, output.ScriptPubKey); output = result.Outputs[1]; Assert.Equal(7500, output.Value); Assert.Equal(destinationKeys.PubKey.ScriptPubKey, output.ScriptPubKey); Assert.Equal(addressTransaction.Amount - context.TransactionFee, result.TotalOut); Assert.NotNull(transactionResult.GetHash()); Assert.Equal(result.GetHash(), transactionResult.GetHash()); }