forked from Qortal/qortal
Add support for multiple P2SH funding transactions rather than requiring only one
This commit is contained in:
parent
0ae232b8ba
commit
bef1828404
@ -543,7 +543,7 @@ public class CrossChainResource {
|
|||||||
|
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
|
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.canRedeem = now >= medianBlockTime * 1000L;
|
||||||
p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L;
|
p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L;
|
||||||
}
|
}
|
||||||
@ -642,10 +642,9 @@ public class CrossChainResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||||
|
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
|
||||||
if (fundingOutputs.size() != 1)
|
if (fundingOutputs.isEmpty())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
TransactionOutput fundingOutput = fundingOutputs.get(0);
|
|
||||||
boolean canRefund = now >= crossChainTradeData.lockTime * 1000L;
|
boolean canRefund = now >= crossChainTradeData.lockTime * 1000L;
|
||||||
if (!canRefund)
|
if (!canRefund)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
|
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()));
|
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);
|
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(refundTransaction);
|
||||||
|
|
||||||
if (!wasBroadcast)
|
if (!wasBroadcast)
|
||||||
@ -758,17 +757,16 @@ public class CrossChainResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE);
|
||||||
|
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString());
|
||||||
if (fundingOutputs.size() != 1)
|
if (fundingOutputs.isEmpty())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
TransactionOutput fundingOutput = fundingOutputs.get(0);
|
|
||||||
boolean canRedeem = now >= medianBlockTime * 1000L;
|
boolean canRedeem = now >= medianBlockTime * 1000L;
|
||||||
if (!canRedeem)
|
if (!canRedeem)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON);
|
||||||
|
|
||||||
Coin redeemAmount = p2shBalance.subtract(Coin.valueOf(redeemRequest.bitcoinMinerFee.unscaledValue().longValue()));
|
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);
|
boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction);
|
||||||
|
|
||||||
if (!wasBroadcast)
|
if (!wasBroadcast)
|
||||||
|
@ -122,7 +122,7 @@ public class BTCACCT {
|
|||||||
* @param scriptSigBuilder function for building scriptSig using transaction input signature
|
* @param scriptSigBuilder function for building scriptSig using transaction input signature
|
||||||
* @return Signed Bitcoin transaction for spending P2SH
|
* @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();
|
NetworkParameters params = BTC.getInstance().getNetworkParameters();
|
||||||
|
|
||||||
Transaction transaction = new Transaction(params);
|
Transaction transaction = new Transaction(params);
|
||||||
@ -131,30 +131,36 @@ public class BTCACCT {
|
|||||||
// Output is back to P2SH funder
|
// Output is back to P2SH funder
|
||||||
transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(spendKey.getPubKeyHash()));
|
transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(spendKey.getPubKeyHash()));
|
||||||
|
|
||||||
// Input (without scriptSig prior to signing)
|
for (int inputIndex = 0; inputIndex < fundingOutputs.size(); ++inputIndex) {
|
||||||
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
|
TransactionOutput fundingOutput = fundingOutputs.get(inputIndex);
|
||||||
if (lockTime != null)
|
|
||||||
input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF
|
// Input (without scriptSig prior to signing)
|
||||||
else
|
TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor());
|
||||||
input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF
|
if (lockTime != null)
|
||||||
transaction.addInput(input);
|
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
|
// Set locktime after inputs added but before input signatures are generated
|
||||||
if (lockTime != null)
|
if (lockTime != null)
|
||||||
transaction.setLockTime(lockTime);
|
transaction.setLockTime(lockTime);
|
||||||
|
|
||||||
// Generate transaction signature for input
|
for (int inputIndex = 0; inputIndex < fundingOutputs.size(); ++inputIndex) {
|
||||||
final boolean anyoneCanPay = false;
|
// Generate transaction signature for input
|
||||||
TransactionSignature txSig = transaction.calculateSignature(0, spendKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay);
|
final boolean anyoneCanPay = false;
|
||||||
|
TransactionSignature txSig = transaction.calculateSignature(inputIndex, spendKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay);
|
||||||
|
|
||||||
// Calculate transaction signature
|
// Calculate transaction signature
|
||||||
byte[] txSigBytes = txSig.encodeToBitcoin();
|
byte[] txSigBytes = txSig.encodeToBitcoin();
|
||||||
|
|
||||||
// Build scriptSig using lambda and tx signature
|
// Build scriptSig using lambda and tx signature
|
||||||
Script scriptSig = scriptSigBuilder.apply(txSigBytes);
|
Script scriptSig = scriptSigBuilder.apply(txSigBytes);
|
||||||
|
|
||||||
// Set input scriptSig
|
// Set input scriptSig
|
||||||
transaction.getInput(0).setScriptSig(scriptSig);
|
transaction.getInput(inputIndex).setScriptSig(scriptSig);
|
||||||
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
@ -169,7 +175,7 @@ public class BTCACCT {
|
|||||||
* @param lockTime transaction nLockTime - must be at least locktime used in redeemScript
|
* @param lockTime transaction nLockTime - must be at least locktime used in redeemScript
|
||||||
* @return Signed Bitcoin transaction for refunding P2SH
|
* @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) -> {
|
Function<byte[], Script> refundSigScriptBuilder = (txSigBytes) -> {
|
||||||
// Build scriptSig with...
|
// Build scriptSig with...
|
||||||
ScriptBuilder scriptBuilder = new ScriptBuilder();
|
ScriptBuilder scriptBuilder = new ScriptBuilder();
|
||||||
@ -187,7 +193,7 @@ public class BTCACCT {
|
|||||||
return scriptBuilder.build();
|
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
|
* @param secret actual 32-byte secret used when building redeemScript
|
||||||
* @return Signed Bitcoin transaction for redeeming P2SH
|
* @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) -> {
|
Function<byte[], Script> redeemSigScriptBuilder = (txSigBytes) -> {
|
||||||
// Build scriptSig with...
|
// Build scriptSig with...
|
||||||
ScriptBuilder scriptBuilder = new ScriptBuilder();
|
ScriptBuilder scriptBuilder = new ScriptBuilder();
|
||||||
@ -221,7 +227,7 @@ public class BTCACCT {
|
|||||||
return scriptBuilder.build();
|
return scriptBuilder.build();
|
||||||
};
|
};
|
||||||
|
|
||||||
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder);
|
return buildP2shTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, null, redeemSigScriptBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,16 +173,16 @@ public class Redeem {
|
|||||||
|
|
||||||
if (fundingOutputs.size() != 1) {
|
if (fundingOutputs.size() != 1) {
|
||||||
System.err.println(String.format("Expecting only one unspent output for P2SH"));
|
System.err.println(String.format("Expecting only one unspent output for P2SH"));
|
||||||
System.exit(2);
|
// No longer fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionOutput fundingOutput = fundingOutputs.get(0);
|
for (TransactionOutput fundingOutput : fundingOutputs)
|
||||||
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
|
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
|
||||||
|
|
||||||
Coin redeemAmount = p2shBalance.subtract(bitcoinFee);
|
Coin redeemAmount = p2shBalance.subtract(bitcoinFee);
|
||||||
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(redeemAmount), BTC.FORMAT.format(bitcoinFee)));
|
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(redeemAmount), BTC.FORMAT.format(bitcoinFee)));
|
||||||
|
|
||||||
Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, secret);
|
Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secret);
|
||||||
|
|
||||||
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
|
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
|
||||||
|
|
||||||
|
@ -177,16 +177,16 @@ public class Refund {
|
|||||||
|
|
||||||
if (fundingOutputs.size() != 1) {
|
if (fundingOutputs.size() != 1) {
|
||||||
System.err.println(String.format("Expecting only one unspent output for P2SH"));
|
System.err.println(String.format("Expecting only one unspent output for P2SH"));
|
||||||
System.exit(2);
|
// No longer fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionOutput fundingOutput = fundingOutputs.get(0);
|
for (TransactionOutput fundingOutput : fundingOutputs)
|
||||||
System.out.println(String.format("Using output %s:%d for refund", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
|
System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex()));
|
||||||
|
|
||||||
Coin refundAmount = p2shBalance.subtract(bitcoinFee);
|
Coin refundAmount = p2shBalance.subtract(bitcoinFee);
|
||||||
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(refundAmount), BTC.FORMAT.format(bitcoinFee)));
|
System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(refundAmount), BTC.FORMAT.format(bitcoinFee)));
|
||||||
|
|
||||||
Transaction redeemTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, lockTime);
|
Transaction redeemTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutputs, redeemScriptBytes, lockTime);
|
||||||
|
|
||||||
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
|
byte[] redeemBytes = redeemTransaction.bitcoinSerialize();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user