Add support for multiple P2SH funding transactions rather than requiring only one

This commit is contained in:
catbref
2020-05-29 19:10:20 +01:00
parent 0ae232b8ba
commit bef1828404
4 changed files with 40 additions and 36 deletions

View File

@@ -543,7 +543,7 @@ public class CrossChainResource {
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
if (p2shBalance.value >= crossChainTradeData.expectedBitcoin && fundingOutputs.size() == 1) {
if (p2shBalance.value >= crossChainTradeData.expectedBitcoin && !fundingOutputs.isEmpty()) {
p2shStatus.canRedeem = now >= medianBlockTime * 1000L;
p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L;
}
@@ -642,10 +642,9 @@ public class CrossChainResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
if (fundingOutputs.size() != 1)
if (fundingOutputs.isEmpty())
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
TransactionOutput fundingOutput = fundingOutputs.get(0);
boolean canRefund = now >= crossChainTradeData.lockTime * 1000L;
if (!canRefund)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
@@ -655,7 +654,7 @@ public class CrossChainResource {
Coin refundAmount = p2shBalance.subtract(Coin.valueOf(refundRequest.bitcoinMinerFee.unscaledValue().longValue()));
org.bitcoinj.core.Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, crossChainTradeData.lockTime);
org.bitcoinj.core.Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, crossChainTradeData.lockTime);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(refundTransaction);
if (!wasBroadcast)
@@ -758,17 +757,16 @@ public class CrossChainResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE);
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
if (fundingOutputs.size() != 1)
if (fundingOutputs.isEmpty())
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
TransactionOutput fundingOutput = fundingOutputs.get(0);
boolean canRedeem = now >= medianBlockTime * 1000L;
if (!canRedeem)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
Coin redeemAmount = p2shBalance.subtract(Coin.valueOf(redeemRequest.bitcoinMinerFee.unscaledValue().longValue()));
org.bitcoinj.core.Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, redeemRequest.secret);
org.bitcoinj.core.Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, redeemRequest.secret);
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction);
if (!wasBroadcast)

View File

@@ -122,7 +122,7 @@ public class BTCACCT {
* @param scriptSigBuilder function for building scriptSig using transaction input signature
* @return Signed Bitcoin transaction for spending P2SH
*/
public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, Long lockTime, Function<byte[], Script> scriptSigBuilder) {
public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, List<TransactionOutput> fundingOutputs, byte[] redeemScriptBytes, Long lockTime, Function<byte[], Script> scriptSigBuilder) {
NetworkParameters params = BTC.getInstance().getNetworkParameters();
Transaction transaction = new Transaction(params);
@@ -131,30 +131,36 @@ public class BTCACCT {
// Output is back to P2SH funder
transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(spendKey.getPubKeyHash()));
// Input (without scriptSig prior to signing)
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
if (lockTime != null)
input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
else
input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
transaction.addInput(input);
for (int inputIndex = 0; inputIndex < fundingOutputs.size(); ++inputIndex) {
TransactionOutput fundingOutput = fundingOutputs.get(inputIndex);
// Input (without scriptSig prior to signing)
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
if (lockTime != null)
input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
else
input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
transaction.addInput(input);
}
// Set locktime after inputs added but before input signatures are generated
if (lockTime != null)
transaction.setLockTime(lockTime);
// Generate transaction signature for input
final boolean anyoneCanPay = false;
TransactionSignature txSig = transaction.calculateSignature(0, spendKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay);
for (int inputIndex = 0; inputIndex < fundingOutputs.size(); ++inputIndex) {
// Generate transaction signature for input
final boolean anyoneCanPay = false;
TransactionSignature txSig = transaction.calculateSignature(inputIndex, spendKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay);
// Calculate transaction signature
byte[] txSigBytes = txSig.encodeToBitcoin();
// Calculate transaction signature
byte[] txSigBytes = txSig.encodeToBitcoin();
// Build scriptSig using lambda and tx signature
Script scriptSig = scriptSigBuilder.apply(txSigBytes);
// Build scriptSig using lambda and tx signature
Script scriptSig = scriptSigBuilder.apply(txSigBytes);
// Set input scriptSig
transaction.getInput(0).setScriptSig(scriptSig);
// Set input scriptSig
transaction.getInput(inputIndex).setScriptSig(scriptSig);
}
return transaction;
}
@@ -169,7 +175,7 @@ public class BTCACCT {
* @param lockTime transaction nLockTime - must be at least locktime used in redeemScript
* @return Signed Bitcoin transaction for refunding P2SH
*/
public static Transaction buildRefundTransaction(Coin refundAmount, ECKey refundKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) {
public static Transaction buildRefundTransaction(Coin refundAmount, ECKey refundKey, List<TransactionOutput> fundingOutputs, byte[] redeemScriptBytes, long lockTime) {
Function<byte[], Script> refundSigScriptBuilder = (txSigBytes) -> {
// Build scriptSig with...
ScriptBuilder scriptBuilder = new ScriptBuilder();
@@ -187,7 +193,7 @@ public class BTCACCT {
return scriptBuilder.build();
};
return buildP2shTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, lockTime, refundSigScriptBuilder);
return buildP2shTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime, refundSigScriptBuilder);
}
/**
@@ -200,7 +206,7 @@ public class BTCACCT {
* @param secret actual 32-byte secret used when building redeemScript
* @return Signed Bitcoin transaction for redeeming P2SH
*/
public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, byte[] secret) {
public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, List<TransactionOutput> fundingOutputs, byte[] redeemScriptBytes, byte[] secret) {
Function<byte[], Script> redeemSigScriptBuilder = (txSigBytes) -> {
// Build scriptSig with...
ScriptBuilder scriptBuilder = new ScriptBuilder();
@@ -221,7 +227,7 @@ public class BTCACCT {
return scriptBuilder.build();
};
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder);
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder);
}
/**