public void BuildTransactionNoChangeAdressesLeftCreatesNewChangeAddress() { WalletTransactionHandlerTestContext testContext = SetupWallet(); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); Transaction result = this.Network.CreateTransaction(transactionResult.ToHex()); (PubKey PubKey, BitcoinPubKeyAddress Address)expectedChangeAddressKeys = WalletTestsHelpers.GenerateAddressKeys(testContext.Wallet, testContext.AccountKeys.ExtPubKey, "1/0"); Assert.Single(result.Inputs); Assert.Equal(testContext.AddressTransaction.Id, result.Inputs[0].PrevOut.Hash); Assert.Equal(2, result.Outputs.Count); TxOut output = result.Outputs[0]; Assert.Equal((testContext.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(testContext.DestinationKeys.PubKey.ScriptPubKey, output.ScriptPubKey); Assert.Equal(testContext.AddressTransaction.Amount - context.TransactionFee, result.TotalOut); Assert.NotNull(transactionResult.GetHash()); Assert.Equal(result.GetHash(), transactionResult.GetHash()); }
public void BuildTransactionNoChangeAdressSpecified() { WalletTransactionHandlerTestContext testContext = SetupWallet(); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); Transaction result = this.Network.CreateTransaction(transactionResult.ToHex()); HdAccount account = testContext.Wallet.GetAccount("account1"); Script expectedChangeAddressScript = account.InternalAddresses.First().ScriptPubKey; Assert.Single(result.Inputs); Assert.Equal(testContext.AddressTransaction.Id, result.Inputs[0].PrevOut.Hash); Assert.Equal(2, result.Outputs.Count); TxOut output = result.Outputs[0]; Assert.Equal((testContext.AddressTransaction.Amount - context.TransactionFee - 7500), output.Value); Assert.Equal(expectedChangeAddressScript, output.ScriptPubKey); output = result.Outputs[1]; Assert.Equal(7500, output.Value); Assert.Equal(testContext.DestinationKeys.PubKey.ScriptPubKey, output.ScriptPubKey); Assert.Equal(testContext.AddressTransaction.Amount - context.TransactionFee, result.TotalOut); Assert.NotNull(transactionResult.GetHash()); Assert.Equal(result.GetHash(), transactionResult.GetHash()); }
public void BuildTransaction_When_OpReturnAmount_Is_Populated_Should_Add_Extra_Output_With_Data_And_Amount() { WalletTransactionHandlerTestContext testContext = SetupWallet(); string opReturnData = "some extra transaction info"; byte[] expectedBytes = Encoding.UTF8.GetBytes(opReturnData); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, opReturnData); context.OpReturnAmount = Money.Coins(0.0001m); Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); IEnumerable <TxOut> unspendableOutputs = transactionResult.Outputs.Where(o => o.ScriptPubKey.IsUnspendable).ToList(); unspendableOutputs.Count().Should().Be(1); unspendableOutputs.Single().Value.Should().Be(Money.Coins(0.0001m)); IEnumerable <Op> ops = unspendableOutputs.Single().ScriptPubKey.ToOps(); ops.Count().Should().Be(2); ops.First().Code.Should().Be(OpcodeType.OP_RETURN); ops.Last().PushData.Should().BeEquivalentTo(expectedBytes); }
public void BuildTransactionNoSpendableTransactionsThrowsWalletException() { Assert.Throws <WalletException>(() => { WalletTransactionHandlerTestContext testContext = SetupWallet(coinBaseBlocks: 0); testContext.WalletTransactionHandler.BuildTransaction(CreateContext(this.Network, testContext.WalletReference, "password", new Script(), new Money(500), FeeType.Medium, 2)); }); }
public void BuildTransactionFeeTooLowDefaultsToMinimumFee() { WalletTransactionHandlerTestContext testContext = SetupWallet(new FeeRate(0)); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); Assert.Equal(new Money(this.Network.MinTxFee, MoneyUnit.Satoshi), context.TransactionFee); }
public void BuildTransaction_When_OpReturnData_Is_Null_Should_Not_Add_Extra_Output() { WalletTransactionHandlerTestContext testContext = SetupWallet(); string opReturnData = null; TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, opReturnData); Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); transactionResult.Outputs.Where(o => o.ScriptPubKey.IsUnspendable).Should() .BeEmpty("because opReturnData is null"); }
public void CacheSecretWithValidPasswordReturnsValidSecret() { WalletTransactionHandlerTestContext testContext = SetupWallet(); SecureString secret = testContext.WalletTransactionHandler.CacheSecret(testContext.WalletReference, "password", new TimeSpan(0, 5, 0)); string calculatedSecret = new System.Net.NetworkCredential(string.Empty, secret).Password; Key privateKey = Key.Parse(testContext.Wallet.EncryptedSeed, "password", testContext.Wallet.Network); string expectedSecret = privateKey.ToString(testContext.Wallet.Network); calculatedSecret.Should().Be(expectedSecret); }
public void BuildTransaction_When_OpReturnData_Is_Too_Long_Should_Fail_With_Helpful_Message() { WalletTransactionHandlerTestContext testContext = SetupWallet(); byte[] eightyOneBytes = Encoding.UTF8.GetBytes(this.CostlyOpReturnData).Concat(Convert.ToByte(1)); string tooLongOpReturnString = Encoding.UTF8.GetString(eightyOneBytes); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, tooLongOpReturnString); new Action(() => testContext.WalletTransactionHandler.BuildTransaction(context)) .Should().Throw <ArgumentOutOfRangeException>() .And.Message.Should().Contain(" maximum size of 83"); }
public void EstimateFee_Without_OpReturnData_Should_Be_Less_Than_Estimate_Fee_With_Costly_OpReturnData() { WalletTransactionHandlerTestContext testContext = SetupWallet(); // Context with OpReturnData TransactionBuildContext estimateContextWithOpReturn = CreateContext(this.Network, testContext.WalletReference, null, testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, this.CostlyOpReturnData); Money feeEstimateWithOpReturn = testContext.WalletTransactionHandler.EstimateFee(estimateContextWithOpReturn); // Context without OpReturnData TransactionBuildContext estimateContextWithoutOpReturn = CreateContext(this.Network, testContext.WalletReference, null, testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, null); Money feeEstimateWithoutOpReturn = testContext.WalletTransactionHandler.EstimateFee(estimateContextWithoutOpReturn); feeEstimateWithOpReturn.Should().NotBe(feeEstimateWithoutOpReturn); feeEstimateWithoutOpReturn.Satoshi.Should().BeLessThan(feeEstimateWithOpReturn.Satoshi); }
public void EstimateFee_WithLowFee_Matches_BuildTransaction_WithLowFee_With_Long_OpReturnData_added() { WalletTransactionHandlerTestContext testContext = SetupWallet(); // Context to build requires password in order to sign transaction. TransactionBuildContext buildContext = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, this.CostlyOpReturnData); testContext.WalletTransactionHandler.BuildTransaction(buildContext); // Context for estimate does not need password. TransactionBuildContext estimateContext = CreateContext(this.Network, testContext.WalletReference, null, testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, this.CostlyOpReturnData); Money feeEstimate = testContext.WalletTransactionHandler.EstimateFee(estimateContext); feeEstimate.Should().Be(buildContext.TransactionFee); }
public void EstimateFeeWithLowFeeMatchesBuildTxLowFee() { WalletTransactionHandlerTestContext testContext = SetupWallet(); // Context to build requires password in order to sign transaction. TransactionBuildContext buildContext = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); testContext.WalletTransactionHandler.BuildTransaction(buildContext); // Context for estimate does not need password. TransactionBuildContext estimateContext = CreateContext(this.Network, testContext.WalletReference, null, testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); Money fee = testContext.WalletTransactionHandler.EstimateFee(estimateContext); Assert.Equal(fee, buildContext.TransactionFee); }
public void Actual_Fee_Without_OpReturnData_Should_Be_Less_Than_Actual_Fee_With_Costly_OpReturnData() { WalletTransactionHandlerTestContext testContext = SetupWallet(); // Context with OpReturnData TransactionBuildContext contextWithOpReturn = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, this.CostlyOpReturnData); testContext.WalletTransactionHandler.BuildTransaction(contextWithOpReturn); // Context without OpReturnData TransactionBuildContext contextWithoutOpReturn = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0, null); testContext.WalletTransactionHandler.BuildTransaction(contextWithoutOpReturn); contextWithoutOpReturn.TransactionFee.Should().NotBe(contextWithOpReturn.TransactionFee); contextWithoutOpReturn.TransactionFee.Satoshi.Should().BeLessThan(contextWithOpReturn.TransactionFee.Satoshi); }
public void BuildTransactionUsesGivenChangeAddress() { WalletTransactionHandlerTestContext testContext = SetupWallet(); TransactionBuildContext context = CreateContext(this.Network, testContext.WalletReference, "password", testContext.DestinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0); var key = new Key(); BitcoinPubKeyAddress address = key.PubKey.GetAddress(this.Network); HdAddress changeAddress = context.ChangeAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = address.ToString(), Pubkey = key.PubKey.ScriptPubKey, ScriptPubKey = address.ScriptPubKey, Transactions = new List <TransactionData>() }; Transaction transactionResult = testContext.WalletTransactionHandler.BuildTransaction(context); Transaction result = this.Network.CreateTransaction(transactionResult.ToHex()); Assert.Single(result.Inputs); Assert.Equal(testContext.AddressTransaction.Id, result.Inputs[0].PrevOut.Hash); Assert.Equal(2, result.Outputs.Count); TxOut output = result.Outputs[0]; Assert.Equal((testContext.AddressTransaction.Amount - context.TransactionFee - 7500), output.Value); Assert.Equal(changeAddress.ScriptPubKey, output.ScriptPubKey); output = result.Outputs[1]; Assert.Equal(7500, output.Value); Assert.Equal(testContext.DestinationKeys.PubKey.ScriptPubKey, output.ScriptPubKey); Assert.Equal(testContext.AddressTransaction.Amount - context.TransactionFee, result.TotalOut); Assert.NotNull(transactionResult.GetHash()); Assert.Equal(result.GetHash(), transactionResult.GetHash()); }
public void FundTransaction_Given__a_wallet_has_enough_inputs__When__adding_inputs_to_an_existing_transaction__Then__the_transaction_is_funded_successfully() { // Wallet with 4 coinbase outputs of 50 = 200 Bitcoin. WalletTransactionHandlerTestContext testContext = SetupWallet(coinBaseBlocks: 4); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys1 = WalletTestsHelpers.GenerateAddressKeys(testContext.Wallet, testContext.AccountKeys.ExtPubKey, "0/1"); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys2 = WalletTestsHelpers.GenerateAddressKeys(testContext.Wallet, testContext.AccountKeys.ExtPubKey, "0/2"); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys3 = WalletTestsHelpers.GenerateAddressKeys(testContext.Wallet, testContext.AccountKeys.ExtPubKey, "0/3"); // create a trx with 3 outputs 50 + 50 + 50 = 150 BTC var context = new TransactionBuildContext(this.Network) { AccountReference = testContext.WalletReference, MinConfirmations = 0, FeeType = FeeType.Low, WalletPassword = "******", Recipients = 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(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys3.PubKey.ScriptPubKey } }.ToList() }; Transaction fundTransaction = testContext.WalletTransactionHandler.BuildTransaction(context); Assert.Equal(4, fundTransaction.Inputs.Count); // 4 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 3 inputs they will be added back by fund transaction fundTransaction.Inputs.RemoveAt(3); fundTransaction.Inputs.RemoveAt(2); fundTransaction.Inputs.RemoveAt(1); Assert.Single(fundTransaction.Inputs); // 4 inputs Transaction fundTransactionClone = this.Network.CreateTransaction(fundTransaction.ToBytes()); var fundContext = new TransactionBuildContext(this.Network) { AccountReference = testContext.WalletReference, MinConfirmations = 0, FeeType = FeeType.Low, WalletPassword = "******", Recipients = new List <Recipient>() }; var overrideFeeRate = new FeeRate(20000); fundContext.OverrideFeeRate = overrideFeeRate; testContext.WalletTransactionHandler.FundTransaction(fundContext, fundTransaction); foreach (TxIn input in fundTransactionClone.Inputs) // all original inputs are still in the trx { Assert.Contains(fundTransaction.Inputs, a => a.PrevOut == input.PrevOut); } Assert.Equal(4, fundTransaction.Inputs.Count); // we expect 4 inputs Assert.Equal(4, fundTransaction.Outputs.Count); // we expect 4 outputs Assert.Equal(new Money(200, 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); }