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