diff --git a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java index dfc5dfd8..e8d38703 100644 --- a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java @@ -33,9 +33,6 @@ public class CrossChainBuildRequest { @Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080") public Integer tradeTimeout; - @Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx") - public String receiveAddress; - public CrossChainBuildRequest() { } diff --git a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java index 730421a4..f87471d0 100644 --- a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java @@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainCancelRequest { - @Schema(description = "AT's trade public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry") + @Schema(description = "AT creator's 'trade' public key", example = "K6wuddsBV3HzRrXFFezE7P5MoRXp5m3mEDokRDGZB6ry") public byte[] tradePublicKey; @Schema(description = "Qortal trade AT address") diff --git a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java index 52b9f125..7ad825d4 100644 --- a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java @@ -8,8 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainSecretRequest { - @Schema(description = "Public key to match AT's 'recipient'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") - public byte[] recipientPublicKey; + @Schema(description = "Public key to match AT's trade 'partner'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + public byte[] partnerPublicKey; @Schema(description = "Qortal AT address") public String atAddress; @@ -20,6 +20,9 @@ public class CrossChainSecretRequest { @Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx") public byte[] secretB; + @Schema(description = "Qortal address for receiving QORT from AT") + public String receivingAddress; + public CrossChainSecretRequest() { } diff --git a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java index 3a632bf3..1afd7290 100644 --- a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java @@ -8,13 +8,13 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainTradeRequest { - @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + @Schema(description = "AT creator's 'trade' public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") public byte[] tradePublicKey; @Schema(description = "Qortal AT address") public String atAddress; - @Schema(description = "Signature of trading partner's MESSAGE transaction") + @Schema(description = "Signature of trading partner's 'offer' MESSAGE transaction") public byte[] messageTransactionSignature; public CrossChainTradeRequest() { diff --git a/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java b/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java index 2c319fd9..4e947a9b 100644 --- a/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java +++ b/src/main/java/org/qortal/api/model/TradeBotRespondRequest.java @@ -14,6 +14,9 @@ public class TradeBotRespondRequest { @Schema(description = "Bitcoin BIP32 extended private key", example = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TbTVGajEB55L1HYLg2aQMecZLXLre5YJcawpdFG66STVAWPJ") public String xprv58; + @Schema(description = "Qortal address for receiving QORT from AT") + public String receivingAddress; + public TradeBotRespondRequest() { } diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 73e0e050..dd77ed3b 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -59,7 +59,6 @@ import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.crosschain.TradeBotData; -import org.qortal.data.crosschain.CrossChainTradeData.Mode; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.MessageTransactionData; @@ -182,23 +181,11 @@ public class CrossChainResource { if (tradeRequest.bitcoinAmount <= 0) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); - Address bitcoinReceiveAddress; - try { - bitcoinReceiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), tradeRequest.receiveAddress); - } catch (AddressFormatException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); - } - byte[] bitcoinReceivePublicKeyHash = bitcoinReceiveAddress.getHash(); - - // We only support P2PKH addresses at this time - if (bitcoinReceiveAddress.getOutputScriptType() != ScriptType.P2PKH) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); - try (final Repository repository = RepositoryManager.getRepository()) { PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey); byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.hashOfSecretB, - tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout, bitcoinReceivePublicKeyHash); + tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout); long txTimestamp = NTP.getTime(); byte[] lastReference = creatorAccount.getLastReference(); @@ -233,11 +220,11 @@ public class CrossChainResource { } @POST - @Path("/tradeoffer/recipient") + @Path("/tradeoffer/trademessage") @Operation( - summary = "Builds raw, unsigned MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode", - description = "Specify address of cross-chain AT that needs to be messaged, and address of Qortal recipient.
" - + "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" + summary = "Builds raw, unsigned 'trade' MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode", + description = "Specify address of cross-chain AT that needs to be messaged, and signature of 'offer' MESSAGE from trade partner.
" + + "AT needs to be in 'offer' mode. Messages sent to an AT in any other mode will be ignored, but still cost fees to send!
" + "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.", requestBody = @RequestBody( required = true, @@ -259,7 +246,7 @@ public class CrossChainResource { } ) @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) - public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) { + public String buildTradeMessage(CrossChainTradeRequest tradeRequest) { Security.checkApiCallAllowed(request); byte[] tradePublicKey = tradeRequest.tradePublicKey; @@ -277,11 +264,11 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) + if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); // Does supplied public key match trade public key? - if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress)) + if (!Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature); @@ -299,7 +286,7 @@ public class CrossChainResource { // Good to make MESSAGE - byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH; + byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH; byte[] hashOfSecretA = offerMessageData.hashOfSecretA; int lockTimeA = (int) offerMessageData.lockTimeA; @@ -316,12 +303,12 @@ public class CrossChainResource { } @POST - @Path("/tradeoffer/secret") + @Path("/tradeoffer/redeemmessage") @Operation( - summary = "Builds raw, unsigned MESSAGE transaction that sends secrets to AT, releasing funds to recipient", - description = "Specify address of cross-chain AT that needs to be messaged, and both 32-byte secrets.
" - + "AT needs to be in 'trade' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" - + "You need to sign output with account the AT considers the 'recipient' otherwise the MESSAGE transaction will be invalid.", + summary = "Builds raw, unsigned 'redeem' MESSAGE transaction that sends secrets to AT, releasing funds to partner", + description = "Specify address of cross-chain AT that needs to be messaged, both 32-byte secrets and an address for receiving QORT from AT.
" + + "AT needs to be in 'trade' mode. Messages sent to an AT in any other mode will be ignored, but still cost fees to send!
" + + "You need to sign output with account the AT considers the trade 'partner' otherwise the MESSAGE transaction will be invalid.", requestBody = @RequestBody( required = true, content = @Content( @@ -342,12 +329,12 @@ public class CrossChainResource { } ) @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_DATA, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) - public String sendSecret(CrossChainSecretRequest secretRequest) { + public String buildRedeemMessage(CrossChainSecretRequest secretRequest) { Security.checkApiCallAllowed(request); - byte[] recipientPublicKey = secretRequest.recipientPublicKey; + byte[] partnerPublicKey = secretRequest.partnerPublicKey; - if (recipientPublicKey == null || recipientPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) + if (partnerPublicKey == null || partnerPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress)) @@ -359,24 +346,26 @@ public class CrossChainResource { if (secretRequest.secretB == null || secretRequest.secretB.length != BTCACCT.SECRET_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + if (secretRequest.receivingAddress == null || !Crypto.isValidAddress(secretRequest.receivingAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER) + if (crossChainTradeData.mode != BTCACCT.Mode.TRADING) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - PublicKeyAccount recipientAccount = new PublicKeyAccount(repository, recipientPublicKey); - String recipientAddress = recipientAccount.getAddress(); + String partnerAddress = Crypto.toAddress(partnerPublicKey); - // MESSAGE must come from address that AT considers trade partner / 'recipient' - if (!crossChainTradeData.qortalRecipient.equals(recipientAddress)) + // MESSAGE must come from address that AT considers trade partner + if (!crossChainTradeData.qortalPartnerAddress.equals(partnerAddress)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); // Good to make MESSAGE - byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB); - byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, messageData); + byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB, secretRequest.receivingAddress); + byte[] messageTransactionBytes = buildAtMessage(repository, partnerPublicKey, secretRequest.atAddress, messageData); return Base58.encode(messageTransactionBytes); } catch (DataException e) { @@ -387,7 +376,7 @@ public class CrossChainResource { @DELETE @Path("/tradeoffer") @Operation( - summary = "Builds raw, unsigned MESSAGE transaction that cancels cross-chain trade offer", + summary = "Builds raw, unsigned 'cancel' MESSAGE transaction that cancels cross-chain trade offer", description = "Specify address of cross-chain AT that needs to be cancelled.
" + "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" + "You need to sign output with trade's private key otherwise the MESSAGE transaction will be invalid.", @@ -411,7 +400,7 @@ public class CrossChainResource { } ) @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) - public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) { + public String buildCancelMessage(CrossChainCancelRequest cancelRequest) { Security.checkApiCallAllowed(request); byte[] tradePublicKey = cancelRequest.tradePublicKey; @@ -426,17 +415,17 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) + if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); // Does supplied public key match trade public key? - if (tradePublicKey != null && !Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress)) + if (!Crypto.toAddress(tradePublicKey).equals(crossChainTradeData.qortalCreatorTradeAddress)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); // Good to make MESSAGE String atCreatorAddress = crossChainTradeData.qortalCreator; - byte[] messageData = BTCACCT.buildRefundMessage(atCreatorAddress); + byte[] messageData = BTCACCT.buildCancelMessage(atCreatorAddress); byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData); @@ -516,7 +505,7 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == Mode.OFFER) + if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, lockTimeFn.applyAsInt(crossChainTradeData), templateRequest.redeemPublicKeyHash, hashOfSecretFn.apply(crossChainTradeData)); @@ -599,7 +588,7 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == Mode.OFFER) + if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); int lockTime = lockTimeFn.applyAsInt(crossChainTradeData); @@ -732,7 +721,7 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == Mode.OFFER) + if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); int lockTime = lockTimeFn.applyAsInt(crossChainTradeData); @@ -874,7 +863,7 @@ public class CrossChainResource { ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode == Mode.OFFER) + if (crossChainTradeData.mode == BTCACCT.Mode.OFFERING || crossChainTradeData.mode == BTCACCT.Mode.CANCELLED) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); int lockTime = lockTimeFn.applyAsInt(crossChainTradeData); @@ -1031,15 +1020,18 @@ public class CrossChainResource { if (!BTC.getInstance().isValidXprv(tradeBotRespondRequest.xprv58)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + if (tradeBotRespondRequest.receivingAddress == null || !Crypto.isValidAddress(tradeBotRespondRequest.receivingAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + // Extract data from cross-chain trading AT try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, atAddress); CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); - if (crossChainTradeData.mode != Mode.OFFER) + if (crossChainTradeData.mode != BTCACCT.Mode.OFFERING) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - boolean result = TradeBot.startResponse(repository, crossChainTradeData, tradeBotRespondRequest.xprv58); + boolean result = TradeBot.startResponse(repository, crossChainTradeData, tradeBotRespondRequest.xprv58, tradeBotRespondRequest.receivingAddress); return result ? "true" : "false"; } catch (DataException e) { diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java index 5db223ac..8c085256 100644 --- a/src/main/java/org/qortal/controller/TradeBot.java +++ b/src/main/java/org/qortal/controller/TradeBot.java @@ -40,6 +40,7 @@ import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.DeployAtTransactionTransformer; import org.qortal.utils.Amounts; +import org.qortal.utils.Base58; import org.qortal.utils.NTP; public class TradeBot { @@ -133,7 +134,7 @@ public class TradeBot { String aTType = "ACCT"; String tags = "ACCT QORT BTC"; byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount, - tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout, bitcoinReceivePublicKeyHash); + tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout); long amount = tradeBotCreateRequest.fundingQortAmount; DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT); @@ -150,7 +151,7 @@ public class TradeBot { tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secretB, hashOfSecretB, tradeForeignPublicKey, tradeForeignPublicKeyHash, - tradeBotCreateRequest.bitcoinAmount, null, null, null); + tradeBotCreateRequest.bitcoinAmount, null, null, null, bitcoinReceivePublicKeyHash); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -188,7 +189,7 @@ public class TradeBot { *

* It is envisaged that the value in xprv58 will actually come from a Qortal-UI-managed wallet. *

- * If sufficient funds are available, this method will actually fund the P2SH-A + * If sufficient funds are available, this method will actually fund the P2SH-A * with the Bitcoin amount expected by 'Bob'. *

* If the Bitcoin transaction is successfully broadcast to the network then the trade-bot entry @@ -202,7 +203,7 @@ public class TradeBot { * @return true if P2SH-A funding transaction successfully broadcast to Bitcoin network, false otherwise * @throws DataException */ - public static boolean startResponse(Repository repository, CrossChainTradeData crossChainTradeData, String xprv58) throws DataException { + public static boolean startResponse(Repository repository, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException { byte[] tradePrivateKey = generateTradePrivateKey(); byte[] secretA = generateSecret(); byte[] hashOfSecretA = Crypto.hash160(secretA); @@ -213,6 +214,7 @@ public class TradeBot { byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey); byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); + byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH // We need to generate lockTime-A: halfway of refundTimeout from now int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L); @@ -222,7 +224,7 @@ public class TradeBot { tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secretA, hashOfSecretA, tradeForeignPublicKey, tradeForeignPublicKeyHash, - crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA); + crossChainTradeData.expectedBitcoin, xprv58, null, lockTimeA, receivingPublicKeyHash); // Check we have enough funds via xprv58 to fund both P2SHs to cover expectedBitcoin String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash); @@ -499,7 +501,7 @@ public class TradeBot { if (offerMessageData == null) continue; - byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH; + byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH; byte[] hashOfSecretA = offerMessageData.hashOfSecretA; int lockTimeA = (int) offerMessageData.lockTimeA; // Determine P2SH-A address and confirm funded @@ -592,11 +594,11 @@ public class TradeBot { } // We're waiting for AT to be in TRADE mode - if (crossChainTradeData.mode != CrossChainTradeData.Mode.TRADE) + if (crossChainTradeData.mode != BTCACCT.Mode.TRADING) return; // We're expecting AT to be locked to our native trade address - if (!crossChainTradeData.qortalRecipient.equals(tradeBotData.getTradeNativeAddress())) { + if (!crossChainTradeData.qortalPartnerAddress.equals(tradeBotData.getTradeNativeAddress())) { // AT locked to different address! We shouldn't continue but wait and refund. byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret()); @@ -604,7 +606,7 @@ public class TradeBot { LOGGER.warn(() -> String.format("AT %s locked to %s, not us (%s). Refunding %s - aborting trade", tradeBotData.getAtAddress(), - crossChainTradeData.qortalRecipient, + crossChainTradeData.qortalPartnerAddress, tradeBotData.getTradeNativeAddress(), p2shAddress)); @@ -701,7 +703,7 @@ public class TradeBot { // AT yet to process MESSAGE return; - byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.recipientBitcoinPKH, crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB); + byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.partnerBitcoinPKH, crossChainTradeData.lockTimeB, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretB); String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes); Long balance = BTC.getInstance().getBalance(p2shAddress); @@ -716,7 +718,7 @@ public class TradeBot { Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress); - byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH; + byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash(); Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash); @@ -783,9 +785,10 @@ public class TradeBot { // Secret not revealed at this time return; - // Send MESSAGE to AT using both secrets + // Send 'redeem' MESSAGE to AT using both secrets byte[] secretA = tradeBotData.getSecret(); - byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB); + String qortalReceiveAddress = Base58.encode(tradeBotData.getReceivingPublicKeyHash()); // Actually contains whole address, not just PKH + byte[] messageData = BTCACCT.buildRedeemMessage(secretA, secretB, qortalReceiveAddress); PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, tradeBotData.getAtAddress(), messageData, false, false); @@ -846,7 +849,7 @@ public class TradeBot { } // We check variable in AT that is set when trade successfully completes - if (!crossChainTradeData.hasRedeemed) { + if (crossChainTradeData.mode != BTCACCT.Mode.REDEEMED) { tradeBotData.setState(TradeBotData.State.BOB_REFUNDED); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -864,13 +867,13 @@ public class TradeBot { // Use secret-A to redeem P2SH-A - byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.recipientBitcoinPKH, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretA); + byte[] redeemScriptBytes = BTCP2SH.buildScript(crossChainTradeData.partnerBitcoinPKH, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, crossChainTradeData.hashOfSecretA); String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes); Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin); ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress); - byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH; + byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash(); Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash); diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index ab64ae09..8e3312c6 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -1,10 +1,13 @@ package org.qortal.crosschain; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toMap; import static org.ciyam.at.OpCode.calcOffset; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.ciyam.at.API; import org.ciyam.at.CompilationException; @@ -37,7 +40,7 @@ import com.google.common.primitives.Bytes; *

  • Bob generates Bitcoin & Qortal 'trade' keys, and secret-b *
      *
    • private key required to sign P2SH redeem tx
    • - *
    • private key can be used to create 'secret' (e.g. double-SHA256)
    • + *
    • private key could be used to create 'secret' (e.g. double-SHA256)
    • *
    • encrypted private key could be stored in Qortal AT for access by Bob from any node
    • *
    *
  • @@ -48,42 +51,53 @@ import com.google.common.primitives.Bytes; *
  • Alice finds Qortal AT and wants to trade *
      *
    • Alice generates Bitcoin & Qortal 'trade' keys
    • - *
    • Alice funds Bitcoin P2SH-a
    • - *
    • Alice MESSAGEs Bob from her Qortal trade address, sending secret-hash-a and Bitcoin PKH
    • + *
    • Alice funds Bitcoin P2SH-A
    • + *
    • Alice sends 'offer' MESSAGE to Bob from her Qortal trade address, containing: + *
        + *
      • hash-of-secret-A
      • + *
      • her 'trade' Bitcoin PKH
      • + *
      + *
    • *
    *
  • - *
  • Bob receives MESSAGE + *
  • Bob receives "offer" MESSAGE *
      - *
    • Checks Alice's P2SH-a
    • - *
    • Sends MESSAGE to Qortal AT from his trade address, containing: + *
    • Checks Alice's P2SH-A
    • + *
    • Sends 'trade' MESSAGE to Qortal AT from his trade address, containing: *
        *
      • Alice's trade Qortal address
      • *
      • Alice's trade Bitcoin PKH
      • - *
      • secret-hash-a
      • + *
      • hash-of-secret-A
      • *
      *
    • *
    *
  • *
  • Alice checks Qortal AT to confirm it's locked to her *
      - *
    • Alice creates/funds Bitcoin P2SH-b
    • + *
    • Alice creates/funds Bitcoin P2SH-B
    • *
    *
  • - *
  • Bob checks P2SH-b is funded + *
  • Bob checks P2SH-B is funded *
      - *
    • Bob redeems P2SH-b using his Bitcoin trade key and secret-b
    • + *
    • Bob redeems P2SH-B using his Bitcoin trade key and secret-B
    • *
    *
  • - *
  • Alice scans P2SH-b redeem tx to extract secret-b + *
  • Alice scans P2SH-B redeem transaction to extract secret-B *
      - *
    • Alice MESSAGEs Qortal AT from her trade address, sending secret-a & secret-b
    • - *
    • AT's QORT funds end up at Qortal address derived from Alice's trade private key
    • + *
    • Alice sends 'redeem' MESSAGE to Qortal AT from her trade address, containing: + *
        + *
      • secret-A
      • + *
      • secret-B
      • + *
      • Qortal receive address of her chosing
      • + *
      + *
    • + *
    • AT's QORT funds are sent to Qortal receive address
    • *
    *
  • - *
  • Bob checks AT, extracts secret-a + *
  • Bob checks AT, extracts secret-A *
      - *
    • Bob redeems P2SH-a using his Bitcoin trade key and secret-a
    • - *
    • P2SH-a funds end up in at Bitcoin address derived from Bob's trade private key
    • + *
    • Bob redeems P2SH-A using his Bitcoin trade key and secret-A
    • + *
    • P2SH-A BTC funds end up at Bitcoin address determined by redeem transaction output(s)
    • *
    *
  • * @@ -92,13 +106,36 @@ public class BTCACCT { public static final int SECRET_LENGTH = 32; public static final int MIN_LOCKTIME = 1500000000; - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("f62e1447e8361703c261c8e8e1973713d29b713716582f491431cb7f6ee99e80").asBytes(); // SHA256 of AT code bytes + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("fad14381b77ae1a2bfe7e16a1a8b571839c5f405fca0490ead08499ac170f65b").asBytes(); // SHA256 of AT code bytes public static class OfferMessageData { - public byte[] recipientBitcoinPKH; + public byte[] partnerBitcoinPKH; public byte[] hashOfSecretA; public long lockTimeA; } + public static final int OFFER_MESSAGE_LENGTH = 20 /*partnerBitcoinPKH*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/; + public static final int TRADE_MESSAGE_LENGTH = 32 /*partner's Qortal trade address (padded from 25 to 32)*/ + + 24 /*partner's Bitcoin PKH (padded from 20 to 24)*/ + + 8 /*lockTimeB*/ + + 24 /*hash of secret-A (padded from 20 to 24)*/ + + 8 /*lockTimeA*/; + public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret*/ + 32 /*secret*/ + 32 /*partner's Qortal receive address padded from 25 to 32*/; + public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/; + + public enum Mode { + OFFERING(0), TRADING(1), CANCELLED(2), REFUNDED(3), REDEEMED(4); + + public final int value; + private static final Map map = stream(Mode.values()).collect(toMap(mode -> mode.value, mode -> mode)); + + Mode(int value) { + this.value = value; + } + + public static Mode valueOf(int value) { + return map.get(value); + } + } private BTCACCT() { } @@ -106,7 +143,7 @@ public class BTCACCT { /** * Returns Qortal AT creation bytes for cross-chain trading AT. *

    - * tradeTimeout (minutes) is the time window for the recipient to send the + * tradeTimeout (minutes) is the time window for the trade partner to send the * 32-byte secret to the AT, before the AT automatically refunds the AT's creator. * * @param creatorTradeAddress AT creator's trade Qortal address, also used for refunds @@ -117,7 +154,7 @@ public class BTCACCT { * @param tradeTimeout suggested timeout for entire trade * @return */ - public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout, byte[] bitcoinReceivePublicKeyHash) { + public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout) { // Labels for data segment addresses int addrCounter = 0; @@ -138,28 +175,26 @@ public class BTCACCT { final int addrBitcoinAmount = addrCounter++; final int addrTradeTimeout = addrCounter++; - final int addrMessageTxType = addrCounter++; - final int addrExpectedOfferMessageLength = addrCounter++; + final int addrMessageTxnType = addrCounter++; final int addrExpectedTradeMessageLength = addrCounter++; + final int addrExpectedRedeemMessageLength = addrCounter++; final int addrCreatorAddressPointer = addrCounter++; final int addrHashOfSecretBPointer = addrCounter++; - final int addrQortalRecipientPointer = addrCounter++; + final int addrQortalPartnerAddressPointer = addrCounter++; final int addrMessageSenderPointer = addrCounter++; - final int addrOfferMessageRecipientBitcoinPKHOffset = addrCounter++; - final int addrRecipientBitcoinPKHPointer = addrCounter++; - final int addrOfferMessageHashOfSecretAOffset = addrCounter++; + final int addrTradeMessagePartnerBitcoinPKHOffset = addrCounter++; + final int addrPartnerBitcoinPKHPointer = addrCounter++; + final int addrTradeMessageHashOfSecretAOffset = addrCounter++; final int addrHashOfSecretAPointer = addrCounter++; - final int addrTradeMessageSecretBOffset = addrCounter++; + final int addrRedeemMessageSecretBOffset = addrCounter++; + final int addrRedeemMessageReceiveAddressOffset = addrCounter++; final int addrMessageDataPointer = addrCounter++; final int addrMessageDataLength = addrCounter++; - final int addrBitcoinReceivePublicKeyHash = addrCounter; - addrCounter += 4; - final int addrEndOfConstants = addrCounter; // Variables @@ -169,18 +204,18 @@ public class BTCACCT { final int addrCreatorAddress3 = addrCounter++; final int addrCreatorAddress4 = addrCounter++; - final int addrQortalRecipient1 = addrCounter++; - final int addrQortalRecipient2 = addrCounter++; - final int addrQortalRecipient3 = addrCounter++; - final int addrQortalRecipient4 = addrCounter++; + final int addrQortalPartnerAddress1 = addrCounter++; + final int addrQortalPartnerAddress2 = addrCounter++; + final int addrQortalPartnerAddress3 = addrCounter++; + final int addrQortalPartnerAddress4 = addrCounter++; final int addrLockTimeA = addrCounter++; final int addrLockTimeB = addrCounter++; final int addrRefundTimeout = addrCounter++; final int addrRefundTimestamp = addrCounter++; - final int addrLastTxTimestamp = addrCounter++; + final int addrLastTxnTimestamp = addrCounter++; final int addrBlockTimestamp = addrCounter++; - final int addrTxType = addrCounter++; + final int addrTxnType = addrCounter++; final int addrResult = addrCounter++; final int addrMessageSender1 = addrCounter++; @@ -196,13 +231,11 @@ public class BTCACCT { final int addrHashOfSecretA = addrCounter; addrCounter += 4; - final int addrRecipientBitcoinPKH = addrCounter; + final int addrPartnerBitcoinPKH = addrCounter; addrCounter += 4; final int addrMode = addrCounter++; - final int addrRedeemFlag = addrCounter++; - // Data segment ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE); @@ -232,16 +265,16 @@ public class BTCACCT { dataByteBuffer.putLong(tradeTimeout); // We're only interested in MESSAGE transactions - assert dataByteBuffer.position() == addrMessageTxType * MachineState.VALUE_SIZE : "addrMessageTxType incorrect"; + assert dataByteBuffer.position() == addrMessageTxnType * MachineState.VALUE_SIZE : "addrMessageTxnType incorrect"; dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); - // Expected length of OFFER MESSAGE data from AT creator - assert dataByteBuffer.position() == addrExpectedOfferMessageLength * MachineState.VALUE_SIZE : "addrExpectedOfferMessageLength incorrect"; - dataByteBuffer.putLong(32L + 32L + 32L); - - // Expected length of TRADE MESSAGE data from trade partner / "recipient" + // Expected length of 'trade' MESSAGE data from AT creator assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect"; - dataByteBuffer.putLong(32L + 32L); + dataByteBuffer.putLong(TRADE_MESSAGE_LENGTH); + + // Expected length of 'redeem' MESSAGE data from trade partner + assert dataByteBuffer.position() == addrExpectedRedeemMessageLength * MachineState.VALUE_SIZE : "addrExpectedRedeemMessageLength incorrect"; + dataByteBuffer.putLong(REDEEM_MESSAGE_LENGTH); // Index into data segment of AT creator's address, used by GET_B_IND assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect"; @@ -252,56 +285,56 @@ public class BTCACCT { dataByteBuffer.putLong(addrHashOfSecretB); // Index into data segment of recipient address, used by SET_B_IND - assert dataByteBuffer.position() == addrQortalRecipientPointer * MachineState.VALUE_SIZE : "addrQortalRecipientPointer incorrect"; - dataByteBuffer.putLong(addrQortalRecipient1); + assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect"; + dataByteBuffer.putLong(addrQortalPartnerAddress1); // Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect"; dataByteBuffer.putLong(addrMessageSender1); - // Offset into OFFER MESSAGE data payload for extracting recipient's Bitcoin PKH - assert dataByteBuffer.position() == addrOfferMessageRecipientBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrOfferMessageRecipientBitcoinPKHOffset incorrect"; + // Offset into 'trade' MESSAGE data payload for extracting partner's Bitcoin PKH + assert dataByteBuffer.position() == addrTradeMessagePartnerBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerBitcoinPKHOffset incorrect"; dataByteBuffer.putLong(32L); - // Index into data segment of recipient's Bitcoin PKH, used by GET_B_IND - assert dataByteBuffer.position() == addrRecipientBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrRecipientBitcoinPKHPointer incorrect"; - dataByteBuffer.putLong(addrRecipientBitcoinPKH); + // Index into data segment of partner's Bitcoin PKH, used by GET_B_IND + assert dataByteBuffer.position() == addrPartnerBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerBitcoinPKHPointer incorrect"; + dataByteBuffer.putLong(addrPartnerBitcoinPKH); - // Offset into OFFER MESSAGE data payload for extracting hash-of-secret-A - assert dataByteBuffer.position() == addrOfferMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrOfferMessageHashOfSecretAOffset incorrect"; + // Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A + assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect"; dataByteBuffer.putLong(64L); - // Index into data segment of hash of secret A, used by GET_B_IND + // Index into data segment to hash of secret A, used by GET_B_IND assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect"; dataByteBuffer.putLong(addrHashOfSecretA); - // Offset into TRADE MESSAGE data payload for extracting secret-B - assert dataByteBuffer.position() == addrTradeMessageSecretBOffset * MachineState.VALUE_SIZE : "addrTradeMessageSecretBOffset incorrect"; + // Offset into 'redeem' MESSAGE data payload for extracting secret-B + assert dataByteBuffer.position() == addrRedeemMessageSecretBOffset * MachineState.VALUE_SIZE : "addrRedeemMessageSecretBOffset incorrect"; dataByteBuffer.putLong(32L); + // Offset into 'redeem' MESSAGE data payload for extracting Qortal receive address + assert dataByteBuffer.position() == addrRedeemMessageReceiveAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceiveAddressOffset incorrect"; + dataByteBuffer.putLong(64L); + // Source location and length for hashing any passed secret assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect"; dataByteBuffer.putLong(addrMessageData); assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect"; dataByteBuffer.putLong(32L); - // Bitcoin receive public key hash - assert dataByteBuffer.position() == addrBitcoinReceivePublicKeyHash * MachineState.VALUE_SIZE : "addrBitcoinReceivePublicKeyHash incorrect"; - dataByteBuffer.put(Bytes.ensureCapacity(bitcoinReceivePublicKeyHash, 32, 0)); - assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants"; // Code labels Integer labelRefund = null; - Integer labelOfferTxLoop = null; - Integer labelCheckOfferTx = null; + Integer labelTradeTxnLoop = null; + Integer labelCheckTradeTxn = null; - Integer labelCheckNonRefundOfferTx = null; - Integer labelOfferTxExtract = null; - Integer labelTradeTxLoop = null; - Integer labelCheckTradeTx = null; - Integer labelCheckTradeSender = null; + Integer labelCheckNonRefundTradeTxn = null; + Integer labelTradeTxnExtract = null; + Integer labelRedeemTxnLoop = null; + Integer labelCheckRedeemTxn = null; + Integer labelCheckRedeemTxnSender = null; Integer labelCheckSecretB = null; Integer labelPayout = null; @@ -315,7 +348,7 @@ public class BTCACCT { /* Initialization */ // Use AT creation 'timestamp' as starting point for finding transactions sent to AT - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp)); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp)); // Load B register with AT creator's address so we can save it into addrCreatorAddress1-4 codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B)); @@ -327,26 +360,26 @@ public class BTCACCT { /* Loop, waiting for message from AT creator's trade address containing trade partner details, or AT owner's address to cancel offer */ /* Transaction processing loop */ - labelOfferTxLoop = codeByteBuffer.position(); + labelTradeTxnLoop = codeByteBuffer.position(); - // Find next transaction to this AT since the last one (if any) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp)); - // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. + // Find next transaction (if any) to this AT since the last one (referenced by addrLastTxnTimestamp) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp)); + // If no transaction found, A will be zero. If A is zero, set addrResult to 1, otherwise 0. codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckOfferTx))); + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTxn))); // Stop and wait for next block codeByteBuffer.put(OpCode.STP_IMD.compile()); /* Check transaction */ - labelCheckOfferTx = codeByteBuffer.position(); + labelCheckTradeTxn = codeByteBuffer.position(); // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp)); - // Extract transaction type (message/payment) from transaction and save type in addrTxType - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType)); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp)); + // Extract transaction type (message/payment) from transaction and save type in addrTxnType + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType)); // If transaction type is not MESSAGE type then go look for another transaction - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelOfferTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelTradeTxnLoop))); /* Check transaction's sender. We're expecting AT creator's trade address. */ @@ -355,45 +388,50 @@ public class BTCACCT { // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelOfferTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelOfferTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelOfferTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelOfferTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelTradeTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelTradeTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelTradeTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelTradeTxnLoop))); /* Extract trade partner info from message */ // Extract message from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrQortalRecipient1 (as pointed to by addrQortalRecipientPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalRecipientPointer)); - // Compare each of recipient address with creator's address (for offer-cancel scenario). If they don't match, assume recipient is trade partner. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx))); - // Recipient address is AT creator's address, so cancel offer and finish. - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); + // Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer)); + // Compare each of partner address with creator's address (for offer-cancel scenario). If they don't match, assume address is trade partner. + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn))); + // Partner address is AT creator's address, so cancel offer and finish. + codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.CANCELLED.value)); + // We're finished forever (finishing auto-refunds remaining balance to AT creator) + codeByteBuffer.put(OpCode.FIN_IMD.compile()); /* Possible switch-to-trade-mode message */ - labelCheckNonRefundOfferTx = codeByteBuffer.position(); + labelCheckNonRefundTradeTxn = codeByteBuffer.position(); - // Not off-cancel scenario so check we received expected number of message bytes + // Not offer-cancel scenario so check we received expected number of message bytes codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength)); - codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedOfferMessageLength, calcOffset(codeByteBuffer, labelOfferTxExtract))); - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop)); + // If message length matches, branch to info extraction code + codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelTradeTxnExtract))); + // Message length didn't match - go back to finding another 'trade' MESSAGE transaction + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop)); - labelOfferTxExtract = codeByteBuffer.position(); + /* Extracting info from 'trade' MESSAGE transaction */ + labelTradeTxnExtract = codeByteBuffer.position(); // Message is expected length, grab next 32 bytes - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrOfferMessageRecipientBitcoinPKHOffset)); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerBitcoinPKHOffset)); - // Extract recipient's Bitcoin PKH (we only really use values from B1-B3) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrRecipientBitcoinPKHPointer)); + // Extract partner's Bitcoin PKH (we only really use values from B1-B3) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerBitcoinPKHPointer)); // Also extract lockTimeB codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeB)); // Grab next 32 bytes - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrOfferMessageHashOfSecretAOffset)); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset)); // Extract hash-of-secret-a (we only really use values from B1-B3) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashOfSecretAPointer)); @@ -404,66 +442,67 @@ public class BTCACCT { codeByteBuffer.put(OpCode.SET_DAT.compile(addrRefundTimeout, addrLockTimeA)); // refundTimeout = lockTimeA codeByteBuffer.put(OpCode.SUB_DAT.compile(addrRefundTimeout, addrLockTimeB)); // refundTimeout -= lockTimeB codeByteBuffer.put(OpCode.DIV_VAL.compile(addrRefundTimeout, 2L * 60L)); // refundTimeout /= 2 * 60 - // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this tx 'timestamp', then save into addrRefundTimestamp - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxTimestamp, addrRefundTimeout)); + // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this transaction's 'timestamp', then save into addrRefundTimestamp + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout)); /* We are in 'trade mode' */ - codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, 1)); + codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.TRADING.value)); // Set restart position to after this opcode codeByteBuffer.put(OpCode.SET_PCS.compile()); - /* Loop, waiting for trade timeout or message from Qortal trade recipient containing secret-a and secret-b */ + /* Loop, waiting for trade timeout or 'redeem' MESSAGE from Qortal trade partner */ // Fetch current block 'timestamp' codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); // If we're not past refund 'timestamp' then look for next transaction - codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); // We're past refund 'timestamp' so go refund everything back to AT creator codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); /* Transaction processing loop */ - labelTradeTxLoop = codeByteBuffer.position(); + labelRedeemTxnLoop = codeByteBuffer.position(); // Find next transaction to this AT since the last one (if any) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp)); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp)); // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTx))); + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckRedeemTxn))); // Stop and wait for next block codeByteBuffer.put(OpCode.STP_IMD.compile()); /* Check transaction */ - labelCheckTradeTx = codeByteBuffer.position(); + labelCheckRedeemTxn = codeByteBuffer.position(); // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp)); - // Extract transaction type (message/payment) from transaction and save type in addrTxType - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType)); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp)); + // Extract transaction type (message/payment) from transaction and save type in addrTxnType + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType)); // If transaction type is not MESSAGE type then go look for another transaction - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); /* Check message payload length */ codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength)); - codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelCheckTradeSender))); - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop)); + // If message length matches, branch to sender checking code + codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedRedeemMessageLength, calcOffset(codeByteBuffer, labelCheckRedeemTxnSender))); + // Message length didn't match - go back to finding another 'redeem' MESSAGE transaction + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop)); /* Check transaction's sender */ - - labelCheckTradeSender = codeByteBuffer.position(); + labelCheckRedeemTxnSender = codeByteBuffer.position(); // Extract sender address from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalRecipient1, calcOffset(codeByteBuffer, labelTradeTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalRecipient2, calcOffset(codeByteBuffer, labelTradeTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalRecipient3, calcOffset(codeByteBuffer, labelTradeTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalRecipient4, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalPartnerAddress4, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - /* Check 'secret-a' in transaction's message */ + /* Check 'secret-A' in transaction's message */ // Extract secret-A from first 32 bytes of message from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); @@ -476,14 +515,14 @@ public class BTCACCT { codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength)); // If hashes don't match, addrResult will be zero so go find another transaction codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckSecretB))); - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxLoop == null ? 0 : labelTradeTxLoop)); + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop)); - /* Check 'secret-b' in transaction's message */ + /* Check 'secret-B' in transaction's message */ labelCheckSecretB = codeByteBuffer.position(); // Extract secret-B from next 32 bytes of message from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageSecretBOffset)); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageSecretBOffset)); // Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer)); // Load B register with expected hash result (as pointed to by addrHashOfSecretBPointer) @@ -493,28 +532,28 @@ public class BTCACCT { codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength)); // If hashes don't match, addrResult will be zero so go find another transaction codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout))); - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxLoop == null ? 0 : labelTradeTxLoop)); + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop)); - /* Success! Pay arranged amount to intended recipient */ + /* Success! Pay arranged amount to receive address */ labelPayout = codeByteBuffer.position(); - // Load B register with intended recipient address (as pointed to by addrQortalRecipientPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrQortalRecipientPointer)); + // Extract Qortal receive address from next 32 bytes of message from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceiveAddressOffset)); // Pay AT's balance to recipient codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount)); - // Set redeem flag - codeByteBuffer.put(OpCode.INC_DAT.compile(addrRedeemFlag)); + // Set redeemed mode + codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REDEEMED.value)); + // We're finished forever (finishing auto-refunds remaining balance to AT creator) + codeByteBuffer.put(OpCode.FIN_IMD.compile()); // Fall-through to refunding any remaining balance back to AT creator /* Refund balance back to AT creator */ labelRefund = codeByteBuffer.position(); - // Load B register with AT creator's address. - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B)); - // Pay AT's balance back to AT's creator. - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B)); - // We're finished forever + // Set refunded mode + codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REFUNDED.value)); + // We're finished forever (finishing auto-refunds remaining balance to AT creator) codeByteBuffer.put(OpCode.FIN_IMD.compile()); } catch (CompilationException e) { throw new IllegalStateException("Unable to compile BTC-QORT ACCT?", e); @@ -587,15 +626,16 @@ public class BTCACCT { // Expected BTC amount tradeData.expectedBitcoin = dataByteBuffer.getLong(); + // Trade timeout tradeData.tradeTimeout = (int) dataByteBuffer.getLong(); // Skip MESSAGE transaction type dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip expected OFFER message length + // Skip expected 'trade' message length dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip expected TRADE message length + // Skip expected 'redeem' message length dataByteBuffer.position(dataByteBuffer.position() + 8); // Skip pointer to creator's address @@ -604,25 +644,28 @@ public class BTCACCT { // Skip pointer to hash-of-secret-B dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip pointer to Qortal recipient + // Skip pointer to partner's Qortal trade address dataByteBuffer.position(dataByteBuffer.position() + 8); // Skip pointer to message sender dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip OFFER message data offset for recipient's bitcoin PKH + // Skip 'trade' message data offset for recipient's bitcoin PKH dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip pointer to recipient's bitcoin PKH + // Skip pointer to partner's bitcoin PKH dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip OFFER message data offset for hash-of-secret-A + // Skip 'trade' message data offset for hash-of-secret-A dataByteBuffer.position(dataByteBuffer.position() + 8); // Skip pointer to hash-of-secret-A dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip TRADE message data offset for secret-B + // Skip 'redeem' message data offset for secret-B + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip 'redeem' message data offset for partner's Qortal receive address dataByteBuffer.position(dataByteBuffer.position() + 8); // Skip pointer to message data @@ -631,17 +674,12 @@ public class BTCACCT { // Skip message data length dataByteBuffer.position(dataByteBuffer.position() + 8); - // Creator's Bitcoin/foreign receiving public key hash - tradeData.creatorReceiveBitcoinPKH = new byte[20]; - dataByteBuffer.get(tradeData.creatorReceiveBitcoinPKH); - dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorReceiveBitcoinPKH.length); // skip to 32 bytes - /* End of constants / begin variables */ // Skip AT creator's address dataByteBuffer.position(dataByteBuffer.position() + 8 * 4); - // Recipient's trade address (if present) + // Partner's trade address (if present) dataByteBuffer.get(addressBytes); String qortalRecipient = Base58.encode(addressBytes); dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length); @@ -655,7 +693,7 @@ public class BTCACCT { // AT refund timeout (probably only useful for debugging) int refundTimeout = (int) dataByteBuffer.getLong(); - // Trade offer timeout (AT 'timestamp' converted to Qortal block height) + // Trade-mode refund timestamp (AT 'timestamp' converted to Qortal block height) long tradeRefundTimestamp = dataByteBuffer.getLong(); // Skip last transaction timestamp @@ -684,60 +722,58 @@ public class BTCACCT { dataByteBuffer.get(hashOfSecretA); dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes - // Potential recipient's Bitcoin PKH + // Potential partner's Bitcoin PKH byte[] recipientBitcoinPKH = new byte[20]; dataByteBuffer.get(recipientBitcoinPKH); dataByteBuffer.position(dataByteBuffer.position() + 32 - recipientBitcoinPKH.length); // skip to 32 bytes - long mode = dataByteBuffer.getLong(); + long modeValue = dataByteBuffer.getLong(); + Mode mode = Mode.valueOf((int) (modeValue & 0xffL)); - long redeemFlag = dataByteBuffer.getLong(); - - if (mode != 0) { - tradeData.mode = CrossChainTradeData.Mode.TRADE; + if (mode != null && mode != Mode.OFFERING) { + tradeData.mode = mode; tradeData.refundTimeout = refundTimeout; tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight; - tradeData.qortalRecipient = qortalRecipient; + tradeData.qortalPartnerAddress = qortalRecipient; tradeData.hashOfSecretA = hashOfSecretA; - tradeData.recipientBitcoinPKH = recipientBitcoinPKH; + tradeData.partnerBitcoinPKH = recipientBitcoinPKH; tradeData.lockTimeA = lockTimeA; tradeData.lockTimeB = lockTimeB; - tradeData.hasRedeemed = redeemFlag != 0; } else { - tradeData.mode = CrossChainTradeData.Mode.OFFER; + tradeData.mode = Mode.OFFERING; } return tradeData; } - /** Returns trade-info MESSAGE payload for trade partner/recipient to send to AT creator's trade address. */ + /** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */ public static byte[] buildOfferMessage(byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) { byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); return Bytes.concat(recipientBitcoinPKH, hashOfSecretA, lockTimeABytes); } - /** Returns trade-info extracted from MESSAGE payload sent by trade partner/recipient, or null if not valid. */ + /** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */ public static OfferMessageData extractOfferMessageData(byte[] messageData) { - if (messageData == null || messageData.length != 20 + 20 + 8) + if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH) return null; OfferMessageData offerMessageData = new OfferMessageData(); - offerMessageData.recipientBitcoinPKH = Arrays.copyOfRange(messageData, 0, 20); + offerMessageData.partnerBitcoinPKH = Arrays.copyOfRange(messageData, 0, 20); offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40); offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40); return offerMessageData; } - /** Returns trade-info MESSAGE payload for AT creator to send to AT. */ - public static byte[] buildTradeMessage(String recipientQortalAddress, byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) { - byte[] data = new byte[32 + 32 + 32]; - byte[] recipientQortalAddressBytes = Base58.decode(recipientQortalAddress); + /** Returns 'trade' MESSAGE payload for AT creator to send to AT. */ + public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) { + byte[] data = new byte[TRADE_MESSAGE_LENGTH]; + byte[] recipientQortalAddressBytes = Base58.decode(partnerQortalTradeAddress); byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); byte[] lockTimeBBytes = BitTwiddling.toBEByteArray((long) lockTimeB); System.arraycopy(recipientQortalAddressBytes, 0, data, 0, recipientQortalAddressBytes.length); - System.arraycopy(recipientBitcoinPKH, 0, data, 32, recipientBitcoinPKH.length); + System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length); System.arraycopy(lockTimeBBytes, 0, data, 56, lockTimeBBytes.length); System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length); System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length); @@ -745,9 +781,9 @@ public class BTCACCT { return data; } - /** Returns refund MESSAGE payload for AT creator to cancel trade AT. */ - public static byte[] buildRefundMessage(String creatorQortalAddress) { - byte[] data = new byte[32]; + /** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */ + public static byte[] buildCancelMessage(String creatorQortalAddress) { + byte[] data = new byte[CANCEL_MESSAGE_LENGTH]; byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress); System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length); @@ -755,31 +791,33 @@ public class BTCACCT { return data; } - /** Returns redeem MESSAGE payload for trade partner/recipient to send to AT. */ - public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB) { - byte[] data = new byte[32 + 32]; + /** Returns 'redeem' MESSAGE payload for trade partner/ to send to AT. */ + public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB, String qortalReceiveAddress) { + byte[] data = new byte[REDEEM_MESSAGE_LENGTH]; + byte[] qortalReceiveAddressBytes = Base58.decode(qortalReceiveAddress); System.arraycopy(secretA, 0, data, 0, secretA.length); System.arraycopy(secretB, 0, data, 32, secretB.length); + System.arraycopy(qortalReceiveAddressBytes, 0, data, 64, qortalReceiveAddressBytes.length); return data; } - /** Returns P2SH-B lockTime (epoch seconds) based on trade partner/recipient's MESSAGE timestamp and P2SH-A locktime. */ - public static int calcLockTimeB(long recipientMessageTimestamp, int lockTimeA) { - // lockTimeB is halfway between recipientMessageTimesamp and lockTimeA - return (int) ((lockTimeA + (recipientMessageTimestamp / 1000L)) / 2L); + /** Returns P2SH-B lockTime (epoch seconds) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */ + public static int calcLockTimeB(long offerMessageTimestamp, int lockTimeA) { + // lockTimeB is halfway between offerMessageTimesamp and lockTimeA + return (int) ((lockTimeA + (offerMessageTimestamp / 1000L)) / 2L); } public static byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException { String atAddress = crossChainTradeData.qortalAtAddress; - String redeemerAddress = crossChainTradeData.qortalRecipient; + String redeemerAddress = crossChainTradeData.qortalPartnerAddress; List messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(atAddress, null, null, null); if (messageTransactionsData == null) return null; - // Find redeem message + // Find 'redeem' message for (MessageTransactionData messageTransactionData : messageTransactionsData) { // Check message payload type/encryption if (messageTransactionData.isText() || messageTransactionData.isEncrypted()) @@ -787,7 +825,7 @@ public class BTCACCT { // Check message payload size byte[] messageData = messageTransactionData.getData(); - if (messageData.length != 32 + 32) + if (messageData.length != REDEEM_MESSAGE_LENGTH) // Wrong payload length continue; diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java index 68a6124e..c5ffea39 100644 --- a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -6,6 +6,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; import io.swagger.v3.oas.annotations.media.Schema; @@ -13,8 +14,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainTradeData { - public enum Mode { OFFER, TRADE }; - // Properties @Schema(description = "AT's Qortal address") @@ -29,9 +28,6 @@ public class CrossChainTradeData { @Schema(description = "AT creator's Bitcoin trade public-key-hash (PKH)") public byte[] creatorBitcoinPKH; - @Schema(description = "AT creator's Bitcoin receiving public-key-hash (PKH)") - public byte[] creatorReceiveBitcoinPKH; - @Schema(description = "Timestamp when AT was created (milliseconds since epoch)") public long creationTimestamp; @@ -53,7 +49,7 @@ public class CrossChainTradeData { public long qortAmount; @Schema(description = "Trade partner's Qortal address (trade begins when this is set)") - public String qortalRecipient; + public String qortalPartnerAddress; @Schema(description = "Timestamp when AT switched to trade mode") public Long tradeModeTimestamp; @@ -68,7 +64,7 @@ public class CrossChainTradeData { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public long expectedBitcoin; - public Mode mode; + public BTCACCT.Mode mode; @Schema(description = "Suggested Bitcoin P2SH-A nLockTime based on trade timeout") public Integer lockTimeA; @@ -77,10 +73,7 @@ public class CrossChainTradeData { public Integer lockTimeB; @Schema(description = "Trade partner's Bitcoin public-key-hash (PKH)") - public byte[] recipientBitcoinPKH; - - @Schema(description = "Whether AT has paid out to trade partner") - public Boolean hasRedeemed; + public byte[] partnerBitcoinPKH; // Constructors @@ -102,20 +95,10 @@ public class CrossChainTradeData { @XmlElement(name = "recipientBitcoinAddress") @Schema(description = "Trade partner's trading Bitcoin PKH in address form") public String getRecipientBitcoinAddress() { - if (this.recipientBitcoinPKH == null) + if (this.partnerBitcoinPKH == null) return null; - return BTC.getInstance().pkhToAddress(this.recipientBitcoinPKH); - } - - // We can represent BitcoinPKH as an address - @XmlElement(name = "creatorBitcoinReceivingAddress") - @Schema(description = "AT creator's Bitcoin receiving address") - public String getCreatorBitcoinReceivingAddress() { - if (this.creatorReceiveBitcoinPKH == null) - return null; - - return BTC.getInstance().pkhToAddress(this.creatorReceiveBitcoinPKH); + return BTC.getInstance().pkhToAddress(this.partnerBitcoinPKH); } } diff --git a/src/main/java/org/qortal/data/crosschain/TradeBotData.java b/src/main/java/org/qortal/data/crosschain/TradeBotData.java index 2e935ee5..87dfde21 100644 --- a/src/main/java/org/qortal/data/crosschain/TradeBotData.java +++ b/src/main/java/org/qortal/data/crosschain/TradeBotData.java @@ -48,7 +48,7 @@ public class TradeBotData { private long bitcoinAmount; - // Never expose this + // Never expose this via API @XmlTransient @Schema(hidden = true) private String xprv58; @@ -57,6 +57,9 @@ public class TradeBotData { private Integer lockTimeA; + // Could be Bitcoin or Qortal... + private byte[] receivingPublicKeyHash; + protected TradeBotData() { /* JAXB */ } @@ -65,7 +68,7 @@ public class TradeBotData { byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress, byte[] secret, byte[] hashOfSecret, byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash, - long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA) { + long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA, byte[] receivingPublicKeyHash) { this.tradePrivateKey = tradePrivateKey; this.tradeState = tradeState; this.atAddress = atAddress; @@ -80,6 +83,7 @@ public class TradeBotData { this.xprv58 = xprv58; this.lastTransactionSignature = lastTransactionSignature; this.lockTimeA = lockTimeA; + this.receivingPublicKeyHash = receivingPublicKeyHash; } public byte[] getTradePrivateKey() { @@ -154,4 +158,8 @@ public class TradeBotData { this.lockTimeA = lockTimeA; } + public byte[] getReceivingPublicKeyHash() { + return this.receivingPublicKeyHash; + } + } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java index 3c30444e..23fe2801 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java @@ -23,7 +23,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { + "trade_native_public_key, trade_native_public_key_hash, " + "trade_native_address, secret, hash_of_secret, " + "trade_foreign_public_key, trade_foreign_public_key_hash, " - + "bitcoin_amount, xprv58, last_transaction_signature, locktime_a " + + "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_public_key_hash " + "FROM TradeBotStates " + "WHERE trade_private_key = ?"; @@ -50,13 +50,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { Integer lockTimeA = resultSet.getInt(13); if (lockTimeA == 0 && resultSet.wasNull()) lockTimeA = null; + byte[] receivingPublicKeyHash = resultSet.getBytes(14); return new TradeBotData(tradePrivateKey, tradeState, atAddress, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secret, hashOfSecret, tradeForeignPublicKey, tradeForeignPublicKeyHash, - bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA); + bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash); } catch (SQLException e) { throw new DataException("Unable to fetch trade-bot trading state from repository", e); } @@ -68,7 +69,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { + "trade_native_public_key, trade_native_public_key_hash, " + "trade_native_address, secret, hash_of_secret, " + "trade_foreign_public_key, trade_foreign_public_key_hash, " - + "bitcoin_amount, xprv58, last_transaction_signature, locktime_a " + + "bitcoin_amount, xprv58, last_transaction_signature, locktime_a, receiving_public_key_hash " + "FROM TradeBotStates"; List allTradeBotData = new ArrayList<>(); @@ -98,13 +99,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { Integer lockTimeA = resultSet.getInt(14); if (lockTimeA == 0 && resultSet.wasNull()) lockTimeA = null; + byte[] receivingPublicKeyHash = resultSet.getBytes(15); TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState, atAddress, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secret, hashOfSecret, tradeForeignPublicKey, tradeForeignPublicKeyHash, - bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA); + bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash); allTradeBotData.add(tradeBotData); } while (resultSet.next()); @@ -130,7 +132,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { .bind("bitcoin_amount", tradeBotData.getBitcoinAmount()) .bind("xprv58", tradeBotData.getXprv58()) .bind("last_transaction_signature", tradeBotData.getLastTransactionSignature()) - .bind("locktime_a", tradeBotData.getLockTimeA()); + .bind("locktime_a", tradeBotData.getLockTimeA()) + .bind("receiving_public_key_hash", tradeBotData.getReceivingPublicKeyHash()); try { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 3ea10454..11e5a6a0 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -626,7 +626,7 @@ public class HSQLDBDatabaseUpdates { + "trade_native_address QortalAddress NOT NULL, secret VARBINARY(32) NOT NULL, hash_of_secret VARBINARY(32) NOT NULL, " + "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, " + "bitcoin_amount BIGINT NOT NULL, xprv58 VARCHAR(200), last_transaction_signature Signature, locktime_a BIGINT, " - + "PRIMARY KEY (trade_private_key))"); + + "receiving_public_key_hash VARBINARY(32) NOT NULL, PRIMARY KEY (trade_private_key))"); break; default: diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index c5150daa..79a67c9b 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -19,7 +19,6 @@ import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; import org.qortal.block.Block; -import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; @@ -65,7 +64,7 @@ public class AtTests extends Common { public void testCompile() { Account deployer = Common.getTestAccount(null, "chloe"); - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout, bitcoinReceivePublicKeyHash); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); } @@ -73,10 +72,10 @@ public class AtTests extends Common { public void testDeploy() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); @@ -90,10 +89,10 @@ public class AtTests extends Common { assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - expectedBalance = recipientsInitialBalance; - actualBalance = recipient.getConfirmedBalance(Asset.QORT); + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); - assertEquals("Recipient's post-deployment balance incorrect", expectedBalance, actualBalance); + assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); // Test orphaning BlockUtils.orphanLastBlock(repository); @@ -108,10 +107,10 @@ public class AtTests extends Common { assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - expectedBalance = recipientsInitialBalance; - actualBalance = recipient.getConfirmedBalance(Asset.QORT); + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); - assertEquals("Recipient's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); } } @@ -120,10 +119,10 @@ public class AtTests extends Common { public void testOfferCancel() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); @@ -132,12 +131,12 @@ public class AtTests extends Common { long deployAtFee = deployAtTransaction.getTransactionData().getFee(); long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - // Send creator's address to AT - byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0); - MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + // Send creator's address to AT, instead of typical partner's address + byte[] partnerAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, partnerAddressBytes, atAddress); long messageFee = messageTransaction.getTransactionData().getFee(); - // Refund should happen 1st block after receiving recipient address + // Refund should happen 1st block after receiving 'cancel' message BlockUtils.mintBlock(repository); long expectedMinimumBalance = deployersPostDeploymentBalance; @@ -154,6 +153,10 @@ public class AtTests extends Common { ATData atData = repository.getATRepository().fromATAddress(atAddress); assertTrue(atData.getIsFinished()); + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.CANCELLED, tradeData.mode); + // Test orphaning BlockUtils.orphanLastBlock(repository); @@ -169,21 +172,21 @@ public class AtTests extends Common { public void testTradingInfoProcessing() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); Block postDeploymentBlock = BlockUtils.mintBlock(repository); @@ -199,16 +202,16 @@ public class AtTests extends Common { CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); // AT should be in TRADE mode - assertEquals(CrossChainTradeData.Mode.TRADE, tradeData.mode); + assertEquals(BTCACCT.Mode.TRADING, tradeData.mode); // Check hashOfSecretA was extracted correctly assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - // Check trade partner/recipient Qortal address was extracted correctly - assertEquals(recipient.getAddress(), tradeData.qortalRecipient); + // Check trade partner Qortal address was extracted correctly + assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - // Check trade partner/recipient's Bitcoin PKH was extracted correctly - assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.recipientBitcoinPKH)); + // Check trade partner's Bitcoin PKH was extracted correctly + assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerBitcoinPKH)); // Test orphaning BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); @@ -226,30 +229,30 @@ public class AtTests extends Common { public void testIncorrectTradeSender() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); BlockUtils.mintBlock(repository); - long expectedBalance = recipientsInitialBalance; - long actualBalance = recipient.getConfirmedBalance(Asset.QORT); + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); - assertEquals("Recipient's post-initial-payout balance incorrect", expectedBalance, actualBalance); + assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); describeAt(repository, atAddress); @@ -257,7 +260,7 @@ public class AtTests extends Common { CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); // AT should still be in OFFER mode - assertEquals(CrossChainTradeData.Mode.OFFER, tradeData.mode); + assertEquals(BTCACCT.Mode.OFFERING, tradeData.mode); } } @@ -266,21 +269,21 @@ public class AtTests extends Common { public void testAutomaticTradeRefund() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); Block postDeploymentBlock = BlockUtils.mintBlock(repository); @@ -298,6 +301,10 @@ public class AtTests extends Common { ATData atData = repository.getATRepository().fromATAddress(atAddress); assertTrue(atData.getIsFinished()); + // AT should be in REFUNDED mode + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.REFUNDED, tradeData.mode); + // Test orphaning BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); @@ -313,29 +320,29 @@ public class AtTests extends Common { public void testCorrectSecretsCorrectSender() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); // Give AT time to process message BlockUtils.mintBlock(repository); // Send correct secrets to AT, from correct account - messageData = BTCACCT.buildRedeemMessage(secretA, secretB); - messageTransaction = sendMessage(repository, recipient, messageData, atAddress); + messageData = BTCACCT.buildRedeemMessage(secretA, secretB, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); // AT should send funds in the next block ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -347,18 +354,22 @@ public class AtTests extends Common { ATData atData = repository.getATRepository().fromATAddress(atAddress); assertTrue(atData.getIsFinished()); - long expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = recipient.getConfirmedBalance(Asset.QORT); + // AT should be in REDEEMED mode + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.REDEEMED, tradeData.mode); - assertEquals("Recipent's post-redeem balance incorrect", expectedBalance, actualBalance); + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); // Orphan redeem BlockUtils.orphanLastBlock(repository); - expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = recipient.getConfirmedBalance(Asset.QORT); + expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + actualBalance = partner.getConfirmedBalance(Asset.QORT); - assertEquals("Recipent's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); + assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); // Check AT state ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -372,11 +383,11 @@ public class AtTests extends Common { public void testCorrectSecretsIncorrectSender() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); long deployAtFee = deployAtTransaction.getTransactionData().getFee(); @@ -384,19 +395,19 @@ public class AtTests extends Common { Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); // Give AT time to process message BlockUtils.mintBlock(repository); // Send correct secrets to AT, but from wrong account - messageData = BTCACCT.buildRedeemMessage(secretA, secretB); + messageData = BTCACCT.buildRedeemMessage(secretA, secretB, partner.getAddress()); messageTransaction = sendMessage(repository, bystander, messageData, atAddress); // AT should NOT send funds in the next block @@ -409,10 +420,14 @@ public class AtTests extends Common { ATData atData = repository.getATRepository().fromATAddress(atAddress); assertFalse(atData.getIsFinished()); - long expectedBalance = recipientsInitialBalance; - long actualBalance = recipient.getConfirmedBalance(Asset.QORT); + // AT should still be in TRADE mode + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.TRADING, tradeData.mode); - assertEquals("Recipent's balance incorrect", expectedBalance, actualBalance); + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); } @@ -423,10 +438,10 @@ public class AtTests extends Common { public void testIncorrectSecretsCorrectSender() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); long deployAtFee = deployAtTransaction.getTransactionData().getFee(); @@ -434,12 +449,12 @@ public class AtTests extends Common { Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); - long recipientMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp); - int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA); + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); // Send trade info to AT - byte[] messageData = BTCACCT.buildTradeMessage(recipient.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); + byte[] messageData = BTCACCT.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); // Give AT time to process message @@ -449,8 +464,8 @@ public class AtTests extends Common { byte[] wrongSecret = new byte[32]; Random random = new Random(); random.nextBytes(wrongSecret); - messageData = BTCACCT.buildRedeemMessage(wrongSecret, secretB); - messageTransaction = sendMessage(repository, recipient, messageData, atAddress); + messageData = BTCACCT.buildRedeemMessage(wrongSecret, secretB, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); // AT should NOT send funds in the next block ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -462,14 +477,18 @@ public class AtTests extends Common { ATData atData = repository.getATRepository().fromATAddress(atAddress); assertFalse(atData.getIsFinished()); - long expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = recipient.getConfirmedBalance(Asset.QORT); + // AT should still be in TRADE mode + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.TRADING, tradeData.mode); - assertEquals("Recipent's balance incorrect", expectedBalance, actualBalance); + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); // Send incorrect secrets to AT, from correct account - messageData = BTCACCT.buildRedeemMessage(secretA, wrongSecret); - messageTransaction = sendMessage(repository, recipient, messageData, atAddress); + messageData = BTCACCT.buildRedeemMessage(secretA, wrongSecret, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); // AT should NOT send funds in the next block BlockUtils.mintBlock(repository); @@ -480,10 +499,14 @@ public class AtTests extends Common { atData = repository.getATRepository().fromATAddress(atAddress); assertFalse(atData.getIsFinished()); - expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee() * 2; - actualBalance = recipient.getConfirmedBalance(Asset.QORT); + // AT should still be in TRADE mode + tradeData = BTCACCT.populateTradeData(repository, atData); + assertEquals(BTCACCT.Mode.TRADING, tradeData.mode); - assertEquals("Recipent's balance incorrect", expectedBalance, actualBalance); + expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() * 2; + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); } @@ -494,10 +517,10 @@ public class AtTests extends Common { public void testDescribeDeployed() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); @@ -528,7 +551,7 @@ public class AtTests extends Common { } private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException { - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout, bitcoinReceivePublicKeyHash); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); long txTimestamp = System.currentTimeMillis(); byte[] lastReference = deployer.getLastReference(); @@ -611,6 +634,7 @@ public class AtTests extends Common { int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); System.out.print(String.format("%s:\n" + + "\tmode: %s\n" + "\tcreator: %s,\n" + "\tcreation timestamp: %s,\n" + "\tcurrent balance: %s QORT,\n" @@ -618,9 +642,9 @@ public class AtTests extends Common { + "\tHASH160 of secret-B: %s,\n" + "\tredeem payout: %s QORT,\n" + "\texpected bitcoin: %s BTC,\n" - + "\treceiving bitcoin address: %s,\n" + "\tcurrent block height: %d,\n", tradeData.qortalAtAddress, + tradeData.mode.name(), tradeData.qortalCreator, epochMilliFormatter.apply(tradeData.creationTimestamp), Amounts.prettyAmount(tradeData.qortBalance), @@ -628,26 +652,19 @@ public class AtTests extends Common { HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40), Amounts.prettyAmount(tradeData.qortAmount), Amounts.prettyAmount(tradeData.expectedBitcoin), - BTC.getInstance().pkhToAddress(tradeData.creatorReceiveBitcoinPKH), currentBlockHeight)); - // Are we in 'offer' or 'trade' stage? - if (tradeData.tradeRefundHeight == null) { - // Offer - System.out.println(String.format("\tstatus: 'offer mode'")); - } else { - // Trade - System.out.println(String.format("\tstatus: 'trade mode',\n" - + "\trefund height: block %d,\n" + if (tradeData.mode != BTCACCT.Mode.OFFERING && tradeData.mode != BTCACCT.Mode.CANCELLED) { + System.out.println(String.format("\trefund height: block %d,\n" + "\tHASH160 of secret-A: %s,\n" + "\tBitcoin P2SH-A nLockTime: %d (%s),\n" + "\tBitcoin P2SH-B nLockTime: %d (%s),\n" - + "\ttrade recipient: %s", + + "\ttrade partner: %s", tradeData.tradeRefundHeight, HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), tradeData.lockTimeB, epochMilliFormatter.apply(tradeData.lockTimeB * 1000L), - tradeData.qortalRecipient)); + tradeData.qortalPartnerAddress)); } } diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 4f33353d..ef5a0295 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -2,13 +2,10 @@ package org.qortal.test.btcacct; import java.security.Security; -import org.bitcoinj.core.Address; -import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; import org.qortal.controller.Controller; -import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; @@ -37,7 +34,7 @@ public class DeployAT { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT ")); System.err.println(String.format("example: DeployAT " + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" + "\t80.4020 \\\n" @@ -45,13 +42,12 @@ public class DeployAT { + "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb \\\n" + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t123.456 \\\n" - + "\t10080 \\\n" - + "\tn2iQZCtKZ5SrFDJENGJkd4RpAuQp3SEoix")); + + "\t10080")); System.exit(1); } public static void main(String[] args) { - if (args.length != 8) + if (args.length != 7) usage(null); Security.insertProviderAt(new BouncyCastleProvider(), 0); @@ -64,7 +60,6 @@ public class DeployAT { byte[] secretHash = null; long fundingAmount = 0; int tradeTimeout = 0; - byte[] bitcoinReceivePublicKeyHash = null; int argIndex = 0; try { @@ -95,12 +90,6 @@ public class DeployAT { tradeTimeout = Integer.parseInt(args[argIndex++]); if (tradeTimeout < 60 || tradeTimeout > 50000) usage("Trade timeout (minutes) must be between 60 and 50000"); - - Address receiveAddress = Address.fromString(BTC.getInstance().getNetworkParameters(), args[argIndex++]); - if (receiveAddress.getOutputScriptType() != ScriptType.P2PKH) - usage("Bitcoin receive address must be P2PKH form"); - - bitcoinReceivePublicKeyHash = receiveAddress.getHash(); } catch (IllegalArgumentException e) { usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } @@ -125,7 +114,7 @@ public class DeployAT { System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); // Deploy AT - byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, secretHash, redeemAmount, expectedBitcoin, tradeTimeout, bitcoinReceivePublicKeyHash); + byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), bitcoinPublicKeyHash, secretHash, redeemAmount, expectedBitcoin, tradeTimeout); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis();