forked from Qortal/qortal
WIP: trade-bot: more receive address support, some terminology clarification
Bitcoin receive address no longer stored in AT but dealt with by trade-bot. This allows 'Bob' to have his BTC sent anywhere he likes when redeeming P2SH-A thus saving a step, typically incurred by UI. DB shape change due to this. Similarly, AT code has been updated to expect a Qortal receiving address when Alice sends MESSAGE to redeem AT. This means both trade-bot entries (Alice/Bob) can be safely wiped once trade completes. Some terms were confusing like "trade recipient" which actually referred to Alice and so have been unified as "trade partner" as to not be confused with (say) "recipient address" The MESSAGEs sent from Alice to Bob, from Bob to AT and from Alice to AT have been given more useful names: 'offer', 'trade' and 'redeem'. There is also a cancel MESSAGE sent from Bob to AT to cancel AT before trading occurs. Some API calls have been renamed in light of above. AT's 'mode' has been expanded from simply OFFER/TRADE to: OFFERING, TRADING, REFUNDED, REDEEMED, CANCELLED Tests updated, but MORE TESTING REQUIRED BEFORE RELEASE
This commit is contained in:
parent
6be67d0d92
commit
876bfb525b
@ -33,9 +33,6 @@ public class CrossChainBuildRequest {
|
|||||||
@Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080")
|
@Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080")
|
||||||
public Integer tradeTimeout;
|
public Integer tradeTimeout;
|
||||||
|
|
||||||
@Schema(description = "Bitcoin address for receiving", example = "1NCTG9oLk41bU6pcehLNo9DVJup77EHAVx")
|
|
||||||
public String receiveAddress;
|
|
||||||
|
|
||||||
public CrossChainBuildRequest() {
|
public CrossChainBuildRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class CrossChainCancelRequest {
|
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;
|
public byte[] tradePublicKey;
|
||||||
|
|
||||||
@Schema(description = "Qortal trade AT address")
|
@Schema(description = "Qortal trade AT address")
|
||||||
|
@ -8,8 +8,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class CrossChainSecretRequest {
|
public class CrossChainSecretRequest {
|
||||||
|
|
||||||
@Schema(description = "Public key to match AT's 'recipient'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
|
@Schema(description = "Public key to match AT's trade 'partner'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry")
|
||||||
public byte[] recipientPublicKey;
|
public byte[] partnerPublicKey;
|
||||||
|
|
||||||
@Schema(description = "Qortal AT address")
|
@Schema(description = "Qortal AT address")
|
||||||
public String atAddress;
|
public String atAddress;
|
||||||
@ -20,6 +20,9 @@ public class CrossChainSecretRequest {
|
|||||||
@Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx")
|
@Schema(description = "secret-B (32 bytes)", example = "EN2Bgx3BcEMtxFCewmCVSMkfZjVKYhx3KEXC5A21KBGx")
|
||||||
public byte[] secretB;
|
public byte[] secretB;
|
||||||
|
|
||||||
|
@Schema(description = "Qortal address for receiving QORT from AT")
|
||||||
|
public String receivingAddress;
|
||||||
|
|
||||||
public CrossChainSecretRequest() {
|
public CrossChainSecretRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,13 +8,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class CrossChainTradeRequest {
|
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;
|
public byte[] tradePublicKey;
|
||||||
|
|
||||||
@Schema(description = "Qortal AT address")
|
@Schema(description = "Qortal AT address")
|
||||||
public String atAddress;
|
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 byte[] messageTransactionSignature;
|
||||||
|
|
||||||
public CrossChainTradeRequest() {
|
public CrossChainTradeRequest() {
|
||||||
|
@ -14,6 +14,9 @@ public class TradeBotRespondRequest {
|
|||||||
@Schema(description = "Bitcoin BIP32 extended private key", example = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TbTVGajEB55L1HYLg2aQMecZLXLre5YJcawpdFG66STVAWPJ")
|
@Schema(description = "Bitcoin BIP32 extended private key", example = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TbTVGajEB55L1HYLg2aQMecZLXLre5YJcawpdFG66STVAWPJ")
|
||||||
public String xprv58;
|
public String xprv58;
|
||||||
|
|
||||||
|
@Schema(description = "Qortal address for receiving QORT from AT")
|
||||||
|
public String receivingAddress;
|
||||||
|
|
||||||
public TradeBotRespondRequest() {
|
public TradeBotRespondRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,6 @@ import org.qortal.crypto.Crypto;
|
|||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
import org.qortal.data.crosschain.TradeBotData;
|
import org.qortal.data.crosschain.TradeBotData;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData.Mode;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
import org.qortal.data.transaction.MessageTransactionData;
|
||||||
@ -182,23 +181,11 @@ public class CrossChainResource {
|
|||||||
if (tradeRequest.bitcoinAmount <= 0)
|
if (tradeRequest.bitcoinAmount <= 0)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
|
PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
|
||||||
|
|
||||||
byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.hashOfSecretB,
|
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();
|
long txTimestamp = NTP.getTime();
|
||||||
byte[] lastReference = creatorAccount.getLastReference();
|
byte[] lastReference = creatorAccount.getLastReference();
|
||||||
@ -233,11 +220,11 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/tradeoffer/recipient")
|
@Path("/tradeoffer/trademessage")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Builds raw, unsigned MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode",
|
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 address of Qortal recipient.<br>"
|
description = "Specify address of cross-chain AT that needs to be messaged, and signature of 'offer' MESSAGE from trade partner.<br>"
|
||||||
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
+ "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!<br>"
|
||||||
+ "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.",
|
+ "You need to sign output with trade private key otherwise the MESSAGE transaction will be invalid.",
|
||||||
requestBody = @RequestBody(
|
requestBody = @RequestBody(
|
||||||
required = true,
|
required = true,
|
||||||
@ -259,7 +246,7 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
@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);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
byte[] tradePublicKey = tradeRequest.tradePublicKey;
|
byte[] tradePublicKey = tradeRequest.tradePublicKey;
|
||||||
@ -277,11 +264,11 @@ public class CrossChainResource {
|
|||||||
ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, tradeRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
// Does supplied public key match trade public key?
|
// 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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||||
|
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature);
|
TransactionData transactionData = repository.getTransactionRepository().fromSignature(tradeRequest.messageTransactionSignature);
|
||||||
@ -299,7 +286,7 @@ public class CrossChainResource {
|
|||||||
|
|
||||||
// Good to make MESSAGE
|
// Good to make MESSAGE
|
||||||
|
|
||||||
byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
|
byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH;
|
||||||
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
||||||
int lockTimeA = (int) offerMessageData.lockTimeA;
|
int lockTimeA = (int) offerMessageData.lockTimeA;
|
||||||
|
|
||||||
@ -316,12 +303,12 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/tradeoffer/secret")
|
@Path("/tradeoffer/redeemmessage")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Builds raw, unsigned MESSAGE transaction that sends secrets to AT, releasing funds to recipient",
|
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, and both 32-byte secrets.<br>"
|
description = "Specify address of cross-chain AT that needs to be messaged, both 32-byte secrets and an address for receiving QORT from AT.<br>"
|
||||||
+ "AT needs to be in 'trade' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
+ "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!<br>"
|
||||||
+ "You need to sign output with account the AT considers the 'recipient' otherwise the MESSAGE transaction will be invalid.",
|
+ "You need to sign output with account the AT considers the trade 'partner' otherwise the MESSAGE transaction will be invalid.",
|
||||||
requestBody = @RequestBody(
|
requestBody = @RequestBody(
|
||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
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})
|
@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);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||||
|
|
||||||
if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress))
|
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)
|
if (secretRequest.secretB == null || secretRequest.secretB.length != BTCACCT.SECRET_LENGTH)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, secretRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
PublicKeyAccount recipientAccount = new PublicKeyAccount(repository, recipientPublicKey);
|
String partnerAddress = Crypto.toAddress(partnerPublicKey);
|
||||||
String recipientAddress = recipientAccount.getAddress();
|
|
||||||
|
|
||||||
// MESSAGE must come from address that AT considers trade partner / 'recipient'
|
// MESSAGE must come from address that AT considers trade partner
|
||||||
if (!crossChainTradeData.qortalRecipient.equals(recipientAddress))
|
if (!crossChainTradeData.qortalPartnerAddress.equals(partnerAddress))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
// Good to make MESSAGE
|
// Good to make MESSAGE
|
||||||
|
|
||||||
byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB);
|
byte[] messageData = BTCACCT.buildRedeemMessage(secretRequest.secretA, secretRequest.secretB, secretRequest.receivingAddress);
|
||||||
byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, messageData);
|
byte[] messageTransactionBytes = buildAtMessage(repository, partnerPublicKey, secretRequest.atAddress, messageData);
|
||||||
|
|
||||||
return Base58.encode(messageTransactionBytes);
|
return Base58.encode(messageTransactionBytes);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -387,7 +376,7 @@ public class CrossChainResource {
|
|||||||
@DELETE
|
@DELETE
|
||||||
@Path("/tradeoffer")
|
@Path("/tradeoffer")
|
||||||
@Operation(
|
@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.<br>"
|
description = "Specify address of cross-chain AT that needs to be cancelled.<br>"
|
||||||
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
+ "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!<br>"
|
||||||
+ "You need to sign output with trade's private key otherwise the MESSAGE transaction will be invalid.",
|
+ "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})
|
@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);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
byte[] tradePublicKey = cancelRequest.tradePublicKey;
|
byte[] tradePublicKey = cancelRequest.tradePublicKey;
|
||||||
@ -426,17 +415,17 @@ public class CrossChainResource {
|
|||||||
ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, cancelRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
// Does supplied public key match trade public key?
|
// 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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||||
|
|
||||||
// Good to make MESSAGE
|
// Good to make MESSAGE
|
||||||
|
|
||||||
String atCreatorAddress = crossChainTradeData.qortalCreator;
|
String atCreatorAddress = crossChainTradeData.qortalCreator;
|
||||||
byte[] messageData = BTCACCT.buildRefundMessage(atCreatorAddress);
|
byte[] messageData = BTCACCT.buildCancelMessage(atCreatorAddress);
|
||||||
|
|
||||||
byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData);
|
byte[] messageTransactionBytes = buildAtMessage(repository, tradePublicKey, cancelRequest.atAddress, messageData);
|
||||||
|
|
||||||
@ -516,7 +505,7 @@ public class CrossChainResource {
|
|||||||
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
byte[] redeemScriptBytes = BTCP2SH.buildScript(templateRequest.refundPublicKeyHash, lockTimeFn.applyAsInt(crossChainTradeData), templateRequest.redeemPublicKeyHash, hashOfSecretFn.apply(crossChainTradeData));
|
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);
|
ATData atData = fetchAtDataWithChecking(repository, templateRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
||||||
@ -732,7 +721,7 @@ public class CrossChainResource {
|
|||||||
ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, refundRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
||||||
@ -874,7 +863,7 @@ public class CrossChainResource {
|
|||||||
ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, redeemRequest.atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
int lockTime = lockTimeFn.applyAsInt(crossChainTradeData);
|
||||||
@ -1031,15 +1020,18 @@ public class CrossChainResource {
|
|||||||
if (!BTC.getInstance().isValidXprv(tradeBotRespondRequest.xprv58))
|
if (!BTC.getInstance().isValidXprv(tradeBotRespondRequest.xprv58))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
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
|
// Extract data from cross-chain trading AT
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
ATData atData = fetchAtDataWithChecking(repository, atAddress);
|
ATData atData = fetchAtDataWithChecking(repository, atAddress);
|
||||||
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
|
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);
|
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";
|
return result ? "true" : "false";
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
|
@ -40,6 +40,7 @@ import org.qortal.transaction.Transaction.ValidationResult;
|
|||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
public class TradeBot {
|
public class TradeBot {
|
||||||
@ -133,7 +134,7 @@ public class TradeBot {
|
|||||||
String aTType = "ACCT";
|
String aTType = "ACCT";
|
||||||
String tags = "ACCT QORT BTC";
|
String tags = "ACCT QORT BTC";
|
||||||
byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount,
|
byte[] creationBytes = BTCACCT.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount,
|
||||||
tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout, bitcoinReceivePublicKeyHash);
|
tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout);
|
||||||
long amount = tradeBotCreateRequest.fundingQortAmount;
|
long amount = tradeBotCreateRequest.fundingQortAmount;
|
||||||
|
|
||||||
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
||||||
@ -150,7 +151,7 @@ public class TradeBot {
|
|||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secretB, hashOfSecretB,
|
secretB, hashOfSecretB,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
tradeBotCreateRequest.bitcoinAmount, null, null, null);
|
tradeBotCreateRequest.bitcoinAmount, null, null, null, bitcoinReceivePublicKeyHash);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
@ -188,7 +189,7 @@ public class TradeBot {
|
|||||||
* <p>
|
* <p>
|
||||||
* It is envisaged that the value in <tt>xprv58</tt> will actually come from a Qortal-UI-managed wallet.
|
* It is envisaged that the value in <tt>xprv58</tt> will actually come from a Qortal-UI-managed wallet.
|
||||||
* <p>
|
* <p>
|
||||||
* If sufficient funds are available, <b>this method will actually fund the P2SH-A<b>
|
* If sufficient funds are available, <b>this method will actually fund the P2SH-A</b>
|
||||||
* with the Bitcoin amount expected by 'Bob'.
|
* with the Bitcoin amount expected by 'Bob'.
|
||||||
* <p>
|
* <p>
|
||||||
* If the Bitcoin transaction is successfully broadcast to the network then the trade-bot entry
|
* 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
|
* @return true if P2SH-A funding transaction successfully broadcast to Bitcoin network, false otherwise
|
||||||
* @throws DataException
|
* @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[] tradePrivateKey = generateTradePrivateKey();
|
||||||
byte[] secretA = generateSecret();
|
byte[] secretA = generateSecret();
|
||||||
byte[] hashOfSecretA = Crypto.hash160(secretA);
|
byte[] hashOfSecretA = Crypto.hash160(secretA);
|
||||||
@ -213,6 +214,7 @@ public class TradeBot {
|
|||||||
|
|
||||||
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
|
||||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
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
|
// We need to generate lockTime-A: halfway of refundTimeout from now
|
||||||
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
|
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
|
||||||
@ -222,7 +224,7 @@ public class TradeBot {
|
|||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secretA, hashOfSecretA,
|
secretA, hashOfSecretA,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
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
|
// Check we have enough funds via xprv58 to fund both P2SHs to cover expectedBitcoin
|
||||||
String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash);
|
String tradeForeignAddress = BTC.getInstance().pkhToAddress(tradeForeignPublicKeyHash);
|
||||||
@ -499,7 +501,7 @@ public class TradeBot {
|
|||||||
if (offerMessageData == null)
|
if (offerMessageData == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
byte[] aliceForeignPublicKeyHash = offerMessageData.recipientBitcoinPKH;
|
byte[] aliceForeignPublicKeyHash = offerMessageData.partnerBitcoinPKH;
|
||||||
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
||||||
int lockTimeA = (int) offerMessageData.lockTimeA;
|
int lockTimeA = (int) offerMessageData.lockTimeA;
|
||||||
// Determine P2SH-A address and confirm funded
|
// Determine P2SH-A address and confirm funded
|
||||||
@ -592,11 +594,11 @@ public class TradeBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We're waiting for AT to be in TRADE mode
|
// We're waiting for AT to be in TRADE mode
|
||||||
if (crossChainTradeData.mode != CrossChainTradeData.Mode.TRADE)
|
if (crossChainTradeData.mode != BTCACCT.Mode.TRADING)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// We're expecting AT to be locked to our native trade address
|
// 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.
|
// 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());
|
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",
|
LOGGER.warn(() -> String.format("AT %s locked to %s, not us (%s). Refunding %s - aborting trade",
|
||||||
tradeBotData.getAtAddress(),
|
tradeBotData.getAtAddress(),
|
||||||
crossChainTradeData.qortalRecipient,
|
crossChainTradeData.qortalPartnerAddress,
|
||||||
tradeBotData.getTradeNativeAddress(),
|
tradeBotData.getTradeNativeAddress(),
|
||||||
p2shAddress));
|
p2shAddress));
|
||||||
|
|
||||||
@ -701,7 +703,7 @@ public class TradeBot {
|
|||||||
// AT yet to process MESSAGE
|
// AT yet to process MESSAGE
|
||||||
return;
|
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);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
Long balance = BTC.getInstance().getBalance(p2shAddress);
|
||||||
@ -716,7 +718,7 @@ public class TradeBot {
|
|||||||
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A
|
Coin redeemAmount = Coin.ZERO; // The real funds are in P2SH-A
|
||||||
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||||
byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH;
|
byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash();
|
||||||
|
|
||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash);
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, tradeBotData.getSecret(), receivePublicKeyHash);
|
||||||
|
|
||||||
@ -783,9 +785,10 @@ public class TradeBot {
|
|||||||
// Secret not revealed at this time
|
// Secret not revealed at this time
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Send MESSAGE to AT using both secrets
|
// Send 'redeem' MESSAGE to AT using both secrets
|
||||||
byte[] secretA = tradeBotData.getSecret();
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, tradeBotData.getAtAddress(), messageData, false, false);
|
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
|
// 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);
|
tradeBotData.setState(TradeBotData.State.BOB_REFUNDED);
|
||||||
repository.getCrossChainRepository().save(tradeBotData);
|
repository.getCrossChainRepository().save(tradeBotData);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
@ -864,13 +867,13 @@ public class TradeBot {
|
|||||||
|
|
||||||
// Use secret-A to redeem P2SH-A
|
// 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);
|
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||||
|
|
||||||
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedBitcoin);
|
||||||
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||||
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
List<TransactionOutput> fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress);
|
||||||
byte[] receivePublicKeyHash = crossChainTradeData.creatorReceiveBitcoinPKH;
|
byte[] receivePublicKeyHash = tradeBotData.getReceivingPublicKeyHash();
|
||||||
|
|
||||||
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash);
|
Transaction p2shRedeemTransaction = BTCP2SH.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutputs, redeemScriptBytes, secretA, receivePublicKeyHash);
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package org.qortal.crosschain;
|
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 static org.ciyam.at.OpCode.calcOffset;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.ciyam.at.API;
|
import org.ciyam.at.API;
|
||||||
import org.ciyam.at.CompilationException;
|
import org.ciyam.at.CompilationException;
|
||||||
@ -37,7 +40,7 @@ import com.google.common.primitives.Bytes;
|
|||||||
* <li>Bob generates Bitcoin & Qortal 'trade' keys, and secret-b
|
* <li>Bob generates Bitcoin & Qortal 'trade' keys, and secret-b
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>private key required to sign P2SH redeem tx</li>
|
* <li>private key required to sign P2SH redeem tx</li>
|
||||||
* <li>private key can be used to create 'secret' (e.g. double-SHA256)</li>
|
* <li>private key could be used to create 'secret' (e.g. double-SHA256)</li>
|
||||||
* <li>encrypted private key could be stored in Qortal AT for access by Bob from any node</li>
|
* <li>encrypted private key could be stored in Qortal AT for access by Bob from any node</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
@ -48,42 +51,53 @@ import com.google.common.primitives.Bytes;
|
|||||||
* <li>Alice finds Qortal AT and wants to trade
|
* <li>Alice finds Qortal AT and wants to trade
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Alice generates Bitcoin & Qortal 'trade' keys</li>
|
* <li>Alice generates Bitcoin & Qortal 'trade' keys</li>
|
||||||
* <li>Alice funds Bitcoin P2SH-a</li>
|
* <li>Alice funds Bitcoin P2SH-A</li>
|
||||||
* <li>Alice MESSAGEs Bob from her Qortal trade address, sending secret-hash-a and Bitcoin PKH</li>
|
* <li>Alice sends 'offer' MESSAGE to Bob from her Qortal trade address, containing:
|
||||||
|
* <ul>
|
||||||
|
* <li>hash-of-secret-A</li>
|
||||||
|
* <li>her 'trade' Bitcoin PKH</li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li>Bob receives MESSAGE
|
* <li>Bob receives "offer" MESSAGE
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Checks Alice's P2SH-a</li>
|
* <li>Checks Alice's P2SH-A</li>
|
||||||
* <li>Sends MESSAGE to Qortal AT from his trade address, containing:
|
* <li>Sends 'trade' MESSAGE to Qortal AT from his trade address, containing:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Alice's trade Qortal address</li>
|
* <li>Alice's trade Qortal address</li>
|
||||||
* <li>Alice's trade Bitcoin PKH</li>
|
* <li>Alice's trade Bitcoin PKH</li>
|
||||||
* <li>secret-hash-a</li>
|
* <li>hash-of-secret-A</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li>Alice checks Qortal AT to confirm it's locked to her
|
* <li>Alice checks Qortal AT to confirm it's locked to her
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Alice creates/funds Bitcoin P2SH-b</li>
|
* <li>Alice creates/funds Bitcoin P2SH-B</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li>Bob checks P2SH-b is funded
|
* <li>Bob checks P2SH-B is funded
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Bob redeems P2SH-b using his Bitcoin trade key and secret-b</li>
|
* <li>Bob redeems P2SH-B using his Bitcoin trade key and secret-B</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li>Alice scans P2SH-b redeem tx to extract secret-b
|
* <li>Alice scans P2SH-B redeem transaction to extract secret-B
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Alice MESSAGEs Qortal AT from her trade address, sending secret-a & secret-b</li>
|
* <li>Alice sends 'redeem' MESSAGE to Qortal AT from her trade address, containing:
|
||||||
* <li>AT's QORT funds end up at Qortal address derived from Alice's trade private key</li>
|
* <ul>
|
||||||
|
* <li>secret-A</li>
|
||||||
|
* <li>secret-B</li>
|
||||||
|
* <li>Qortal receive address of her chosing</li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* <li>AT's QORT funds are sent to Qortal receive address</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li>Bob checks AT, extracts secret-a
|
* <li>Bob checks AT, extracts secret-A
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Bob redeems P2SH-a using his Bitcoin trade key and secret-a</li>
|
* <li>Bob redeems P2SH-A using his Bitcoin trade key and secret-A</li>
|
||||||
* <li>P2SH-a funds end up in at Bitcoin address derived from Bob's trade private key</li>
|
* <li>P2SH-A BTC funds end up at Bitcoin address determined by redeem transaction output(s)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@ -92,13 +106,36 @@ public class BTCACCT {
|
|||||||
|
|
||||||
public static final int SECRET_LENGTH = 32;
|
public static final int SECRET_LENGTH = 32;
|
||||||
public static final int MIN_LOCKTIME = 1500000000;
|
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 static class OfferMessageData {
|
||||||
public byte[] recipientBitcoinPKH;
|
public byte[] partnerBitcoinPKH;
|
||||||
public byte[] hashOfSecretA;
|
public byte[] hashOfSecretA;
|
||||||
public long lockTimeA;
|
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<Integer, Mode> 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() {
|
private BTCACCT() {
|
||||||
}
|
}
|
||||||
@ -106,7 +143,7 @@ public class BTCACCT {
|
|||||||
/**
|
/**
|
||||||
* Returns Qortal AT creation bytes for cross-chain trading AT.
|
* Returns Qortal AT creation bytes for cross-chain trading AT.
|
||||||
* <p>
|
* <p>
|
||||||
* <tt>tradeTimeout</tt> (minutes) is the time window for the recipient to send the
|
* <tt>tradeTimeout</tt> (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.
|
* 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
|
* @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
|
* @param tradeTimeout suggested timeout for entire trade
|
||||||
* @return
|
* @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
|
// Labels for data segment addresses
|
||||||
int addrCounter = 0;
|
int addrCounter = 0;
|
||||||
|
|
||||||
@ -138,28 +175,26 @@ public class BTCACCT {
|
|||||||
final int addrBitcoinAmount = addrCounter++;
|
final int addrBitcoinAmount = addrCounter++;
|
||||||
final int addrTradeTimeout = addrCounter++;
|
final int addrTradeTimeout = addrCounter++;
|
||||||
|
|
||||||
final int addrMessageTxType = addrCounter++;
|
final int addrMessageTxnType = addrCounter++;
|
||||||
final int addrExpectedOfferMessageLength = addrCounter++;
|
|
||||||
final int addrExpectedTradeMessageLength = addrCounter++;
|
final int addrExpectedTradeMessageLength = addrCounter++;
|
||||||
|
final int addrExpectedRedeemMessageLength = addrCounter++;
|
||||||
|
|
||||||
final int addrCreatorAddressPointer = addrCounter++;
|
final int addrCreatorAddressPointer = addrCounter++;
|
||||||
final int addrHashOfSecretBPointer = addrCounter++;
|
final int addrHashOfSecretBPointer = addrCounter++;
|
||||||
final int addrQortalRecipientPointer = addrCounter++;
|
final int addrQortalPartnerAddressPointer = addrCounter++;
|
||||||
final int addrMessageSenderPointer = addrCounter++;
|
final int addrMessageSenderPointer = addrCounter++;
|
||||||
|
|
||||||
final int addrOfferMessageRecipientBitcoinPKHOffset = addrCounter++;
|
final int addrTradeMessagePartnerBitcoinPKHOffset = addrCounter++;
|
||||||
final int addrRecipientBitcoinPKHPointer = addrCounter++;
|
final int addrPartnerBitcoinPKHPointer = addrCounter++;
|
||||||
final int addrOfferMessageHashOfSecretAOffset = addrCounter++;
|
final int addrTradeMessageHashOfSecretAOffset = addrCounter++;
|
||||||
final int addrHashOfSecretAPointer = addrCounter++;
|
final int addrHashOfSecretAPointer = addrCounter++;
|
||||||
|
|
||||||
final int addrTradeMessageSecretBOffset = addrCounter++;
|
final int addrRedeemMessageSecretBOffset = addrCounter++;
|
||||||
|
final int addrRedeemMessageReceiveAddressOffset = addrCounter++;
|
||||||
|
|
||||||
final int addrMessageDataPointer = addrCounter++;
|
final int addrMessageDataPointer = addrCounter++;
|
||||||
final int addrMessageDataLength = addrCounter++;
|
final int addrMessageDataLength = addrCounter++;
|
||||||
|
|
||||||
final int addrBitcoinReceivePublicKeyHash = addrCounter;
|
|
||||||
addrCounter += 4;
|
|
||||||
|
|
||||||
final int addrEndOfConstants = addrCounter;
|
final int addrEndOfConstants = addrCounter;
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
@ -169,18 +204,18 @@ public class BTCACCT {
|
|||||||
final int addrCreatorAddress3 = addrCounter++;
|
final int addrCreatorAddress3 = addrCounter++;
|
||||||
final int addrCreatorAddress4 = addrCounter++;
|
final int addrCreatorAddress4 = addrCounter++;
|
||||||
|
|
||||||
final int addrQortalRecipient1 = addrCounter++;
|
final int addrQortalPartnerAddress1 = addrCounter++;
|
||||||
final int addrQortalRecipient2 = addrCounter++;
|
final int addrQortalPartnerAddress2 = addrCounter++;
|
||||||
final int addrQortalRecipient3 = addrCounter++;
|
final int addrQortalPartnerAddress3 = addrCounter++;
|
||||||
final int addrQortalRecipient4 = addrCounter++;
|
final int addrQortalPartnerAddress4 = addrCounter++;
|
||||||
|
|
||||||
final int addrLockTimeA = addrCounter++;
|
final int addrLockTimeA = addrCounter++;
|
||||||
final int addrLockTimeB = addrCounter++;
|
final int addrLockTimeB = addrCounter++;
|
||||||
final int addrRefundTimeout = addrCounter++;
|
final int addrRefundTimeout = addrCounter++;
|
||||||
final int addrRefundTimestamp = addrCounter++;
|
final int addrRefundTimestamp = addrCounter++;
|
||||||
final int addrLastTxTimestamp = addrCounter++;
|
final int addrLastTxnTimestamp = addrCounter++;
|
||||||
final int addrBlockTimestamp = addrCounter++;
|
final int addrBlockTimestamp = addrCounter++;
|
||||||
final int addrTxType = addrCounter++;
|
final int addrTxnType = addrCounter++;
|
||||||
final int addrResult = addrCounter++;
|
final int addrResult = addrCounter++;
|
||||||
|
|
||||||
final int addrMessageSender1 = addrCounter++;
|
final int addrMessageSender1 = addrCounter++;
|
||||||
@ -196,13 +231,11 @@ public class BTCACCT {
|
|||||||
final int addrHashOfSecretA = addrCounter;
|
final int addrHashOfSecretA = addrCounter;
|
||||||
addrCounter += 4;
|
addrCounter += 4;
|
||||||
|
|
||||||
final int addrRecipientBitcoinPKH = addrCounter;
|
final int addrPartnerBitcoinPKH = addrCounter;
|
||||||
addrCounter += 4;
|
addrCounter += 4;
|
||||||
|
|
||||||
final int addrMode = addrCounter++;
|
final int addrMode = addrCounter++;
|
||||||
|
|
||||||
final int addrRedeemFlag = addrCounter++;
|
|
||||||
|
|
||||||
// Data segment
|
// Data segment
|
||||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||||
|
|
||||||
@ -232,16 +265,16 @@ public class BTCACCT {
|
|||||||
dataByteBuffer.putLong(tradeTimeout);
|
dataByteBuffer.putLong(tradeTimeout);
|
||||||
|
|
||||||
// We're only interested in MESSAGE transactions
|
// 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);
|
dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value);
|
||||||
|
|
||||||
// Expected length of OFFER MESSAGE data from AT creator
|
// Expected length of 'trade' 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"
|
|
||||||
assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect";
|
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
|
// Index into data segment of AT creator's address, used by GET_B_IND
|
||||||
assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect";
|
assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect";
|
||||||
@ -252,56 +285,56 @@ public class BTCACCT {
|
|||||||
dataByteBuffer.putLong(addrHashOfSecretB);
|
dataByteBuffer.putLong(addrHashOfSecretB);
|
||||||
|
|
||||||
// Index into data segment of recipient address, used by SET_B_IND
|
// Index into data segment of recipient address, used by SET_B_IND
|
||||||
assert dataByteBuffer.position() == addrQortalRecipientPointer * MachineState.VALUE_SIZE : "addrQortalRecipientPointer incorrect";
|
assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect";
|
||||||
dataByteBuffer.putLong(addrQortalRecipient1);
|
dataByteBuffer.putLong(addrQortalPartnerAddress1);
|
||||||
|
|
||||||
// Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND
|
// 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";
|
assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect";
|
||||||
dataByteBuffer.putLong(addrMessageSender1);
|
dataByteBuffer.putLong(addrMessageSender1);
|
||||||
|
|
||||||
// Offset into OFFER MESSAGE data payload for extracting recipient's Bitcoin PKH
|
// Offset into 'trade' MESSAGE data payload for extracting partner's Bitcoin PKH
|
||||||
assert dataByteBuffer.position() == addrOfferMessageRecipientBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrOfferMessageRecipientBitcoinPKHOffset incorrect";
|
assert dataByteBuffer.position() == addrTradeMessagePartnerBitcoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerBitcoinPKHOffset incorrect";
|
||||||
dataByteBuffer.putLong(32L);
|
dataByteBuffer.putLong(32L);
|
||||||
|
|
||||||
// Index into data segment of recipient's Bitcoin PKH, used by GET_B_IND
|
// Index into data segment of partner's Bitcoin PKH, used by GET_B_IND
|
||||||
assert dataByteBuffer.position() == addrRecipientBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrRecipientBitcoinPKHPointer incorrect";
|
assert dataByteBuffer.position() == addrPartnerBitcoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerBitcoinPKHPointer incorrect";
|
||||||
dataByteBuffer.putLong(addrRecipientBitcoinPKH);
|
dataByteBuffer.putLong(addrPartnerBitcoinPKH);
|
||||||
|
|
||||||
// Offset into OFFER MESSAGE data payload for extracting hash-of-secret-A
|
// Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A
|
||||||
assert dataByteBuffer.position() == addrOfferMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrOfferMessageHashOfSecretAOffset incorrect";
|
assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect";
|
||||||
dataByteBuffer.putLong(64L);
|
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";
|
assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect";
|
||||||
dataByteBuffer.putLong(addrHashOfSecretA);
|
dataByteBuffer.putLong(addrHashOfSecretA);
|
||||||
|
|
||||||
// Offset into TRADE MESSAGE data payload for extracting secret-B
|
// Offset into 'redeem' MESSAGE data payload for extracting secret-B
|
||||||
assert dataByteBuffer.position() == addrTradeMessageSecretBOffset * MachineState.VALUE_SIZE : "addrTradeMessageSecretBOffset incorrect";
|
assert dataByteBuffer.position() == addrRedeemMessageSecretBOffset * MachineState.VALUE_SIZE : "addrRedeemMessageSecretBOffset incorrect";
|
||||||
dataByteBuffer.putLong(32L);
|
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
|
// Source location and length for hashing any passed secret
|
||||||
assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
|
assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
|
||||||
dataByteBuffer.putLong(addrMessageData);
|
dataByteBuffer.putLong(addrMessageData);
|
||||||
assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect";
|
assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect";
|
||||||
dataByteBuffer.putLong(32L);
|
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";
|
assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants";
|
||||||
|
|
||||||
// Code labels
|
// Code labels
|
||||||
Integer labelRefund = null;
|
Integer labelRefund = null;
|
||||||
|
|
||||||
Integer labelOfferTxLoop = null;
|
Integer labelTradeTxnLoop = null;
|
||||||
Integer labelCheckOfferTx = null;
|
Integer labelCheckTradeTxn = null;
|
||||||
|
|
||||||
Integer labelCheckNonRefundOfferTx = null;
|
Integer labelCheckNonRefundTradeTxn = null;
|
||||||
Integer labelOfferTxExtract = null;
|
Integer labelTradeTxnExtract = null;
|
||||||
Integer labelTradeTxLoop = null;
|
Integer labelRedeemTxnLoop = null;
|
||||||
Integer labelCheckTradeTx = null;
|
Integer labelCheckRedeemTxn = null;
|
||||||
Integer labelCheckTradeSender = null;
|
Integer labelCheckRedeemTxnSender = null;
|
||||||
Integer labelCheckSecretB = null;
|
Integer labelCheckSecretB = null;
|
||||||
Integer labelPayout = null;
|
Integer labelPayout = null;
|
||||||
|
|
||||||
@ -315,7 +348,7 @@ public class BTCACCT {
|
|||||||
/* Initialization */
|
/* Initialization */
|
||||||
|
|
||||||
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
|
// 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
|
// 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));
|
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 */
|
/* 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 */
|
/* Transaction processing loop */
|
||||||
labelOfferTxLoop = codeByteBuffer.position();
|
labelTradeTxnLoop = codeByteBuffer.position();
|
||||||
|
|
||||||
// Find next transaction to this AT since the last one (if any)
|
// 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, 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.
|
// 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));
|
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
|
// 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
|
// Stop and wait for next block
|
||||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||||
|
|
||||||
/* Check transaction */
|
/* Check transaction */
|
||||||
labelCheckOfferTx = codeByteBuffer.position();
|
labelCheckTradeTxn = codeByteBuffer.position();
|
||||||
|
|
||||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
// 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));
|
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 addrTxType
|
// 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, addrTxType));
|
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
|
// 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. */
|
/* 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)
|
// 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));
|
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.
|
// 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(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelOfferTxLoop)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelOfferTxLoop)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelOfferTxLoop)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
|
||||||
|
|
||||||
/* Extract trade partner info from message */
|
/* Extract trade partner info from message */
|
||||||
|
|
||||||
// Extract message from transaction into B register
|
// Extract message from transaction into B register
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
|
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)
|
// 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, addrQortalRecipientPointer));
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer));
|
||||||
// Compare each of recipient address with creator's address (for offer-cancel scenario). If they don't match, assume recipient is trade partner.
|
// 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(addrQortalRecipient1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundOfferTx)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalPartnerAddress4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelCheckNonRefundTradeTxn)));
|
||||||
// Recipient address is AT creator's address, so cancel offer and finish.
|
// Partner address is AT creator's address, so cancel offer and finish.
|
||||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
|
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 */
|
/* 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.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)));
|
// If message length matches, branch to info extraction code
|
||||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop));
|
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
|
// 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)
|
// Extract partner's Bitcoin PKH (we only really use values from B1-B3)
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrRecipientBitcoinPKHPointer));
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerBitcoinPKHPointer));
|
||||||
// Also extract lockTimeB
|
// Also extract lockTimeB
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeB));
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeB));
|
||||||
|
|
||||||
// Grab next 32 bytes
|
// 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)
|
// 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));
|
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.SET_DAT.compile(addrRefundTimeout, addrLockTimeA)); // refundTimeout = lockTimeA
|
||||||
codeByteBuffer.put(OpCode.SUB_DAT.compile(addrRefundTimeout, addrLockTimeB)); // refundTimeout -= lockTimeB
|
codeByteBuffer.put(OpCode.SUB_DAT.compile(addrRefundTimeout, addrLockTimeB)); // refundTimeout -= lockTimeB
|
||||||
codeByteBuffer.put(OpCode.DIV_VAL.compile(addrRefundTimeout, 2L * 60L)); // refundTimeout /= 2 * 60
|
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
|
// 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, addrLastTxTimestamp, addrRefundTimeout));
|
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout));
|
||||||
|
|
||||||
/* We are in 'trade mode' */
|
/* 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
|
// Set restart position to after this opcode
|
||||||
codeByteBuffer.put(OpCode.SET_PCS.compile());
|
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'
|
// Fetch current block 'timestamp'
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
|
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
|
||||||
// If we're not past refund 'timestamp' then look for next transaction
|
// 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
|
// We're past refund 'timestamp' so go refund everything back to AT creator
|
||||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
|
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
|
||||||
|
|
||||||
/* Transaction processing loop */
|
/* Transaction processing loop */
|
||||||
labelTradeTxLoop = codeByteBuffer.position();
|
labelRedeemTxnLoop = codeByteBuffer.position();
|
||||||
|
|
||||||
// Find next transaction to this AT since the last one (if any)
|
// 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.
|
// 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));
|
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
|
// 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
|
// Stop and wait for next block
|
||||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||||
|
|
||||||
/* Check transaction */
|
/* Check transaction */
|
||||||
labelCheckTradeTx = codeByteBuffer.position();
|
labelCheckRedeemTxn = codeByteBuffer.position();
|
||||||
|
|
||||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
// 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));
|
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 addrTxType
|
// 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, addrTxType));
|
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
|
// 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 */
|
/* Check message payload length */
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
|
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)));
|
// If message length matches, branch to sender checking code
|
||||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelOfferTxLoop == null ? 0 : labelOfferTxLoop));
|
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 */
|
/* Check transaction's sender */
|
||||||
|
labelCheckRedeemTxnSender = codeByteBuffer.position();
|
||||||
labelCheckTradeSender = codeByteBuffer.position();
|
|
||||||
|
|
||||||
// Extract sender address from transaction into B register
|
// Extract sender address from transaction into B register
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
|
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)
|
// 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));
|
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.
|
// 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(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalRecipient2, calcOffset(codeByteBuffer, labelTradeTxLoop)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalRecipient3, calcOffset(codeByteBuffer, labelTradeTxLoop)));
|
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalRecipient4, calcOffset(codeByteBuffer, labelTradeTxLoop)));
|
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
|
// 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));
|
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));
|
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
|
// 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.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();
|
labelCheckSecretB = codeByteBuffer.position();
|
||||||
|
|
||||||
// Extract secret-B from next 32 bytes of message from transaction into B register
|
// 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)
|
// 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));
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer));
|
||||||
// Load B register with expected hash result (as pointed to by addrHashOfSecretBPointer)
|
// 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));
|
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
|
// 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.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();
|
labelPayout = codeByteBuffer.position();
|
||||||
|
|
||||||
// Load B register with intended recipient address (as pointed to by addrQortalRecipientPointer)
|
// Extract Qortal receive address from next 32 bytes of message from transaction into B register
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrQortalRecipientPointer));
|
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
|
// Pay AT's balance to recipient
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
|
||||||
// Set redeem flag
|
// Set redeemed mode
|
||||||
codeByteBuffer.put(OpCode.INC_DAT.compile(addrRedeemFlag));
|
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
|
// Fall-through to refunding any remaining balance back to AT creator
|
||||||
|
|
||||||
/* Refund balance back to AT creator */
|
/* Refund balance back to AT creator */
|
||||||
labelRefund = codeByteBuffer.position();
|
labelRefund = codeByteBuffer.position();
|
||||||
|
|
||||||
// Load B register with AT creator's address.
|
// Set refunded mode
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
|
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, Mode.REFUNDED.value));
|
||||||
// Pay AT's balance back to AT's creator.
|
// We're finished forever (finishing auto-refunds remaining balance to AT creator)
|
||||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B));
|
|
||||||
// We're finished forever
|
|
||||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||||
} catch (CompilationException e) {
|
} catch (CompilationException e) {
|
||||||
throw new IllegalStateException("Unable to compile BTC-QORT ACCT?", e);
|
throw new IllegalStateException("Unable to compile BTC-QORT ACCT?", e);
|
||||||
@ -587,15 +626,16 @@ public class BTCACCT {
|
|||||||
// Expected BTC amount
|
// Expected BTC amount
|
||||||
tradeData.expectedBitcoin = dataByteBuffer.getLong();
|
tradeData.expectedBitcoin = dataByteBuffer.getLong();
|
||||||
|
|
||||||
|
// Trade timeout
|
||||||
tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
|
tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
|
||||||
|
|
||||||
// Skip MESSAGE transaction type
|
// Skip MESSAGE transaction type
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip expected OFFER message length
|
// Skip expected 'trade' message length
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip expected TRADE message length
|
// Skip expected 'redeem' message length
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to creator's address
|
// Skip pointer to creator's address
|
||||||
@ -604,25 +644,28 @@ public class BTCACCT {
|
|||||||
// Skip pointer to hash-of-secret-B
|
// Skip pointer to hash-of-secret-B
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to Qortal recipient
|
// Skip pointer to partner's Qortal trade address
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to message sender
|
// Skip pointer to message sender
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
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);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to recipient's bitcoin PKH
|
// Skip pointer to partner's bitcoin PKH
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
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);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to hash-of-secret-A
|
// Skip pointer to hash-of-secret-A
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
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);
|
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||||
|
|
||||||
// Skip pointer to message data
|
// Skip pointer to message data
|
||||||
@ -631,17 +674,12 @@ public class BTCACCT {
|
|||||||
// Skip message data length
|
// Skip message data length
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
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 */
|
/* End of constants / begin variables */
|
||||||
|
|
||||||
// Skip AT creator's address
|
// Skip AT creator's address
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
|
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
|
||||||
|
|
||||||
// Recipient's trade address (if present)
|
// Partner's trade address (if present)
|
||||||
dataByteBuffer.get(addressBytes);
|
dataByteBuffer.get(addressBytes);
|
||||||
String qortalRecipient = Base58.encode(addressBytes);
|
String qortalRecipient = Base58.encode(addressBytes);
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
|
dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
|
||||||
@ -655,7 +693,7 @@ public class BTCACCT {
|
|||||||
// AT refund timeout (probably only useful for debugging)
|
// AT refund timeout (probably only useful for debugging)
|
||||||
int refundTimeout = (int) dataByteBuffer.getLong();
|
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();
|
long tradeRefundTimestamp = dataByteBuffer.getLong();
|
||||||
|
|
||||||
// Skip last transaction timestamp
|
// Skip last transaction timestamp
|
||||||
@ -684,60 +722,58 @@ public class BTCACCT {
|
|||||||
dataByteBuffer.get(hashOfSecretA);
|
dataByteBuffer.get(hashOfSecretA);
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
|
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];
|
byte[] recipientBitcoinPKH = new byte[20];
|
||||||
dataByteBuffer.get(recipientBitcoinPKH);
|
dataByteBuffer.get(recipientBitcoinPKH);
|
||||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - recipientBitcoinPKH.length); // skip to 32 bytes
|
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 != null && mode != Mode.OFFERING) {
|
||||||
|
tradeData.mode = mode;
|
||||||
if (mode != 0) {
|
|
||||||
tradeData.mode = CrossChainTradeData.Mode.TRADE;
|
|
||||||
tradeData.refundTimeout = refundTimeout;
|
tradeData.refundTimeout = refundTimeout;
|
||||||
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
|
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
|
||||||
tradeData.qortalRecipient = qortalRecipient;
|
tradeData.qortalPartnerAddress = qortalRecipient;
|
||||||
tradeData.hashOfSecretA = hashOfSecretA;
|
tradeData.hashOfSecretA = hashOfSecretA;
|
||||||
tradeData.recipientBitcoinPKH = recipientBitcoinPKH;
|
tradeData.partnerBitcoinPKH = recipientBitcoinPKH;
|
||||||
tradeData.lockTimeA = lockTimeA;
|
tradeData.lockTimeA = lockTimeA;
|
||||||
tradeData.lockTimeB = lockTimeB;
|
tradeData.lockTimeB = lockTimeB;
|
||||||
tradeData.hasRedeemed = redeemFlag != 0;
|
|
||||||
} else {
|
} else {
|
||||||
tradeData.mode = CrossChainTradeData.Mode.OFFER;
|
tradeData.mode = Mode.OFFERING;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tradeData;
|
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) {
|
public static byte[] buildOfferMessage(byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
|
||||||
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
||||||
return Bytes.concat(recipientBitcoinPKH, hashOfSecretA, lockTimeABytes);
|
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) {
|
public static OfferMessageData extractOfferMessageData(byte[] messageData) {
|
||||||
if (messageData == null || messageData.length != 20 + 20 + 8)
|
if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
OfferMessageData offerMessageData = new OfferMessageData();
|
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.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40);
|
||||||
offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40);
|
offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40);
|
||||||
|
|
||||||
return offerMessageData;
|
return offerMessageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns trade-info MESSAGE payload for AT creator to send to AT. */
|
/** Returns 'trade' MESSAGE payload for AT creator to send to AT. */
|
||||||
public static byte[] buildTradeMessage(String recipientQortalAddress, byte[] recipientBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) {
|
public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int lockTimeB) {
|
||||||
byte[] data = new byte[32 + 32 + 32];
|
byte[] data = new byte[TRADE_MESSAGE_LENGTH];
|
||||||
byte[] recipientQortalAddressBytes = Base58.decode(recipientQortalAddress);
|
byte[] recipientQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
|
||||||
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
||||||
byte[] lockTimeBBytes = BitTwiddling.toBEByteArray((long) lockTimeB);
|
byte[] lockTimeBBytes = BitTwiddling.toBEByteArray((long) lockTimeB);
|
||||||
|
|
||||||
System.arraycopy(recipientQortalAddressBytes, 0, data, 0, recipientQortalAddressBytes.length);
|
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(lockTimeBBytes, 0, data, 56, lockTimeBBytes.length);
|
||||||
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
|
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
|
||||||
System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length);
|
System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length);
|
||||||
@ -745,9 +781,9 @@ public class BTCACCT {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns refund MESSAGE payload for AT creator to cancel trade AT. */
|
/** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */
|
||||||
public static byte[] buildRefundMessage(String creatorQortalAddress) {
|
public static byte[] buildCancelMessage(String creatorQortalAddress) {
|
||||||
byte[] data = new byte[32];
|
byte[] data = new byte[CANCEL_MESSAGE_LENGTH];
|
||||||
byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
|
byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
|
||||||
|
|
||||||
System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
|
System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
|
||||||
@ -755,31 +791,33 @@ public class BTCACCT {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns redeem MESSAGE payload for trade partner/recipient to send to AT. */
|
/** Returns 'redeem' MESSAGE payload for trade partner/ to send to AT. */
|
||||||
public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB) {
|
public static byte[] buildRedeemMessage(byte[] secretA, byte[] secretB, String qortalReceiveAddress) {
|
||||||
byte[] data = new byte[32 + 32];
|
byte[] data = new byte[REDEEM_MESSAGE_LENGTH];
|
||||||
|
byte[] qortalReceiveAddressBytes = Base58.decode(qortalReceiveAddress);
|
||||||
|
|
||||||
System.arraycopy(secretA, 0, data, 0, secretA.length);
|
System.arraycopy(secretA, 0, data, 0, secretA.length);
|
||||||
System.arraycopy(secretB, 0, data, 32, secretB.length);
|
System.arraycopy(secretB, 0, data, 32, secretB.length);
|
||||||
|
System.arraycopy(qortalReceiveAddressBytes, 0, data, 64, qortalReceiveAddressBytes.length);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns P2SH-B lockTime (epoch seconds) based on trade partner/recipient's MESSAGE timestamp and P2SH-A locktime. */
|
/** Returns P2SH-B lockTime (epoch seconds) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */
|
||||||
public static int calcLockTimeB(long recipientMessageTimestamp, int lockTimeA) {
|
public static int calcLockTimeB(long offerMessageTimestamp, int lockTimeA) {
|
||||||
// lockTimeB is halfway between recipientMessageTimesamp and lockTimeA
|
// lockTimeB is halfway between offerMessageTimesamp and lockTimeA
|
||||||
return (int) ((lockTimeA + (recipientMessageTimestamp / 1000L)) / 2L);
|
return (int) ((lockTimeA + (offerMessageTimestamp / 1000L)) / 2L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
public static byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
||||||
String atAddress = crossChainTradeData.qortalAtAddress;
|
String atAddress = crossChainTradeData.qortalAtAddress;
|
||||||
String redeemerAddress = crossChainTradeData.qortalRecipient;
|
String redeemerAddress = crossChainTradeData.qortalPartnerAddress;
|
||||||
|
|
||||||
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(atAddress, null, null, null);
|
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(atAddress, null, null, null);
|
||||||
if (messageTransactionsData == null)
|
if (messageTransactionsData == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Find redeem message
|
// Find 'redeem' message
|
||||||
for (MessageTransactionData messageTransactionData : messageTransactionsData) {
|
for (MessageTransactionData messageTransactionData : messageTransactionsData) {
|
||||||
// Check message payload type/encryption
|
// Check message payload type/encryption
|
||||||
if (messageTransactionData.isText() || messageTransactionData.isEncrypted())
|
if (messageTransactionData.isText() || messageTransactionData.isEncrypted())
|
||||||
@ -787,7 +825,7 @@ public class BTCACCT {
|
|||||||
|
|
||||||
// Check message payload size
|
// Check message payload size
|
||||||
byte[] messageData = messageTransactionData.getData();
|
byte[] messageData = messageTransactionData.getData();
|
||||||
if (messageData.length != 32 + 32)
|
if (messageData.length != REDEEM_MESSAGE_LENGTH)
|
||||||
// Wrong payload length
|
// Wrong payload length
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import javax.xml.bind.annotation.XmlElement;
|
|||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
|
||||||
import org.qortal.crosschain.BTC;
|
import org.qortal.crosschain.BTC;
|
||||||
|
import org.qortal.crosschain.BTCACCT;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
@ -13,8 +14,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class CrossChainTradeData {
|
public class CrossChainTradeData {
|
||||||
|
|
||||||
public enum Mode { OFFER, TRADE };
|
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
@Schema(description = "AT's Qortal address")
|
@Schema(description = "AT's Qortal address")
|
||||||
@ -29,9 +28,6 @@ public class CrossChainTradeData {
|
|||||||
@Schema(description = "AT creator's Bitcoin trade public-key-hash (PKH)")
|
@Schema(description = "AT creator's Bitcoin trade public-key-hash (PKH)")
|
||||||
public byte[] creatorBitcoinPKH;
|
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)")
|
@Schema(description = "Timestamp when AT was created (milliseconds since epoch)")
|
||||||
public long creationTimestamp;
|
public long creationTimestamp;
|
||||||
|
|
||||||
@ -53,7 +49,7 @@ public class CrossChainTradeData {
|
|||||||
public long qortAmount;
|
public long qortAmount;
|
||||||
|
|
||||||
@Schema(description = "Trade partner's Qortal address (trade begins when this is set)")
|
@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")
|
@Schema(description = "Timestamp when AT switched to trade mode")
|
||||||
public Long tradeModeTimestamp;
|
public Long tradeModeTimestamp;
|
||||||
@ -68,7 +64,7 @@ public class CrossChainTradeData {
|
|||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long expectedBitcoin;
|
public long expectedBitcoin;
|
||||||
|
|
||||||
public Mode mode;
|
public BTCACCT.Mode mode;
|
||||||
|
|
||||||
@Schema(description = "Suggested Bitcoin P2SH-A nLockTime based on trade timeout")
|
@Schema(description = "Suggested Bitcoin P2SH-A nLockTime based on trade timeout")
|
||||||
public Integer lockTimeA;
|
public Integer lockTimeA;
|
||||||
@ -77,10 +73,7 @@ public class CrossChainTradeData {
|
|||||||
public Integer lockTimeB;
|
public Integer lockTimeB;
|
||||||
|
|
||||||
@Schema(description = "Trade partner's Bitcoin public-key-hash (PKH)")
|
@Schema(description = "Trade partner's Bitcoin public-key-hash (PKH)")
|
||||||
public byte[] recipientBitcoinPKH;
|
public byte[] partnerBitcoinPKH;
|
||||||
|
|
||||||
@Schema(description = "Whether AT has paid out to trade partner")
|
|
||||||
public Boolean hasRedeemed;
|
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -102,20 +95,10 @@ public class CrossChainTradeData {
|
|||||||
@XmlElement(name = "recipientBitcoinAddress")
|
@XmlElement(name = "recipientBitcoinAddress")
|
||||||
@Schema(description = "Trade partner's trading Bitcoin PKH in address form")
|
@Schema(description = "Trade partner's trading Bitcoin PKH in address form")
|
||||||
public String getRecipientBitcoinAddress() {
|
public String getRecipientBitcoinAddress() {
|
||||||
if (this.recipientBitcoinPKH == null)
|
if (this.partnerBitcoinPKH == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return BTC.getInstance().pkhToAddress(this.recipientBitcoinPKH);
|
return BTC.getInstance().pkhToAddress(this.partnerBitcoinPKH);
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class TradeBotData {
|
|||||||
|
|
||||||
private long bitcoinAmount;
|
private long bitcoinAmount;
|
||||||
|
|
||||||
// Never expose this
|
// Never expose this via API
|
||||||
@XmlTransient
|
@XmlTransient
|
||||||
@Schema(hidden = true)
|
@Schema(hidden = true)
|
||||||
private String xprv58;
|
private String xprv58;
|
||||||
@ -57,6 +57,9 @@ public class TradeBotData {
|
|||||||
|
|
||||||
private Integer lockTimeA;
|
private Integer lockTimeA;
|
||||||
|
|
||||||
|
// Could be Bitcoin or Qortal...
|
||||||
|
private byte[] receivingPublicKeyHash;
|
||||||
|
|
||||||
protected TradeBotData() {
|
protected TradeBotData() {
|
||||||
/* JAXB */
|
/* JAXB */
|
||||||
}
|
}
|
||||||
@ -65,7 +68,7 @@ public class TradeBotData {
|
|||||||
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress,
|
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress,
|
||||||
byte[] secret, byte[] hashOfSecret,
|
byte[] secret, byte[] hashOfSecret,
|
||||||
byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash,
|
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.tradePrivateKey = tradePrivateKey;
|
||||||
this.tradeState = tradeState;
|
this.tradeState = tradeState;
|
||||||
this.atAddress = atAddress;
|
this.atAddress = atAddress;
|
||||||
@ -80,6 +83,7 @@ public class TradeBotData {
|
|||||||
this.xprv58 = xprv58;
|
this.xprv58 = xprv58;
|
||||||
this.lastTransactionSignature = lastTransactionSignature;
|
this.lastTransactionSignature = lastTransactionSignature;
|
||||||
this.lockTimeA = lockTimeA;
|
this.lockTimeA = lockTimeA;
|
||||||
|
this.receivingPublicKeyHash = receivingPublicKeyHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getTradePrivateKey() {
|
public byte[] getTradePrivateKey() {
|
||||||
@ -154,4 +158,8 @@ public class TradeBotData {
|
|||||||
this.lockTimeA = lockTimeA;
|
this.lockTimeA = lockTimeA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getReceivingPublicKeyHash() {
|
||||||
|
return this.receivingPublicKeyHash;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
+ "trade_native_public_key, trade_native_public_key_hash, "
|
+ "trade_native_public_key, trade_native_public_key_hash, "
|
||||||
+ "trade_native_address, secret, hash_of_secret, "
|
+ "trade_native_address, secret, hash_of_secret, "
|
||||||
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
+ "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 "
|
+ "FROM TradeBotStates "
|
||||||
+ "WHERE trade_private_key = ?";
|
+ "WHERE trade_private_key = ?";
|
||||||
|
|
||||||
@ -50,13 +50,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
Integer lockTimeA = resultSet.getInt(13);
|
Integer lockTimeA = resultSet.getInt(13);
|
||||||
if (lockTimeA == 0 && resultSet.wasNull())
|
if (lockTimeA == 0 && resultSet.wasNull())
|
||||||
lockTimeA = null;
|
lockTimeA = null;
|
||||||
|
byte[] receivingPublicKeyHash = resultSet.getBytes(14);
|
||||||
|
|
||||||
return new TradeBotData(tradePrivateKey, tradeState,
|
return new TradeBotData(tradePrivateKey, tradeState,
|
||||||
atAddress,
|
atAddress,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secret, hashOfSecret,
|
secret, hashOfSecret,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA);
|
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch trade-bot trading state from repository", 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_public_key, trade_native_public_key_hash, "
|
||||||
+ "trade_native_address, secret, hash_of_secret, "
|
+ "trade_native_address, secret, hash_of_secret, "
|
||||||
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
|
+ "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";
|
+ "FROM TradeBotStates";
|
||||||
|
|
||||||
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
List<TradeBotData> allTradeBotData = new ArrayList<>();
|
||||||
@ -98,13 +99,14 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
Integer lockTimeA = resultSet.getInt(14);
|
Integer lockTimeA = resultSet.getInt(14);
|
||||||
if (lockTimeA == 0 && resultSet.wasNull())
|
if (lockTimeA == 0 && resultSet.wasNull())
|
||||||
lockTimeA = null;
|
lockTimeA = null;
|
||||||
|
byte[] receivingPublicKeyHash = resultSet.getBytes(15);
|
||||||
|
|
||||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
|
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
|
||||||
atAddress,
|
atAddress,
|
||||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||||
secret, hashOfSecret,
|
secret, hashOfSecret,
|
||||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||||
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA);
|
bitcoinAmount, xprv58, lastTransactionSignature, lockTimeA, receivingPublicKeyHash);
|
||||||
allTradeBotData.add(tradeBotData);
|
allTradeBotData.add(tradeBotData);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -130,7 +132,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
|
|||||||
.bind("bitcoin_amount", tradeBotData.getBitcoinAmount())
|
.bind("bitcoin_amount", tradeBotData.getBitcoinAmount())
|
||||||
.bind("xprv58", tradeBotData.getXprv58())
|
.bind("xprv58", tradeBotData.getXprv58())
|
||||||
.bind("last_transaction_signature", tradeBotData.getLastTransactionSignature())
|
.bind("last_transaction_signature", tradeBotData.getLastTransactionSignature())
|
||||||
.bind("locktime_a", tradeBotData.getLockTimeA());
|
.bind("locktime_a", tradeBotData.getLockTimeA())
|
||||||
|
.bind("receiving_public_key_hash", tradeBotData.getReceivingPublicKeyHash());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -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_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, "
|
+ "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, "
|
+ "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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -19,7 +19,6 @@ import org.qortal.account.Account;
|
|||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.Block;
|
import org.qortal.block.Block;
|
||||||
import org.qortal.crosschain.BTC;
|
|
||||||
import org.qortal.crosschain.BTCACCT;
|
import org.qortal.crosschain.BTCACCT;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
@ -65,7 +64,7 @@ public class AtTests extends Common {
|
|||||||
public void testCompile() {
|
public void testCompile() {
|
||||||
Account deployer = Common.getTestAccount(null, "chloe");
|
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());
|
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 {
|
public void testDeploy() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
|
|
||||||
@ -90,10 +89,10 @@ public class AtTests extends Common {
|
|||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
expectedBalance = recipientsInitialBalance;
|
expectedBalance = partnersInitialBalance;
|
||||||
actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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
|
// Test orphaning
|
||||||
BlockUtils.orphanLastBlock(repository);
|
BlockUtils.orphanLastBlock(repository);
|
||||||
@ -108,10 +107,10 @@ public class AtTests extends Common {
|
|||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
expectedBalance = recipientsInitialBalance;
|
expectedBalance = partnersInitialBalance;
|
||||||
actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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 {
|
public void testOfferCancel() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
@ -132,12 +131,12 @@ public class AtTests extends Common {
|
|||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
|
||||||
// Send creator's address to AT
|
// Send creator's address to AT, instead of typical partner's address
|
||||||
byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0);
|
byte[] partnerAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0);
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, partnerAddressBytes, atAddress);
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
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);
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||||
@ -154,6 +153,10 @@ public class AtTests extends Common {
|
|||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertTrue(atData.getIsFinished());
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in CANCELLED mode
|
||||||
|
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
assertEquals(BTCACCT.Mode.CANCELLED, tradeData.mode);
|
||||||
|
|
||||||
// Test orphaning
|
// Test orphaning
|
||||||
BlockUtils.orphanLastBlock(repository);
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
@ -169,21 +172,21 @@ public class AtTests extends Common {
|
|||||||
public void testTradingInfoProcessing() throws DataException {
|
public void testTradingInfoProcessing() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||||
@ -199,16 +202,16 @@ public class AtTests extends Common {
|
|||||||
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
// AT should be in TRADE mode
|
||||||
assertEquals(CrossChainTradeData.Mode.TRADE, tradeData.mode);
|
assertEquals(BTCACCT.Mode.TRADING, tradeData.mode);
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
// Check hashOfSecretA was extracted correctly
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
||||||
|
|
||||||
// Check trade partner/recipient Qortal address was extracted correctly
|
// Check trade partner Qortal address was extracted correctly
|
||||||
assertEquals(recipient.getAddress(), tradeData.qortalRecipient);
|
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
||||||
|
|
||||||
// Check trade partner/recipient's Bitcoin PKH was extracted correctly
|
// Check trade partner's Bitcoin PKH was extracted correctly
|
||||||
assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.recipientBitcoinPKH));
|
assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerBitcoinPKH));
|
||||||
|
|
||||||
// Test orphaning
|
// Test orphaning
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||||
@ -226,30 +229,30 @@ public class AtTests extends Common {
|
|||||||
public void testIncorrectTradeSender() throws DataException {
|
public void testIncorrectTradeSender() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert");
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
long expectedBalance = recipientsInitialBalance;
|
long expectedBalance = partnersInitialBalance;
|
||||||
long actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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);
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
@ -257,7 +260,7 @@ public class AtTests extends Common {
|
|||||||
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
// 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 {
|
public void testAutomaticTradeRefund() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||||
@ -298,6 +301,10 @@ public class AtTests extends Common {
|
|||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertTrue(atData.getIsFinished());
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in REFUNDED mode
|
||||||
|
CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData);
|
||||||
|
assertEquals(BTCACCT.Mode.REFUNDED, tradeData.mode);
|
||||||
|
|
||||||
// Test orphaning
|
// Test orphaning
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||||
|
|
||||||
@ -313,29 +320,29 @@ public class AtTests extends Common {
|
|||||||
public void testCorrectSecretsCorrectSender() throws DataException {
|
public void testCorrectSecretsCorrectSender() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
|
||||||
// Give AT time to process message
|
// Give AT time to process message
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
// Send correct secrets to AT, from correct account
|
// Send correct secrets to AT, from correct account
|
||||||
messageData = BTCACCT.buildRedeemMessage(secretA, secretB);
|
messageData = BTCACCT.buildRedeemMessage(secretA, secretB, partner.getAddress());
|
||||||
messageTransaction = sendMessage(repository, recipient, messageData, atAddress);
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
// AT should send funds in the next block
|
// AT should send funds in the next block
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
@ -347,18 +354,22 @@ public class AtTests extends Common {
|
|||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertTrue(atData.getIsFinished());
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
long expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
// AT should be in REDEEMED mode
|
||||||
long actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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
|
// Orphan redeem
|
||||||
BlockUtils.orphanLastBlock(repository);
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee();
|
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||||
actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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
|
// Check AT state
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
@ -372,11 +383,11 @@ public class AtTests extends Common {
|
|||||||
public void testCorrectSecretsIncorrectSender() throws DataException {
|
public void testCorrectSecretsIncorrectSender() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert");
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
@ -384,19 +395,19 @@ public class AtTests extends Common {
|
|||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
|
||||||
// Give AT time to process message
|
// Give AT time to process message
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
// Send correct secrets to AT, but from wrong account
|
// 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);
|
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
// AT should NOT send funds in the next block
|
||||||
@ -409,10 +420,14 @@ public class AtTests extends Common {
|
|||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertFalse(atData.getIsFinished());
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
long expectedBalance = recipientsInitialBalance;
|
// AT should still be in TRADE mode
|
||||||
long actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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);
|
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||||
}
|
}
|
||||||
@ -423,10 +438,10 @@ public class AtTests extends Common {
|
|||||||
public void testIncorrectSecretsCorrectSender() throws DataException {
|
public void testIncorrectSecretsCorrectSender() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
@ -434,12 +449,12 @@ public class AtTests extends Common {
|
|||||||
Account at = deployAtTransaction.getATAccount();
|
Account at = deployAtTransaction.getATAccount();
|
||||||
String atAddress = at.getAddress();
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
long recipientMessageTransactionTimestamp = System.currentTimeMillis();
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
int lockTimeA = calcTestLockTimeA(recipientMessageTransactionTimestamp);
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
int lockTimeB = BTCACCT.calcLockTimeB(recipientMessageTransactionTimestamp, lockTimeA);
|
int lockTimeB = BTCACCT.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
// Send trade info to AT
|
// 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);
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
|
||||||
// Give AT time to process message
|
// Give AT time to process message
|
||||||
@ -449,8 +464,8 @@ public class AtTests extends Common {
|
|||||||
byte[] wrongSecret = new byte[32];
|
byte[] wrongSecret = new byte[32];
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
random.nextBytes(wrongSecret);
|
random.nextBytes(wrongSecret);
|
||||||
messageData = BTCACCT.buildRedeemMessage(wrongSecret, secretB);
|
messageData = BTCACCT.buildRedeemMessage(wrongSecret, secretB, partner.getAddress());
|
||||||
messageTransaction = sendMessage(repository, recipient, messageData, atAddress);
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
// AT should NOT send funds in the next block
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
@ -462,14 +477,18 @@ public class AtTests extends Common {
|
|||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertFalse(atData.getIsFinished());
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
long expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee();
|
// AT should still be in TRADE mode
|
||||||
long actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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
|
// Send incorrect secrets to AT, from correct account
|
||||||
messageData = BTCACCT.buildRedeemMessage(secretA, wrongSecret);
|
messageData = BTCACCT.buildRedeemMessage(secretA, wrongSecret, partner.getAddress());
|
||||||
messageTransaction = sendMessage(repository, recipient, messageData, atAddress);
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
// AT should NOT send funds in the next block
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
@ -480,10 +499,14 @@ public class AtTests extends Common {
|
|||||||
atData = repository.getATRepository().fromATAddress(atAddress);
|
atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
assertFalse(atData.getIsFinished());
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
expectedBalance = recipientsInitialBalance - messageTransaction.getTransactionData().getFee() * 2;
|
// AT should still be in TRADE mode
|
||||||
actualBalance = recipient.getConfirmedBalance(Asset.QORT);
|
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);
|
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||||
}
|
}
|
||||||
@ -494,10 +517,10 @@ public class AtTests extends Common {
|
|||||||
public void testDescribeDeployed() throws DataException {
|
public void testDescribeDeployed() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
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 deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
long recipientsInitialBalance = recipient.getConfirmedBalance(Asset.QORT);
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer);
|
||||||
|
|
||||||
@ -528,7 +551,7 @@ public class AtTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException {
|
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();
|
long txTimestamp = System.currentTimeMillis();
|
||||||
byte[] lastReference = deployer.getLastReference();
|
byte[] lastReference = deployer.getLastReference();
|
||||||
@ -611,6 +634,7 @@ public class AtTests extends Common {
|
|||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
System.out.print(String.format("%s:\n"
|
||||||
|
+ "\tmode: %s\n"
|
||||||
+ "\tcreator: %s,\n"
|
+ "\tcreator: %s,\n"
|
||||||
+ "\tcreation timestamp: %s,\n"
|
+ "\tcreation timestamp: %s,\n"
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
+ "\tcurrent balance: %s QORT,\n"
|
||||||
@ -618,9 +642,9 @@ public class AtTests extends Common {
|
|||||||
+ "\tHASH160 of secret-B: %s,\n"
|
+ "\tHASH160 of secret-B: %s,\n"
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
+ "\tredeem payout: %s QORT,\n"
|
||||||
+ "\texpected bitcoin: %s BTC,\n"
|
+ "\texpected bitcoin: %s BTC,\n"
|
||||||
+ "\treceiving bitcoin address: %s,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
+ "\tcurrent block height: %d,\n",
|
||||||
tradeData.qortalAtAddress,
|
tradeData.qortalAtAddress,
|
||||||
|
tradeData.mode.name(),
|
||||||
tradeData.qortalCreator,
|
tradeData.qortalCreator,
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
Amounts.prettyAmount(tradeData.qortBalance),
|
||||||
@ -628,26 +652,19 @@ public class AtTests extends Common {
|
|||||||
HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40),
|
HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40),
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
Amounts.prettyAmount(tradeData.qortAmount),
|
||||||
Amounts.prettyAmount(tradeData.expectedBitcoin),
|
Amounts.prettyAmount(tradeData.expectedBitcoin),
|
||||||
BTC.getInstance().pkhToAddress(tradeData.creatorReceiveBitcoinPKH),
|
|
||||||
currentBlockHeight));
|
currentBlockHeight));
|
||||||
|
|
||||||
// Are we in 'offer' or 'trade' stage?
|
if (tradeData.mode != BTCACCT.Mode.OFFERING && tradeData.mode != BTCACCT.Mode.CANCELLED) {
|
||||||
if (tradeData.tradeRefundHeight == null) {
|
System.out.println(String.format("\trefund height: block %d,\n"
|
||||||
// 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"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
+ "\tHASH160 of secret-A: %s,\n"
|
||||||
+ "\tBitcoin P2SH-A nLockTime: %d (%s),\n"
|
+ "\tBitcoin P2SH-A nLockTime: %d (%s),\n"
|
||||||
+ "\tBitcoin P2SH-B nLockTime: %d (%s),\n"
|
+ "\tBitcoin P2SH-B nLockTime: %d (%s),\n"
|
||||||
+ "\ttrade recipient: %s",
|
+ "\ttrade partner: %s",
|
||||||
tradeData.tradeRefundHeight,
|
tradeData.tradeRefundHeight,
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
||||||
tradeData.lockTimeB, epochMilliFormatter.apply(tradeData.lockTimeB * 1000L),
|
tradeData.lockTimeB, epochMilliFormatter.apply(tradeData.lockTimeB * 1000L),
|
||||||
tradeData.qortalRecipient));
|
tradeData.qortalPartnerAddress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,10 @@ package org.qortal.test.btcacct;
|
|||||||
|
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
|
||||||
import org.bitcoinj.core.Address;
|
|
||||||
import org.bitcoinj.script.Script.ScriptType;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.crosschain.BTC;
|
|
||||||
import org.qortal.crosschain.BTCACCT;
|
import org.qortal.crosschain.BTCACCT;
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||||
@ -37,7 +34,7 @@ public class DeployAT {
|
|||||||
if (error != null)
|
if (error != null)
|
||||||
System.err.println(error);
|
System.err.println(error);
|
||||||
|
|
||||||
System.err.println(String.format("usage: DeployAT <your Qortal PRIVATE key> <QORT amount> <BTC amount> <your Bitcoin PKH> <HASH160-of-secret> <AT funding amount> <trade-timeout> <your bitcoin receive address"));
|
System.err.println(String.format("usage: DeployAT <your Qortal PRIVATE key> <QORT amount> <BTC amount> <your Bitcoin PKH> <HASH160-of-secret> <AT funding amount> <trade-timeout>"));
|
||||||
System.err.println(String.format("example: DeployAT "
|
System.err.println(String.format("example: DeployAT "
|
||||||
+ "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n"
|
+ "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n"
|
||||||
+ "\t80.4020 \\\n"
|
+ "\t80.4020 \\\n"
|
||||||
@ -45,13 +42,12 @@ public class DeployAT {
|
|||||||
+ "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb \\\n"
|
+ "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb \\\n"
|
||||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||||
+ "\t123.456 \\\n"
|
+ "\t123.456 \\\n"
|
||||||
+ "\t10080 \\\n"
|
+ "\t10080"));
|
||||||
+ "\tn2iQZCtKZ5SrFDJENGJkd4RpAuQp3SEoix"));
|
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length != 8)
|
if (args.length != 7)
|
||||||
usage(null);
|
usage(null);
|
||||||
|
|
||||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||||
@ -64,7 +60,6 @@ public class DeployAT {
|
|||||||
byte[] secretHash = null;
|
byte[] secretHash = null;
|
||||||
long fundingAmount = 0;
|
long fundingAmount = 0;
|
||||||
int tradeTimeout = 0;
|
int tradeTimeout = 0;
|
||||||
byte[] bitcoinReceivePublicKeyHash = null;
|
|
||||||
|
|
||||||
int argIndex = 0;
|
int argIndex = 0;
|
||||||
try {
|
try {
|
||||||
@ -95,12 +90,6 @@ public class DeployAT {
|
|||||||
tradeTimeout = Integer.parseInt(args[argIndex++]);
|
tradeTimeout = Integer.parseInt(args[argIndex++]);
|
||||||
if (tradeTimeout < 60 || tradeTimeout > 50000)
|
if (tradeTimeout < 60 || tradeTimeout > 50000)
|
||||||
usage("Trade timeout (minutes) must be between 60 and 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) {
|
} catch (IllegalArgumentException e) {
|
||||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
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)));
|
System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash)));
|
||||||
|
|
||||||
// Deploy AT
|
// 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());
|
System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
long txTimestamp = System.currentTimeMillis();
|
||||||
|
Loading…
Reference in New Issue
Block a user