diff --git a/src/main/java/org/qortal/crosschain/PirateChainHTLC.java b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java index 5ad1126d..0220e718 100644 --- a/src/main/java/org/qortal/crosschain/PirateChainHTLC.java +++ b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java @@ -173,6 +173,69 @@ public class PirateChainHTLC { return null; } + /** + * Returns a string containing the txid of the transaction that funded supplied p2shAddress + * We have to do this in a bit of a roundabout way due to the Pirate Light Client server omitting + * transaction hashes from the raw transaction data. + *

+ * @throws ForeignBlockchainException if error occurs + */ + public static String getFundingTxid(BitcoinyBlockchainProvider blockchain, String p2shAddress) throws ForeignBlockchainException { + byte[] ourScriptPubKey = addressToScriptPubKey(p2shAddress); + // HASH160(redeem script) for this p2shAddress + byte[] ourRedeemScriptHash = addressToRedeemScriptHash(p2shAddress); + + + // Firstly look for an unspent output + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List unspentOutputs = blockchain.getUnspentOutputs(p2shAddress, false); + for (UnspentOutput unspentOutput : unspentOutputs) { + + if (!Arrays.equals(ourScriptPubKey, unspentOutput.script)) { + continue; + } + + return HashCode.fromBytes(unspentOutput.hash).toString(); + } + + + // No valid unspent outputs, so must be already spent... + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List transactions = blockchain.getAddressBitcoinyTransactions(p2shAddress, BitcoinyBlockchainProvider.EXCLUDE_UNCONFIRMED); + + // Sort by confirmed first, followed by ascending height + transactions.sort(BitcoinyTransaction.CONFIRMED_FIRST.thenComparing(BitcoinyTransaction::getHeight)); + + for (BitcoinyTransaction bitcoinyTransaction : transactions) { + + // Acceptable funding is one transaction output, so we're expecting only one input + if (bitcoinyTransaction.inputs.size() != 1) + // Wrong number of inputs + continue; + + String scriptSig = bitcoinyTransaction.inputs.get(0).scriptSig; + + List scriptSigChunks = extractScriptSigChunks(HashCode.fromString(scriptSig).asBytes()); + if (scriptSigChunks.size() < 3 || scriptSigChunks.size() > 4) + // Not valid chunks for our form of HTLC + continue; + + // Last chunk is redeem script + byte[] redeemScriptBytes = scriptSigChunks.get(scriptSigChunks.size() - 1); + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + if (!Arrays.equals(redeemScriptHash, ourRedeemScriptHash)) + // Not spending our specific HTLC redeem script + continue; + + return bitcoinyTransaction.inputs.get(0).outputTxHash; + + } + + return null; + } + /** * Returns HTLC status, given P2SH address and expected redeem/refund amount *

diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java index e6f1e4b2..8c8a1e88 100644 --- a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -111,6 +111,32 @@ public class PirateChainTests extends Common { assertEquals(REFUNDED, htlcStatus); } + @Test + public void testGetTxidForUnspentAddress() throws ForeignBlockchainException { + String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; + String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + + // Reverse the byte order of the txid used by block explorers, to get to big-endian form + byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes(); + Bytes.reverse(expectedTxidLE); + String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString(); + + assertEquals(expectedTxidBE, txid); + } + + @Test + public void testGetTxidForSpentAddress() throws ForeignBlockchainException { + String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA"; + String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + + // Reverse the byte order of the txid used by block explorers, to get to big-endian form + byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes(); + Bytes.reverse(expectedTxidLE); + String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString(); + + assertEquals(expectedTxidBE, txid); + } + @Test public void testGetTransactionsForAddress() throws ForeignBlockchainException { String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";