From 3fdef9ea6d6adcbf2e3fb7220c46c8dd773879c2 Mon Sep 17 00:00:00 2001 From: catbref Date: Mon, 28 Dec 2020 15:44:45 +0000 Subject: [PATCH] Fix P2SH refund "non-final" error issue According to Bitcoin source, CheckFinalTx() in validation.cpp ~line 223, we need to make sure median blocktime has passed P2SH refund transaction's nLockTime. Previously we were erroneously checking that median blocktime was in the past. This should fix issues where refunding P2SH results in a "non-final" error from the ElectrumX server network. --- .../tradebot/BitcoinACCTv1TradeBot.java | 28 +++++++++++-------- .../tradebot/LitecoinACCTv1TradeBot.java | 11 ++++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv1TradeBot.java b/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv1TradeBot.java index 5af8aa45..6fe746bd 100644 --- a/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv1TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv1TradeBot.java @@ -1085,21 +1085,23 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot { */ private void handleAliceRefundingP2shB(Repository repository, TradeBotData tradeBotData, ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { + int lockTimeB = crossChainTradeData.lockTimeB; + // We can't refund P2SH-B until lockTime-B has passed - if (NTP.getTime() <= crossChainTradeData.lockTimeB * 1000L) + if (NTP.getTime() <= lockTimeB * 1000L) return; Bitcoin bitcoin = Bitcoin.getInstance(); - // We can't refund P2SH-B until we've passed median block time + // We can't refund P2SH-B until median block time has passed lockTime-B (see BIP113) int medianBlockTime = bitcoin.getMedianBlockTime(); - if (NTP.getTime() <= medianBlockTime * 1000L) + if (medianBlockTime <= lockTimeB) return; - byte[] redeemScriptB = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), crossChainTradeData.lockTimeB, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretB); + byte[] redeemScriptB = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeB, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretB); String p2shAddressB = bitcoin.deriveP2shAddress(redeemScriptB); - long feeTimestampB = calcP2shBFeeTimestamp(crossChainTradeData.lockTimeA, crossChainTradeData.lockTimeB); + long feeTimestampB = calcP2shBFeeTimestamp(crossChainTradeData.lockTimeA, lockTimeB); long p2shFeeB = bitcoin.getP2shFee(feeTimestampB); final long minimumAmountB = P2SH_B_OUTPUT_AMOUNT + p2shFeeB; @@ -1136,7 +1138,7 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot { Address receiving = Address.fromString(bitcoin.getNetworkParameters(), receiveAddress); Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoin.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptB, crossChainTradeData.lockTimeB, receiving.getHash()); + fundingOutputs, redeemScriptB, lockTimeB, receiving.getHash()); bitcoin.broadcastTransaction(p2shRefundTransaction); break; @@ -1153,22 +1155,24 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot { */ private void handleAliceRefundingP2shA(Repository repository, TradeBotData tradeBotData, ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { + int lockTimeA = tradeBotData.getLockTimeA(); + // We can't refund P2SH-A until lockTime-A has passed - if (NTP.getTime() <= tradeBotData.getLockTimeA() * 1000L) + if (NTP.getTime() <= lockTimeA * 1000L) return; Bitcoin bitcoin = Bitcoin.getInstance(); - // We can't refund P2SH-A until we've passed median block time + // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) int medianBlockTime = bitcoin.getMedianBlockTime(); - if (NTP.getTime() <= medianBlockTime * 1000L) + if (medianBlockTime <= lockTimeA) return; - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); + byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); String p2shAddressA = bitcoin.deriveP2shAddress(redeemScriptA); // Fee for redeem/refund is subtracted from P2SH-A balance. - long feeTimestampA = calcP2shAFeeTimestamp(tradeBotData.getLockTimeA(), crossChainTradeData.tradeTimeout); + long feeTimestampA = calcP2shAFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); long p2shFeeA = bitcoin.getP2shFee(feeTimestampA); long minimumAmountA = crossChainTradeData.expectedForeignAmount - P2SH_B_OUTPUT_AMOUNT + p2shFeeA; BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); @@ -1200,7 +1204,7 @@ public class BitcoinACCTv1TradeBot implements AcctTradeBot { Address receiving = Address.fromString(bitcoin.getNetworkParameters(), receiveAddress); Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoin.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptA, tradeBotData.getLockTimeA(), receiving.getHash()); + fundingOutputs, redeemScriptA, lockTimeA, receiving.getHash()); bitcoin.broadcastTransaction(p2shRefundTransaction); break; diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java index b87b2bcf..ea244c27 100644 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java @@ -766,18 +766,19 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot { */ private void handleAliceRefundingP2shA(Repository repository, TradeBotData tradeBotData, ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { + int lockTimeA = tradeBotData.getLockTimeA(); + // We can't refund P2SH-A until lockTime-A has passed - if (NTP.getTime() <= tradeBotData.getLockTimeA() * 1000L) + if (NTP.getTime() <= lockTimeA * 1000L) return; Litecoin litecoin = Litecoin.getInstance(); - // We can't refund P2SH-A until we've passed median block time + // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) int medianBlockTime = litecoin.getMedianBlockTime(); - if (NTP.getTime() <= medianBlockTime * 1000L) + if (medianBlockTime <= lockTimeA) return; - int lockTimeA = tradeBotData.getLockTimeA(); byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); String p2shAddressA = litecoin.deriveP2shAddress(redeemScriptA); @@ -814,7 +815,7 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot { Address receiving = Address.fromString(litecoin.getNetworkParameters(), receiveAddress); Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(litecoin.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptA, tradeBotData.getLockTimeA(), receiving.getHash()); + fundingOutputs, redeemScriptA, lockTimeA, receiving.getHash()); litecoin.broadcastTransaction(p2shRefundTransaction); break;