forked from Qortal/qortal
Improved refund/refundAll HTLC code, to handle cases where there have been multiple purchase attempts for the same AT.
This commit is contained in:
parent
b0486f44bb
commit
eb569304ba
@ -8,11 +8,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
@ -25,7 +24,6 @@ import org.bitcoinj.core.*;
|
|||||||
import org.bitcoinj.script.Script;
|
import org.bitcoinj.script.Script;
|
||||||
import org.qortal.api.*;
|
import org.qortal.api.*;
|
||||||
import org.qortal.api.model.CrossChainBitcoinyHTLCStatus;
|
import org.qortal.api.model.CrossChainBitcoinyHTLCStatus;
|
||||||
import org.qortal.controller.Controller;
|
|
||||||
import org.qortal.crosschain.*;
|
import org.qortal.crosschain.*;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
@ -586,98 +584,103 @@ public class CrossChainHtlcResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
|
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
|
||||||
TradeBotData tradeBotData = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).findFirst().orElse(null);
|
List<TradeBotData> tradeBotDataList = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).collect(Collectors.toList());
|
||||||
if (tradeBotData == null)
|
if (tradeBotDataList == null || tradeBotDataList.isEmpty())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
// Loop through all matching entries for this AT address, as there might be more than one
|
||||||
int lockTime = tradeBotData.getLockTimeA();
|
for (TradeBotData tradeBotData : tradeBotDataList) {
|
||||||
|
|
||||||
// We can't refund P2SH-A until lockTime-A has passed
|
if (tradeBotData == null)
|
||||||
if (NTP.getTime() <= lockTime * 1000L)
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
|
||||||
|
|
||||||
// We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113)
|
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||||
int medianBlockTime = bitcoiny.getMedianBlockTime();
|
int lockTime = tradeBotData.getLockTimeA();
|
||||||
if (medianBlockTime <= lockTime)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
|
||||||
|
|
||||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
// We can't refund P2SH-A until lockTime-A has passed
|
||||||
long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout);
|
if (NTP.getTime() <= lockTime * 1000L)
|
||||||
long p2shFee = bitcoiny.getP2shFee(feeTimestamp);
|
continue;
|
||||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
|
||||||
|
|
||||||
// Create redeem script based on destination chain
|
// We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113)
|
||||||
byte[] redeemScriptA;
|
int medianBlockTime = bitcoiny.getMedianBlockTime();
|
||||||
String p2shAddressA;
|
if (medianBlockTime <= lockTime)
|
||||||
BitcoinyHTLC.Status htlcStatusA;
|
continue;
|
||||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
|
||||||
redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
|
||||||
p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA);
|
|
||||||
htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
|
||||||
p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
|
||||||
htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
|
||||||
}
|
|
||||||
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
|
||||||
|
|
||||||
switch (htlcStatusA) {
|
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||||
case UNFUNDED:
|
long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout);
|
||||||
case FUNDING_IN_PROGRESS:
|
long p2shFee = bitcoiny.getP2shFee(feeTimestamp);
|
||||||
// Still waiting for P2SH-A to be funded...
|
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
|
||||||
|
|
||||||
case REDEEM_IN_PROGRESS:
|
// Create redeem script based on destination chain
|
||||||
case REDEEMED:
|
byte[] redeemScriptA;
|
||||||
case REFUND_IN_PROGRESS:
|
String p2shAddressA;
|
||||||
case REFUNDED:
|
BitcoinyHTLC.Status htlcStatusA;
|
||||||
// Too late!
|
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||||
return false;
|
redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||||
|
p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA);
|
||||||
|
htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||||
|
} else {
|
||||||
|
redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||||
|
p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
||||||
|
htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||||
|
}
|
||||||
|
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
||||||
|
|
||||||
case FUNDED:{
|
switch (htlcStatusA) {
|
||||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
case UNFUNDED:
|
||||||
|
case FUNDING_IN_PROGRESS:
|
||||||
|
// Still waiting for P2SH-A to be funded...
|
||||||
|
continue;
|
||||||
|
|
||||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
case REDEEM_IN_PROGRESS:
|
||||||
// Pirate Chain custom integration
|
case REDEEMED:
|
||||||
|
case REFUND_IN_PROGRESS:
|
||||||
|
case REFUNDED:
|
||||||
|
// Too late!
|
||||||
|
continue;
|
||||||
|
|
||||||
PirateChain pirateChain = PirateChain.getInstance();
|
case FUNDED: {
|
||||||
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA);
|
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||||
|
|
||||||
// Get funding txid
|
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||||
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
// Pirate Chain custom integration
|
||||||
if (fundingTxidHex == null) {
|
|
||||||
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
|
PirateChain pirateChain = PirateChain.getInstance();
|
||||||
|
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA);
|
||||||
|
|
||||||
|
// Get funding txid
|
||||||
|
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||||
|
if (fundingTxidHex == null) {
|
||||||
|
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
|
||||||
|
}
|
||||||
|
String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes());
|
||||||
|
|
||||||
|
byte[] privateKey = tradeBotData.getTradePrivateKey();
|
||||||
|
String privateKey58 = Base58.encode(privateKey);
|
||||||
|
String redeemScript58 = Base58.encode(redeemScriptA);
|
||||||
|
|
||||||
|
String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3,
|
||||||
|
receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58);
|
||||||
|
LOGGER.info("Refund txid: {}", txid);
|
||||||
|
} else {
|
||||||
|
// ElectrumX coins
|
||||||
|
|
||||||
|
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
|
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
||||||
|
|
||||||
|
// Validate the destination foreign blockchain address
|
||||||
|
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
||||||
|
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
|
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||||
|
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
||||||
|
|
||||||
|
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
||||||
}
|
}
|
||||||
String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes());
|
|
||||||
|
|
||||||
byte[] privateKey = tradeBotData.getTradePrivateKey();
|
return true;
|
||||||
String privateKey58 = Base58.encode(privateKey);
|
|
||||||
String redeemScript58 = Base58.encode(redeemScriptA);
|
|
||||||
|
|
||||||
String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3,
|
|
||||||
receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58);
|
|
||||||
LOGGER.info("Refund txid: {}", txid);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// ElectrumX coins
|
|
||||||
|
|
||||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
|
||||||
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
|
||||||
|
|
||||||
// Validate the destination foreign blockchain address
|
|
||||||
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
|
||||||
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
|
||||||
|
|
||||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
|
||||||
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
|
||||||
|
|
||||||
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user