mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
More work on cross-chain trading, including API calls.
Added API calls to aid Qortal-side of cross-chain trading. POST /crosschain/build - for building Qortal AT POST /crosschain/tradeoffer/recipient - for sending trade partner/recipient to AT POST /crosschain/tradeoffer/secret - for sending secret to AT DELETE /crosschain/tradeoffer - for cancelling AT More fixes regarding Blocks processing/orphaning ATs. More fixes regarding sending/receiving blocks containing AT data. AT-related fix to genesis block. Improved cross-chain trading AT code, removing offer-mode timeout and replacing that with allowing AT creator to cancel offer/end AT by sending AT the creator's own address as trade partner/recipient. After all, they're not going to trade with themselves. Added assertion to check BTCACCT.CODE_BYTES_HASH matches compiled code hash. Added cross-chain AT's 'mode' for easier diagnosis, either OFFER or TRADE. We can't use AT's signature to generate AT address because address is needed before DEPLOY_AT transaction is signed. So we use a hash of signature-less transaction bytes. Corresponding changes to tests.
This commit is contained in:
@@ -61,7 +61,7 @@ public class AtTests extends Common {
|
||||
public void testCompile() {
|
||||
Account deployer = Common.getTestAccount(null, "chloe");
|
||||
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount);
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount);
|
||||
System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class AtTests extends Common {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testAutomaticOfferRefund() throws DataException {
|
||||
public void testOfferCancel() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert");
|
||||
@@ -128,15 +128,29 @@ public class AtTests extends Common {
|
||||
BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee);
|
||||
|
||||
checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
// Send creator's address to AT
|
||||
byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress);
|
||||
BigDecimal messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// Refund should happen 1st block after receiving recipient address
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
BigDecimal expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
BigDecimal expectedMaximumBalance = deployersInitialBalance.subtract(deployAtFee).subtract(messageFee);
|
||||
|
||||
BigDecimal actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance.toPlainString(), expectedMinimumBalance.toPlainString()), actualBalance.compareTo(expectedMinimumBalance) > 0);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance.toPlainString(), expectedMaximumBalance.toPlainString()), actualBalance.compareTo(expectedMaximumBalance) < 0);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
BigDecimal expectedBalance = deployersPostDeploymentBalance;
|
||||
BigDecimal actualBalance = deployer.getBalance(Asset.QORT);
|
||||
BigDecimal expectedBalance = deployersPostDeploymentBalance.subtract(messageFee);
|
||||
actualBalance = deployer.getBalance(Asset.QORT);
|
||||
|
||||
Common.assertEqualBigDecimals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
@@ -237,7 +251,7 @@ public class AtTests extends Common {
|
||||
BigDecimal messageFee = messageTransaction.getTransactionData().getFee();
|
||||
BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee).subtract(messageFee);
|
||||
|
||||
checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
@@ -340,7 +354,7 @@ public class AtTests extends Common {
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +396,7 @@ public class AtTests extends Common {
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +435,7 @@ public class AtTests extends Common {
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException {
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount);
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount);
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
byte[] lastReference = deployer.getLastReference();
|
||||
@@ -475,7 +489,7 @@ public class AtTests extends Common {
|
||||
return messageTransaction;
|
||||
}
|
||||
|
||||
private void checkAtRefund(Repository repository, Account deployer, BigDecimal deployersInitialBalance, BigDecimal deployAtFee) throws DataException {
|
||||
private void checkTradeRefund(Repository repository, Account deployer, BigDecimal deployersInitialBalance, BigDecimal deployAtFee) throws DataException {
|
||||
BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee);
|
||||
|
||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
||||
@@ -520,7 +534,6 @@ public class AtTests extends Common {
|
||||
+ "\tinitial payout: %s QORT,\n"
|
||||
+ "\tredeem payout: %s QORT,\n"
|
||||
+ "\texpected bitcoin: %s BTC,\n"
|
||||
+ "\toffer timeout: %d minutes (from creation),\n"
|
||||
+ "\ttrade timeout: %d minutes (from trade start),\n"
|
||||
+ "\tcurrent block height: %d,\n",
|
||||
tradeData.qortalAddress,
|
||||
@@ -531,18 +544,17 @@ public class AtTests extends Common {
|
||||
tradeData.initialPayout.toPlainString(),
|
||||
tradeData.redeemPayout.toPlainString(),
|
||||
tradeData.expectedBitcoin.toPlainString(),
|
||||
tradeData.offerRefundTimeout,
|
||||
tradeData.tradeRefundTimeout,
|
||||
currentBlockHeight));
|
||||
|
||||
// Are we in 'offer' or 'trade' stage?
|
||||
if (tradeData.tradeRefundHeight == null) {
|
||||
// Offer
|
||||
System.out.println(String.format("\toffer timeout: block %d",
|
||||
tradeData.offerRefundHeight));
|
||||
System.out.println(String.format("\tstatus: 'offer mode'"));
|
||||
} else {
|
||||
// Trade
|
||||
System.out.println(String.format("\ttrade timeout: block %d,\n"
|
||||
System.out.println(String.format("\tstatus: 'trade mode',\n"
|
||||
+ "\ttrade timeout: block %d,\n"
|
||||
+ "\ttrade recipient: %s",
|
||||
tradeData.tradeRefundHeight,
|
||||
tradeData.qortalRecipient));
|
||||
|
@@ -54,7 +54,7 @@ public class BuildP2SH {
|
||||
Address redeemBitcoinAddress = null;
|
||||
byte[] secretHash = null;
|
||||
int lockTime = 0;
|
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE;
|
||||
Coin bitcoinFee = Common.DEFAULT_BTC_FEE;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
@@ -74,8 +74,8 @@ public class BuildP2SH {
|
||||
|
||||
lockTime = Integer.parseInt(args[argIndex++]);
|
||||
int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L);
|
||||
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 7 * 24 * 60 * 60)
|
||||
usage("Locktime (seconds) should be at between 10 minutes and 1 week from now");
|
||||
if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60)
|
||||
usage("Locktime (seconds) should be at between 10 minutes and 1 month from now");
|
||||
|
||||
if (args.length > argIndex)
|
||||
bitcoinFee = Coin.parseCoin(args[argIndex++]);
|
||||
|
@@ -58,7 +58,7 @@ public class CheckP2SH {
|
||||
Address redeemBitcoinAddress = null;
|
||||
byte[] secretHash = null;
|
||||
int lockTime = 0;
|
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE;
|
||||
Coin bitcoinFee = Common.DEFAULT_BTC_FEE;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
|
9
src/test/java/org/qortal/test/btcacct/Common.java
Normal file
9
src/test/java/org/qortal/test/btcacct/Common.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.qortal.test.btcacct;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
public abstract class Common {
|
||||
|
||||
public static final Coin DEFAULT_BTC_FEE = Coin.parseCoin("0.00001000");
|
||||
|
||||
}
|
@@ -34,19 +34,20 @@ 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> <HASH160-of-secret> [<initial QORT payout> [<AT funding amount>]]"));
|
||||
System.err.println(String.format("usage: DeployAT <your Qortal PRIVATE key> <QORT amount> <BTC amount> <HASH160-of-secret> <initial QORT payout> <AT funding amount> <AT trade timeout>"));
|
||||
System.err.println(String.format("example: DeployAT "
|
||||
+ "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n"
|
||||
+ "\t80.4020 \\\n"
|
||||
+ "\t0.00864200 \\\n"
|
||||
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
|
||||
+ "\t0.0001 \\\n"
|
||||
+ "\t123.456"));
|
||||
+ "\t123.456 \\\n"
|
||||
+ "\t10"));
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 5 || args.length > 7)
|
||||
if (args.length != 8)
|
||||
usage(null);
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
@@ -58,6 +59,7 @@ public class DeployAT {
|
||||
byte[] secretHash = null;
|
||||
BigDecimal initialPayout = BigDecimal.ZERO.setScale(8);
|
||||
BigDecimal fundingAmount = null;
|
||||
int tradeTimeout = 0;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
@@ -77,15 +79,15 @@ public class DeployAT {
|
||||
if (secretHash.length != 20)
|
||||
usage("Hash of secret must be 20 bytes");
|
||||
|
||||
if (args.length > argIndex)
|
||||
initialPayout = new BigDecimal(args[argIndex++]).setScale(8);
|
||||
initialPayout = new BigDecimal(args[argIndex++]).setScale(8);
|
||||
|
||||
if (args.length > argIndex) {
|
||||
fundingAmount = new BigDecimal(args[argIndex++]).setScale(8);
|
||||
fundingAmount = new BigDecimal(args[argIndex++]).setScale(8);
|
||||
if (fundingAmount.compareTo(redeemAmount) <= 0)
|
||||
usage("AT funding amount must be greater than QORT redeem amount");
|
||||
|
||||
if (fundingAmount.compareTo(redeemAmount) <= 0)
|
||||
usage("AT funding amount must be greater than QORT redeem amount");
|
||||
}
|
||||
tradeTimeout = Integer.parseInt(args[argIndex++]);
|
||||
if (tradeTimeout < 10 || tradeTimeout > 50000)
|
||||
usage("AT trade timeout should be between 10 and 50,000 minutes");
|
||||
} catch (IllegalArgumentException e) {
|
||||
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
|
||||
}
|
||||
@@ -105,17 +107,12 @@ public class DeployAT {
|
||||
|
||||
System.out.println(String.format("QORT redeem amount: %s", redeemAmount.toPlainString()));
|
||||
|
||||
if (fundingAmount == null)
|
||||
fundingAmount = redeemAmount.add(atFundingExtra);
|
||||
System.out.println(String.format("AT funding amount: %s", fundingAmount.toPlainString()));
|
||||
|
||||
System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash)));
|
||||
|
||||
// Deploy AT
|
||||
final int offerTimeout = 2 * 60; // minutes
|
||||
final int tradeTimeout = 60; // minutes
|
||||
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, offerTimeout, tradeTimeout, initialPayout, fundingAmount, expectedBitcoin);
|
||||
byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, tradeTimeout, initialPayout, redeemAmount, expectedBitcoin);
|
||||
System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
@@ -133,7 +130,7 @@ public class DeployAT {
|
||||
String tags = "QORT-BTC ACCT";
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, refundAccount.getPublicKey(), fee, null);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, redeemAmount, Asset.QORT);
|
||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||
|
||||
Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
|
||||
|
@@ -64,7 +64,7 @@ public class Redeem {
|
||||
byte[] redeemPrivateKey = null;
|
||||
byte[] secret = null;
|
||||
int lockTime = 0;
|
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE;
|
||||
Coin bitcoinFee = Common.DEFAULT_BTC_FEE;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
|
@@ -64,7 +64,7 @@ public class Refund {
|
||||
Address redeemBitcoinAddress = null;
|
||||
byte[] secretHash = null;
|
||||
int lockTime = 0;
|
||||
Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE;
|
||||
Coin bitcoinFee = Common.DEFAULT_BTC_FEE;
|
||||
|
||||
int argIndex = 0;
|
||||
try {
|
||||
|
Reference in New Issue
Block a user