public Transaction Finalize(Key payeeKey) { var builder = new TransactionBuilder(); var tx = builder .AddCoins(FundCoin) .AddKeys(payeeKey) .SignTransaction(Payment); if(!builder.Verify(tx, Arguments.Fees)) throw new MicroPaymentException("Payment incorrectly signed"); return tx; }
public void soft_delete_only_updates_parent_type() { var dto = new ParentDto { ParentKey = 1, OneToManyChildDto = new[] { new OneToManyChildDto { ChildKey = 2 } } }; var cache = new DtoMetadataCache(); var builder = new TransactionBuilder(cache); var scripts = builder.BuildUpdateScripts(dto, null, true); Assert.AreEqual(1, scripts.Count, "Unexpected number of scripts."); var script = scripts[0]; var sql = script.Buffer.ToString(); Assert.IsTrue(sql.Contains("UPDATE dbo.[Parent]"), "No update on parent."); Assert.AreEqual(1, Regex.Matches(sql, "UPDATE").Count, "Unexpected number of UPDATEs."); Assert.AreEqual(0, Regex.Matches(sql, "INSERT").Count, "Should be no INSERTs."); Assert.AreEqual(0, Regex.Matches(sql, "DELETE").Count, "Should be no DELETEs."); Assert.AreEqual(0, Regex.Matches(sql, "SELECT").Count, "Should be no SELECTs."); }
public void AssertAckOpenChannel(OpenChannelAckMessage ackMsg) { var builder = new TransactionBuilder(); var fullySigned = builder .AddCoins(FundCoin) .CombineSignatures(Refund, ackMsg.SignedRefund); if(!builder.Verify(fullySigned, Arguments.Fees)) { throw new MicroPaymentException("Transaction incorrectly signed"); } Refund = fullySigned; }
public OpenChannelAckMessage AckOpenChannel(OpenChannelMessage openMsg, Key payeeKey) { if(payeeKey.PubKey != Arguments.Payee.PaymentPubKey) throw new ArgumentException("Invalid payeeKey", "payeeKey"); Arguments.Assert(openMsg.UnsignedRefund, true, Arguments.Fees); var fundCoin = new Coin(openMsg.UnsignedRefund.Inputs[0].PrevOut, new TxOut(Arguments.Amount + Arguments.Fees, Arguments.Redeem.Hash)).ToScriptCoin(Arguments.Redeem); var signed = new TransactionBuilder() .AddCoins(fundCoin) .AddKeys(payeeKey) .SignTransaction(openMsg.UnsignedRefund); Refund = signed; return new OpenChannelAckMessage() { SignedRefund = signed }; }
public OpenChannelMessage OpenChannel(ICoin[] fundingCoins, Key[] fundingKeys, Key payerKey) { if(payerKey.PubKey != Arguments.Payer.PaymentPubKey) throw new ArgumentException("Invalid payerKey", "payerKey"); var p2sh = Arguments.Redeem.Hash.ScriptPubKey; var builder = new TransactionBuilder(); Fund = builder .AddCoins(fundingCoins) .AddKeys(fundingKeys) .Send(p2sh, Arguments.GetFundAmount()) .SetChange(Arguments.Payer.ScriptPubkey) .SendFees(Arguments.Fees) .Shuffle() .BuildTransaction(true); if(!builder.Verify(Fund, Arguments.Fees)) throw new MicroPaymentException("Funding transaction incorreclty signed"); var fundCoin = Fund.Outputs.AsCoins().First(c => c.ScriptPubKey == p2sh).ToScriptCoin(Arguments.Redeem); var unsignedRefund = Arguments.CreatePayment(Arguments.Fees, fundCoin); unsignedRefund.LockTime = Arguments.Expiration; builder = new TransactionBuilder() .AddKeys(payerKey) .AddCoins(fundCoin); Refund = builder.SignTransaction(unsignedRefund); return new OpenChannelMessage() { UnsignedRefund = unsignedRefund }; }
public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase) { using (await RoundSyncronizerLock.LockAsync()) { try { Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase change requested: {expectedPhase.ToString()}."); if (Status == CcjRoundStatus.NotStarted) // So start the input registration phase { if (expectedPhase != CcjRoundPhase.InputRegistration) { return; } // Calculate fees var inputSizeInBytes = (int)Math.Ceiling(((3 * Constants.P2wpkhInputSizeInBytes) + Constants.P2pkhInputSizeInBytes) / 4m); var outputSizeInBytes = Constants.OutputSizeInBytes; try { var estimateSmartFeeResponse = await RpcClient.EstimateSmartFeeAsync(ConfirmationTarget, EstimateSmartFeeMode.Conservative, simulateIfRegTest : true); if (estimateSmartFeeResponse == null) { throw new InvalidOperationException("FeeRate is not yet initialized"); } var feeRate = estimateSmartFeeResponse.FeeRate; Money feePerBytes = (feeRate.FeePerK / 1000); // Make sure min relay fee (1000 sat) is hit. FeePerInputs = Math.Max(feePerBytes * inputSizeInBytes, new Money(500)); FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250)); } catch (Exception ex) { // If fee hasn't been initialized once, fall back. if (FeePerInputs == null || FeePerOutputs == null) { var feePerBytes = new Money(100); // 100 satoshi per byte // Make sure min relay fee (1000 sat) is hit. FeePerInputs = Math.Max(feePerBytes * inputSizeInBytes, new Money(500)); FeePerOutputs = Math.Max(feePerBytes * outputSizeInBytes, new Money(250)); } Logger.LogError <CcjRound>(ex); } Status = CcjRoundStatus.Running; } else if (Status != CcjRoundStatus.Running) // Failed or succeeded, swallow { return; } else if (Phase == CcjRoundPhase.InputRegistration) { if (expectedPhase != CcjRoundPhase.ConnectionConfirmation) { return; } RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.OutPoint)); Phase = CcjRoundPhase.ConnectionConfirmation; } else if (Phase == CcjRoundPhase.ConnectionConfirmation) { if (expectedPhase != CcjRoundPhase.OutputRegistration) { return; } Phase = CcjRoundPhase.OutputRegistration; } else if (Phase == CcjRoundPhase.OutputRegistration) { if (expectedPhase != CcjRoundPhase.Signing) { return; } // Build CoinJoin // 1. Set new denomination: minor optimization. Money newDenomination = Alices.Min(x => x.OutputSumWithoutCoordinatorFeeAndDenomination); var transaction = new Transaction(); // 2. Add Bob outputs. foreach (Bob bob in Bobs) { transaction.AddOutput(newDenomination, bob.ActiveOutputAddress.ScriptPubKey); } BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(RpcClient.Network); // 3. If there are less Bobs than Alices, then add our own address. The malicious Alice, who will refuse to sign. for (int i = 0; i < Alices.Count - Bobs.Count; i++) { transaction.AddOutput(newDenomination, coordinatorAddress); } // 4. Start building Coordinator fee. Money coordinatorFeePerAlice = newDenomination.Percentange(CoordinatorFeePercent); Money coordinatorFee = Alices.Count * coordinatorFeePerAlice; // 5. Add the inputs and the changes of Alices. foreach (Alice alice in Alices) { foreach (var input in alice.Inputs) { transaction.AddInput(new TxIn(input.OutPoint)); } Money changeAmount = alice.GetChangeAmount(newDenomination, coordinatorFeePerAlice); if (changeAmount > Money.Zero) // If the coordinator fee would make change amount to be negative or zero then no need to pay it. { Money minimumOutputAmount = Money.Coins(0.0001m); // If the change would be less than about $1 then add it to the coordinator. Money onePercentOfDenomination = newDenomination.Percentange(1m); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee. Money minimumChangeAmount = Math.Max(minimumOutputAmount, onePercentOfDenomination); if (changeAmount < minimumChangeAmount) { coordinatorFee += changeAmount; } else { transaction.AddOutput(changeAmount, alice.ChangeOutputAddress.ScriptPubKey); } } else { coordinatorFee -= coordinatorFeePerAlice; } } // 6. Add Coordinator fee only if > about $3, else just let it to be miner fee. if (coordinatorFee > Money.Coins(0.0003m)) { transaction.AddOutput(coordinatorFee, coordinatorAddress); } // 7. Create the unsigned transaction. var builder = new TransactionBuilder(); UnsignedCoinJoin = builder .ContinueToBuild(transaction) .Shuffle() .BuildTransaction(false); SignedCoinJoin = new Transaction(UnsignedCoinJoin.ToHex()); Phase = CcjRoundPhase.Signing; } else { return; } Logger.LogInfo <CcjRound>($"Round ({RoundId}): Phase initialized: {expectedPhase.ToString()}."); } catch (Exception ex) { Logger.LogError <CcjRound>(ex); Status = CcjRoundStatus.Failed; throw; } } #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Run(async() => { TimeSpan timeout; switch (expectedPhase) { case CcjRoundPhase.InputRegistration: timeout = InputRegistrationTimeout; break; case CcjRoundPhase.ConnectionConfirmation: timeout = ConnectionConfirmationTimeout; break; case CcjRoundPhase.OutputRegistration: timeout = OutputRegistrationTimeout; break; case CcjRoundPhase.Signing: timeout = SigningTimeout; break; default: throw new InvalidOperationException("This is impossible to happen."); } // Delay asyncronously to the requested timeout. await Task.Delay(timeout); var executeRunFailure = false; using (await RoundSyncronizerLock.LockAsync()) { executeRunFailure = Status == CcjRoundStatus.Running && Phase == expectedPhase; } if (executeRunFailure) { Logger.LogInfo <CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timed out after {timeout.TotalSeconds} seconds. Failure mode is executing."); // This will happen outside the lock. Task.Run(async() => { try { switch (expectedPhase) { case CcjRoundPhase.InputRegistration: { // Only fail if less two one Alice is registered. // Don't ban anyone, it's ok if they lost connection. await RemoveAlicesIfInputsSpentAsync(); int aliceCountAfterInputRegistrationTimeout = CountAlices(); if (aliceCountAfterInputRegistrationTimeout < 2) { Fail(); } else { UpdateAnonymitySet(aliceCountAfterInputRegistrationTimeout); // Progress to the next phase, which will be ConnectionConfirmation await ExecuteNextPhaseAsync(CcjRoundPhase.ConnectionConfirmation); } } break; case CcjRoundPhase.ConnectionConfirmation: { // Only fail if less than two one alices are registered. // What if an attacker registers all the time many alices, then drops out. He'll achieve only 2 alices to participate? // If he registers many alices at InputRegistration // AND never confirms in connection confirmation // THEN connection confirmation will go with 2 alices in every round // Therefore Alices those didn't confirm, nor requested dsconnection should be banned: IEnumerable <Alice> alicesToBan1 = GetAlicesBy(AliceState.InputsRegistered); IEnumerable <Alice> alicesToBan2 = await RemoveAlicesIfInputsSpentAsync(); // So ban only those who confirmed participation, yet spent their inputs. IEnumerable <OutPoint> inputsToBan = alicesToBan1.SelectMany(x => x.Inputs).Select(y => y.OutPoint).Concat(alicesToBan2.SelectMany(x => x.Inputs).Select(y => y.OutPoint).ToArray()).Distinct(); if (inputsToBan.Any()) { await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, inputsToBan.ToArray()); } RemoveAlicesBy(alicesToBan1.Select(x => x.UniqueId).Concat(alicesToBan2.Select(y => y.UniqueId)).Distinct().ToArray()); int aliceCountAfterConnectionConfirmationTimeout = CountAlices(); if (aliceCountAfterConnectionConfirmationTimeout < 2) { Fail(); } else { UpdateAnonymitySet(aliceCountAfterConnectionConfirmationTimeout); // Progress to the next phase, which will be OutputRegistration await ExecuteNextPhaseAsync(CcjRoundPhase.OutputRegistration); } } break; case CcjRoundPhase.OutputRegistration: { // Output registration never fails. // We don't know which Alice to ban. // Therefore proceed to signing, and whichever Alice doesn't sign ban. await ExecuteNextPhaseAsync(CcjRoundPhase.Signing); } break; case CcjRoundPhase.Signing: { var outpointsToBan = new List <OutPoint>(); using (await RoundSyncronizerLock.LockAsync()) { foreach (Alice alice in Alices) { if (alice.State != AliceState.SignedCoinJoin) { outpointsToBan.AddRange(alice.Inputs.Select(x => x.OutPoint)); } } } if (outpointsToBan.Any()) { await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, outpointsToBan.ToArray()); } Fail(); } break; default: throw new InvalidOperationException("This is impossible to happen."); } } catch (Exception ex) { Logger.LogWarning <CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timeout failed with exception: {ex}"); } }); } }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed }
public void Native_MultiSig_two_of_three_no_scripthash_no_witness() { var network = ObsidianXNetworksSelector.Obsidian.Mainnet(); Key bob = new Key(); Key alice = new Key(); Key satoshi = new Key(); Script scriptPubKey = PayToMultiSigTemplate .Instance .GenerateScriptPubKey(2, new[] { bob.PubKey.Compress(), alice.PubKey.Compress(), satoshi.PubKey.Compress() }); this.output.WriteLine(scriptPubKey.ToString()); Transaction received = network.CreateTransaction(); received.Outputs.Add(new TxOut(Money.Coins(5), scriptPubKey)); Coin coin = received.Outputs.AsCoins().First(); BitcoinWitPubKeyAddress spendMultiSigToAddress = new Key().PubKey.Compress().GetSegwitAddress(network); TransactionBuilder builder = new TransactionBuilder(network); Transaction unsigned = builder .AddCoins(coin) .Send(spendMultiSigToAddress, Money.Coins(1.0m)) .SetChange(scriptPubKey) .SendFees(this.fee) .BuildTransaction(sign: false); Transaction aliceSigned = builder .AddCoins(coin) .AddKeys(alice) .SignTransaction(unsigned); Transaction bobSigned = builder .AddCoins(coin) .AddKeys(bob) //At this line, SignTransaction(unSigned) has the identical functionality with the SignTransaction(aliceSigned). //It's because unsigned transaction has already been signed by Alice privateKey from above. .SignTransaction(aliceSigned); Transaction fullySigned = builder .AddCoins(coin) .CombineSignatures(aliceSigned, bobSigned); bool isVerifyPassing = builder.Verify(fullySigned, out var errors); foreach (var err in errors) { this.output.WriteLine(err.ToString()); } this.output.WriteLine($"isVerifyPassing: {isVerifyPassing}"); this.output.WriteLine(fullySigned.ToString()); Assert.True(isVerifyPassing); Assert.False(fullySigned.HasWitness); }
//https://gist.github.com/gavinandresen/3966071 public void CanBuildTransactionWithDustPrevention() { var bob = new Key(); var alice = new Key(); var tx = new Transaction() { Outputs = { new TxOut(Money.Coins(1.0m), bob) } }; var coins = tx.Outputs.AsCoins().ToArray(); var builder = new TransactionBuilder(); var signed = builder .AddCoins(coins) .AddKeys(bob) .Send(alice, Money.Coins(0.99m)) .Send(alice, Money.Satoshis(599)) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(new byte[] { 1, 2 }), Money.Zero) .SendFees(Money.Coins(0.0001m)) .SetChange(bob) .BuildTransaction(true); Assert.True(signed.Outputs.Count == 3); Assert.True(builder.Verify(signed, Money.Coins(0.0001m))); builder.DustPrevention = false; var ex = Assert.Throws<NotEnoughFundsException>(() => builder.Verify(signed, Money.Coins(0.0001m))); Assert.True((Money)ex.Missing == Money.Parse("-0.00000599")); }
public Transaction CreatePayment(Money paid, ScriptCoin fundingCoin) { if(paid > GetFundAmount()) throw new MicroPaymentException("Payment reached the maximum"); var builder = new TransactionBuilder(); if(fundingCoin.Redeem != Redeem || fundingCoin.Amount != Amount + Fees) throw new MicroPaymentException("Invalid funding coin"); var fees = Money.Min(paid, Fees); var toPayer = GetFundAmount() - paid; var toPayee = paid - fees; return builder .AddCoins(fundingCoin) .Send(Payee.ScriptPubkey, toPayee) .Send(Payer.ScriptPubkey, toPayer) .SendFees(fees) .Shuffle() .BuildTransaction(false); }
public void CanBuildColoredTransaction() { var gold = new Key(); var silver = new Key(); var goldId = gold.PubKey.ScriptPubKey.Hash.ToAssetId(); var silverId = silver.PubKey.ScriptPubKey.Hash.ToAssetId(); var satoshi = new Key(); var bob = new Key(); var alice = new Key(); var repo = new NoSqlColoredTransactionRepository(); var init = new Transaction() { Outputs = { new TxOut("1.0", gold.PubKey), new TxOut("1.0", silver.PubKey), new TxOut("1.0", satoshi.PubKey) } }; repo.Transactions.Put(init); var issuanceCoins = init .Outputs .AsCoins() .Take(2) .Select((c, i) => new IssuanceCoin(c)) .OfType<ICoin>().ToArray(); var satoshiBTC = init.Outputs.AsCoins().Last(); var coins = new List<ICoin>(); coins.AddRange(issuanceCoins); var txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; //Can issue gold to satoshi and bob var tx = txBuilder .AddCoins(coins.ToArray()) .AddKeys(gold) .IssueAsset(satoshi.PubKey, new AssetMoney(goldId, 1000)) .IssueAsset(bob.PubKey, new AssetMoney(goldId, 500)) .SendFees("0.1") .SetChange(gold.PubKey) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx, "0.1")); //Ensure BTC from the IssuanceCoin are returned Assert.Equal(Money.Parse("0.89994240"), tx.Outputs[2].Value); Assert.Equal(gold.PubKey.ScriptPubKey, tx.Outputs[2].ScriptPubKey); repo.Transactions.Put(tx); var colored = tx.GetColoredTransaction(repo); Assert.Equal(2, colored.Issuances.Count); Assert.True(colored.Issuances.All(i => i.Asset.Id == goldId)); AssertHasAsset(tx, colored, colored.Issuances[0], goldId, 500, bob.PubKey); AssertHasAsset(tx, colored, colored.Issuances[1], goldId, 1000, satoshi.PubKey); var coloredCoins = ColoredCoin.Find(tx, colored).ToArray(); Assert.Equal(2, coloredCoins.Length); //Can issue silver to bob, and send some gold to satoshi coins.Add(coloredCoins.First(c => c.ScriptPubKey == bob.PubKey.ScriptPubKey)); txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(coins.ToArray()) .AddKeys(silver, bob) .SetChange(bob.PubKey) .IssueAsset(bob.PubKey, new AssetMoney(silverId, 10)) .SendAsset(satoshi.PubKey, new AssetMoney(goldId, 30)) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); colored = tx.GetColoredTransaction(repo); Assert.Equal(1, colored.Inputs.Count); Assert.Equal(goldId, colored.Inputs[0].Asset.Id); Assert.Equal(500, colored.Inputs[0].Asset.Quantity); Assert.Equal(1, colored.Issuances.Count); Assert.Equal(2, colored.Transfers.Count); AssertHasAsset(tx, colored, colored.Transfers[0], goldId, 470, bob.PubKey); AssertHasAsset(tx, colored, colored.Transfers[1], goldId, 30, satoshi.PubKey); repo.Transactions.Put(tx); //Can swap : //satoshi wants to send 100 gold to bob //bob wants to send 200 silver, 5 gold and 0.9 BTC to satoshi //Satoshi receive gold txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; tx = txBuilder .AddKeys(gold) .AddCoins(issuanceCoins) .IssueAsset(satoshi.PubKey, new AssetMoney(goldId, 1000UL)) .SetChange(gold.PubKey) .SendFees(Money.Coins(0.0004m)) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); repo.Transactions.Put(tx); var satoshiCoin = ColoredCoin.Find(tx, repo).First(); //Gold receive 2.5 BTC tx = new Transaction() { Outputs = { new TxOut("2.5",gold.PubKey) } }; repo.Transactions.Put(tx.GetHash(), tx); //Bob receive silver and 2 btc txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; tx = txBuilder .AddKeys(silver, gold) .AddCoins(issuanceCoins) .AddCoins(new Coin(new OutPoint(tx.GetHash(), 0), new TxOut("2.5", gold.PubKey.ScriptPubKey))) .IssueAsset(bob.PubKey, new AssetMoney(silverId, 300UL)) .Send(bob.PubKey, "2.00") .SendFees(Money.Coins(0.0004m)) .SetChange(gold.PubKey) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); repo.Transactions.Put(tx); var bobSilverCoin = ColoredCoin.Find(tx, repo).First(); var bobBitcoin = new Coin(new OutPoint(tx.GetHash(), 2), tx.Outputs[2]); //Bob receive gold txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; tx = txBuilder .AddKeys(gold) .AddCoins(issuanceCoins) .IssueAsset(bob.PubKey, new AssetMoney(goldId, 50UL)) .SetChange(gold.PubKey) .SendFees(Money.Coins(0.0004m)) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); repo.Transactions.Put(tx.GetHash(), tx); var bobGoldCoin = ColoredCoin.Find(tx, repo).First(); txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; tx = txBuilder .AddCoins(satoshiCoin) .AddCoins(satoshiBTC) .SendAsset(bob.PubKey, new AssetMoney(goldId, 100)) .SendFees(Money.Coins(0.0004m)) .SetChange(satoshi.PubKey) .Then() .AddCoins(bobSilverCoin, bobGoldCoin, bobBitcoin) .SendAsset(satoshi.PubKey, new AssetMoney(silverId, 200)) .Send(satoshi.PubKey, "0.9") .SendAsset(satoshi.PubKey, new AssetMoney(goldId, 5)) .SetChange(bob.PubKey) .BuildTransaction(false); colored = tx.GetColoredTransaction(repo); AssertHasAsset(tx, colored, colored.Inputs[0], goldId, 1000, null); AssertHasAsset(tx, colored, colored.Inputs[1], silverId, 300, null); AssertHasAsset(tx, colored, colored.Transfers[0], goldId, 900, satoshi.PubKey); AssertHasAsset(tx, colored, colored.Transfers[1], goldId, 100, bob.PubKey); AssertHasAsset(tx, colored, colored.Transfers[2], silverId, 100, bob.PubKey); AssertHasAsset(tx, colored, colored.Transfers[3], silverId, 200, satoshi.PubKey); AssertHasAsset(tx, colored, colored.Transfers[4], goldId, 45, bob.PubKey); AssertHasAsset(tx, colored, colored.Transfers[5], goldId, 5, satoshi.PubKey); Assert.True(tx.Outputs[8].Value == Money.Parse("1.0999424")); Assert.True(tx.Outputs[8].ScriptPubKey == bob.PubKey.ScriptPubKey); Assert.True(tx.Outputs[9].Value == Money.Parse("0.9")); Assert.True(tx.Outputs[9].ScriptPubKey == satoshi.PubKey.ScriptPubKey); tx = txBuilder.AddKeys(satoshi, bob).SignTransaction(tx); Assert.True(txBuilder.Verify(tx)); //Bob send coins to Satoshi, but alice pay for the dust var funding = new TransactionBuilder() { StandardTransactionPolicy = RelayPolicy } .AddCoins(issuanceCoins) .AddKeys(gold) .IssueAsset(bob.PubKey.Hash, new AssetMoney(goldId, 100UL)) .SetChange(gold.PubKey.Hash) .SendFees(Money.Coins(0.0004m)) .BuildTransaction(true); repo.Transactions.Put(funding); var bobGold = ColoredCoin.Find(funding, repo).ToArray(); Transaction transfer = null; try { transfer = new TransactionBuilder() { StandardTransactionPolicy = RelayPolicy } .AddCoins(bobGold) .SendAsset(alice.PubKey.Hash, new AssetMoney(goldId, 40UL)) .SetChange(bob.PubKey.Hash) .BuildTransaction(true); Assert.False(true, "Should have thrown"); } catch(NotEnoughFundsException ex) //Not enough dust to send the change { Assert.True(((Money)ex.Missing).Satoshi == 2730); var rate = new FeeRate(Money.Coins(0.0004m)); txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = RelayPolicy; transfer = txBuilder .AddCoins(bobGold) .AddCoins(((IssuanceCoin)issuanceCoins[0]).Bearer) .AddKeys(gold, bob) .SendAsset(alice.PubKey, new AssetMoney(goldId, 40UL)) .SetChange(bob.PubKey, ChangeType.Colored) .SetChange(gold.PubKey.Hash, ChangeType.Uncolored) .SendEstimatedFees(rate) .BuildTransaction(true); var fee = transfer.GetFee(txBuilder.FindSpentCoins(transfer)); Assert.True(txBuilder.Verify(transfer, fee)); repo.Transactions.Put(funding.GetHash(), funding); colored = ColoredTransaction.FetchColors(transfer, repo); AssertHasAsset(transfer, colored, colored.Transfers[0], goldId, 60, bob.PubKey); AssertHasAsset(transfer, colored, colored.Transfers[1], goldId, 40, alice.PubKey); var change = transfer.Outputs.Last(o => o.ScriptPubKey == gold.PubKey.Hash.ScriptPubKey); Assert.Equal(Money.Coins(0.99980450m), change.Value); Assert.Equal(gold.PubKey.Hash, change.ScriptPubKey.GetDestination()); //Verify issuancecoin can have an url var issuanceCoin = (IssuanceCoin)issuanceCoins[0]; issuanceCoin.DefinitionUrl = new Uri("http://toto.com/"); txBuilder = new TransactionBuilder(); tx = txBuilder .AddKeys(gold) .AddCoins(issuanceCoin) .IssueAsset(bob, new AssetMoney(gold.PubKey, 10)) .SetChange(gold) .BuildTransaction(true); Assert.Equal("http://toto.com/", tx.GetColoredMarker().GetMetadataUrl().AbsoluteUri); } }
private static TransactionBuilder CreateBuilder(CKeyStore keystore, Transaction txFrom) { var coins = txFrom.Outputs.AsCoins().ToArray(); var builder = new TransactionBuilder() { StandardTransactionPolicy = new StandardTransactionPolicy() { CheckFee = false, MinRelayTxFee = null, UseConsensusLib = false, CheckScriptPubKey = false } } .AddCoins(coins) .AddKeys(keystore._Keys.Select(k => k.Item1).ToArray()) .AddKnownRedeems(keystore._Scripts.ToArray()); return builder; }
public void CanBuildTransaction() { var keys = Enumerable.Range(0, 5).Select(i => new Key()).ToArray(); var multiSigPubKey = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, keys.Select(k => k.PubKey).Take(3).ToArray()); var pubKeyPubKey = PayToPubkeyTemplate.Instance.GenerateScriptPubKey(keys[4].PubKey); var pubKeyHashPubKey = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(keys[4].PubKey.Hash); var scriptHashPubKey1 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(multiSigPubKey.Hash); var scriptHashPubKey2 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(pubKeyPubKey.Hash); var scriptHashPubKey3 = PayToScriptHashTemplate.Instance.GenerateScriptPubKey(pubKeyHashPubKey.Hash); var coins = new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }.Select((script, i) => new Coin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), script) )).ToList(); var scriptCoins = new[] { scriptHashPubKey1, scriptHashPubKey2, scriptHashPubKey3 } .Zip(new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }, (script, redeem) => new { script, redeem }) .Select((_, i) => new ScriptCoin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), _.script), _.redeem )).ToList(); var witCoins = new[] { scriptHashPubKey1, scriptHashPubKey2, scriptHashPubKey3 } .Zip(new[] { multiSigPubKey, pubKeyPubKey, pubKeyHashPubKey }, (script, redeem) => new { script, redeem }) .Select((_, i) => new WitScriptCoin ( new OutPoint(Rand(), i), new TxOut(new Money((i + 1) * Money.COIN), _.redeem.WitHash.ScriptPubKey.Hash), _.redeem )).ToList(); var a = witCoins.Select(c => c.Amount).Sum(); var allCoins = coins.Concat(scriptCoins).Concat(witCoins).ToArray(); var destinations = keys.Select(k => k.PubKey.GetAddress(Network.Main)).ToArray(); var txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; var tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9999")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx, "0.0001")); Assert.Equal(3, tx.Outputs.Count); txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .SetGroupName("test") .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .BuildTransaction(true); Assert.Equal(4, tx.Outputs.Count); //+ Change txBuilder.Send(destinations[4], Money.Parse("1")); var ex = Assert.Throws<NotEnoughFundsException>(() => txBuilder.BuildTransaction(true)); Assert.True(ex.Group == "test"); Assert.True((Money)ex.Missing == Money.Parse("0.9999")); //Can sign partially txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys.Skip(2).ToArray()) //One of the multi key missing .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .Shuffle() .BuildTransaction(true); Assert.False(txBuilder.Verify(tx, "0.0001")); txBuilder = new TransactionBuilder(0); tx = txBuilder .AddKeys(keys[0]) .AddCoins(allCoins) .SignTransaction(tx); Assert.True(txBuilder.Verify(tx)); //Test if signing separatly txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys.Skip(2).ToArray()) //One of the multi key missing .Send(destinations[0], Money.Parse("6") * 2) .Send(destinations[2], Money.Parse("5")) .Send(destinations[2], Money.Parse("0.9998")) .SendFees(Money.Parse("0.0001")) .SetChange(destinations[3]) .Shuffle() .BuildTransaction(false); var signed1 = txBuilder.SignTransaction(tx); txBuilder = new TransactionBuilder(0); var signed2 = txBuilder .AddKeys(keys[0]) .AddCoins(allCoins) .SignTransaction(tx); Assert.False(txBuilder.Verify(signed1)); Assert.False(txBuilder.Verify(signed2)); txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .CombineSignatures(signed1, signed2); Assert.True(txBuilder.Verify(tx)); //Check if can deduce scriptPubKey from P2SH and P2SPKH scriptSig allCoins = new[] { RandomCoin(Money.Parse("1.0"), keys[0].PubKey.Hash.ScriptPubKey, false), RandomCoin(Money.Parse("1.0"), keys[0].PubKey.Hash.ScriptPubKey, false), RandomCoin(Money.Parse("1.0"), keys[1].PubKey.Hash.ScriptPubKey, false) }; txBuilder = new TransactionBuilder(0); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder.AddCoins(allCoins) .Send(destinations[0], Money.Parse("3.0")) .BuildTransaction(false); signed1 = new TransactionBuilder(0) .AddCoins(allCoins) .AddKeys(keys[0]) .SignTransaction(tx); signed2 = new TransactionBuilder(0) .AddCoins(allCoins) .AddKeys(keys[1]) .SignTransaction(tx); Assert.False(txBuilder.Verify(signed1)); Assert.False(txBuilder.Verify(signed2)); tx = new TransactionBuilder(0) .CombineSignatures(signed1, signed2); Assert.True(txBuilder.Verify(tx)); //Using the same set of coin in 2 group should not use two times the sames coins for(int i = 0 ; i < 3 ; i++) { txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy = EasyPolicy; tx = txBuilder .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("2.0")) .Then() .AddCoins(allCoins) .AddKeys(keys) .Send(destinations[0], Money.Parse("1.0")) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx)); } }
public void WhenGetUnspentTransactionsThreeBlocks() { RemoveBlockChain(); var serviceProvider = BuildServiceProvider(); var blockChainFactory = serviceProvider.GetService <IBlockChainFactory>(); var blockChain = blockChainFactory.Build(_network); var genesisBlock = blockChain.GetCurrentBlock(); var firstTransaction = genesisBlock.Transactions.First() as BcBaseTransaction; var firstTransactionOut = firstTransaction.TransactionOut.First(); var genesisKey = KeyStore.GetGenesisKey(); var genesisAdr = new BlockChainAddress(_scriptTypes, _network, genesisKey); // Create block chain address. var destinationBlockChainAddress = GenerateBlockChainAddress(); var minerBlockChainAddress = GenerateBlockChainAddress(); var signature = genesisKey.GetSignature(); // Create the script. var scriptBuilder = new ScriptBuilder(); var genesisScript = scriptBuilder .New() .AddToStack(signature) .AddToStack(genesisKey.GetPublicKey()) .Build(); var destinationScript = Script.CreateP2PKHScript(destinationBlockChainAddress.PublicKeyHash); var minerScript = Script.CreateP2PKHScript(minerBlockChainAddress.PublicKeyHash); var genesisScriptDest = Script.CreateP2PKHScript(genesisKey.GetPublicKeyHashed()); var transactionBuilder = new TransactionBuilder(); var coinBaseTransaction = transactionBuilder // Add COIN-BASE TRANSACTION. .NewCoinbaseTransaction() .SetBlockNumber(1) .AddOutput(1, minerScript) .Build(); var noneCoinBaseTransaction = transactionBuilder // ADD GENESIS (10 BTC) => DESTINATION TRANSACTION. .NewNoneCoinbaseTransaction() .Spend(firstTransaction, 0, genesisScript.Serialize()) .AddOutput(10, destinationScript) .Build(); var otherCoinBaseTransaction = transactionBuilder .NewNoneCoinbaseTransaction() .Spend(firstTransaction, 0, genesisScript.Serialize()) .AddOutput(39, genesisScriptDest) .Build(); var nonce = NonceHelper.GetNonceUInt32(); // CREATE A BLOCK. var firstBlock = new Block(genesisBlock.GetHashHeader(), Constants.DEFAULT_NBITS, nonce); firstBlock.Transactions.Add(coinBaseTransaction); firstBlock.Transactions.Add(noneCoinBaseTransaction); firstBlock.Transactions.Add(otherCoinBaseTransaction); firstBlock.UpdateMerkleRoot(); blockChain.AddBlock(firstBlock); var unspentTransactions = blockChain.GetUnspentTransactions(); Assert.IsNotNull(unspentTransactions); Assert.IsTrue(unspentTransactions.Count() == 3); // ADD THIRD BLOCK. coinBaseTransaction = transactionBuilder // Add COIN-BASE TRANSACTION. .NewCoinbaseTransaction() .SetBlockNumber(2) .AddOutput(1, minerScript) .Build(); noneCoinBaseTransaction = transactionBuilder // ADD GENESIS (2 BTC) => DESTINATION TRANSACTION. .NewNoneCoinbaseTransaction() .Spend(otherCoinBaseTransaction, 0, genesisScript.Serialize()) .AddOutput(2, destinationScript) .Build(); otherCoinBaseTransaction = transactionBuilder .NewNoneCoinbaseTransaction() .Spend(otherCoinBaseTransaction, 0, genesisScript.Serialize()) .AddOutput(36, genesisScriptDest) .Build(); nonce = NonceHelper.GetNonceUInt32(); // CREATE A BLOCK. var secondBlock = new Block(genesisBlock.GetHashHeader(), Constants.DEFAULT_NBITS, nonce); secondBlock.Transactions.Add(coinBaseTransaction); secondBlock.Transactions.Add(noneCoinBaseTransaction); secondBlock.Transactions.Add(otherCoinBaseTransaction); secondBlock.UpdateMerkleRoot(); blockChain.AddBlock(secondBlock); unspentTransactions = blockChain.GetUnspentTransactions(); Assert.IsTrue(unspentTransactions.Count() == 5); Assert.IsTrue(unspentTransactions.Sum(t => t.Value) == 50); }
public void CanSplitFees() { var satoshi = new Key(); var alice = new Key(); var bob = new Key(); var aliceCoins = new ICoin[] { RandomCoin("0.4", alice), RandomCoin("0.6", alice) }; var bobCoins = new ICoin[] { RandomCoin("0.2", bob), RandomCoin("0.3", bob) }; TransactionBuilder builder = new TransactionBuilder(); FeeRate rate = new FeeRate(Money.Coins(0.0004m)); var tx = builder .AddCoins(aliceCoins) .AddKeys(alice) .Send(satoshi, Money.Coins(0.1m)) .SetChange(alice) .Then() .AddCoins(bobCoins) .AddKeys(bob) .Send(satoshi, Money.Coins(0.01m)) .SetChange(bob) .SendEstimatedFeesSplit(rate) .BuildTransaction(true); var estimated = builder.EstimateFees(tx, rate); Assert.True(builder.Verify(tx, estimated)); }
public void CanGetWalletOrderedBalances() { using(var tester = CreateTester()) { var bob = new Key(); var alice1 = new Key(); var alice2 = new Key(); var satoshi = new Key(); var expectedRule = tester.Client.AddWalletRule("Alice", new ScriptRule(alice1) { CustomData = "hello" }); Assert.True(expectedRule.Rule.ToString().Contains("hello")); var rules = tester.Client.GetWalletRules("Alice"); Assert.Equal(1, rules.Length); Assert.Equal(expectedRule.WalletId, rules[0].WalletId); Assert.Equal(expectedRule.Rule.ToString(), rules[0].Rule.ToString()); var aliceR1 = expectedRule.Rule; var chainBuilder = tester.CreateChainBuilder(); chainBuilder.EmitMoney(bob, "50.0"); var tx = chainBuilder.EmitMoney(alice1, "10.0"); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 1); Assert.Equal("Alice", aliceBalance[0].BalanceId.GetWalletId()); Assert.True(aliceBalance[0].Amount == Money.Parse("10.0")); Assert.True(aliceBalance[0].IsCoinbase); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); Assert.True(!aliceBalance[0].HasOpReturn); Assert.Equal( aliceR1.ToString() , aliceBalance[0].GetMatchedRules(0, MatchLocation.Output).First().ToString()); var aliceR2 = tester.Client.AddWalletRule("Alice", new ScriptRule(alice2)).Rule; rules = tester.Client.GetWalletRules("Alice"); Assert.Equal(2, rules.Length); //Adding two time same rule should be idempotent tester.Client.AddWalletRule("Alice", new ScriptRule(alice2)); Assert.Equal(2, rules.Length); ///////////////////////////////////////////// tx = new TransactionBuilder() .AddKeys(alice1) .AddCoins(new Coin(tx.GetHash(), 0, tx.Outputs[0].Value, tx.Outputs[0].ScriptPubKey)) .Send(alice2, "2.0") .Send(alice1, "3.9") .Send(bob, "2.1") .Send(alice1, "0.1") .SendFees("1.9") .BuildTransaction(true); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance[0].Amount == Money.Parse("-4.0")); Assert.Equal( aliceR1.ToString() , aliceBalance[0].GetMatchedRules(aliceBalance[0].SpentCoins[0]).First().ToString()); Assert.Equal( aliceR2.ToString() , aliceBalance[0].GetMatchedRules(0, MatchLocation.Output).First().ToString()); Assert.Equal( aliceR1.ToString() , aliceBalance[0].GetMatchedRules(1, MatchLocation.Output).First().ToString()); Assert.Equal( aliceR1.ToString() , aliceBalance[0].GetMatchedRules(3, MatchLocation.Output).First().ToString()); Assert.True(aliceBalance[0].GetMatchedRules(2, MatchLocation.Output).Count() == 0); var prevTx = tx; var newtx = new Transaction() { Inputs = { new TxIn(new OutPoint(tx,0)), //alice2 2 new TxIn(new OutPoint(tx,1)), //alice1 3.9 new TxIn(new OutPoint(tx,2)), //bob 2.1 new TxIn(new OutPoint(tx,3)), //alice1 0.1 } }; tx = new TransactionBuilder() .ContinueToBuild(newtx) .AddKeys(alice1, alice2) .AddCoins(new Coin(prevTx.GetHash(), 0, prevTx.Outputs[0].Value, prevTx.Outputs[0].ScriptPubKey)) .AddCoins(new Coin(prevTx.GetHash(), 1, prevTx.Outputs[1].Value, prevTx.Outputs[1].ScriptPubKey)) .AddCoins(new Coin(prevTx.GetHash(), 3, prevTx.Outputs[3].Value, prevTx.Outputs[3].ScriptPubKey)) .Then() .AddKeys(bob) .AddCoins(new Coin(prevTx.GetHash(), 2, prevTx.Outputs[2].Value, prevTx.Outputs[2].ScriptPubKey)) .Send(alice1, "0.10") .Send(alice2, "0.22") .Send(bob, "1.0") .Send(alice2, "0.23") .SetChange(satoshi) .BuildTransaction(true); chainBuilder.Emit(tx); var b3 = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); var entry = aliceBalance[0]; Assert.Equal(entry.GetMatchedRules(new OutPoint(prevTx, 0)).First().ToString(), aliceR2.ToString()); Assert.Equal(entry.GetMatchedRules(new OutPoint(prevTx, 1)).First().ToString(), aliceR1.ToString()); Assert.Null(entry.GetMatchedRules(new OutPoint(prevTx, 2)).FirstOrDefault()); Assert.Equal(entry.GetMatchedRules(new OutPoint(prevTx, 3)).First().ToString(), aliceR1.ToString()); var receivedOutpoints = tx.Outputs.Select((o, i) => new OutPoint(tx.GetHash(), i)).ToArray(); Assert.Equal(entry.GetMatchedRules(new OutPoint(tx, 1)).First().ToString(), aliceR1.ToString()); Assert.Equal(entry.GetMatchedRules(new OutPoint(tx, 2)).First().ToString(), aliceR2.ToString()); Assert.Null(entry.GetMatchedRules(new OutPoint(tx, 3)).FirstOrDefault()); Assert.Equal(entry.GetMatchedRules(new OutPoint(tx, 4)).First().ToString(), aliceR2.ToString()); //// //Send money to P2SH address, should receive script coins tester.Client.AddWalletRule("Alice", new ScriptRule(alice1.PubKey, true)); tester.Client.AddWalletRule("Alice", new ScriptRule(alice2.PubKey.ScriptPubKey.Hash, false)); tx = new TransactionBuilder() .ContinueToBuild(newtx) .AddKeys(alice1, alice2) .AddCoins(new Coin(prevTx.GetHash(), 0, prevTx.Outputs[0].Value, prevTx.Outputs[0].ScriptPubKey)) .AddCoins(new Coin(prevTx.GetHash(), 1, prevTx.Outputs[1].Value, prevTx.Outputs[1].ScriptPubKey)) .AddCoins(new Coin(prevTx.GetHash(), 3, prevTx.Outputs[3].Value, prevTx.Outputs[3].ScriptPubKey)) .Then() .AddKeys(bob) .AddCoins(new Coin(prevTx.GetHash(), 2, prevTx.Outputs[2].Value, prevTx.Outputs[2].ScriptPubKey)) .Send(alice1.PubKey.ScriptPubKey.Hash, "0.10") .Send(alice2.PubKey.ScriptPubKey.Hash, "0.22") .Send(bob, "1.0") .Send(alice2.PubKey.ScriptPubKey.Hash, "0.23") .SetChange(satoshi) .BuildTransaction(true); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance[0].ReceivedCoins[0] is ScriptCoin); Assert.True(aliceBalance[0].ReceivedCoins[0].TxOut.ScriptPubKey == alice1.PubKey.ScriptPubKey.Hash.ScriptPubKey); Assert.True(((ScriptCoin)(aliceBalance[0].ReceivedCoins[0])).Redeem == alice1.PubKey.ScriptPubKey); Assert.False(aliceBalance[0].ReceivedCoins[1] is ScriptCoin); Assert.False(aliceBalance[0].ReceivedCoins[2] is ScriptCoin); tx = new TransactionBuilder() .AddKeys(alice1, alice2) .AddCoins(aliceBalance[0].ReceivedCoins[0]) .Send(satoshi, "0.0001") .SetChange(alice1) .BuildTransaction(true); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var aliceBalance2 = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(((ScriptCoin)(aliceBalance2[0].SpentCoins[0])).Redeem == alice1.PubKey.ScriptPubKey); ///// } }
public void CanMergeBalance() { using(var tester = CreateTester()) { var bob = new Key(); var alice1 = new Key(); var alice2 = new Key(); var satoshi = new Key(); var chainBuilder = tester.CreateChainBuilder(); chainBuilder.EmitMoney(bob, "50.0"); var tx = chainBuilder.EmitMoney(alice1, "10.0"); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); //Can merge address balance into wallet tester.Client.MergeIntoWallet("Alice", alice1); var aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 1); Assert.True(aliceBalance[0].Amount == Money.Parse("10.0")); Assert.True(aliceBalance[0].IsCoinbase); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); Assert.True(!aliceBalance[0].HasOpReturn); //// //Merging duplicate order balance should not change anything tester.Client.AddWalletRule("Alice", new ScriptRule(alice1)); chainBuilder.EmitMoney(alice1, "9.0"); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 2); Assert.True(aliceBalance[0].Amount == Money.Parse("9.0")); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); tester.Client.MergeIntoWallet("Alice", alice1); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 2); Assert.True(aliceBalance[0].Amount == Money.Parse("9.0")); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); Assert.True(aliceBalance[1].Amount == Money.Parse("10.0")); Assert.True(aliceBalance[1].ScriptPubKey == alice1.ScriptPubKey); //// //Merge alice2 into Alice with a tx involving alice1 tx = new TransactionBuilder() .AddKeys(alice1) .AddCoins(new Coin(tx.GetHash(), 0, tx.Outputs[0].Value, tx.Outputs[0].ScriptPubKey)) //Alice1 10 .Send(alice2, "2.0") .Send(alice1, "3.9") .Send(bob, "2.1") .Send(alice1, "0.1") .SendFees("1.9") .BuildTransaction(true); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 3); Assert.True(aliceBalance[0].Amount == -Money.Parse("10.0") + Money.Parse("3.9") + Money.Parse("0.1")); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); tester.Client.MergeIntoWallet("Alice", alice2); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 3); Assert.True(aliceBalance[0].Amount == -Money.Parse("10.0") + Money.Parse("3.9") + Money.Parse("0.1") + Money.Parse("2.0")); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); var newtx = new Transaction() { Inputs = { new TxIn(new OutPoint(tx,0)), //alice2 2 new TxIn(new OutPoint(tx,1)), //alice1 3.9 new TxIn(new OutPoint(tx,2)), //bob 2.1 new TxIn(new OutPoint(tx,3)), //alice1 0.1 } }; tx = new TransactionBuilder() .ContinueToBuild(newtx) .AddKeys(alice1, alice2) .AddCoins(new Coin(tx.GetHash(), 0, tx.Outputs[0].Value, tx.Outputs[0].ScriptPubKey)) .AddCoins(new Coin(tx.GetHash(), 1, tx.Outputs[1].Value, tx.Outputs[1].ScriptPubKey)) .AddCoins(new Coin(tx.GetHash(), 3, tx.Outputs[3].Value, tx.Outputs[3].ScriptPubKey)) .Then() .AddKeys(bob) .AddCoins(new Coin(tx.GetHash(), 2, tx.Outputs[2].Value, tx.Outputs[2].ScriptPubKey)) .Send(alice1, "0.10") .Send(alice2, "0.22") .Send(bob, "1.0") .Send(alice2, "0.23") .SetChange(satoshi) .BuildTransaction(true); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 4); Assert.True(aliceBalance[0].Amount == -Money.Parse("3.9") - Money.Parse("0.1") + Money.Parse("0.10") ); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); tester.Client.MergeIntoWallet("Alice", alice2, new ScriptRule() { ScriptPubKey = alice2.ScriptPubKey, CustomData = "hello" }); aliceBalance = tester.Client.GetOrderedBalance("Alice").ToArray(); Assert.True(aliceBalance.Length == 4); Assert.True(aliceBalance[0].Amount == -Money.Parse("3.9") - Money.Parse("0.1") + Money.Parse("0.10") - Money.Parse("2.0") + Money.Parse("0.22") + Money.Parse("0.23") ); Assert.True(aliceBalance[0].ScriptPubKey == alice1.ScriptPubKey); Assert.True(aliceBalance[0].MatchedRules.Any(m => m.Rule.CustomData == "hello")); //// } }
public void CanGetColoredBalance() { using(var tester = CreateTester()) { var chainBuilder = tester.CreateChainBuilder(); tester.Client.ColoredBalance = true; //Colored coin Payment //GoldGuy emits gold to Nico var txBuilder = new TransactionBuilder(); var issuanceCoinsTransaction = new Transaction() { Outputs = { new TxOut("1.0", goldGuy.PrivateKey.PubKey), new TxOut("1.0", silverGuy.PrivateKey.PubKey), new TxOut("1.0", nico.GetAddress()), new TxOut("1.0", alice.GetAddress()), } }; IssuanceCoin[] issuanceCoins = issuanceCoinsTransaction .Outputs .Take(2) .Select((o, i) => new Coin(new OutPoint(issuanceCoinsTransaction.GetHash(), i), o)) .Select(c => new IssuanceCoin(c)) .ToArray(); var goldIssuanceCoin = issuanceCoins[0]; var silverIssuanceCoin = issuanceCoins[1]; var nicoCoin = new Coin(new OutPoint(issuanceCoinsTransaction, 2), issuanceCoinsTransaction.Outputs[2]); var aliceCoin = new Coin(new OutPoint(issuanceCoinsTransaction, 3), issuanceCoinsTransaction.Outputs[3]); var goldId = goldIssuanceCoin.AssetId; var silverId = silverIssuanceCoin.AssetId; chainBuilder.Emit(issuanceCoinsTransaction); var b = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var balance = tester.Client.GetOrderedBalance(nico).ToArray(); var entry = balance[0]; Assert.NotNull(entry.ColoredTransaction); Assert.Equal(Money.Parse("1.0"), entry.Amount); txBuilder = new TransactionBuilder(); txBuilder.StandardTransactionPolicy.MinRelayTxFee = new FeeRate(Money.Satoshis(1000)); var tx = txBuilder .AddKeys(goldGuy) .AddCoins(goldIssuanceCoin) .IssueAsset(nico.GetAddress(), new AssetMoney(goldId, 30)) .SetChange(goldGuy.PrivateKey.PubKey) .BuildTransaction(true); chainBuilder.Emit(tx); b = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var ctx = new IndexerColoredTransactionRepository(tester.Indexer.Configuration); balance = tester.Client.GetOrderedBalance(nico.GetAddress()).ToArray(); var coloredEntry = balance[0]; Assert.Equal(Money.Parse("0.0"), coloredEntry.Amount); Assert.True(coloredEntry.GetAssetAmount(goldId).CompareTo(30L) == 0); var coloredCoins = ColoredCoin.Find(tx, ctx).ToArray(); var nicoGold = coloredCoins[0]; txBuilder = new TransactionBuilder(1); txBuilder.StandardTransactionPolicy.MinRelayTxFee = new FeeRate(Money.Satoshis(1000)); //GoldGuy sends 20 gold to alice against 0.6 BTC. Nico sends 10 gold to alice + 0.02 BTC. tx = txBuilder .AddKeys(goldGuy) .AddCoins(goldIssuanceCoin) .IssueAsset(alice.GetAddress(), new AssetMoney(goldId, 20)) .SetChange(goldGuy.PrivateKey.PubKey) .Then() .AddKeys(nico.PrivateKey) .AddCoins(nicoCoin) .AddCoins(nicoGold) .SendAsset(alice.GetAddress(), new AssetMoney(goldId, 10)) .Send(alice.GetAddress(), Money.Parse("0.02")) .SetChange(nico.GetAddress()) .Then() .AddKeys(alice) .AddCoins(aliceCoin) .Send(goldGuy.GetAddress(), Money.Parse("0.6")) .SetChange(alice.GetAddress()) .Shuffle() .BuildTransaction(true); chainBuilder.Emit(tx); b = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); //Nico, should have lost 0.02 BTC and 10 gold balance = tester.Client.GetOrderedBalance(nico.GetAddress()).ToArray(); balance = tester.Client.GetOrderedBalance(nico.GetAddress()).ToArray(); coloredEntry = balance[0]; Assert.Equal(Money.Parse("-0.02") - Money.Satoshis(546), coloredEntry.Amount); Assert.True(coloredEntry.GetAssetAmount(goldId).CompareTo(-10L) == 0); //Alice, should have lost 0.58 BTC, but win 10 + 20 gold (one is a transfer, the other issuance) balance = tester.Client.GetOrderedBalance(alice.GetAddress()).ToArray(); coloredEntry = balance[0]; Assert.Equal(Money.Parse("-0.58"), coloredEntry.Amount); Assert.True(coloredEntry.GetAssetAmount(goldId).CompareTo(30L) == 0); } }
public void CanGetOrderedBalances() { using(var tester = CreateTester()) { var bob = new Key(); var alice = new Key(); var satoshi = new Key(); var chainBuilder = tester.CreateChainBuilder(); chainBuilder.EmitMoney(bob, "50.0"); chainBuilder.EmitMoney(alice, "50.0"); chainBuilder.SubmitBlock(); chainBuilder.EmitMoney(bob, "20.0"); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var bobBalance = tester.Client.GetOrderedBalance(bob).ToArray(); Assert.True(bobBalance.Length == 2); Assert.True(bobBalance[0].Amount == Money.Parse("20.0")); Assert.True(bobBalance[0].IsCoinbase); Assert.True(!bobBalance[0].HasOpReturn); Assert.True(bobBalance[1].Amount == Money.Parse("50.0")); var aliceBalance = tester.Client.GetOrderedBalance(alice).ToArray(); var tx = new TransactionBuilder() .AddCoins(bobBalance[0].ReceivedCoins) .AddKeys(bob) .Send(alice, "5.0") .SetChange(bob) .Then() .AddCoins(aliceBalance[0].ReceivedCoins) .AddKeys(alice) .Send(satoshi, "1.0") .SendFees("0.05") .SetChange(alice) .BuildTransaction(true); tx.AddOutput(new TxOut(Money.Zero, TxNullDataTemplate.Instance.GenerateScriptPubKey(RandomUtils.GetBytes(3)))); //Add OP_RETURN chainBuilder.Emit(tx, false); var mempoolDate1 = tester.Client.GetTransaction(tx.GetHash()).MempoolDate.Value; var block = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var mempoolDate2 = tester.Client.GetTransaction(tx.GetHash()).MempoolDate.Value; Assert.Equal(mempoolDate1, mempoolDate2); bobBalance = tester.Client.GetOrderedBalance(bob).ToArray(); Assert.True(bobBalance[0].Amount == -Money.Parse("5.0")); for(int i = 0; i < 2; i++) { aliceBalance = tester.Client.GetOrderedBalance(alice).ToArray(); Assert.True(aliceBalance[0].Amount == -Money.Parse("1.0") - Money.Parse("0.05") + Money.Parse("5.0")); Assert.True(aliceBalance[0].SpentIndices.Count == 1); Assert.True(aliceBalance[0].SpentIndices[0] == 1); Assert.True(aliceBalance[0].SpentOutpoints[0] == tx.Inputs[1].PrevOut); Assert.True(aliceBalance[0].SpentCoins[0].Outpoint == aliceBalance[1].ReceivedCoins[0].Outpoint); Assert.True(aliceBalance[0].TransactionId == tx.GetHash()); Assert.True(aliceBalance[0].Height == 3); Assert.True(aliceBalance[0].BlockId == block.GetHash()); Assert.True(!aliceBalance[0].IsCoinbase); Assert.True(aliceBalance[0].HasOpReturn); Assert.True(aliceBalance[0].ReceivedCoins[0].Outpoint == new OutPoint(tx.GetHash(), 1)); //Bob coin Assert.True(aliceBalance[0].ReceivedCoins[1].Outpoint == new OutPoint(tx.GetHash(), 2)); //Change } var satoshiBalance = tester.Client.GetOrderedBalance(satoshi).ToArray(); Assert.True(satoshiBalance[0].Amount == Money.Parse("1.0")); tx = new TransactionBuilder() .AddCoins(satoshiBalance[0].ReceivedCoins) .AddKeys(satoshi) .Send(alice, "0.2") .SetChange(satoshi) .BuildTransaction(true); tester.Indexer.Index(new TransactionEntry.Entity(null, tx, null)); tester.Indexer.IndexOrderedBalance(tx); tx = new TransactionBuilder() .AddCoins(satoshiBalance[0].ReceivedCoins) .AddKeys(satoshi) .Send(alice, "0.3") .SetChange(satoshi) .BuildTransaction(true); tester.Indexer.Index(new TransactionEntry.Entity(null, tx, null)); tester.Indexer.IndexOrderedBalance(tx); satoshiBalance = tester.Client.GetOrderedBalance(satoshi).ToArray(); Assert.True(satoshiBalance[0].Amount == -Money.Parse("0.3")); Assert.True(satoshiBalance[1].Amount == -Money.Parse("0.2")); tx = new TransactionBuilder() .AddCoins(satoshiBalance[0].ReceivedCoins) .AddKeys(satoshi) .Send(alice, "0.1") .SetChange(satoshi) .BuildTransaction(true); Thread.Sleep(1000); chainBuilder.Emit(tx); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); satoshiBalance = tester.Client.GetOrderedBalance(satoshi).ToArray(); Assert.True(satoshiBalance[0].Amount == -Money.Parse("0.1")); tester.Client.CleanUnconfirmedChanges(satoshi, TimeSpan.Zero); satoshiBalance = tester.Client.GetOrderedBalance(satoshi).ToArray(); Assert.True(satoshiBalance.Length == 2); } }
public void CanGetOrderedBalancesP2WSH() { using(var tester = CreateTester()) { var bob = new Key(); var alice = new Key(); var satoshi = new Key(); var chainBuilder = tester.CreateChainBuilder(); chainBuilder.EmitMoney(bob.PubKey.ScriptPubKey.WitHash, "20.0"); chainBuilder.EmitMoney(alice.PubKey.ScriptPubKey.WitHash, "50.0"); chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); var aliceBalance = tester.Client.GetOrderedBalance(alice.PubKey.ScriptPubKey.WitHash).ToArray(); Assert.True(aliceBalance.Length == 1); var tx = new TransactionBuilder() .AddCoins(new WitScriptCoin((Coin)aliceBalance[0].ReceivedCoins[0], alice.PubKey.ScriptPubKey)) .AddKeys(alice) .Send(bob.PubKey.ScriptPubKey.WitHash, "5.0") .SetChange(alice.PubKey.ScriptPubKey.WitHash) .BuildTransaction(true); chainBuilder.Emit(tx); var block = chainBuilder.SubmitBlock(); chainBuilder.SyncIndexer(); aliceBalance = tester.Client.GetOrderedBalance(alice.PubKey.ScriptPubKey.WitHash).ToArray(); Assert.True(aliceBalance.Length == 2); Assert.True(aliceBalance[0].Amount == -Money.Coins(5.0m)); var bobBalance = tester.Client.GetOrderedBalance(bob.PubKey.ScriptPubKey.WitHash).ToArray(); Assert.True(bobBalance.Length == 2); Assert.True(bobBalance[0].Amount == Money.Coins(5.0m)); } }
public void TestPuzzleSolver() { RsaKey key = TestKeys.Default; PuzzleSolution expectedSolution = null; Puzzle puzzle = key.PubKey.GeneratePuzzle(ref expectedSolution); var parameters = new SolverParameters { FakePuzzleCount = 50, RealPuzzleCount = 10, ServerKey = key.PubKey }; SolverClientSession client = new SolverClientSession(parameters); SolverServerSession server = new SolverServerSession(key, parameters); var clientEscrow = new Key(); var serverEscrow = new Key(); var escrow = CreateEscrowCoin(clientEscrow.PubKey, serverEscrow.PubKey); var redeemDestination = new Key().ScriptPubKey; client.ConfigureEscrowedCoin(escrow, clientEscrow, redeemDestination); client.AcceptPuzzle(puzzle.PuzzleValue); RoundTrip(ref client, parameters); Assert.True(client.GetInternalState().RedeemDestination == redeemDestination); PuzzleValue[] puzzles = client.GeneratePuzzles(); RoundTrip(ref client, parameters); RoundTrip(ref puzzles); server.ConfigureEscrowedCoin(escrow, serverEscrow); var commitments = server.SolvePuzzles(puzzles); RoundTrip(ref server, parameters, key); RoundTrip(ref commitments); var revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); SolutionKey[] fakePuzzleKeys = server.CheckRevelation(revelation); RoundTrip(ref server, parameters, key); RoundTrip(ref fakePuzzleKeys); BlindFactor[] blindFactors = client.GetBlindFactors(fakePuzzleKeys); RoundTrip(ref client, parameters); RoundTrip(ref blindFactors); var offerInformation = server.CheckBlindedFactors(blindFactors, FeeRate); RoundTrip(ref server, parameters, key); var clientOfferSig = client.SignOffer(offerInformation); //Verify if the scripts are correctly created var fulfill = server.FulfillOffer(clientOfferSig, new Key().ScriptPubKey, FeeRate); var offerRedeem = client.CreateOfferRedeemTransaction(FeeRate); var offerTransaction = server.GetSignedOfferTransaction(); var offerCoin = offerTransaction.Transaction.Outputs.AsCoins().First(); var resigned = offerTransaction.ReSign(client.EscrowedCoin); TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder.AddCoins(client.EscrowedCoin); Assert.True(txBuilder.Verify(resigned)); resigned = fulfill.ReSign(offerCoin); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerCoin); Assert.True(txBuilder.Verify(resigned)); var offerRedeemTx = offerRedeem.ReSign(offerCoin); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(offerCoin); Assert.True(txBuilder.Verify(offerRedeemTx)); client.CheckSolutions(fulfill.Transaction); RoundTrip(ref client, parameters); var clientEscapeSignature = client.SignEscape(); var escapeTransaction = server.GetSignedEscapeTransaction(clientEscapeSignature, FeeRate, new Key().ScriptPubKey); txBuilder = new TransactionBuilder(); txBuilder.AddCoins(client.EscrowedCoin); Assert.True(txBuilder.Verify(escapeTransaction)); var solution = client.GetSolution(); RoundTrip(ref client, parameters); Assert.True(solution == expectedSolution); }
public void CanBuildWitTransaction() { Key alice = new Key(); Key bob = new Key(); Transaction previousTx = null; Coin previousCoin = null; WitScriptCoin witnessCoin = null; TransactionBuilder builder = null; Transaction signedTx = null; ScriptCoin scriptCoin = null; //P2WPKH previousTx = new Transaction(); previousTx.Outputs.Add(new TxOut(Money.Coins(1.0m), alice.PubKey.WitHash)); previousCoin = previousTx.Outputs.AsCoins().First(); builder = new TransactionBuilder(); builder.AddKeys(alice); builder.AddCoins(previousCoin); builder.Send(bob, Money.Coins(0.4m)); builder.SendFees(Money.Satoshis(30000)); builder.SetChange(alice); signedTx = builder.BuildTransaction(true); Assert.True(builder.Verify(signedTx)); //P2WSH previousTx = new Transaction(); previousTx.Outputs.Add(new TxOut(Money.Coins(1.0m), alice.PubKey.ScriptPubKey.WitHash)); previousCoin = previousTx.Outputs.AsCoins().First(); witnessCoin = new WitScriptCoin(previousCoin, alice.PubKey.ScriptPubKey); builder = new TransactionBuilder(); builder.AddKeys(alice); builder.AddCoins(witnessCoin); builder.Send(bob, Money.Coins(0.4m)); builder.SendFees(Money.Satoshis(30000)); builder.SetChange(alice); signedTx = builder.BuildTransaction(true); Assert.True(builder.Verify(signedTx)); //P2SH(P2WPKH) previousTx = new Transaction(); previousTx.Outputs.Add(new TxOut(Money.Coins(1.0m), alice.PubKey.WitHash.ScriptPubKey.Hash)); previousCoin = previousTx.Outputs.AsCoins().First(); scriptCoin = new ScriptCoin(previousCoin, alice.PubKey.WitHash.ScriptPubKey); builder = new TransactionBuilder(); builder.AddKeys(alice); builder.AddCoins(scriptCoin); builder.Send(bob, Money.Coins(0.4m)); builder.SendFees(Money.Satoshis(30000)); builder.SetChange(alice); signedTx = builder.BuildTransaction(true); Assert.True(builder.Verify(signedTx)); //P2SH(P2WSH) previousTx = new Transaction(); previousTx.Outputs.Add(new TxOut(Money.Coins(1.0m), alice.PubKey.ScriptPubKey.WitHash.ScriptPubKey.Hash)); previousCoin = previousTx.Outputs.AsCoins().First(); witnessCoin = new WitScriptCoin(previousCoin, alice.PubKey.ScriptPubKey); builder = new TransactionBuilder(); builder.AddKeys(alice); builder.AddCoins(witnessCoin); builder.Send(bob, Money.Coins(0.4m)); builder.SendFees(Money.Satoshis(30000)); builder.SetChange(alice); signedTx = builder.BuildTransaction(true); Assert.True(builder.Verify(signedTx)); //Can remove witness data from tx var signedTx2 = signedTx.WithOptions(TransactionOptions.None); Assert.Equal(signedTx.GetHash(), signedTx2.GetHash()); Assert.True(signedTx2.GetSerializedSize() < signedTx.GetSerializedSize()); }
public void Send(string address, string btc = "all", string path = "") { var walletFilePath = GetWalletFilePath(path); BitcoinAddress addressToSend; try { addressToSend = BitcoinAddress.Create(address, Config.network); } catch (Exception ex) { Exit(ex.ToString()); throw ex; } Safe safe = DecryptWalletByAskingForPassword(walletFilePath); if (Config.connectionType == ConnectionType.Http) { Dictionary<BitcoinAddress, List<BalanceOperation>> operationsPerAddresses = QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe, 7); // 获取非空私钥 WriteLine("Finding not empty private keys..."); var operationsPerNotEmptyPrivateKeys = new Dictionary<BitcoinExtKey, List<BalanceOperation>>(); foreach (var elem in operationsPerAddresses) { var balance = Money.Zero; foreach (var op in elem.Value) balance += op.Amount; if (balance > Money.Zero) { var secret = safe.FindPrivateKey(elem.Key); operationsPerNotEmptyPrivateKeys.Add(secret, elem.Value); } } // 获取 pubkey 脚本 WriteLine("Select change address..."); Script changeScriptPubKey = null; Dictionary<BitcoinAddress, List<BalanceOperation>> operationsPerChangeAddresses = QBitNinjaJutsus.QueryOperationsPerSafeAddresses(safe, minUnusedKeys: 1, hdPathType: HdPathType.Change); foreach (var elem in operationsPerChangeAddresses) { if (elem.Value.Count == 0) changeScriptPubKey = safe.FindPrivateKey(elem.Key).ScriptPubKey; } if (changeScriptPubKey == null) throw new ArgumentNullException(); // 获取 UXTO WriteLine("Gathering unspent coins..."); Dictionary<Coin, bool> unspentCoins = QBitNinjaJutsus.GetUnspentCoins(operationsPerNotEmptyPrivateKeys.Keys); // 获取费用 WriteLine("Calculating transaction fee..."); Money fee; try { var txSizeInBytes = 250; using (var client = new HttpClient()) { const string request = @"https://bitcoinfees.21.co/api/v1/fees/recommended"; var result = client.GetAsync(request, HttpCompletionOption.ResponseContentRead).Result; var json = JObject.Parse(result.Content.ReadAsStringAsync().Result); var fastestSatoshiPerByteFee = json.Value<decimal>("fastestFee"); fee = new Money(fastestSatoshiPerByteFee * txSizeInBytes, MoneyUnit.Satoshi); } } catch { Exit("Couldn't calculate transaction fee, try it again later."); throw new Exception("Can't get tx fee"); } WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc"); // 有多少 btc 可花费 (有尚未确认的 btc) Money availableAmount = Money.Zero; Money unconfirmedAvailableAmount = Money.Zero; foreach (var elem in unspentCoins) { // If can spend unconfirmed add all if (Config.canSpendUnconfirmed) { availableAmount += elem.Key.Amount; if (!elem.Value) unconfirmedAvailableAmount += elem.Key.Amount; } // else only add confirmed ones else { if (elem.Value) { availableAmount += elem.Key.Amount; } } } // 花费多少 Money amountToSend = null; string amountString = btc; if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase)) { amountToSend = availableAmount; amountToSend -= fee; } else { amountToSend = ParseBtcString(amountString); } // 检查 if (amountToSend < Money.Zero || availableAmount < amountToSend + fee) Exit("Not enough coins."); decimal feePc = Math.Round((100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC)); if (feePc > 1) { WriteLine(); WriteLine($"The transaction fee is {feePc.ToString("0.#")}% of your transaction amount."); WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc"); WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc"); ConsoleKey response = GetYesNoAnswerFromUser(); if (response == ConsoleKey.N) { Exit("User interruption."); } } var confirmedAvailableAmount = availableAmount - unconfirmedAvailableAmount; var totalOutAmount = amountToSend + fee; if (confirmedAvailableAmount < totalOutAmount) { var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount; WriteLine(); WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")} unconfirmed btc."); ConsoleKey response = GetYesNoAnswerFromUser(); if (response == ConsoleKey.N) { Exit("User interruption."); } } // 选哪些 UTXO WriteLine("Selecting coins..."); var coinsToSpend = new HashSet<Coin>(); var unspentConfirmedCoins = new List<Coin>(); var unspentUnconfirmedCoins = new List<Coin>(); foreach (var elem in unspentCoins) if (elem.Value) unspentConfirmedCoins.Add(elem.Key); else unspentUnconfirmedCoins.Add(elem.Key); bool haveEnough = QBitNinjaJutsus.SelectCoins(ref coinsToSpend, totalOutAmount, unspentConfirmedCoins); if (!haveEnough) haveEnough = QBitNinjaJutsus.SelectCoins(ref coinsToSpend, totalOutAmount, unspentUnconfirmedCoins); if (!haveEnough) throw new Exception("Not enough funds."); // 获取签名密钥 var signingKeys = new HashSet<ISecret>(); foreach (var coin in coinsToSpend) { foreach (var elem in operationsPerNotEmptyPrivateKeys) { if (elem.Key.ScriptPubKey == coin.ScriptPubKey) signingKeys.Add(elem.Key); } } // 创建交易 WriteLine("Signing transaction..."); var builder = new TransactionBuilder(); var tx = builder .AddCoins(coinsToSpend) .AddKeys(signingKeys.ToArray()) .Send(addressToSend, amountToSend) .SetChange(changeScriptPubKey) .SendFees(fee) .BuildTransaction(true); if (!builder.Verify(tx)) Exit("Couldn't build the transaction."); WriteLine($"Transaction Id: {tx.GetHash()}"); var qBitClient = new QBitNinjaClient(Config.network); // Qbit 相应有 bug, 手动确认 BroadcastResponse broadcastResponse; var success = false; var tried = 0; var maxTry = 7; do { tried++; WriteLine($"Try broadcasting transaction... ({tried})"); broadcastResponse = qBitClient.Broadcast(tx).Result; var getTxResp = qBitClient.GetTransaction(tx.GetHash()).Result; if (getTxResp == null) { Thread.Sleep(3000); continue; } else { success = true; break; } } while (tried <= maxTry); if (!success) { if (broadcastResponse.Error != null) { WriteLine($"Error code: {broadcastResponse.Error.ErrorCode} Reason: {broadcastResponse.Error.Reason}"); } Exit($"The transaction might not have been successfully broadcasted. Please check the Transaction ID in a block explorer.", ConsoleColor.Blue); } Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green); } else if (Config.connectionType == ConnectionType.FullNode) { throw new NotImplementedException(); } else { Exit("Invalid connection type."); } }
public void CanMutateSignature() { Transaction funding = new Transaction("010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d6000000008b483045022100abbc8a73fe2054480bda3f3281da2d0c51e2841391abd4c09f4f908a2034c18d02205bc9e4d68eafb918f3e9662338647a4419c0de1a650ab8983f1d216e2a31d8e30141046f55d7adeff6011c7eac294fe540c57830be80e9355c83869c9260a4b8bf4767a66bacbd70b804dc63d5beeb14180292ad7f3b083372b1d02d7a37dd97ff5c9effffffff0140420f000000000017a914f815b036d9bbbce5e9f2a00abd1bf3dc91e955108700000000"); Transaction spending = new Transaction("0100000001aca7f3b45654c230e0886a57fb988c3044ef5e8f7f39726d305c61d5e818903c00000000fd5d010048304502200187af928e9d155c4b1ac9c1c9118153239aba76774f775d7c1f9c3e106ff33c0221008822b0f658edec22274d0b6ae9de10ebf2da06b1bbdaaba4e50eb078f39e3d78014730440220795f0f4f5941a77ae032ecb9e33753788d7eb5cb0c78d805575d6b00a1d9bfed02203e1f4ad9332d1416ae01e27038e945bc9db59c732728a383a6f1ed2fb99da7a4014cc952410491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8334f864104865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccffef4ec687441048d2455d2403e08708fc1f556002f1b6cd83f992d085097f9974ab08a28838f07896fbab08f39495e15fa6fad6edbfb1e754e35fa1c7844c41f322a1863d4621353aeffffffff0140420f00000000001976a914ae56b4db13554d321c402db3961187aed1bbed5b88ac00000000"); TransactionBuilder builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; builder.AddCoins(funding.Outputs.AsCoins()); Assert.True(builder.Verify(spending)); foreach(var input in spending.Inputs.AsIndexedInputs()) { var ops = input.TxIn.ScriptSig.ToOps().ToArray(); foreach(var sig in ops.Select(o => { try { return new TransactionSignature(o.PushData); } catch { return null; } }) .Select((sig, i) => new { sig, i }) .Where(i => i.sig != null)) { ops[sig.i] = Op.GetPushOp(sig.sig.MakeCanonical().ToBytes()); } input.TxIn.ScriptSig = new Script(ops); } Assert.True(builder.Verify(spending)); }
public void SegWit_CS_Setup_Tx_and_Withdrawal() { var network = ObsidianXNetworksSelector.Obsidian.Mainnet(); // I have received 100_000 in my wallet in this address Key myBudgetKey = new Key(); PubKey myBudgetPubKey = myBudgetKey.PubKey.Compress(); Transaction received = network.CreateTransaction(); received.Outputs.Add(new TxOut(Money.Coins(100_000), myBudgetPubKey.WitHash.ScriptPubKey)); List <Coin> myBudgetCoins = received.Outputs.AsCoins().ToList(); // for 90_000, I want to set up ColdStaking Key coldKey = new Key(); PubKey coldPubKey = coldKey.PubKey.Compress(); Key hotKey = new Key(); PubKey hotPubKey = hotKey.PubKey.Compress(); Script csRedeemScript = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKey.WitHash.AsKeyId(), coldPubKey.WitHash.AsKeyId()); this.output.WriteLine($"csRedeemScript: {csRedeemScript}"); string csScriptAddress = csRedeemScript.WitHash.GetAddress(network).ToString(); this.output.WriteLine($"{nameof(csScriptAddress)}: {csScriptAddress}"); Script csScriptAddressScriptPubKey = csRedeemScript.WitHash.ScriptPubKey; this.output.WriteLine($"{nameof(csScriptAddressScriptPubKey)}: {csScriptAddressScriptPubKey}"); var builder = new TransactionBuilder(network); Transaction csSetupTx = builder .AddCoins(myBudgetCoins) .Send(csScriptAddressScriptPubKey, Money.Coins(90_000)) // 90_000 to cold staking script address .SetChange(myBudgetPubKey.WitHash.ScriptPubKey) // 10_000 back to original source .SendFees(this.fee) .AddKeys(myBudgetKey) .BuildTransaction(sign: true); bool isVerifyPassing = builder.Verify(csSetupTx, out var errors); foreach (var err in errors) { this.output.WriteLine(err.ToString()); } bool hasEmptyScriptSig = csSetupTx.Inputs.All(i => i.ScriptSig.Length == 0); this.output.WriteLine($"isVerifyPassing: {isVerifyPassing}"); this.output.WriteLine($"hasWitness: {csSetupTx.HasWitness}"); this.output.WriteLine($"hasEmptyScriptSig: {hasEmptyScriptSig}"); this.output.WriteLine(csSetupTx.ToString()); Assert.True(isVerifyPassing); Assert.True(csSetupTx.HasWitness); Assert.True(hasEmptyScriptSig); this.output.WriteLine("*** Now, immediate Withdrawal after the CS Setup tx ***"); var csScriptCoins = csSetupTx.Outputs.AsCoins().Select(cs => cs.ToScriptCoin(csRedeemScript)).ToList(); var builderWithdrawal = new TransactionBuilder(network); builderWithdrawal.Extensions.Add(new ColdStakingBuilderExtension(false)); Transaction withDrawTx = builderWithdrawal .AddCoins(csScriptCoins) .Send(myBudgetPubKey.WitHash.ScriptPubKey, Money.Coins(80_000)) // withdraw 80_000 of the 90_000 I previously sent to the cold staking script address .SetChange(csScriptAddressScriptPubKey) // Change goes back the CS script address .SendFees(this.fee) .AddKeys(coldKey) // use the cold private key for the withdrawal .BuildTransaction(sign: true); bool isWithdrawVerifyPassing = builderWithdrawal.Verify(withDrawTx, out var errors2); foreach (var err in errors2) { this.output.WriteLine(err.ToString()); } bool hasEmptyScriptSig2 = withDrawTx.Inputs.All(i => i.ScriptSig.Length == 0); this.output.WriteLine($"isVerifyPassing: {isWithdrawVerifyPassing}"); this.output.WriteLine($"hasWitness: {withDrawTx.HasWitness}"); this.output.WriteLine($"hasEmptyScriptSig: {hasEmptyScriptSig2}"); this.output.WriteLine(withDrawTx.ToString()); Assert.True(isWithdrawVerifyPassing); Assert.True(withDrawTx.HasWitness); Assert.True(hasEmptyScriptSig2); }
//https://github.com/NicolasDorier/NBitcoin/issues/34 public void CanBuildAnyoneCanPayTransaction() { //Carla is buying from Alice. Bob is acting as a mediator between Alice and Carla. var aliceKey = new Key(); var bobKey = new Key(); var carlaKey = new Key(); // Alice + Bob 2 of 2 multisig "wallet" var aliceBobRedeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, new PubKey[] { aliceKey.PubKey, bobKey.PubKey }); var txBuilder = new TransactionBuilder(); var funding = txBuilder .AddCoins(GetCoinSource(aliceKey)) .AddKeys(aliceKey) .Send(aliceBobRedeemScript.Hash, "0.5") .SetChange(aliceKey.PubKey.Hash) .SendFees(Money.Satoshis(5000)) .BuildTransaction(true); Assert.True(txBuilder.Verify(funding)); List<ICoin> aliceBobCoins = new List<ICoin>(); aliceBobCoins.Add(new ScriptCoin(funding, funding.Outputs.To(aliceBobRedeemScript.Hash).First(), aliceBobRedeemScript)); // first Bob constructs the TX txBuilder = new TransactionBuilder(); var unsigned = txBuilder // spend from the Alice+Bob wallet to Carla .AddCoins(aliceBobCoins) .Send(carlaKey.PubKey.Hash, "0.01") //and Carla pays Alice .Send(aliceKey.PubKey.Hash, "0.02") .CoverOnly("0.01") .SetChange(aliceBobRedeemScript.Hash) // Bob does not sign anything yet .BuildTransaction(false); Assert.True(unsigned.Outputs.Count == 3); Assert.True(unsigned.Outputs[0].IsTo(aliceBobRedeemScript.Hash)); //Only 0.01 should be covered, not 0.03 so 0.49 goes back to Alice+Bob Assert.True(unsigned.Outputs[0].Value == Money.Parse("0.49")); Assert.True(unsigned.Outputs[1].IsTo(carlaKey.PubKey.Hash)); Assert.True(unsigned.Outputs[1].Value == Money.Parse("0.01")); Assert.True(unsigned.Outputs[2].IsTo(aliceKey.PubKey.Hash)); Assert.True(unsigned.Outputs[2].Value == Money.Parse("0.02")); //Alice signs txBuilder = new TransactionBuilder(); var aliceSigned = txBuilder .AddCoins(aliceBobCoins) .AddKeys(aliceKey) .SignTransaction(unsigned, SigHash.All | SigHash.AnyoneCanPay); var carlaCoins = GetCoinSource(carlaKey, "1.0", "0.8", "0.6", "0.2", "0.05"); //Scenario 1 : Carla knows aliceBobCoins so she can calculate how much coin she need to complete the transaction //Carla fills and signs txBuilder = new TransactionBuilder(); var carlaSigned = txBuilder .AddCoins(aliceBobCoins) .Then() .AddKeys(carlaKey) //Carla should complete 0.02, but with 0.03 of fees, she should have a coins of 0.05 .AddCoins(carlaCoins) .ContinueToBuild(aliceSigned) .SendFees("0.03") .CoverTheRest() .BuildTransaction(true); //Bob review and signs txBuilder = new TransactionBuilder(); var bobSigned = txBuilder .AddCoins(aliceBobCoins) .AddKeys(bobKey) .SignTransaction(carlaSigned); txBuilder.AddCoins(carlaCoins); Assert.True(txBuilder.Verify(bobSigned)); //Scenario 2 : Carla is told by Bob to complete 0.05 BTC //Carla fills and signs txBuilder = new TransactionBuilder(); carlaSigned = txBuilder .AddKeys(carlaKey) .AddCoins(carlaCoins) //Carla should complete 0.02, but with 0.03 of fees, she should have a coins of 0.05 .ContinueToBuild(aliceSigned) .CoverOnly("0.05") .BuildTransaction(true); //Bob review and signs txBuilder = new TransactionBuilder(); bobSigned = txBuilder .AddCoins(aliceBobCoins) .AddKeys(bobKey) .SignTransaction(carlaSigned); txBuilder.AddCoins(carlaCoins); Assert.True(txBuilder.Verify(bobSigned)); }
public async Task AcceptToMemoryPool_WithMultiInOutValidTxns_IsSuccessfullAsync() { string dataDir = GetTestDirectoryPath(this); var miner = new BitcoinSecret(new Key(), KnownNetworks.RegTest); ITestChainContext context = await TestChainFactory.CreateAsync(KnownNetworks.RegTest, miner.PubKey.Hash.ScriptPubKey, dataDir); IMempoolValidator validator = context.MempoolValidator; Assert.NotNull(validator); var alice = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var bob = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var satoshi = new BitcoinSecret(new Key(), KnownNetworks.RegTest); // Fund Alice, Bob, Satoshi // 50 Coins come from first tx on chain - send satoshi 1, bob 2, Alice 1.5 and change back to miner var coin = new Coin(context.SrcTxs[0].GetHash(), 0, context.SrcTxs[0].TotalOut, miner.ScriptPubKey); var txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction multiOutputTx = txBuilder .AddCoins(new List <Coin> { coin }) .AddKeys(miner) .Send(satoshi.GetAddress(), "1.00") .Send(bob.GetAddress(), "2.00") .Send(alice.GetAddress(), "1.50") .SendFees("0.001") .SetChange(miner.GetAddress()) .BuildTransaction(true); Assert.True(txBuilder.Verify(multiOutputTx)); //check fully signed var state = new MempoolValidationState(false); Assert.True(await validator.AcceptToMemoryPool(state, multiOutputTx), $"Transaction: {nameof(multiOutputTx)} failed mempool validation."); // Alice then Bob sends to Satoshi Coin[] aliceCoins = multiOutputTx.Outputs .Where(o => o.ScriptPubKey == alice.ScriptPubKey) .Select(o => new Coin(new OutPoint(multiOutputTx.GetHash(), multiOutputTx.Outputs.IndexOf(o)), o)) .ToArray(); Coin[] bobCoins = multiOutputTx.Outputs .Where(o => o.ScriptPubKey == bob.ScriptPubKey) .Select(o => new Coin(new OutPoint(multiOutputTx.GetHash(), multiOutputTx.Outputs.IndexOf(o)), o)) .ToArray(); txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction multiInputTx = txBuilder .AddCoins(aliceCoins) .AddKeys(alice) .Send(satoshi.GetAddress(), "0.8") .SetChange(alice.GetAddress()) .SendFees("0.0005") .Then() .AddCoins(bobCoins) .AddKeys(bob) .Send(satoshi.GetAddress(), "0.2") .SetChange(bob.GetAddress()) .SendFees("0.0005") .BuildTransaction(true); Assert.True(txBuilder.Verify(multiInputTx)); //check fully signed Assert.True(await validator.AcceptToMemoryPool(state, multiInputTx), $"Transaction: {nameof(multiInputTx)} failed mempool validation."); }
public void CanSplitFees() { var satoshi = new Key(); var alice = new Key(); var bob = new Key(); var aliceCoins = new ICoin[] { RandomCoin("0.4", alice), RandomCoin("0.6", alice) }; var bobCoins = new ICoin[] { RandomCoin("0.2", bob), RandomCoin("0.3", bob) }; TransactionBuilder builder = new TransactionBuilder(); FeeRate rate = new FeeRate(Money.Coins(0.0004m)); var tx = builder .AddCoins(aliceCoins) .AddKeys(alice) .Send(satoshi, Money.Coins(0.1m)) .SetChange(alice) .Then() .AddCoins(bobCoins) .AddKeys(bob) .Send(satoshi, Money.Coins(0.01m)) .SetChange(bob) .SendEstimatedFeesSplit(rate) .BuildTransaction(true); var estimated = builder.EstimateFees(tx, rate); Assert.True(builder.Verify(tx, estimated)); // Alice should pay two times more fee than bob builder = new TransactionBuilder(); tx = builder .AddCoins(aliceCoins) .AddKeys(alice) .SetFeeWeight(2.0m) .Send(satoshi, Money.Coins(0.1m)) .SetChange(alice) .Then() .AddCoins(bobCoins) .AddKeys(bob) .Send(satoshi, Money.Coins(0.01m)) .SetChange(bob) .SendFeesSplit(Money.Coins(0.6m)) .BuildTransaction(true); var spentAlice = builder.FindSpentCoins(tx).Where(c => aliceCoins.Contains(c)).OfType<Coin>().Select(c => c.Amount).Sum(); var receivedAlice = tx.Outputs.AsCoins().Where(c => c.ScriptPubKey == alice.PubKey.Hash.ScriptPubKey).Select(c => c.Amount).Sum(); Assert.Equal(Money.Coins(0.1m + 0.4m), spentAlice - receivedAlice); var spentBob = builder.FindSpentCoins(tx).Where(c => bobCoins.Contains(c)).OfType<Coin>().Select(c => c.Amount).Sum(); var receivedBob = tx.Outputs.AsCoins().Where(c => c.ScriptPubKey == bob.PubKey.Hash.ScriptPubKey).Select(c => c.Amount).Sum(); Assert.Equal(Money.Coins(0.01m + 0.2m), spentBob - receivedBob); }
public async Task AcceptToMemoryPool_WithP2SHValidTxns_IsSuccessfullAsync() { string dataDir = GetTestDirectoryPath(this); var miner = new BitcoinSecret(new Key(), KnownNetworks.RegTest); ITestChainContext context = await TestChainFactory.CreateAsync(KnownNetworks.RegTest, miner.PubKey.Hash.ScriptPubKey, dataDir); IMempoolValidator validator = context.MempoolValidator; Assert.NotNull(validator); var alice = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var bob = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var satoshi = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var nico = new BitcoinSecret(new Key(), KnownNetworks.RegTest); // corp needs two out of three of alice, bob, nico Script corpMultiSig = PayToMultiSigTemplate .Instance .GenerateScriptPubKey(2, new[] { alice.PubKey, bob.PubKey, nico.PubKey }); // P2SH address for corp multi-sig BitcoinScriptAddress corpRedeemAddress = corpMultiSig.GetScriptAddress(KnownNetworks.RegTest); // Fund corp // 50 Coins come from first tx on chain - send corp 42 and change back to miner var coin = new Coin(context.SrcTxs[0].GetHash(), 0, context.SrcTxs[0].TotalOut, miner.ScriptPubKey); var txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction fundP2shTx = txBuilder .AddCoins(new List <Coin> { coin }) .AddKeys(miner) .Send(corpRedeemAddress, "42.00") .SendFees("0.001") .SetChange(miner.GetAddress()) .BuildTransaction(true); Assert.True(txBuilder.Verify(fundP2shTx)); //check fully signed var state = new MempoolValidationState(false); Assert.True(await validator.AcceptToMemoryPool(state, fundP2shTx), $"Transaction: {nameof(fundP2shTx)} failed mempool validation."); // AliceBobNico corp. send 20 to Satoshi Coin[] corpCoins = fundP2shTx.Outputs .Where(o => o.ScriptPubKey == corpRedeemAddress.ScriptPubKey) .Select(o => ScriptCoin.Create(KnownNetworks.RegTest, new OutPoint(fundP2shTx.GetHash(), fundP2shTx.Outputs.IndexOf(o)), o, corpMultiSig)) .ToArray(); txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction p2shSpendTx = txBuilder .AddCoins(corpCoins) .AddKeys(alice, bob) .Send(satoshi.GetAddress(), "20") .SendFees("0.001") .SetChange(corpRedeemAddress) .BuildTransaction(true); Assert.True(txBuilder.Verify(p2shSpendTx)); Assert.True(await validator.AcceptToMemoryPool(state, p2shSpendTx), $"Transaction: {nameof(p2shSpendTx)} failed mempool validation."); }
//take in tx parameters, return tx object public static TxSerial CreateTx(string extPubKey, string pubToAddr, string chgAddr, int walletId, long satToSend, long fee, bool testnet) { CheckNullOrEmpty(new object[] { extPubKey, pubToAddr, ElectrumXhost }, new string[] { "extPubKey", "pubToAddr", "ElectrumXhost" }); string err = ""; if (satToSend == 0) { err += "satoshiToSend = 0, "; } if (fee == 0) { err += "satoshiFee = 0, "; } if (err != "") { throw new Exception("[CreateTx] " + err); } //get first 100+100 child address from ext pub key List <Tuple <string, string> > recAddrList = GetDerivedKeys(extPubKey, 0, 20, false, testnet); //receive addresses List <Tuple <string, string> > chgAddrList = GetDerivedKeys(extPubKey, 0, 20, true, testnet); //change addresses //TODO - create process for getting next change address, so address never used twice if (chgAddr == null || chgAddr == "") //get first chg addr for extPubKey { chgAddr = chgAddrList.First().Item1; } //server status check string info = ElectrumX.GetServerInfo(ElectrumXhost, ElectrumXport); if (info == null) { throw new Exception("[CreateTx] ElectrumX Server Check Failed"); } string[] recAddrListAddr = new string[recAddrList.Count]; //short address string[] recAddrListExt = new string[recAddrList.Count]; //long address int ctr = 0; foreach (var t in recAddrList) { recAddrListAddr[ctr++] = t.Item1; //short } ctr = 0; foreach (var t in recAddrList) { recAddrListExt[ctr++] = t.Item2; //long - hash } string[] chgAddrListAddr = new string[recAddrList.Count]; string[] chgAddrListExt = new string[recAddrList.Count]; ctr = 0; foreach (var t in chgAddrList) { chgAddrListAddr[ctr++] = t.Item1; } ctr = 0; foreach (var t in chgAddrList) { chgAddrListExt[ctr++] = t.Item2; } //get all UTXOs (unspent inputs) from receive addresses UTXO[] recUTXOs = ElectrumX.GetUTXOs(recAddrList, ElectrumXhost, ElectrumXport); UTXO.SetAddressType(recUTXOs, 0); //receiver //get all UTXOs (unspent inputs) from change addresses UTXO[] chgUTXOs = ElectrumX.GetUTXOs(chgAddrList, ElectrumXhost, ElectrumXport); UTXO.SetAddressType(chgUTXOs, 1); //change //start new tx TransactionBuilder bldr = new TransactionBuilder(); bldr.Send(new BitcoinPubKeyAddress(pubToAddr), Money.Satoshis(satToSend)); //amount to send to recipient bldr.SetChange(new BitcoinPubKeyAddress(chgAddr)); //send change to this address bldr.SendFees(Money.Satoshis(fee)); //miner (tx) fee //collect all UTXOs List <UTXO> allUTXOs = new List <UTXO>(); allUTXOs.AddRange(recUTXOs); allUTXOs.AddRange(chgUTXOs); List <ICoin> lstTxCoins = new List <ICoin>(); //Coin is a UTXO //add new coin for each UTXO foreach (UTXO x in allUTXOs) //tx builder will select coins from this list { BitcoinPubKeyAddress fromAddr = new BitcoinPubKeyAddress(x.Address); NBitcoin.Coin cn = null; //create new coin from UTXO bldr.AddCoins(cn = new NBitcoin.Coin( new OutPoint(new uint256(x.Tx_hash), x.Tx_pos), //tx that funded wallet, spend this coin new TxOut(Money.Satoshis(x.Value), fromAddr.ScriptPubKey))); //specify full coin amount, else SetChange ignored lstTxCoins.Add(cn); //add coin to transaction, may not be used x.tmp = cn; //link UTXO with coin } List <UTXO> usedUTXOs = new List <UTXO>(); //coins actually used in tx NBitcoin.Transaction tx = bldr.BuildTransaction(false); //sort\filter coins, some coins will not be needed\used //coin objects not stored in tx, so we need to determine which coins were used //scan tx inputs for matching coins, ignore other coins foreach (UTXO u in allUTXOs) { foreach (TxIn i in tx.Inputs) { if (i.PrevOut == ((NBitcoin.Coin)u.tmp).Outpoint) //this coin in tx { usedUTXOs.Add(u); //this UTXO will be used\spent in tx } } } //populate return object TxSerial txs = new TxSerial() { SendAmt = satToSend, Fee = fee, ExtPublicKey = extPubKey, ToAddress = pubToAddr, ChgAddress = chgAddr, WalletId = walletId }; txs.ExtPublicKey = extPubKey; foreach (UTXO u in usedUTXOs) { u.tmp = null; //don't serialize coin object, will rebuild coins in signing process } txs.InputUTXOs = new List <UTXO>(); txs.InputUTXOs.AddRange(usedUTXOs); //string jsn = Newtonsoft.Json.JsonConvert.SerializeObject(txs, Newtonsoft.Json.Formatting.Indented); return(txs); }
public async Task Test() { var hotWallet = OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r"); var changeWallet = OpenAssetsHelper.ParseAddress("minog49vnNVuWK5kSs5Ut1iPyNZcR1i7he"); var hotWalletOutputs = GenerateOutputs(5); var hotWalletBalance = new Money(hotWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); var maxFeeForTransaction = Money.FromUnit(0.099M, MoneyUnit.BTC); var selectedCoins = new List <Coin>(); var _feeProvider = Config.Services.GetService <IFeeProvider>(); var _transactionBuildHelper = Config.Services.GetService <ITransactionBuildHelper>(); var cashoutRequests = (GenerateCashoutRequest(200)).ToList(); var maxInputsCount = maxFeeForTransaction.Satoshi / (await _feeProvider.GetFeeRate()).GetFee(Constants.InputSize).Satoshi; do { if (selectedCoins.Count > maxInputsCount && cashoutRequests.Count > 1) { cashoutRequests = cashoutRequests.Take(cashoutRequests.Count - 1).ToList(); selectedCoins.Clear(); } else if (selectedCoins.Count > 0) { break; } var totalAmount = Money.FromUnit(cashoutRequests.Select(o => o.Amount).Sum(), MoneyUnit.BTC); if (hotWalletBalance < totalAmount + maxFeeForTransaction) { var changeBalance = Money.Zero; List <Coin> changeWalletOutputs = new List <Coin>(); if (hotWallet != changeWallet) { changeWalletOutputs = GenerateOutputs(1).ToList(); changeBalance = new Money(changeWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0)); } if (changeBalance + hotWalletBalance >= totalAmount + maxFeeForTransaction) { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(OpenAssetsHelper .CoinSelect(changeWalletOutputs, totalAmount + maxFeeForTransaction - hotWalletBalance).OfType <Coin>()); } else { selectedCoins.AddRange(hotWalletOutputs); selectedCoins.AddRange(changeWalletOutputs); int cashoutsUsedCount = 0; var cashoutsAmount = await _transactionBuildHelper.CalcFee(selectedCoins.Count, cashoutRequests.Count + 1); foreach (var cashoutRequest in cashoutRequests) { cashoutsAmount += Money.FromUnit(cashoutRequest.Amount, MoneyUnit.BTC); if (cashoutsAmount > hotWalletBalance + changeBalance) { break; } cashoutsUsedCount++; } if (cashoutsUsedCount == 0) { throw new BackendException("Not enough bitcoin available", ErrorCode.NotEnoughBitcoinAvailable); } cashoutRequests = cashoutRequests.Take(cashoutsUsedCount).ToList(); } } else { selectedCoins.AddRange(OpenAssetsHelper.CoinSelect(hotWalletOutputs, totalAmount + maxFeeForTransaction).OfType <Coin>()); } } while (true); var selectedCoinsAmount = new Money(selectedCoins.Sum(o => o.Amount)); var sendAmount = new Money(cashoutRequests.Sum(o => o.Amount), MoneyUnit.BTC); var builder = new TransactionBuilder(); builder.AddCoins(selectedCoins); foreach (var cashout in cashoutRequests) { var amount = Money.FromUnit(cashout.Amount, MoneyUnit.BTC); builder.Send(OpenAssetsHelper.ParseAddress(cashout.DestinationAddress), amount); } builder.Send(changeWallet, selectedCoinsAmount - sendAmount); builder.SubtractFees(); builder.SendEstimatedFees(await _feeProvider.GetFeeRate()); builder.SetChange(changeWallet); var tx = builder.BuildTransaction(true); _transactionBuildHelper.AggregateOutputs(tx); }
public async Task AcceptToMemoryPool_WithMultiSigValidTxns_IsSuccessfullAsync() { string dataDir = GetTestDirectoryPath(this); var miner = new BitcoinSecret(new Key(), KnownNetworks.RegTest); ITestChainContext context = await TestChainFactory.CreateAsync(KnownNetworks.RegTest, miner.PubKey.Hash.ScriptPubKey, dataDir); IMempoolValidator validator = context.MempoolValidator; Assert.NotNull(validator); var alice = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var bob = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var satoshi = new BitcoinSecret(new Key(), KnownNetworks.RegTest); var nico = new BitcoinSecret(new Key(), KnownNetworks.RegTest); // corp needs two out of three of alice, bob, nico Script corpMultiSig = PayToMultiSigTemplate .Instance .GenerateScriptPubKey(2, new[] { alice.PubKey, bob.PubKey, nico.PubKey }); // Fund corp // 50 Coins come from first tx on chain - send corp 42 and change back to miner var coin = new Coin(context.SrcTxs[0].GetHash(), 0, context.SrcTxs[0].TotalOut, miner.ScriptPubKey); var txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction sendToMultiSigTx = txBuilder .AddCoins(new List <Coin> { coin }) .AddKeys(miner) .Send(corpMultiSig, "42.00") .SendFees("0.001") .SetChange(miner.GetAddress()) .BuildTransaction(true); Assert.True(txBuilder.Verify(sendToMultiSigTx)); //check fully signed var state = new MempoolValidationState(false); Assert.True(await validator.AcceptToMemoryPool(state, sendToMultiSigTx), $"Transaction: {nameof(sendToMultiSigTx)} failed mempool validation."); // AliceBobNico corp. send to Satoshi Coin[] corpCoins = sendToMultiSigTx.Outputs .Where(o => o.ScriptPubKey == corpMultiSig) .Select(o => new Coin(new OutPoint(sendToMultiSigTx.GetHash(), sendToMultiSigTx.Outputs.IndexOf(o)), o)) .ToArray(); // Alice initiates the transaction txBuilder = new TransactionBuilder(KnownNetworks.RegTest); Transaction multiSigTx = txBuilder .AddCoins(corpCoins) .AddKeys(alice) .Send(satoshi.GetAddress(), "4.5") .SendFees("0.001") .SetChange(corpMultiSig) .BuildTransaction(true); Assert.True(!txBuilder.Verify(multiSigTx)); //Well, only one signature on the two required... // Nico completes the transaction txBuilder = new TransactionBuilder(KnownNetworks.RegTest); multiSigTx = txBuilder .AddCoins(corpCoins) .AddKeys(nico) .SignTransaction(multiSigTx); Assert.True(txBuilder.Verify(multiSigTx)); Assert.True(await validator.AcceptToMemoryPool(state, multiSigTx), $"Transaction: {nameof(multiSigTx)} failed mempool validation."); }
public string SendToAddress(decimal Amount, string Address, string ID, TickerSymbol Symbol, WalletType Wallet, int MinerFee = -1) { string result = string.Empty; if (MinerFee < 0) { MinerFee = WalletUtilities.GetMinerFee(Symbol); } if (Symbol == TickerSymbol.BTC) { if (Wallet == WalletType.VaultWallet) { Key wKey = new Key(walletDat.LoadVault(ID)); BitcoinSecret bitSecret = wKey.GetBitcoinSecret(net); //your wallet BitcoinAddress bitAddress = bitSecret.PubKey.GetAddress(net); //your address BitcoinAddress SendAddress = BitcoinAddress.Create(Address, net); // address you are sending to Transaction tx = new Transaction(); Coin[] CoinPurse = walletDat.GetCoinsByAddress(bitAddress, net, 6).ToArray(); TransactionBuilder txBuilder = new TransactionBuilder(); Transaction builtTx = new Transaction(); builtTx = txBuilder .AddCoins(CoinPurse) .AddKeys(wKey) .Send(SendAddress, new Money(Amount, MoneyUnit.BTC)) .SendFees(MinerFee) .SetChange(bitAddress) .BuildTransaction(true); builtTx = txBuilder.SignTransaction(builtTx); // sign the transaction bool very = txBuilder.Verify(builtTx); // verify the transaction for sending if (netNode == null) { using (Node node = Node.ConnectToLocal(net)) //Connect to local if no node is set { node.VersionHandshake(); node.SendMessage(new InvPayload(InventoryType.MSG_TX, builtTx.GetHash())); node.SendMessage(builtTx.CreatePayload()); // broadcast message to send funds System.Threading.Thread.Sleep(500); //Wait a bit } } else { using (Node node = netNode) { node.VersionHandshake(); node.SendMessage(new InvPayload(InventoryType.MSG_TX, builtTx.GetHash())); node.SendMessage(builtTx.CreatePayload()); // broadcast message to send funds System.Threading.Thread.Sleep(500); //Wait a bit } } if (very) { result = "BTC VERIFIED"; // need better error codes / error catching } else { result = "BTC ERROR"; } } } return(result); }
public async Task TestSpendScript() { var multisigFirstPartPk = new BitcoinSecret("cMahea7zqjxrtgAbB7LSGbcZDo359LNtib5kYpwbiSqBqvs6cqPV"); var singlePk = new BitcoinSecret("cPsQrkj1xqQUomDyDHXqsgnjCbZ41yfr923tWR7EuaSKH7WtDXb9"); var revokePk = new BitcoinSecret("cPsQrkj1xqQUomDyDHXqsgnjCbZ41yfr923tWR7EuaSKH7WtDXb9"); var bitcoinClient = Config.Services.GetService <IRpcBitcoinClient>(); var bitcoinOutService = Config.Services.GetService <IBitcoinOutputsService>(); var assetRepo = Config.Services.GetService <IAssetRepository>(); var helper = Config.Services.GetService <ITransactionBuildHelper>(); var prevTx = new Transaction("0100000002347328a86bbd9d2a40e420e8d0a7da9986fd916b3ca02365c8d480936067a36cce0000008a47304402201eebb0365b67e534b72302987453d43833594965d7180c746dd5b1af27a7d6be02204496df6a47858fe9a3f6fd09dff90382e7c22a5a52c41300466bcfed808a1f39014104ff20028f41de7e2bac4f8e90884becad36c1390d2ab991a16fbcf745db478fd37cbf65c57a84e5a485bd5934c659f94aff35fe9fc50ebd2281ede40366190f57ffffffffab496631bf50d77e36303fb6156c3c73809c2023048e7e28b59c7272a8c509fc2f0000008b483045022100c6cb855117224ff9e7334ccf1030649c34a8e48eb7b6c7c4bf746d3ff67c1641022038257e6100c34c568f24d490a69b4591f93a56c433b6ad30102627a5a48ce0da01410496052ef8fb660bb338ba186dd2f52362c66b23f824295a6b74d0c60cf61a12e2b1f8b97512e09c20693f00dba9df3c644f245c120983d0582e4a88cb466ffa69ffffffff0300e1f5050000000017a914a9168848118a24ff8f848bac2eaaa248105b0307870084d717000000001976a91497a515ec03d9aada5e6f0d895f4aa10eb8f07e8d88ac800f0200000000001976a914ed75405f426601f5493117b5a22dc0082269e32288ac00000000"); var coin = new Coin(prevTx, 0); var redeem = CreateScript(multisigFirstPartPk.PubKey, revokePk.PubKey, singlePk.PubKey); var addr = redeem.WitHash.ScriptPubKey.Hash.GetAddress(Network.TestNet); var scriptCoin = new ScriptCoin(coin, redeem); TransactionBuilder builder = new TransactionBuilder(); TransactionBuildContext context = new TransactionBuildContext(Network.TestNet, null, null); //builder.AddKeys(pk); builder.AddCoins(scriptCoin); builder.Send(multisigFirstPartPk.PubKey.ScriptPubKey, "0.5"); builder.SetChange(addr); builder.SendFees("0.001"); var tr = builder.BuildTransaction(false); //tr.Inputs[0].Sequence = new Sequence(144); // tr.Version = 2; var hash = Script.SignatureHash(redeem, tr, 0, SigHash.All, scriptCoin.Amount, HashVersion.Witness); var signature = singlePk.PrivateKey.Sign(hash, SigHash.All).Signature.ToDER().Concat(new byte[] { 0x01 }).ToArray(); var push1 = multisigFirstPartPk.PrivateKey.Sign(hash, SigHash.All).Signature.ToDER().Concat(new byte[] { 0x01 }).ToArray(); var push2 = revokePk.PrivateKey.Sign(hash, SigHash.All).Signature.ToDER().Concat(new byte[] { 0x01 }).ToArray(); var scriptParams = new OffchainScriptParams { IsMultisig = true, RedeemScript = redeem.ToBytes(), Pushes = new[] { new byte[0], new byte[0], new byte[0], } }; tr.Inputs[0].WitScript = OffchainScriptCommitmentTemplate.GenerateScriptSig(scriptParams); tr.Inputs[0].ScriptSig = new Script(Op.GetPushOp(redeem.WitHash.ScriptPubKey.ToBytes(true))); ScriptError error; tr.Inputs.AsIndexedInputs().First().VerifyScript(scriptCoin.ScriptPubKey, out error); await bitcoinClient.BroadcastTransaction(tr, Guid.NewGuid()); //CheckSequence(1, tr, 0); //CheckSig(signature.ToBytes(), pk.PubKey.ToBytes(), redeem, new TransactionChecker(tr, 0, scriptCoin.Amount), 0); }
public static void TestMultisigPayout() { throw new InvalidOperationException("This function is only for testing purposes. It pays real money. Don't use except for dev/test."); // disable "code unreachable" warning for this code // ReSharper disable once CSharpWarnings::CS0162 #pragma warning disable 162,219 Organization organization = Organization.Sandbox; // a few testing cents here in test environment string bitcoinTestAddress = "3KS6AuQbZARSvqnaHoHfL1Xhm3bTLFAzoK"; // Make a small test payment to a multisig address TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder(); Int64 satoshis = new Money(100, Currency.FromCode("SEK")).ToCurrency(Currency.BitcoinCore).Cents; BitcoinTransactionInputs inputs = null; Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash) * 20; // assume max 20k transaction size try { inputs = BitcoinUtility.GetInputsForAmount(organization, satoshis + satoshisMaximumAnticipatedFees); } catch (NotEnoughFundsException) { Debugger.Break(); } // If we arrive at this point, the previous function didn't throw, and we have enough money. Add the inputs to the transaction. txBuilder = txBuilder.AddCoins(inputs.Coins); txBuilder = txBuilder.AddKeys(inputs.PrivateKeys); Int64 satoshisInput = inputs.AmountSatoshisTotal; // Add outputs and prepare notifications Int64 satoshisUsed = 0; //Dictionary<int, List<string>> notificationSpecLookup = new Dictionary<int, List<string>>(); //Dictionary<int, List<Int64>> notificationAmountLookup = new Dictionary<int, List<Int64>>(); Payout masterPayoutPrototype = Payout.Empty; HotBitcoinAddress changeAddress = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // Add the test payment if (bitcoinTestAddress.StartsWith("1")) // regular address { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(bitcoinTestAddress), new Satoshis(satoshis)); } else if (bitcoinTestAddress.StartsWith("3")) // multisig { txBuilder = txBuilder.Send(new BitcoinScriptAddress(bitcoinTestAddress, Network.Main), new Satoshis(satoshis)); } else { throw new InvalidOperationException("Unhandled address case"); } satoshisUsed += satoshis; // Set change address to wallet slush txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address)); // Add fee int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count; // +inputs.Count for size variability Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash); txBuilder = txBuilder.SendFees(new Satoshis(feeSatoshis)); satoshisUsed += feeSatoshis; // Sign transaction - ready to execute Transaction txReady = txBuilder.BuildTransaction(true); // Verify that transaction is ready if (!txBuilder.Verify(txReady)) { // Transaction was not signed with the correct keys. This is a serious condition. NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError, primaryStrings); throw new InvalidOperationException("Transaction is not signed enough"); } // Broadcast transaction BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash); // Note the transaction hash string transactionHash = txReady.GetHash().ToString(); // Delete all old inputs, adjust balance for addresses (re-register unused inputs) inputs.AsUnspents.DeleteAll(); // Log the new unspent created by change (if there is any) if (satoshisInput - satoshisUsed > 0) { SwarmDb.GetDatabaseForWriting() .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash, +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0); } // Restore "code unreachable", "unsued var" warnings #pragma warning restore 162,219 // This puts the ledger out of sync, so only do this on Sandbox for various small-change (cents) testing }
public void P2WSH_MultiSig_one_of_two() { var network = ObsidianXNetworksSelector.Obsidian.Mainnet(); Key myPrivateKey = new Key(); PubKey alicePublicKey = new Key().PubKey.Compress(); Script msRedeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(1, myPrivateKey.PubKey.Compress(), alicePublicKey); this.output.WriteLine($"{nameof(msRedeemScript)}: {msRedeemScript}"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.PaymentScript)}: {msRedeemScript.PaymentScript}"); this.output.WriteLine("In P2SH payments, we refer to the hash of the Redeem Script as the ScriptPubKey:"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}: {msRedeemScript.WitHash}"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}.{nameof(msRedeemScript.WitHash.ScriptPubKey)}: {msRedeemScript.WitHash.ScriptPubKey}"); string bech32ScriptAddress = msRedeemScript.WitHash.GetAddress(network).ToString(); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}.GetAddress(network): {bech32ScriptAddress}"); Script msScriptPubKey = msRedeemScript.WitHash.ScriptPubKey; // In P2SH payments, we refer to the hash of the Redeem Script as the ScriptPubKey. // Receive 5 coins on the ms address we just created Transaction received = network.CreateTransaction(); received.Outputs.Add(new TxOut(Money.Coins(5), msRedeemScript.WitHash.ScriptPubKey)); // Spend 1 coin from the 5 coins we received Coin coin = received.Outputs.AsCoins().First().ToScriptCoin(msRedeemScript); BitcoinWitPubKeyAddress spendMultiSigToAddress = new Key().PubKey.Compress().GetSegwitAddress(network); TransactionBuilder builder = new TransactionBuilder(network); Transaction unsigned = builder .AddCoins(coin) .Send(spendMultiSigToAddress, Money.Coins(1.0m)) .SetChange(msRedeemScript.WitHash.ScriptPubKey) .SendFees(this.fee) .BuildTransaction(sign: false); Transaction mySigned = builder .AddCoins(coin) .AddKeys(myPrivateKey) .SignTransaction(unsigned); Transaction fullySigned = builder .AddCoins(coin) .CombineSignatures(mySigned); bool isVerifyPassing = builder.Verify(fullySigned, out var errors); foreach (var err in errors) { this.output.WriteLine(err.ToString()); } this.output.WriteLine($"isVerifyPassing: {isVerifyPassing}"); this.output.WriteLine(fullySigned.ToString()); Assert.True(isVerifyPassing); Assert.True(fullySigned.HasWitness); }
public override async Task <WalletBuildTransactionModel> OfflineSignRequest(OfflineSignRequest request, CancellationToken cancellationToken) { return(await Task.Run(() => { Transaction unsignedTransaction = this.network.CreateTransaction(request.UnsignedTransaction); uint256 originalTxId = unsignedTransaction.GetHash(); var builder = new TransactionBuilder(this.network); var coins = new List <Coin>(); var signingKeys = new List <ISecret>(); ExtKey seedExtKey = this.walletManager.GetExtKey(new WalletAccountReference() { AccountName = request.WalletAccount, WalletName = request.WalletName }, request.WalletPassword); // Have to determine which private key to use for each UTXO being spent. bool coldStakingWithdrawal = false; foreach (UtxoDescriptor utxo in request.Utxos) { Script scriptPubKey = Script.FromHex(utxo.ScriptPubKey); coins.Add(new Coin(uint256.Parse(utxo.TransactionId), uint.Parse(utxo.Index), Money.Parse(utxo.Amount), scriptPubKey)); // Now try get the associated private key. We therefore need to determine the address that contains the UTXO. string address; if (scriptPubKey.IsScriptType(ScriptType.ColdStaking)) { ColdStakingScriptTemplate.Instance.ExtractScriptPubKeyParameters(scriptPubKey, out KeyId hotPubKeyHash, out KeyId coldPubKeyHash); address = coldPubKeyHash.GetAddress(this.network).ToString(); coldStakingWithdrawal = true; } else { // We assume that if it wasn't a cold staking scriptPubKey then it must have been P2PKH. address = scriptPubKey.GetDestinationAddress(this.network)?.ToString(); if (address == null) { throw new FeatureException(HttpStatusCode.BadRequest, "Could not resolve address.", $"Could not resolve address from UTXO's scriptPubKey '{scriptPubKey.ToHex()}'."); } } IEnumerable <HdAccount> accounts = this.walletManager.GetAccounts(request.WalletName); IEnumerable <HdAddress> addresses = accounts.SelectMany(hdAccount => hdAccount.GetCombinedAddresses()); HdAddress hdAddress = addresses.FirstOrDefault(a => a.Address == address || a.Bech32Address == address); if (coldStakingWithdrawal && hdAddress == null) { var coldStakingManager = this.walletManager as ColdStakingManager; Wallet.Wallet wallet = coldStakingManager.GetWallet(request.WalletName); HdAccount coldAccount = coldStakingManager.GetColdStakingAccount(wallet, true); IEnumerable <HdAddress> coldAccountAddresses = coldAccount.GetCombinedAddresses(); hdAddress = coldAccountAddresses.FirstOrDefault(a => a.Address == address || a.Bech32Address == address); } // It is possible that the address is outside the gap limit. So if it is not found we optimistically presume the address descriptors will fill in the missing information later. if (hdAddress != null) { ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(hdAddress.HdPath)); BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(this.network); signingKeys.Add(addressPrivateKey); } } // Address descriptors are 'easier' to look the private key up against if provided, but may not always be available. foreach (AddressDescriptor address in request.Addresses) { ExtKey addressExtKey = seedExtKey.Derive(new KeyPath(address.KeyPath)); BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(this.network); signingKeys.Add(addressPrivateKey); } // Offline cold staking transaction handling. We check both the offline setup and the offline withdrawal cases here. if (unsignedTransaction.Outputs.Any(o => o.ScriptPubKey.IsScriptType(ScriptType.ColdStaking)) || coldStakingWithdrawal) { // This will always be added in 'cold' mode if we are processing an offline signing request. builder.Extensions.Add(new ColdStakingBuilderExtension(false)); } builder.AddCoins(coins); builder.AddKeys(signingKeys.ToArray()); builder.SignTransactionInPlace(unsignedTransaction); if (!builder.Verify(unsignedTransaction, out TransactionPolicyError[] errors)) { throw new FeatureException(HttpStatusCode.BadRequest, "Failed to validate signed transaction.", $"Failed to validate signed transaction '{unsignedTransaction.GetHash()}' from offline request '{originalTxId}'."); } var builtTransactionModel = new WalletBuildTransactionModel() { TransactionId = unsignedTransaction.GetHash(), Hex = unsignedTransaction.ToHex(), Fee = request.Fee }; return builtTransactionModel; }, cancellationToken));
public void P2WSH_MultiSig_two_of_three() { var network = ObsidianXNetworksSelector.Obsidian.Mainnet(); Key bob = new Key(); Key alice = new Key(); Key satoshi = new Key(); Script msRedeemScript = PayToMultiSigTemplate .Instance .GenerateScriptPubKey(2, new[] { bob.PubKey.Compress(), alice.PubKey.Compress(), satoshi.PubKey.Compress() }); this.output.WriteLine($"{nameof(msRedeemScript)}: {msRedeemScript}"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.PaymentScript)}: {msRedeemScript.PaymentScript}"); this.output.WriteLine("In P2SH payments, we refer to the hash of the Redeem Script as the ScriptPubKey:"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}: {msRedeemScript.WitHash}"); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}.{nameof(msRedeemScript.WitHash.ScriptPubKey)}: {msRedeemScript.WitHash.ScriptPubKey}"); string bech32ScriptAddress = msRedeemScript.WitHash.GetAddress(network).ToString(); Assert.Equal(new BitcoinWitScriptAddress(bech32ScriptAddress, network).ToString(), bech32ScriptAddress); this.output.WriteLine($"{nameof(msRedeemScript)}.{nameof(msRedeemScript.WitHash)}.GetAddress(network): {msRedeemScript.WitHash.GetAddress(network)}"); Script msScriptPubKey = msRedeemScript.WitHash.ScriptPubKey; // In P2SH payments, we refer to the hash of the Redeem Script as the ScriptPubKey. // Receive 5 coins on the ms address we just created Transaction received = network.CreateTransaction(); received.Outputs.Add(new TxOut(Money.Coins(5), msRedeemScript.WitHash.ScriptPubKey)); // Spend 1 coin from the 5 coins we received Coin coin = received.Outputs.AsCoins().First().ToScriptCoin(msRedeemScript); BitcoinWitPubKeyAddress spendMultiSigToAddress = new Key().PubKey.Compress().GetSegwitAddress(network); TransactionBuilder builder = new TransactionBuilder(network); Transaction unsigned = builder .AddCoins(coin) .Send(spendMultiSigToAddress, Money.Coins(1.0m)) .SetChange(msRedeemScript.WitHash.ScriptPubKey) .SendFees(this.fee) .BuildTransaction(sign: false); Transaction aliceSigned = builder .AddCoins(coin) .AddKeys(alice) .SignTransaction(unsigned); Transaction bobSigned = builder .AddCoins(coin) .AddKeys(bob) //At this line, SignTransaction(unSigned) has the identical functionality with the SignTransaction(aliceSigned). //It's because unsigned transaction has already been signed by Alice privateKey from above. .SignTransaction(aliceSigned); Transaction fullySigned = builder .AddCoins(coin) .CombineSignatures(aliceSigned, bobSigned); bool isVerifyPassing = builder.Verify(fullySigned, out var errors); foreach (var err in errors) { this.output.WriteLine(err.ToString()); } this.output.WriteLine($"isVerifyPassing: {isVerifyPassing}"); this.output.WriteLine(fullySigned.ToString()); Assert.True(isVerifyPassing); Assert.True(fullySigned.HasWitness); }
public void NewCreateBasicSwap(uint path, params string[] seed) { List <ExtKey> keys = new List <ExtKey>(); Segwit segwit = new Segwit(NBitcoin.Network.TestNet); for (int i = 0; i < seed.Length; i++) { var key = GetKey(path, seed[i]); //var address = segwit.GetP2SHAddress(key); keys.Add(key); //Console.WriteLine(address.ToString()); } NBitcoin.Network _Network = NBitcoin.Network.TestNet; MultiSig multi = new MultiSig(NBitcoin.Network.TestNet); List <PubKey> pubKeys = new List <PubKey>(); for (int i = 0; i < keys.Count; i++) { pubKeys.Add(keys[i].PrivateKey.PubKey); } Script pubKeyScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, pubKeys.ToArray()); BitcoinAddress address = pubKeyScript.WitHash.GetAddress(_Network); BitcoinScriptAddress p2sh = address.GetScriptAddress(); Console.WriteLine("Send money here: " + p2sh.ToString()); REST.BlockExplorer explorer = new REST.BlockExplorer("https://testnet.blockexplorer.com/"); var response = explorer.GetUnspent(p2sh.ToString()); List <ExplorerUnspent> unspent = response.Convert <List <ExplorerUnspent> >(); List <Transaction> transactions = new List <Transaction>(); foreach (var item in unspent) { ExplorerResponse txResponse = explorer.GetTransaction(item.txid); RawFormat format = RawFormat.Satoshi; var tx = Transaction.Parse(txResponse.data, format, Network.TestNet); transactions.Add(tx); } //Create send transaction. //get redeem script //var redeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, pubKeys.ToArray());// multi.GetRedeemScript(2, keys.ToArray()); Transaction received = transactions[0]; ScriptCoin coin = received.Outputs.AsCoins().First().ToScriptCoin(pubKeyScript.WitHash.ScriptPubKey.Hash.ScriptPubKey); //create transaction: BitcoinAddress destination = BitcoinAddress.Create("2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF"); //the faucet return address TransactionBuilder builder = new TransactionBuilder(); builder.AddCoins(coin); builder.Send(destination, Money.Coins(1.299m)); builder.SendFees(Money.Coins(0.001m)); builder.SetChange(destination); //builder. var unsigned = builder.BuildTransaction(sign: false); var signedA = builder.AddCoins(coin).AddKeys(keys[0].PrivateKey).SignTransaction(unsigned); Transaction signedB = builder.AddCoins(coin).AddKeys(keys[1].PrivateKey).SignTransaction(signedA); Transaction fullySigned = builder.AddCoins(coin).CombineSignatures(signedA, signedB); Console.WriteLine(fullySigned.ToHex()); Console.ReadLine(); }
public void TestPuzzlePromise() { RsaKey key = TestKeys.Default; Key serverEscrow = new Key(); Key clientEscrow = new Key(); var parameters = new PromiseParameters(key.PubKey) { FakeTransactionCount = 5, RealTransactionCount = 5 }; var client = new PromiseClientSession(parameters); var server = new PromiseServerSession(parameters); var coin = CreateEscrowCoin(serverEscrow.PubKey, clientEscrow.PubKey); client.ConfigureEscrowedCoin(coin, clientEscrow); SignaturesRequest request = client.CreateSignatureRequest(clientEscrow.PubKey.Hash, FeeRate); RoundTrip(ref client, parameters); RoundTrip(ref request); server.ConfigureEscrowedCoin(coin, serverEscrow, new Key().ScriptPubKey); PuzzlePromise.ServerCommitment[] commitments = server.SignHashes(request); RoundTrip(ref server, parameters); RoundTrip(ref commitments); PuzzlePromise.ClientRevelation revelation = client.Reveal(commitments); RoundTrip(ref client, parameters); RoundTrip(ref revelation); ServerCommitmentsProof proof = server.CheckRevelation(revelation); RoundTrip(ref server, parameters); RoundTrip(ref proof); var puzzleToSolve = client.CheckCommitmentProof(proof); RoundTrip(ref client, parameters); Assert.NotNull(puzzleToSolve); var solution = key.SolvePuzzle(puzzleToSolve); var transactions = client.GetSignedTransactions(solution).ToArray(); RoundTrip(ref client, parameters); Assert.True(transactions.Length == parameters.RealTransactionCount); var escrow = server.GetInternalState().EscrowedCoin; // In case things do not go well and timeout is hit... var redeemTransaction = server.CreateRedeemTransaction(FeeRate); var resigned = redeemTransaction.ReSign(escrow); TransactionBuilder bb = new TransactionBuilder(); bb.AddCoins(server.GetInternalState().EscrowedCoin); Assert.True(bb.Verify(resigned)); //Check can ve reclaimed if malleated bb = new TransactionBuilder(); escrow.Outpoint = new OutPoint(escrow.Outpoint.Hash, 10); bb.AddCoins(escrow); resigned = redeemTransaction.ReSign(escrow); Assert.False(bb.Verify(redeemTransaction.Transaction)); Assert.True(bb.Verify(resigned)); }
public void CreateBasicSwap(uint path, params string[] seed) { List <ExtKey> keys = new List <ExtKey>(); Segwit segwit = new Segwit(NBitcoin.Network.TestNet); for (int i = 0; i < seed.Length; i++) { var key = GetKey(path, seed[i]); var address = segwit.GetP2SHAddress(key); keys.Add(key); //Console.WriteLine(address.ToString()); } MultiSig multi = new MultiSig(NBitcoin.Network.TestNet); var p2sh = multi.GetP2SHAddress(2, keys.ToArray()); Console.WriteLine(p2sh.ToString()); //multi: b2366808fa2396a5a32120a27f55571055491d6ff8e6bd1e31e52bdd14b91dfb REST.BlockExplorer explorer = new REST.BlockExplorer("https://testnet.blockexplorer.com/"); //var tx = explorer.GetTransaction("b2366808fa2396a5a32120a27f55571055491d6ff8e6bd1e31e52bdd14b91dfb"); var response = explorer.GetUnspent(p2sh.ToString()); List <ExplorerUnspent> unspent = response.Convert <List <ExplorerUnspent> >(); List <Transaction> transactions = new List <Transaction>(); foreach (var item in unspent) { ExplorerResponse txResponse = explorer.GetTransaction(item.txid); RawFormat format = RawFormat.Satoshi; var tx = Transaction.Parse(txResponse.data, format, Network.TestNet); transactions.Add(tx); } //Create send transaction. //get redeem script var redeemScript = multi.GetRedeemScript(2, keys.ToArray()); Transaction received = transactions[0]; ScriptCoin coin = received.Outputs.AsCoins().First().ToScriptCoin(redeemScript); //create transaction: BitcoinAddress destination = BitcoinAddress.Create("2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF"); //the faucet return address TransactionBuilder builder = new TransactionBuilder(); builder.AddCoins(coin); builder.Send(destination, Money.Coins(1.299m)); builder.SendFees(Money.Coins(0.001m)); builder.SetChange(destination); //builder. var unsigned = builder.BuildTransaction(sign: false); var signedA = builder.AddCoins(coin).AddKeys(keys[0].PrivateKey).SignTransaction(unsigned); Transaction signedB = builder.AddCoins(coin).AddKeys(keys[1].PrivateKey).SignTransaction(signedA); Transaction fullySigned = builder.AddCoins(coin).CombineSignatures(signedA, signedB); Console.WriteLine(fullySigned.ToHex()); Console.ReadLine(); }
public static void PerformAutomated(BitcoinChain chain) { // Perform all waiting hot payouts for all orgs in the installation throw new NotImplementedException("Waiting for rewrite for Bitcoin Cash"); // TODO DateTime utcNow = DateTime.UtcNow; foreach (Organization organization in Organizations.GetAll()) { // If this org doesn't do hotwallet, continue if (organization.FinancialAccounts.AssetsBitcoinHot == null) { continue; } Payouts orgPayouts = Payouts.Construct(organization); Payouts bitcoinPayouts = new Payouts(); Dictionary <string, Int64> satoshiPayoutLookup = new Dictionary <string, long>(); Dictionary <string, Int64> nativeCentsPayoutLookup = new Dictionary <string, long>(); Dictionary <int, Int64> satoshiPersonLookup = new Dictionary <int, long>(); Dictionary <int, Int64> nativeCentsPersonLookup = new Dictionary <int, long>(); Int64 satoshisTotal = 0; string currencyCode = organization.Currency.Code; // For each ready payout that can automate, add an output to a constructed transaction TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder(); foreach (Payout payout in orgPayouts) { if (payout.ExpectedTransactionDate > utcNow) { continue; // payout is not due yet } if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length > 2 && payout.Account.Length < 4) { // If the payout address is still in quarantine, don't pay out yet string addressSetTime = payout.RecipientPerson.BitcoinPayoutAddressTimeSet; if (addressSetTime.Length > 4 && DateTime.Parse(addressSetTime, CultureInfo.InvariantCulture).AddHours(48) > utcNow) { continue; // still in quarantine } // Test the payout address - is it valid and can we handle it? if (!BitcoinUtility.IsValidBitcoinAddress(payout.RecipientPerson.BitcoinPayoutAddress)) { // Notify person that address is invalid, then clear it NotificationStrings primaryStrings = new NotificationStrings(); NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; secondaryStrings["BitcoinAddress"] = payout.RecipientPerson.BitcoinPayoutAddress; OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_Bad, primaryStrings, secondaryStrings, People.FromSingle(payout.RecipientPerson)); payout.RecipientPerson.BitcoinPayoutAddress = string.Empty; continue; // do not add this payout } // Ok, so it seems we're making this payout at this time. bitcoinPayouts.Add(payout); int recipientPersonId = payout.RecipientPerson.Identity; if (!satoshiPersonLookup.ContainsKey(recipientPersonId)) { satoshiPersonLookup[recipientPersonId] = 0; nativeCentsPersonLookup[recipientPersonId] = 0; } nativeCentsPersonLookup[recipientPersonId] += payout.AmountCents; // Find the amount of satoshis for this payout if (organization.Currency.IsBitcoinCore) { satoshiPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; satoshisTotal += payout.AmountCents; satoshiPersonLookup[recipientPersonId] += payout.AmountCents; } else { // Convert currency Money payoutAmount = new Money(payout.AmountCents, organization.Currency); Int64 satoshis = payoutAmount.ToCurrency(Currency.BitcoinCore).Cents; satoshiPayoutLookup[payout.ProtoIdentity] = satoshis; nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents; satoshisTotal += satoshis; satoshiPersonLookup[recipientPersonId] += satoshis; } } else if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length < 3 && payout.Account.Length < 4) { // There is a payout for this person, but they don't have a bitcoin payout address set. Send notification to this effect once a day. if (utcNow.Minute != 0) { continue; } if (utcNow.Hour != 12) { continue; } NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_PleaseSet, primaryStrings, People.FromSingle(payout.RecipientPerson)); } else if (payout.Account.StartsWith("bitcoin:")) { } } if (bitcoinPayouts.Count == 0) { // no automated payments pending for this organization, nothing more to do continue; } // We now have our desired payments. The next step is to find enough inputs to reach the required amount (plus fees; we're working a little blind here still). BitcoinTransactionInputs inputs = null; Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain) * 20; // assume max 20k transaction size try { inputs = BitcoinUtility.GetInputsForAmount(organization, satoshisTotal + satoshisMaximumAnticipatedFees); } catch (NotEnoughFundsException) { // If we're at the whole hour, send a notification to people responsible for refilling the hotwallet if (utcNow.Minute != 0) { continue; // we're not at the whole hour, so continue with next org instead } // Send urgent notification to top up the damn wallet so we can make payments NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.CurrencyCode] = organization.Currency.Code; primaryStrings[NotificationString.OrganizationName] = organization.Name; NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); Int64 satoshisAvailable = HotBitcoinAddresses.ForOrganization(organization).BalanceSatoshisTotal; secondaryStrings["AmountMissingMicrocoinsFloat"] = ((satoshisTotal - satoshisAvailable + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2"); if (organization.Currency.IsBitcoinCore) { secondaryStrings["AmountNeededFloat"] = ((satoshisTotal + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2"); secondaryStrings["AmountWalletFloat"] = (satoshisAvailable / 100.0).ToString("N2"); } else { // convert to org native currency secondaryStrings["AmountNeededFloat"] = (new Money(satoshisTotal, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2"); secondaryStrings["AmountWalletFloat"] = (new Money(satoshisAvailable, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2"); } OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Shortage_Critical, primaryStrings, secondaryStrings, People.FromSingle(Person.FromIdentity(1))); continue; // with next organization } // If we arrive at this point, the previous function didn't throw, and we have enough money. // Ensure the existence of a cost account for bitcoin miner fees. organization.EnsureMinerFeeAccountExists(); // Add the inputs to the transaction. txBuilder = txBuilder.AddCoins(inputs.Coins); txBuilder = txBuilder.AddKeys(inputs.PrivateKeys); Int64 satoshisInput = inputs.AmountSatoshisTotal; // Add outputs and prepare notifications Int64 satoshisUsed = 0; Dictionary <int, List <string> > notificationSpecLookup = new Dictionary <int, List <string> >(); Dictionary <int, List <Int64> > notificationAmountLookup = new Dictionary <int, List <Int64> >(); Payout masterPayoutPrototype = Payout.Empty; HotBitcoinAddress changeAddress = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // TODO: CHAIN! foreach (Payout payout in bitcoinPayouts) { int recipientPersonId = payout.RecipientPerson.Identity; if (!notificationSpecLookup.ContainsKey(recipientPersonId)) { notificationSpecLookup[recipientPersonId] = new List <string>(); notificationAmountLookup[recipientPersonId] = new List <Int64>(); } notificationSpecLookup[recipientPersonId].Add(payout.Specification); notificationAmountLookup[recipientPersonId].Add(payout.AmountCents); if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("1")) // regular address { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(payout.RecipientPerson.BitcoinPayoutAddress), new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity])); } else if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("3")) // multisig { txBuilder = txBuilder.Send(new BitcoinScriptAddress(payout.RecipientPerson.BitcoinPayoutAddress, Network.Main), new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity])); } else { throw new InvalidOperationException("Unhandled bitcoin address type in Payouts.PerformAutomated(): " + payout.RecipientPerson.BitcoinPayoutAddress); } satoshisUsed += satoshiPayoutLookup[payout.ProtoIdentity]; payout.MigrateDependenciesTo(masterPayoutPrototype); } // Set change address to wallet slush txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address)); // Add fee int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count; // +inputs.Count for size variability Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain); txBuilder = txBuilder.SendFees(new Satoshis(feeSatoshis)); satoshisUsed += feeSatoshis; // Sign transaction - ready to execute Transaction txReady = txBuilder.BuildTransaction(true); // Verify that transaction is ready if (!txBuilder.Verify(txReady)) { // Transaction was not signed with the correct keys. This is a serious condition. NotificationStrings primaryStrings = new NotificationStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError, primaryStrings); throw new InvalidOperationException("Transaction is not signed enough"); } // Broadcast transaction BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash); // Note the transaction hash string transactionHash = txReady.GetHash().ToString(); // Delete all old inputs, adjust balance for addresses (re-register unused inputs) inputs.AsUnspents.DeleteAll(); // Log the new unspent created by change (if there is any) if (satoshisInput - satoshisUsed > 0) { SwarmDb.GetDatabaseForWriting() .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash, +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0); } // Register new balance of change address, should have increased by (satoshisInput-satoshisUsed) // TODO // Send notifications foreach (int personId in notificationSpecLookup.Keys) { Person person = Person.FromIdentity(personId); string spec = string.Empty; for (int index = 0; index < notificationSpecLookup[personId].Count; index++) { spec += String.Format(" * {0,-40} {1,14:N2} {2,-4}\r\n", notificationSpecLookup[personId][index], notificationAmountLookup[personId][index] / 100.0, currencyCode); } spec = spec.TrimEnd(); NotificationStrings primaryStrings = new NotificationStrings(); NotificationCustomStrings secondaryStrings = new NotificationCustomStrings(); primaryStrings[NotificationString.OrganizationName] = organization.Name; primaryStrings[NotificationString.CurrencyCode] = organization.Currency.DisplayCode; primaryStrings[NotificationString.EmbeddedPreformattedText] = spec; secondaryStrings["AmountFloat"] = (nativeCentsPersonLookup[personId] / 100.0).ToString("N2"); secondaryStrings["BitcoinAmountFloat"] = (satoshiPersonLookup[personId] / 100.0).ToString("N2"); secondaryStrings["BitcoinAddress"] = person.BitcoinPayoutAddress; // warn: potential rare race condition here OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PaidOut, primaryStrings, secondaryStrings, People.FromSingle(person)); } // Create the master payout from its prototype Payout masterPayout = Payout.CreateBitcoinPayoutFromPrototype(organization, masterPayoutPrototype, txReady.GetHash().ToString()); // Finally, create ledger entries and notify NotificationStrings masterPrimaryStrings = new NotificationStrings(); NotificationCustomStrings masterSecondaryStrings = new NotificationCustomStrings(); masterPrimaryStrings[NotificationString.OrganizationName] = organization.Name; masterPrimaryStrings[NotificationString.CurrencyCode] = organization.Currency.DisplayCode; masterSecondaryStrings["AmountFloat"] = (new Swarmops.Logic.Financial.Money(satoshisUsed, Currency.BitcoinCore).ToCurrency( organization.Currency).Cents / 100.0).ToString("N2", CultureInfo.InvariantCulture); masterSecondaryStrings["BitcoinAmountFloat"] = (satoshisUsed / 100.0).ToString("N2", CultureInfo.InvariantCulture); masterSecondaryStrings["PaymentCount"] = bitcoinPayouts.Count.ToString("N0", CultureInfo.InvariantCulture); OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Hotwallet_Outflow, masterPrimaryStrings, masterSecondaryStrings); // TODO: special case for native-bitcoin organizations vs. fiat-currency organizations FinancialTransaction ledgerTransaction = FinancialTransaction.Create(organization, utcNow, "Bitcoin automated payout"); if (organization.Currency.IsBitcoinCore) { ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeSatoshis), null); ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeSatoshis, null); } else { // If the ledger isn't using bitcoin natively, we need to translate the miner fee paid to ledger cents before entering it into ledger Int64 feeCentsLedger = new Money(feeSatoshis, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents; ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeCentsLedger), null).AmountForeignCents = new Money(satoshisUsed, Currency.BitcoinCore); ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeCentsLedger, null); } ledgerTransaction.BlockchainHash = transactionHash; masterPayout.BindToTransactionAndClose(ledgerTransaction, null); } }
public PostAccountTransactionRequest(string requestSourceName, TransactionBuilder builder) : base(requestSourceName) { Builder = builder ?? throw new ArgumentNullException(nameof(builder)); }
public void CanEstimatedFeesCorrectlyIfFeesChangeTransactionSize() { var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, new Key().PubKey, new Key().PubKey, new Key().PubKey); var transactionBuilder = new TransactionBuilder(); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 1), new TxOut("0.00010000", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 2), new TxOut("0.00091824", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 3), new TxOut("0.00100000", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 4), new TxOut("0.00100000", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 5), new TxOut("0.00246414", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 6), new TxOut("0.00250980", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.AddCoins(new Coin(new OutPoint(uint256.Parse("75425c904289f21feef0cffab2081ba22030b633623115adf0780edad443e6c7"), 7), new TxOut("0.01000000", PayToScriptHashTemplate.Instance.GenerateScriptPubKey(redeem).GetDestinationAddress(Network.Main))).ToScriptCoin(redeem)); transactionBuilder.Send(new Key().PubKey.GetAddress(Network.Main), "0.01000000"); transactionBuilder.SetChange(new Key().PubKey.GetAddress(Network.Main)); var feeRate = new FeeRate((long)32563); //Adding the estimated fees will cause 6 more coins to be included, so let's verify the actual sent fees take that into account transactionBuilder.SendEstimatedFees(feeRate); var tx = transactionBuilder.BuildTransaction(false); var estimation = transactionBuilder.EstimateFees(tx, feeRate); Assert.Equal(estimation, tx.GetFee(transactionBuilder.FindSpentCoins(tx))); }
public void Mining() { var(privateKey, publicKey) = SignManager.GenerateKeys(); var publickKeyHash = new HexString(HashUtil.RIPEMD_SHA256(publicKey)); //Genesis Mining var genesis = BlockchainManager.CreateGenesis(); var miner = new Miner { MinerKeyHash = publickKeyHash }; Console.WriteLine("Mining"); miner.Mining(genesis, Context.CancellationToken); BlockchainManager.Chain.Add(genesis); for (var i = 0; i < 10; i++) { var gg = BlockchainManager.CreateCoinBaseTransaction(i + 1, publickKeyHash.Bytes, $"まかろに{i}"); gg.TimeStamp = DateTime.UtcNow; var txs = new List <Transaction>() { gg }; var rootHash = HashUtil.ComputeMerkleRootHash(txs.Select(x => x.Id).ToList()); var b = new Block() { PreviousBlockHash = BlockchainManager.Chain.Last().Id, Transactions = txs, MerkleRootHash = rootHash, Timestamp = DateTime.UtcNow, Bits = 1 }; miner.Mining(b, Context.CancellationToken); BlockchainManager.Chain.Add(b); Task.Delay(10).GetAwaiter().GetResult(); } //Second Block Mining Console.WriteLine($"{genesis.Transactions.Count}"); var tb = new TransactionBuilder(); var ttx = BlockchainManager.Chain.SelectMany(x => x.Transactions).First(x => x.Engraving == "まかろに0"); var input = new Input() { TransactionId = ttx.Id, OutputIndex = 0, }; var output = new Output() { Amount = 10, PublicKeyHash = publickKeyHash.Bytes }; tb.Inputs.Add(input); tb.Outputs.Add(output); var tx = tb.ToSignedTransaction(privateKey, publicKey); BlockchainManager.TransactionPool.Add(tx); miner.Start(); Console.WriteLine($"{BlockchainManager.VerifyBlockchain()} : OK"); Console.ReadLine(); }
//https://gist.github.com/gavinandresen/3966071 public void CanBuildTransactionWithDustPrevention() { var bob = new Key(); var alice = new Key(); var tx = new Transaction() { Outputs = { new TxOut(Money.Coins(1.0m), bob) } }; var coins = tx.Outputs.AsCoins().ToArray(); var builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy.Clone(); builder.StandardTransactionPolicy.MinRelayTxFee = new FeeRate(new Money(1000)); Func<Transaction> create = () => builder .AddCoins(coins) .AddKeys(bob) .Send(alice, Money.Coins(0.99m)) .Send(alice, Money.Satoshis(500)) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(new byte[] { 1, 2 }), Money.Zero) .SendFees(Money.Coins(0.0001m)) .SetChange(bob) .BuildTransaction(true); var signed = create(); Assert.True(signed.Outputs.Count == 3); Assert.True(builder.Verify(signed, Money.Coins(0.0001m))); builder.DustPrevention = false; TransactionPolicyError[] errors; Assert.False(builder.Verify(signed, Money.Coins(0.0001m), out errors)); var ex = (NotEnoughFundsPolicyError)errors.Single(); Assert.True((Money)ex.Missing == Money.Parse("-0.00000500")); builder = new TransactionBuilder(); builder.DustPrevention = false; builder.StandardTransactionPolicy = EasyPolicy.Clone(); builder.StandardTransactionPolicy.MinRelayTxFee = new FeeRate(new Money(1000)); signed = create(); Assert.True(signed.Outputs.Count == 4); Assert.False(builder.Verify(signed, out errors)); Assert.True(errors.Length == 1); Assert.True(errors[0] is DustPolicyError); }
public void CanSyncWallet2() { using (NodeServerTester servers = new NodeServerTester(Network.TestNet)) { var chainBuilder = new BlockchainBuilder(); SetupSPVBehavior(servers, chainBuilder); NodesGroup aliceConnection = CreateGroup(servers, 1); NodesGroup bobConnection = CreateGroup(servers, 1); var aliceKey = new ExtKey(); Wallet alice = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { aliceKey.Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); Wallet bob = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { new ExtKey().Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); alice.Configure(aliceConnection); alice.Connect(); bob.Configure(bobConnection); bob.Connect(); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //New address tracked var addressAlice = alice.GetNextScriptPubKey(); chainBuilder.GiveMoney(addressAlice, Money.Coins(1.0m)); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 0); //Purge TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //Reconnect ////// TestUtils.Eventually(() => alice.GetTransactions().Count == 1); //Alice send tx to bob var coins = alice.GetTransactions().GetSpendableCoins(); var keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); var builder = new TransactionBuilder(); var tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.4m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Alice get change TestUtils.Eventually(() => alice.GetTransactions().Count == 2); coins = alice.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.6m)); ////// //Bob get coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// MemoryStream bobWalletBackup = new MemoryStream(); bob.Save(bobWalletBackup); bobWalletBackup.Position = 0; MemoryStream bobTrakerBackup = new MemoryStream(); bob.Tracker.Save(bobTrakerBackup); bobTrakerBackup.Position = 0; bob.Disconnect(); //Restore bob bob = Wallet.Load(bobWalletBackup); bobConnection.NodeConnectionParameters.TemplateBehaviors.Remove <TrackerBehavior>(); bobConnection.NodeConnectionParameters.TemplateBehaviors.Add(new TrackerBehavior(Tracker.Load(bobTrakerBackup), chainBuilder.Chain)); ///// bob.Configure(bobConnection); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// bob.Connect(); TestUtils.Eventually(() => bobConnection.ConnectedNodes.Count == 1); //New block found ! chainBuilder.FindBlock(); //Alice send tx to bob coins = alice.GetTransactions().GetSpendableCoins(); keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); builder = new TransactionBuilder(); tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.1m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 2); //Bob has both, old and new tx coins = bob.GetTransactions().GetSpendableCoins(); ////// } }
//http://brainwallet.org/#tx public void CanGetTransactionErrors() { Key bob = new Key(); Key alice = new Key(); var funding = new Transaction(); funding.Outputs.Add(new TxOut(Money.Coins(1.0m), bob)); funding.Outputs.Add(new TxOut(Money.Coins(1.1m), bob)); funding.Outputs.Add(new TxOut(Money.Coins(1.2m), alice)); var spending = new Transaction(); spending.Inputs.Add(new TxIn(new OutPoint(funding, 0))); spending.Inputs.Add(new TxIn(new OutPoint(funding, 0))); //Duplicate spending.Inputs.Add(new TxIn(new OutPoint(funding, 1))); spending.Inputs.Add(new TxIn(new OutPoint(funding, 2))); //Alice will not sign spending.Outputs.Add(new TxOut(Money.Coins(4.0m), bob)); TransactionPolicyError[] errors = null; TransactionBuilder builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; builder.AddKeys(bob); builder.AddCoins(funding.Outputs.AsCoins()); builder.SignTransactionInPlace(spending); Assert.False(builder.Verify(spending, Money.Coins(1.0m), out errors)); var dup = errors.OfType<DuplicateInputPolicyError>().Single(); AssertEx.CollectionEquals(new uint[] { 0, 1 }, dup.InputIndices); AssertEx.Equals(new OutPoint(funding, 0), dup.OutPoint); var script = errors.OfType<ScriptPolicyError>().Single(); AssertEx.Equals(alice.ScriptPubKey, script.ScriptPubKey); AssertEx.Equals(3, script.InputIndex); var fees = errors.OfType<NotEnoughFundsPolicyError>().Single(); Assert.Equal(fees.Missing, Money.Coins(0.7m)); spending.Inputs.Add(new TxIn(new OutPoint(funding, 3))); //Coins not found builder.Verify(spending, Money.Coins(1.0m), out errors); var coin = errors.OfType<CoinNotFoundPolicyError>().Single(); Assert.Equal(coin.InputIndex, 4UL); Assert.Equal(coin.OutPoint.N, 3UL); }
public static async Task MainAsync(string[] args) { // Parse options and show helptext if insufficient g_Options = new Options(); Parser.Default.ParseArguments(args, g_Options); if (!g_Options.IsValid()) { var help = HelpText.AutoBuild(g_Options); Console.WriteLine(help.ToString()); return; } // Parse server URI if (String.IsNullOrEmpty(g_Options.Environment) || !g_Options.Environment.StartsWith("http")) { Console.WriteLine($"Invalid URI: {g_Options.Environment}"); return; } // Set up AvaTax g_Client = new AvaTaxClient("AvaTax-Connect", "1.0", Environment.MachineName, new Uri(g_Options.Environment)) .WithSecurity(g_Options.Username, g_Options.Password); // Attempt to connect PingResultModel ping = null; try { ping = g_Client.Ping(); } catch (Exception ex) { Console.WriteLine("Unable to contact AvaTax:"); HandleException(ex); return; } // Fetch the company FetchResult <CompanyModel> companies = null; try { companies = await g_Client.QueryCompaniesAsync(null, $"companyCode eq '{g_Options.CompanyCode}'", null, null, null); } catch (Exception ex) { Console.WriteLine("Exception fetching companies"); HandleException(ex); return; } // Check if the company exists if (companies == null || companies.count != 1) { Console.WriteLine($"Company with code '{g_Options.CompanyCode}' not found.\r\nPlease provide a valid companyCode using the '-c' parameter."); return; } // Check if the company is flagged as a test if ((companies.value[0].isTest != true) && IsPermanent(g_Options.DocType)) { Console.WriteLine($"Company with code '{g_Options.CompanyCode}' is not flagged as a test company.\r\nYour test is configured to use document type '{g_Options.DocType}'.\r\nThis is a permanent document type.\r\nWhen testing with permanent document types, AvaTax-Connect can only be run against a test company."); return; } // Did we authenticate? if (ping.authenticated != true) { Console.WriteLine("Authentication did not succeed. Please check your credentials and try again."); Console.WriteLine($" Username: {g_Options.Username}"); Console.WriteLine($" Password: REDACTED - PLEASE CHECK COMMAND LINE"); Console.WriteLine($" Environment: {g_Options.Environment}"); return; } // Print out information about our configuration Console.WriteLine($"AvaTax-Connect Performance Testing Tool"); Console.WriteLine($"======================================="); Console.WriteLine($" User: {g_Options.Username}"); Console.WriteLine($" Account: {ping.authenticatedAccountId}"); Console.WriteLine($" UserId: {ping.authenticatedUserId}"); Console.WriteLine($" CompanyCode: {g_Options.CompanyCode}"); Console.WriteLine($" SDK: {AvaTaxClient.API_VERSION}"); Console.WriteLine($" Environment: {g_Options.Environment}"); Console.WriteLine($" Tax Lines: {g_Options.Lines}"); Console.WriteLine($" Type: {g_Options.DocType}"); Console.WriteLine($" Threads: {g_Options.Threads}"); Console.WriteLine(); Console.WriteLine(" Call Server DB Svc Net Client Total"); // Use transaction builder var tb = new TransactionBuilder(g_Client, g_Options.CompanyCode, g_Options.DocType, "ABC"); // Add lines for (int i = 0; i < g_Options.Lines; i++) { tb.WithLine(100.0m) .WithLineAddress(TransactionAddressType.PointOfOrderAcceptance, "123 Main Street", null, null, "Irvine", "CA", "92615", "US") .WithLineAddress(TransactionAddressType.PointOfOrderOrigin, "123 Main Street", null, null, "Irvine", "CA", "92615", "US") .WithLineAddress(TransactionAddressType.ShipFrom, "123 Main Street", null, null, "Irvine", "CA", "92615", "US") .WithLineAddress(TransactionAddressType.ShipTo, "123 Main Street", null, null, "Irvine", "CA", "92615", "US"); } g_Model = tb.GetCreateTransactionModel(); // Discard the first call? try { if (g_Options.DiscardFirstCall.HasValue && g_Options.DiscardFirstCall.Value) { var t = g_Client.CreateTransaction(null, g_Model); } } catch (Exception ex) { Console.WriteLine("Cannot connect to AvaTax."); HandleException(ex); return; } // Connect to AvaTax and print debug information g_TotalDuration = new CallDuration(); g_TotalMs = 0; List <Task> threads = new List <Task>(); for (int i = 0; i < g_Options.Threads; i++) { var task = Task.Run(ConnectThread); threads.Add(task); } await Task.WhenAll(threads); // Compute some averages double avg = g_TotalMs * 1.0 / g_Count; double total_overhead = (g_TotalDuration.SetupDuration.TotalMilliseconds + g_TotalDuration.ParseDuration.TotalMilliseconds); double total_transit = g_TotalDuration.TransitDuration.TotalMilliseconds; double total_server = g_TotalDuration.ServerDuration.TotalMilliseconds; double avg_overhead = total_overhead / g_Count; double avg_transit = total_transit / g_Count; double avg_server = total_server / g_Count; double pct_overhead = total_overhead / g_TotalMs; double pct_transit = total_transit / g_TotalMs; double pct_server = total_server / g_TotalMs; // Print out the totals Console.WriteLine(); Console.WriteLine($"Finished {g_Count} calls in {g_TotalMs} milliseconds."); Console.WriteLine($" Average: {avg.ToString("0.00")}ms; {avg_overhead.ToString("0.00")}ms overhead, {avg_transit.ToString("0.00")}ms transit, {avg_server.ToString("0.00")}ms server."); Console.WriteLine($" Percentage: {pct_overhead.ToString("P")} overhead, {pct_transit.ToString("P")} transit, {pct_server.ToString("P")} server."); Console.WriteLine($" Total: {total_overhead} overhead, {total_transit} transit, {total_server} server."); }
public void CanBuildIssueColoredCoinWithMultiSigP2SH() { var satoshi = new Key(); var bob = new Key(); var alice = new Key(); var goldRedeem = PayToMultiSigTemplate.Instance .GenerateScriptPubKey(2, new[] { satoshi.PubKey, bob.PubKey, alice.PubKey }); var goldScriptPubKey = goldRedeem.Hash.ScriptPubKey; var goldAssetId = goldScriptPubKey.Hash.ToAssetId(); var issuanceCoin = new IssuanceCoin( new ScriptCoin(RandOutpoint(), new TxOut(new Money(2880), goldScriptPubKey), goldRedeem)); var nico = new Key(); var bobSigned = new TransactionBuilder() .AddCoins(issuanceCoin) .AddKeys(bob) .IssueAsset(nico.PubKey, new AssetMoney(goldAssetId, 1000)) .BuildTransaction(true); var aliceSigned = new TransactionBuilder() .AddCoins(issuanceCoin) .AddKeys(alice) .SignTransaction(bobSigned); Assert.True( new TransactionBuilder() { StandardTransactionPolicy = EasyPolicy } .AddCoins(issuanceCoin) .Verify(aliceSigned)); //In one two one line var builder = new TransactionBuilder(); builder.StandardTransactionPolicy = RelayPolicy.Clone(); builder.StandardTransactionPolicy.CheckFee = false; var tx = builder .AddCoins(issuanceCoin) .AddKeys(alice, satoshi) .IssueAsset(nico.PubKey, new AssetMoney(goldAssetId, 1000)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); }
static void Main() { /* Create a fake transaction */ var bob = new Key(); var alice = new Key(); var satoshi = new Key(); Script bobAlice = PayToMultiSigTemplate.Instance.GenerateScriptPubKey( 2, bob.PubKey, alice.PubKey); var init = new Transaction(); init.Outputs.Add(new TxOut(Money.Coins(1m), bob.PubKey)); // P2PK init.Outputs.Add(new TxOut(Money.Coins(1m), alice.PubKey.Hash)); // P2PKH init.Outputs.Add(new TxOut(Money.Coins(1m), bobAlice)); /* Get the coins of the initial transaction */ Coin[] coins = init.Outputs.AsCoins().ToArray(); Coin bobCoin = coins[0]; Coin aliceCoin = coins[1]; Coin bobAliceCoin = coins[2]; /* Build the transaction */ var builder = new TransactionBuilder(); Transaction tx = builder .AddCoins(bobCoin) .AddKeys(bob) .Send(satoshi, Money.Coins(0.2m)) .SetChange(bob) .Then() .AddCoins(aliceCoin) .AddKeys(alice) .Send(satoshi, Money.Coins(0.3m)) .SetChange(alice) .Then() .AddCoins(bobAliceCoin) .AddKeys(bob, alice) .Send(satoshi, Money.Coins(0.5m)) .SetChange(bobAlice) .SendFees(Money.Coins(0.0001m)) .BuildTransaction(sign: true); /* Verify you did not screw up */ Console.WriteLine(builder.Verify(tx)); // True /* ScriptCoin */ init = new Transaction(); init.Outputs.Add(new TxOut(Money.Coins(1.0m), bobAlice.Hash)); coins = init.Outputs.AsCoins().ToArray(); ScriptCoin bobAliceScriptCoin = coins[0].ToScriptCoin(bobAlice); builder = new TransactionBuilder(); tx = builder .AddCoins(bobAliceScriptCoin) .AddKeys(bob, alice) .Send(satoshi, Money.Coins(0.9m)) .SetChange(bobAlice.Hash) .SendFees(Money.Coins(0.0001m)) .BuildTransaction(true); Console.WriteLine(builder.Verify(tx)); // True /* STEALTH COIN */ Key scanKey = new Key(); BitcoinStealthAddress darkAliceBob = new BitcoinStealthAddress ( scanKey: scanKey.PubKey, pubKeys: new[] { alice.PubKey, bob.PubKey }, signatureCount: 2, bitfield: null, network: Network.Main ); //Someone sent to darkAliceBob init = new Transaction(); darkAliceBob .SendTo(init, Money.Coins(1.0m)); //Get the stealth coin with the scanKey StealthCoin stealthCoin = StealthCoin.Find(init, darkAliceBob, scanKey); //Spend it tx = builder .AddCoins(stealthCoin) .AddKeys(bob, alice, scanKey) .Send(satoshi, Money.Coins(0.9m)) .SetChange(bobAlice.Hash) .SendFees(Money.Coins(0.0001m)) .BuildTransaction(true); Console.WriteLine(builder.Verify(tx)); // True Console.ReadLine(); }
public void CanBuildShuffleColoredTransaction() { var gold = new Key(); var silver = new Key(); var goldId = gold.PubKey.ScriptPubKey.Hash.ToAssetId(); var silverId = silver.PubKey.ScriptPubKey.Hash.ToAssetId(); var satoshi = new Key(); var bob = new Key(); var repo = new NoSqlColoredTransactionRepository(new NoSqlTransactionRepository(), new InMemoryNoSqlRepository()); var init = new Transaction() { Outputs = { new TxOut("1.0", gold.PubKey), new TxOut("1.0", silver.PubKey), new TxOut("1.0", satoshi.PubKey) } }; repo.Transactions.Put(init.GetHash(), init); var issuanceCoins = init .Outputs .Take(2) .Select((o, i) => new IssuanceCoin(new OutPoint(init.GetHash(), i), init.Outputs[i])) .OfType<ICoin>().ToArray(); var satoshiBTC = new Coin(new OutPoint(init.GetHash(), 2), init.Outputs[2]); var coins = new List<ICoin>(); coins.AddRange(issuanceCoins); var txBuilder = new TransactionBuilder(1); txBuilder.StandardTransactionPolicy = RelayPolicy; //Can issue gold to satoshi and bob var tx = txBuilder .AddCoins(coins.ToArray()) .AddKeys(gold) .IssueAsset(satoshi.PubKey, new AssetMoney(goldId, 1000)) .IssueAsset(bob.PubKey, new AssetMoney(goldId, 500)) .SendFees("0.1") .SetChange(gold.PubKey) .BuildTransaction(true); Assert.True(txBuilder.Verify(tx, "0.1")); //Ensure BTC from the IssuanceCoin are returned Assert.Equal(Money.Parse("0.89994240"), tx.Outputs[2].Value); Assert.Equal(gold.PubKey.ScriptPubKey, tx.Outputs[2].ScriptPubKey); //Can issue and send in same transaction repo.Transactions.Put(tx.GetHash(), tx); var cc = ColoredCoin.Find(tx, repo); for(int i = 0 ; i < 20 ; i++) { txBuilder = new TransactionBuilder(i); txBuilder.StandardTransactionPolicy = RelayPolicy; tx = txBuilder .AddCoins(satoshiBTC) .AddCoins(cc) .AddKeys(satoshi) .SendAsset(gold, new AssetMoney(goldId, 10)) .SetChange(satoshi) .Then() .AddKeys(gold) .AddCoins(issuanceCoins) .IssueAsset(bob, new AssetMoney(goldId, 1)) .SetChange(gold) .Shuffle() .BuildTransaction(true); repo.Transactions.Put(tx.GetHash(), tx); var ctx = tx.GetColoredTransaction(repo); Assert.Equal(1, ctx.Issuances.Count); Assert.Equal(2, ctx.Transfers.Count); } }
static void Main() { var coin = new Coin( fromTxHash: new uint256("eb49a599c749c82d824caf9dd69c4e359261d49bbb0b9d6dc18c59bc9214e43b"), fromOutputIndex: 0, amount: Money.Satoshis(2000000), scriptPubKey: new Script(Encoders.Hex.DecodeData("76a914c81e8e7b7ffca043b088a992795b15887c96159288ac"))); var issuance = new IssuanceCoin(coin); var nico = BitcoinAddress.Create("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe"); //var bookKey = new BitcoinSecret("???????"); var bookKey = new Key().GetBitcoinSecret(Network.Main); // Just a fake key in order to not get an exception var builder = new TransactionBuilder(); Transaction tx = builder .AddKeys(bookKey) .AddCoins(issuance) .IssueAsset(nico, new AssetMoney(issuance.AssetId, quantity: 10)) .SendFees(Money.Coins(0.0001m)) .SetChange(bookKey.GetAddress()) .BuildTransaction(true); Console.WriteLine(tx); Console.WriteLine(builder.Verify(tx)); nico = BitcoinAddress.Create("15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe"); Console.WriteLine(nico.ToColoredAddress()); /* QBITNINJA */ //var client = new QBitNinjaClient(Network.Main); //BroadcastResponse broadcastResponse = client.Broadcast(tx).Result; //if (!broadcastResponse.Success) //{ // Console.WriteLine("ErrorCode: " + broadcastResponse.Error.ErrorCode); // Console.WriteLine("Error message: " + broadcastResponse.Error.Reason); //} //else //{ // Console.WriteLine("Success!"); //} /* OR BITCOIN CORE */ //using (var node = Node.ConnectToLocal(Network.Main)) //Connect to the node //{ // node.VersionHandshake(); //Say hello // //Advertize your transaction (send just the hash) // node.SendMessage(new InvPayload(InventoryType.MSG_TX, tx.GetHash())); // //Send it // node.SendMessage(new TxPayload(tx)); // Thread.Sleep(500); //Wait a bit //} coin = new Coin( fromTxHash: new uint256("fa6db7a2e478f3a8a0d1a77456ca5c9fa593e49fd0cf65c7e349e5a4cbe58842"), fromOutputIndex: 0, amount: Money.Satoshis(2000000), scriptPubKey: new Script(Encoders.Hex.DecodeData("76a914356facdac5f5bcae995d13e667bb5864fd1e7d5988ac"))); BitcoinAssetId assetId = new BitcoinAssetId("AVAVfLSb1KZf9tJzrUVpktjxKUXGxUTD4e"); ColoredCoin colored = coin.ToColoredCoin(assetId, 10); var book = BitcoinAddress.Create("1KF8kUVHK42XzgcmJF4Lxz4wcL5WDL97PB"); var nicoSecret = new BitcoinSecret("??????????"); nico = nicoSecret.GetAddress(); //15sYbVpRh6dyWycZMwPdxJWD4xbfxReeHe var forFees = new Coin( fromTxHash: new uint256("7f296e96ec3525511b836ace0377a9fbb723a47bdfb07c6bc3a6f2a0c23eba26"), fromOutputIndex: 0, amount: Money.Satoshis(4425000), scriptPubKey: new Script(Encoders.Hex.DecodeData("76a914356facdac5f5bcae995d13e667bb5864fd1e7d5988ac"))); builder = new TransactionBuilder(); tx = builder .AddKeys(nicoSecret) .AddCoins(colored, forFees) .SendAsset(book, new AssetMoney(assetId, 10)) .SetChange(nico) .SendFees(Money.Coins(0.0001m)) .BuildTransaction(true); Console.WriteLine(tx); Console.ReadLine(); }
public void CanBuildStealthTransaction() { var stealthKeys = Enumerable.Range(0, 3).Select(_ => new Key()).ToArray(); var scanKey = new Key(); var darkSatoshi = new BitcoinStealthAddress(scanKey.PubKey, stealthKeys.Select(k => k.PubKey).ToArray(), 2, new BitField(3, 5), Network.Main); var bob = new Key(); var coins = new Coin[] { new Coin() { Outpoint = RandOutpoint(), TxOut = new TxOut("1.00",bob.PubKey.Hash) } }; //Bob sends money to satoshi TransactionBuilder builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; var tx = builder .AddCoins(coins) .AddKeys(bob) .Send(darkSatoshi, "1.00") .BuildTransaction(true); Assert.True(builder.Verify(tx)); //Satoshi scans a StealthCoin in the transaction with his scan key var stealthCoin = StealthCoin.Find(tx, darkSatoshi, scanKey); Assert.NotNull(stealthCoin); //Satoshi sends back the money to Bob builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; tx = builder .AddCoins(stealthCoin) .AddKeys(stealthKeys) .AddKeys(scanKey) .Send(bob.PubKey.Hash, "1.00") .BuildTransaction(true); Assert.True(builder.Verify(tx)); //Signed ! //Same scenario, Satoshi wants to send money back to Bob //However, his keys are spread on two machines //He partially signs on the 1st machine builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; tx = builder .AddCoins(stealthCoin) .AddKeys(stealthKeys.Skip(2).ToArray()) //Only one Stealth Key .AddKeys(scanKey) .Send(bob.PubKey.Hash, "1.00") .BuildTransaction(true); Assert.False(builder.Verify(tx)); //Not fully signed //Then he partially signs on the 2nd machine builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; tx = builder .AddCoins(stealthCoin) .AddKeys(stealthKeys[0]) //Other key .AddKeys(scanKey) .SignTransaction(tx); Assert.True(builder.Verify(tx)); //Fully signed ! }
/// <summary> /// Creates a transaction to transfers funds from an old federation to a new federation. /// </summary> /// <param name="isSideChain">Indicates whether the <paramref name="network"/> is the sidechain.</param> /// <param name="network">The network that we are creating the recovery transaction for.</param> /// <param name="counterChainNetwork">The counterchain network.</param> /// <param name="dataDirPath">The root folder containing the old federation.</param> /// <param name="redeemScript">The new redeem script.</param> /// <param name="password">The password required to generate transactions using the federation wallet.</param> /// <param name="txTime">Any deposits beyond this UTC date will be ignored when selecting coin inputs.</param> /// <returns>A funds recovery transaction that moves funds to the new redeem script.</returns> public FundsRecoveryTransactionModel CreateFundsRecoveryTransaction(bool isSideChain, Network network, Network counterChainNetwork, string dataDirPath, Script redeemScript, string password, DateTime txTime) { var model = new FundsRecoveryTransactionModel() { Network = network, IsSideChain = isSideChain, RedeemScript = redeemScript }; // Get the old redeem script from the wallet file. PayToMultiSigTemplateParameters multisigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript); string theChain = isSideChain ? "sidechain" : "mainchain"; var nodeSettings = new NodeSettings(network, args: new string[] { $"datadir={dataDirPath}", $"redeemscript={redeemScript}", $"-{theChain}" }); var walletFileStorage = new FileStorage <FederationWallet>(nodeSettings.DataFolder.WalletPath); FederationWallet wallet = walletFileStorage.LoadByFileName("multisig_wallet.json"); Script oldRedeemScript = wallet.MultiSigAddress.RedeemScript; PayToMultiSigTemplateParameters oldMultisigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(oldRedeemScript); model.oldMultisigAddress = oldRedeemScript.Hash.GetAddress(network); model.newMultisigAddress = redeemScript.Hash.GetAddress(network); // Create dummy inputs to avoid errors when constructing FederatedPegSettings. var extraArgs = new Dictionary <string, string>(); extraArgs[FederatedPegSettings.FederationIpsParam] = oldMultisigParams.PubKeys.Select(p => "0.0.0.0".ToIPEndPoint(nodeSettings.Network.DefaultPort)).Join(","); var privateKey = Key.Parse(wallet.EncryptedSeed, password, network); extraArgs[FederatedPegSettings.PublicKeyParam] = privateKey.PubKey.ToHex(network); (new TextFileConfiguration(extraArgs.Select(i => $"{i.Key}={i.Value}").ToArray())).MergeInto(nodeSettings.ConfigReader); model.PubKey = privateKey.PubKey; var dBreezeSerializer = new DBreezeSerializer(network.Consensus.ConsensusFactory); var blockStore = new BlockRepository(network, nodeSettings.DataFolder, nodeSettings.LoggerFactory, dBreezeSerializer); blockStore.Initialize(); var chain = new ChainRepository(nodeSettings.DataFolder, nodeSettings.LoggerFactory, dBreezeSerializer); Block genesisBlock = network.GetGenesis(); ChainedHeader tip = chain.LoadAsync(new ChainedHeader(genesisBlock.Header, genesisBlock.GetHash(), 0)).GetAwaiter().GetResult(); var chainIndexer = new ChainIndexer(network, tip); var nodeLifetime = new NodeLifetime(); IDateTimeProvider dateTimeProvider = DateTimeProvider.Default; var federatedPegSettings = new FederatedPegSettings(nodeSettings); var opReturnDataReader = new OpReturnDataReader(nodeSettings.LoggerFactory, new CounterChainNetworkWrapper(counterChainNetwork)); var walletFeePolicy = new WalletFeePolicy(nodeSettings); var walletManager = new FederationWalletManager(nodeSettings.LoggerFactory, network, chainIndexer, nodeSettings.DataFolder, walletFeePolicy, new AsyncProvider(nodeSettings.LoggerFactory, new Signals(nodeSettings.LoggerFactory, new DefaultSubscriptionErrorHandler(nodeSettings.LoggerFactory)), nodeLifetime), nodeLifetime, dateTimeProvider, federatedPegSettings, new WithdrawalExtractor(nodeSettings.LoggerFactory, federatedPegSettings, opReturnDataReader, network), blockStore); walletManager.Start(); walletManager.EnableFederationWallet(password); if (!walletManager.IsFederationWalletActive()) { throw new ArgumentException($"Could not activate the federation wallet on {network}."); } // Retrieves the unspent outputs in deterministic order. List <Stratis.Features.FederatedPeg.Wallet.UnspentOutputReference> coinRefs = walletManager.GetSpendableTransactionsInWallet().ToList(); // Exclude coins (deposits) beyond the transaction (switch-over) time! coinRefs = coinRefs.Where(r => r.Transaction.CreationTime < txTime).ToList(); if (!coinRefs.Any()) { throw new ArgumentException($"There are no coins to recover from the federation wallet on {network}."); } Money fee = federatedPegSettings.GetWithdrawalTransactionFee(coinRefs.Count()); var builder = new TransactionBuilder(network); builder.AddKeys(privateKey); builder.AddCoins(coinRefs.Select(c => ScriptCoin.Create(network, c.Transaction.Id, (uint)c.Transaction.Index, c.Transaction.Amount, c.Transaction.ScriptPubKey, oldRedeemScript))); // Split the coins into multiple outputs. Money amount = coinRefs.Sum(r => r.Transaction.Amount) - fee; const int numberOfSplits = 10; Money splitAmount = new Money((long)amount / numberOfSplits); var recipients = new List <Stratis.Features.FederatedPeg.Wallet.Recipient>(); for (int i = 0; i < numberOfSplits; i++) { Money sendAmount = (i != (numberOfSplits - 1)) ? splitAmount : amount - splitAmount * (numberOfSplits - 1); builder.Send(redeemScript.PaymentScript, sendAmount); } builder.SetTimeStamp((uint)(new DateTimeOffset(txTime)).ToUnixTimeSeconds()); builder.CoinSelector = new DeterministicCoinSelector(); builder.SendFees(fee); model.tx = builder.BuildTransaction(true); File.WriteAllText(Path.Combine(dataDirPath, $"{network.Name}_{model.PubKey.ToHex(network).Substring(0, 8)}.hex"), model.tx.ToHex(network)); // Merge our transaction with other transactions which have been placed in the data folder. Transaction oldTransaction = model.tx; string namePattern = $"{network.Name}_*.hex"; int sigCount = 1; foreach (string fileName in Directory.EnumerateFiles(dataDirPath, namePattern)) { Transaction incomingPartialTransaction = network.CreateTransaction(File.ReadAllText(fileName)); // Don't merge with self. if (incomingPartialTransaction.GetHash() == oldTransaction.GetHash()) { continue; } // Transaction times must match. if (incomingPartialTransaction is PosTransaction && incomingPartialTransaction.Time != model.tx.Time) { Console.WriteLine($"The locally generated transaction is time-stamped differently from the transaction contained in '{fileName}'. The imported signature can't be used."); continue; } // Combine signatures. Transaction newTransaction = SigningUtils.CheckTemplateAndCombineSignatures(builder, model.tx, new[] { incomingPartialTransaction }); if (oldTransaction.GetHash() == newTransaction.GetHash()) { Console.WriteLine($"The locally generated transaction is not similar to '{fileName}'. The imported signature can't be used."); continue; } model.tx = newTransaction; sigCount++; } Console.WriteLine($"{sigCount} of {multisigParams.SignatureCount} signatures collected for {network.Name}."); if (sigCount >= multisigParams.SignatureCount) { if (builder.Verify(model.tx)) { // Write the transaction to file. File.WriteAllText(Path.Combine(dataDirPath, $"{(txTime > DateTime.Now ? "Preliminary " : "")}{network.Name}Recovery.txt"), model.tx.ToHex(network)); } else { Console.WriteLine("Could not verify the transaction."); } } // Stop the wallet manager to release the database folder. nodeLifetime.StopApplication(); walletManager.Stop(); return(model); }
public void CanEstimateFees() { var alice = new Key(); var bob = new Key(); var satoshi = new Key(); var bobAlice = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, alice.PubKey, bob.PubKey); //Alice sends money to bobAlice //Bob sends money to bobAlice //bobAlice sends money to satoshi var aliceCoins = new ICoin[] { RandomCoin("0.4", alice), RandomCoin("0.6", alice) }; var bobCoins = new ICoin[] { RandomCoin("0.2", bob), RandomCoin("0.3", bob) }; var bobAliceCoins = new ICoin[] { RandomCoin("1.5", bobAlice, false), RandomCoin("0.25", bobAlice, true) }; TransactionBuilder builder = new TransactionBuilder(); builder.StandardTransactionPolicy = EasyPolicy; var unsigned = builder .AddCoins(aliceCoins) .Send(bobAlice, "1.0") .Then() .AddCoins(bobCoins) .Send(bobAlice, "0.5") .Then() .AddCoins(bobAliceCoins) .Send(satoshi.PubKey, "1.74") .SetChange(bobAlice) .BuildTransaction(false); builder.AddKeys(alice, bob, satoshi); var signed = builder.BuildTransaction(true); Assert.True(builder.Verify(signed)); Assert.True(Math.Abs(signed.ToBytes().Length - builder.EstimateSize(unsigned)) < 20); var rate = new FeeRate(Money.Coins(0.0004m)); var estimatedFees = builder.EstimateFees(unsigned, rate); builder.SendEstimatedFees(rate); signed = builder.BuildTransaction(true); Assert.True(builder.Verify(signed, estimatedFees)); }
private static void UpdateInternal <T>( IDbConnection connection, IEnumerable <Tuple <T, T> > oldAndNewObjects, bool softDelete = false, IDbTransaction transaction = null) { var builder = new TransactionBuilder(MetadataCache); IDictionary <Tuple <T, T>, IList <IScript> > scripts = new Dictionary <Tuple <T, T>, IList <IScript> >(); foreach (var pair in oldAndNewObjects) { scripts[pair] = builder.BuildUpdateScripts(pair.Item1, pair.Item2, softDelete); } Transaction ambient = null; if (transaction == null) { ambient = Transaction.Current; } if (transaction == null && ambient == null) { using (var myTransaction = connection.BeginTransaction()) { try { ExecuteScriptsForTuples( connection, oldAndNewObjects, scripts, softDelete, myTransaction); myTransaction.Commit(); } catch (Exception ex) { if (Logger.Wrapped.IsErrorEnabled) { Logger.Wrapped.Error(new { message = "Error executing internal transaction", exception = ex, scripts }); } myTransaction.Rollback(); throw; } } } else { try { ExecuteScriptsForTuples( connection, oldAndNewObjects, scripts, softDelete, transaction); } catch (Exception ex) { if (Logger.Wrapped.IsErrorEnabled) { Logger.Wrapped.Error(new { message = "Error executing external transaction", exception = ex, scripts }); } throw; } } }
public void CanSyncWallet2() { using(NodeServerTester servers = new NodeServerTester(Network.TestNet)) { var chainBuilder = new BlockchainBuilder(); SetupSPVBehavior(servers, chainBuilder); NodesGroup aliceConnection = CreateGroup(servers, 1); NodesGroup bobConnection = CreateGroup(servers, 1); var aliceKey = new ExtKey(); Wallet alice = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { aliceKey.Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); Wallet bob = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { new ExtKey().Neuter() }, SignatureRequired = 1, UseP2SH = false }, 11); alice.Configure(aliceConnection); alice.Connect(); bob.Configure(bobConnection); bob.Connect(); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //New address tracked var addressAlice = alice.GetNextScriptPubKey(); chainBuilder.GiveMoney(addressAlice, Money.Coins(1.0m)); TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 0); //Purge TestUtils.Eventually(() => aliceConnection.ConnectedNodes.Count == 1); //Reconnect ////// TestUtils.Eventually(() => alice.GetTransactions().Count == 1); //Alice send tx to bob var coins = alice.GetTransactions().GetSpendableCoins(); var keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); var builder = new TransactionBuilder(); var tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.4m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Alice get change TestUtils.Eventually(() => alice.GetTransactions().Count == 2); coins = alice.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.6m)); ////// //Bob get coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// MemoryStream bobWalletBackup = new MemoryStream(); bob.Save(bobWalletBackup); bobWalletBackup.Position = 0; MemoryStream bobTrakerBackup = new MemoryStream(); bob.Tracker.Save(bobTrakerBackup); bobTrakerBackup.Position = 0; bob.Disconnect(); //Restore bob bob = Wallet.Load(bobWalletBackup); bobConnection.NodeConnectionParameters.TemplateBehaviors.Remove<TrackerBehavior>(); bobConnection.NodeConnectionParameters.TemplateBehaviors.Add(new TrackerBehavior(Tracker.Load(bobTrakerBackup), chainBuilder.Chain)); ///// bob.Configure(bobConnection); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 1); coins = bob.GetTransactions().GetSpendableCoins(); Assert.True(coins.Single().Amount == Money.Coins(0.4m)); ////// bob.Connect(); TestUtils.Eventually(() => bobConnection.ConnectedNodes.Count == 1); //New block found ! chainBuilder.FindBlock(); //Alice send tx to bob coins = alice.GetTransactions().GetSpendableCoins(); keys = coins.Select(c => alice.GetKeyPath(c.ScriptPubKey)) .Select(k => aliceKey.Derive(k)) .ToArray(); builder = new TransactionBuilder(); tx = builder .SetTransactionPolicy(new Policy.StandardTransactionPolicy() { MinRelayTxFee = new FeeRate(0) }) .AddCoins(coins) .AddKeys(keys) .Send(bob.GetNextScriptPubKey(), Money.Coins(0.1m)) .SetChange(alice.GetNextScriptPubKey(true)) .BuildTransaction(true); Assert.True(builder.Verify(tx)); chainBuilder.BroadcastTransaction(tx); //Bob still has coins TestUtils.Eventually(() => bob.GetTransactions().Count == 2); //Bob has both, old and new tx coins = bob.GetTransactions().GetSpendableCoins(); ////// } }
private static (int expectedBaseSize, int expectedWitsize, int baseSize, int witSize) VerifyFees(TransactionBuilder builder, FeeRate feeRate = null) { feeRate = feeRate ?? builder.StandardTransactionPolicy.MinRelayTxFee; var result = builder.BuildTransaction(true); builder.EstimateSizes(result, out int witSize, out int baseSize); var expectedWitsize = result.ToBytes().Length - result.WithOptions(TransactionOptions.None).ToBytes().Length; var expectedBaseSize = result.WithOptions(TransactionOptions.None).ToBytes().Length; Assert.True(expectedBaseSize <= baseSize); Assert.True(expectedWitsize <= witSize); Assert.True(feeRate.FeePerK.Almost(result.GetFeeRate(builder.FindSpentCoins(result)).FeePerK, 0.01m)); Assert.True(feeRate.FeePerK <= result.GetFeeRate(builder.FindSpentCoins(result)).FeePerK); return(expectedBaseSize, expectedWitsize, baseSize, witSize); }