Skip to content

Commit

Permalink
Can continue to build a partially buit transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasDorier committed Nov 11, 2014
1 parent bf22ad9 commit 3524dfd
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 23 deletions.
40 changes: 34 additions & 6 deletions NBitcoin.Tests/transaction_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,15 @@ public void CanBuildAnyoneCanPayTransaction()
.Send(carlaKey.PubKey.ID, "0.01")
//and Carla pays Alice
.Send(aliceKey.PubKey.ID, "0.02")
.CoverOnly("0.02")
.CoverOnly("0.01")
.SetChange(aliceBobRedeemScript.ID)
// Bob does not sign anything yet
.BuildTransaction(false);

Assert.True(unsigned.Outputs.Count == 3);
Assert.True(unsigned.Outputs[0].IsTo(aliceBobRedeemScript.ID));
//Only 0.02 should be covered, not 0.03 so 0.48 goes back to Alice+Bob
Assert.True(unsigned.Outputs[0].Value == Money.Parse("0.48"));
//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.ID));
Expand All @@ -198,17 +198,20 @@ public void CanBuildAnyoneCanPayTransaction()
.AddKeys(aliceKey)
.SignTransaction(unsigned, SigHash.All | SigHash.AnyoneCanPay);

var carlaCoins = GetCoinSource(carlaKey, "1.0", "0.8", "0.6", "0.2", "0.04");
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.01, but with 0.03 of fees, she should have a coins of 0.04
//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")
.CompleteTransaction(aliceSigned)
.CoverTheRest()
.BuildTransaction(true);


Expand All @@ -221,6 +224,31 @@ public void CanBuildAnyoneCanPayTransaction()

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
.AddCoins(aliceBobCoins)
.Then()
.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));
}

private ICoin[] GetCoinSource(Key destination, params Money[] amounts)
Expand Down
47 changes: 30 additions & 17 deletions NBitcoin/TransactionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -945,32 +945,45 @@ public TransactionBuilder CoverOnly(Money amount)
Transaction _CompletedTransaction;

/// <summary>
/// Find Coins to complete TxOut of the transaction (Does not support colored coins)
/// Allows to keep building on the top of a partially built transaction
/// </summary>
/// <param name="transaction"></param>
/// <param name="transaction">Transaction to complete</param>
/// <returns></returns>
public TransactionBuilder CompleteTransaction(Transaction transaction)
public TransactionBuilder ContinueToBuild(Transaction transaction)
{
if(_CompletedTransaction != null)
throw new InvalidOperationException("Transaction to complete already set");
_CompletedTransaction = transaction.Clone();
return this;
}

var spent = transaction.Inputs.Select(txin =>
{
var c = FindCoin(txin.PrevOut);
if(c == null)
throw CoinNotFound(txin);
if(c is IColoredCoin)
return null;
return c;
})
.Where(c => c != null)
.Select(c => c.Amount)
.Sum();

var toComplete = transaction.TotalOut - spent;
/// <summary>
/// Will cover the remaining amount of TxOut of a partially built transaction (to call after ContinueToBuild)
/// </summary>
/// <returns></returns>
public TransactionBuilder CoverTheRest()
{
if(_CompletedTransaction == null)
throw new InvalidOperationException("A partially built transaction should be specified by calling ContinueToBuild");

var spent = _CompletedTransaction.Inputs.Select(txin =>
{
var c = FindCoin(txin.PrevOut);
if(c == null)
throw CoinNotFound(txin);
if(c is IColoredCoin)
return null;
return c;
})
.Where(c => c != null)
.Select(c => c.Amount)
.Sum();

var toComplete = _CompletedTransaction.TotalOut - spent;
CurrentGroup.Builders.Add(ctx =>
{
if(toComplete < Money.Zero)
return Money.Zero;
return toComplete;
});
return this;
Expand Down

1 comment on commit 3524dfd

@dthorpe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Scenario 2 is what I'll use most.

Please sign in to comment.