Browse Source

WIP: trade-bot: Alice P2SH_a progress

Qortal AT now includes suggested tradeTimeout again as a constant so trade partner/recipient can use that to calculate a suitable lockTimeA. CODE_HASH changed!

Renamed some secret_hash to hash_of_secret.

Changed TradeBotStates.trade_state back to TINYINT and adjusted values in TradeBotData.State enum to suit.
Added lockTimeA to TradeBotData & repository.

Added JAXB-only extra representations of Bitcoin PKHs as addresses.

Fixed incorrect expected length in BTCACCT.extractOfferMessageData().

CrossChainTradeData.refundTimeout now only present in TRADE mode.

Added BTC.pkhToAddress().

Added initial TradeBot.handleAliceWaitingForP2shA().

Enforce only one TradeBot thread running using 'activeFlag' atomic boolean.

Replace incorrect SHA256 with HASH160 for hashOfSecretA in TradeBot.startResponse().
split-DB
catbref 4 years ago
parent
commit
f179139967
  1. 3
      src/main/java/org/qortal/api/model/TradeBotCreateRequest.java
  2. 5
      src/main/java/org/qortal/api/resource/CrossChainResource.java
  3. 103
      src/main/java/org/qortal/controller/TradeBot.java
  4. 4
      src/main/java/org/qortal/crosschain/BTC.java
  5. 17
      src/main/java/org/qortal/crosschain/BTCACCT.java
  6. 26
      src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java
  7. 25
      src/main/java/org/qortal/data/crosschain/TradeBotData.java
  8. 16
      src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java
  9. 7
      src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java
  10. 4
      src/test/java/org/qortal/test/btcacct/AtTests.java
  11. 14
      src/test/java/org/qortal/test/btcacct/DeployAT.java

3
src/main/java/org/qortal/api/model/TradeBotCreateRequest.java

@ -24,6 +24,9 @@ public class TradeBotCreateRequest {
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long bitcoinAmount;
@Schema(description = "Suggested trade timeout (minutes)", example = "10080")
public int tradeTimeout;
public TradeBotCreateRequest() {
}

5
src/main/java/org/qortal/api/resource/CrossChainResource.java

@ -183,7 +183,7 @@ public class CrossChainResource {
PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey);
byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.bitcoinPublicKeyHash, tradeRequest.hashOfSecretB,
tradeRequest.qortAmount, tradeRequest.bitcoinAmount);
tradeRequest.qortAmount, tradeRequest.bitcoinAmount, tradeRequest.tradeTimeout);
long txTimestamp = NTP.getTime();
byte[] lastReference = creatorAccount.getLastReference();
@ -866,6 +866,9 @@ public class CrossChainResource {
)
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public String tradeBotCreator(TradeBotCreateRequest tradeBotCreateRequest) {
if (tradeBotCreateRequest.tradeTimeout < 600)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
try (final Repository repository = RepositoryManager.getRepository()) {
// Do some simple checking first
Account creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);

103
src/main/java/org/qortal/controller/TradeBot.java

@ -4,13 +4,11 @@ import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.api.model.TradeBotCreateRequest;
@ -43,8 +41,10 @@ public class TradeBot {
private static TradeBot instance;
/** To help ensure only TradeBot is only active on one thread. */
private AtomicBoolean activeFlag = new AtomicBoolean(false);
private TradeBot() {
}
public static synchronized TradeBot getInstance() {
@ -79,7 +79,7 @@ public class TradeBot {
String description = "QORT/BTC cross-chain trade";
String aTType = "ACCT";
String tags = "ACCT QORT BTC";
byte[] creationBytes = BTCACCT.buildQortalAT(tradeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount, tradeBotCreateRequest.bitcoinAmount);
byte[] creationBytes = BTCACCT.buildQortalAT(tradeAddress, tradeForeignPublicKeyHash, hashOfSecretB, tradeBotCreateRequest.qortAmount, tradeBotCreateRequest.bitcoinAmount, tradeBotCreateRequest.tradeTimeout);
long amount = tradeBotCreateRequest.fundingQortAmount;
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
@ -95,7 +95,7 @@ public class TradeBot {
atAddress,
tradeNativePublicKey, tradeNativePublicKeyHash, secretB, hashOfSecretB,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
tradeBotCreateRequest.bitcoinAmount, null);
tradeBotCreateRequest.bitcoinAmount, null, null);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
@ -108,12 +108,9 @@ public class TradeBot {
}
public static String startResponse(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
BTC btc = BTC.getInstance();
NetworkParameters params = btc.getNetworkParameters();
byte[] tradePrivateKey = generateTradePrivateKey();
byte[] secret = generateSecret();
byte[] secretHash = Crypto.digest(secret);
byte[] secretA = generateSecret();
byte[] hashOfSecretA = Crypto.hash160(secretA);
byte[] tradeNativePublicKey = deriveTradeNativePublicKey(tradePrivateKey);
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
@ -121,20 +118,20 @@ public class TradeBot {
byte[] tradeForeignPublicKey = deriveTradeForeignPublicKey(tradePrivateKey);
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
// We need to generate lockTimeA: halfway of refundTimeout from now
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L);
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A,
crossChainTradeData.qortalAtAddress,
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
tradeNativePublicKey, tradeNativePublicKeyHash, secretA, hashOfSecretA,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
crossChainTradeData.expectedBitcoin, null);
crossChainTradeData.expectedBitcoin, null, lockTimeA);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
// P2SH_a to be funded
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, crossChainTradeData.lockTimeA, crossChainTradeData.creatorBitcoinPKH, secretHash);
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);
return p2shAddress.toString();
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorBitcoinPKH, hashOfSecretA);
return BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
}
private static byte[] generateTradePrivateKey() {
@ -158,11 +155,17 @@ public class TradeBot {
}
public void onChainTipChange() {
if (!activeFlag.compareAndSet(false, true))
// Trade bot already active on another thread
return;
// Get repo for trade situations
try (final Repository repository = RepositoryManager.getRepository()) {
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
for (TradeBotData tradeBotData : allTradeBotData)
for (TradeBotData tradeBotData : allTradeBotData) {
repository.discardChanges();
switch (tradeBotData.getState()) {
case BOB_WAITING_FOR_AT_CONFIRM:
handleBobWaitingForAtConfirm(repository, tradeBotData);
@ -172,11 +175,18 @@ public class TradeBot {
handleBobWaitingForMessage(repository, tradeBotData);
break;
case ALICE_WAITING_FOR_P2SH_A:
handleAliceWaitingForP2shA(repository, tradeBotData);
break;
default:
LOGGER.warn(() -> String.format("Unhandled trade-bot state %s", tradeBotData.getState().name()));
}
}
} catch (DataException e) {
LOGGER.error("Couldn't run trade bot due to repository issue", e);
} finally {
activeFlag.set(false);
}
}
@ -200,10 +210,12 @@ public class TradeBot {
String address = Crypto.toAddress(tradeBotData.getTradeNativePublicKey());
List<MessageTransactionData> messageTransactionsData = repository.getTransactionRepository().getMessagesByRecipient(address, null, null, null);
final byte[] originalLastTransactionSignature = tradeBotData.getLastTransactionSignature();
// Skip past previously processed messages
if (tradeBotData.getLastTransactionSignature() != null)
if (originalLastTransactionSignature != null)
for (int i = 0; i < messageTransactionsData.size(); ++i)
if (Arrays.equals(messageTransactionsData.get(i).getSignature(), tradeBotData.getLastTransactionSignature())) {
if (Arrays.equals(messageTransactionsData.get(i).getSignature(), originalLastTransactionSignature)) {
messageTransactionsData.subList(0, i + 1).clear();
break;
}
@ -248,17 +260,62 @@ public class TradeBot {
outgoingMessageTransaction.computeNonce();
outgoingMessageTransaction.sign(sender);
// reset repository state to prevent deadlock
repository.discardChanges();
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
if (result != ValidationResult.OK) {
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", tradeBotData.getAtAddress(), result.name()));
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", outgoingMessageTransaction.getRecipient(), result.name()));
return;
}
tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_P2SH_B);
break;
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
return;
}
// Don't resave if we don't need to
if (tradeBotData.getLastTransactionSignature() != originalLastTransactionSignature) {
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
}
}
private void handleAliceWaitingForP2shA(Repository repository, TradeBotData tradeBotData) throws DataException {
ATData atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
if (atData == null) {
LOGGER.warn(() -> String.format("Unable to fetch trade AT '%s' from repository", tradeBotData.getAtAddress()));
return;
}
CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData);
byte[] redeemScriptBytes = BTCP2SH.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getLockTimeA(), crossChainTradeData.creatorBitcoinPKH, tradeBotData.getHashOfSecret());
String p2shAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
Long balance = BTC.getInstance().getBalance(p2shAddress);
if (balance == null || balance < crossChainTradeData.expectedBitcoin)
return;
// Attempt to send MESSAGE to Bob's Qortal trade address
byte[] messageData = BTCACCT.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA());
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, crossChainTradeData.qortalCreatorTradeAddress, messageData, false, false);
messageTransaction.computeNonce();
messageTransaction.sign(sender);
// reset repository state to prevent deadlock
repository.discardChanges();
ValidationResult result = messageTransaction.importAsUnconfirmed();
if (result != ValidationResult.OK) {
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT '%s': %s", messageTransaction.getRecipient(), result.name()));
return;
}
tradeBotData.setState(TradeBotData.State.ALICE_WAITING_FOR_AT_LOCK);
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
}

4
src/main/java/org/qortal/crosschain/BTC.java

@ -98,6 +98,10 @@ public class BTC {
return format(Coin.valueOf(amount));
}
public String pkhToAddress(byte[] publicKeyHash) {
return LegacyAddress.fromPubKeyHash(this.params, publicKeyHash).toString();
}
public String deriveP2shAddress(byte[] redeemScriptBytes) {
byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes);
Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash);

17
src/main/java/org/qortal/crosschain/BTCACCT.java

@ -88,7 +88,7 @@ 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("ca0dc643fdaba4d12cd5550800a8353746f40a0d9824d8c10d8b4bd0324eac0d").asBytes(); // SHA256 of AT code bytes
public static final byte[] CODE_BYTES_HASH = HashCode.fromString("14ee2cb9899f582037901c384bab9ccdd41e48d8c98bf7df5cf79f4e8c236286").asBytes(); // SHA256 of AT code bytes
public static class OfferMessageData {
public byte[] recipientBitcoinPKH;
@ -110,9 +110,10 @@ public class BTCACCT {
* @param hashOfSecretB 20-byte HASH160 of 32-byte secret-B
* @param qortAmount how much QORT to pay trade partner if they send correct 32-byte secrets to AT
* @param bitcoinAmount how much BTC the AT creator is expecting to trade
* @param tradeTimeout suggested timeout for entire trade
* @return
*/
public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount) {
public static byte[] buildQortalAT(String creatorTradeAddress, byte[] bitcoinPublicKeyHash, byte[] hashOfSecretB, long qortAmount, long bitcoinAmount, int tradeTimeout) {
// Labels for data segment addresses
int addrCounter = 0;
@ -131,6 +132,7 @@ public class BTCACCT {
final int addrQortAmount = addrCounter++;
final int addrBitcoinAmount = addrCounter++;
final int addrTradeTimeout = addrCounter++;
final int addrMessageTxType = addrCounter++;
final int addrExpectedOfferMessageLength = addrCounter++;
@ -216,6 +218,10 @@ public class BTCACCT {
assert dataByteBuffer.position() == addrBitcoinAmount * MachineState.VALUE_SIZE : "addrBitcoinAmount incorrect";
dataByteBuffer.putLong(bitcoinAmount);
// Suggested trade timeout (minutes)
assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect";
dataByteBuffer.putLong(tradeTimeout);
// We're only interested in MESSAGE transactions
assert dataByteBuffer.position() == addrMessageTxType * MachineState.VALUE_SIZE : "addrMessageTxType incorrect";
dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value);
@ -565,6 +571,8 @@ public class BTCACCT {
// Expected BTC amount
tradeData.expectedBitcoin = dataByteBuffer.getLong();
tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
// Skip MESSAGE transaction type
dataByteBuffer.position(dataByteBuffer.position() + 8);
@ -624,7 +632,7 @@ public class BTCACCT {
int lockTimeB = (int) dataByteBuffer.getLong();
// AT refund timeout (probably only useful for debugging)
tradeData.refundTimeout = (int) dataByteBuffer.getLong();
int refundTimeout = (int) dataByteBuffer.getLong();
// Trade offer timeout (AT 'timestamp' converted to Qortal block height)
long tradeRefundTimestamp = dataByteBuffer.getLong();
@ -664,6 +672,7 @@ public class BTCACCT {
if (mode != 0) {
tradeData.mode = CrossChainTradeData.Mode.TRADE;
tradeData.refundTimeout = refundTimeout;
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
tradeData.qortalRecipient = qortalRecipient;
tradeData.hashOfSecretA = hashOfSecretA;
@ -685,7 +694,7 @@ public class BTCACCT {
/** Returns trade-info extracted from MESSAGE payload sent by trade partner/recipient, or null if not valid. */
public static OfferMessageData extractOfferMessageData(byte[] messageData) {
if (messageData == null || messageData.length != 32 + 32 + 8)
if (messageData == null || messageData.length != 20 + 20 + 8)
return null;
OfferMessageData offerMessageData = new OfferMessageData();

26
src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java

@ -2,8 +2,11 @@ package org.qortal.data.crosschain;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.crosschain.BTC;
import io.swagger.v3.oas.annotations.media.Schema;
// All properties to be converted to JSON via JAXB
@ -29,6 +32,9 @@ public class CrossChainTradeData {
@Schema(description = "Timestamp when AT was created (milliseconds since epoch)")
public long creationTimestamp;
@Schema(description = "Suggested trade timeout (minutes)", example = "10080")
public int tradeTimeout;
@Schema(description = "AT's current QORT balance")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long qortBalance;
@ -76,4 +82,24 @@ public class CrossChainTradeData {
public CrossChainTradeData() {
}
// We can represent BitcoinPKH as an address
@XmlElement(name = "creatorBitcoinAddress")
@Schema(description = "AT creator's Bitcoin PKH in address form")
public String getCreatorBitcoinAddress() {
if (this.creatorBitcoinPKH == null)
return null;
return BTC.getInstance().pkhToAddress(this.creatorBitcoinPKH);
}
// We can represent BitcoinPKH as an address
@XmlElement(name = "recipientBitcoinAddress")
@Schema(description = "Trade partner's Bitcoin PKH in address form")
public String getRecipientBitcoinAddress() {
if (this.recipientBitcoinPKH == null)
return null;
return BTC.getInstance().pkhToAddress(this.recipientBitcoinPKH);
}
}

25
src/main/java/org/qortal/data/crosschain/TradeBotData.java

@ -22,7 +22,7 @@ public class TradeBotData {
public enum State {
BOB_WAITING_FOR_AT_CONFIRM(10), BOB_WAITING_FOR_MESSAGE(20), BOB_SENDING_MESSAGE_TO_AT(30), BOB_WAITING_FOR_P2SH_B(40), BOB_WAITING_FOR_AT_REDEEM(50),
ALICE_WAITING_FOR_P2SH_A(110), ALICE_WAITING_FOR_AT_LOCK(120), ALICE_WATCH_P2SH_B(130);
ALICE_WAITING_FOR_P2SH_A(80), ALICE_WAITING_FOR_AT_LOCK(90), ALICE_WATCH_P2SH_B(100);
public final int value;
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
@ -43,7 +43,7 @@ public class TradeBotData {
private byte[] tradeNativePublicKeyHash;
private byte[] secret;
private byte[] secretHash;
private byte[] hashOfSecret;
private byte[] tradeForeignPublicKey;
private byte[] tradeForeignPublicKeyHash;
@ -52,21 +52,24 @@ public class TradeBotData {
private byte[] lastTransactionSignature;
private Integer lockTimeA;
public TradeBotData(byte[] tradePrivateKey, State tradeState, String atAddress,
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, byte[] secret, byte[] secretHash,
byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, byte[] secret, byte[] hashOfSecret,
byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash,
long bitcoinAmount, byte[] lastTransactionSignature) {
long bitcoinAmount, byte[] lastTransactionSignature, Integer lockTimeA) {
this.tradePrivateKey = tradePrivateKey;
this.tradeState = tradeState;
this.atAddress = atAddress;
this.tradeNativePublicKey = tradeNativePublicKey;
this.tradeNativePublicKeyHash = tradeNativePublicKeyHash;
this.secret = secret;
this.secretHash = secretHash;
this.hashOfSecret = hashOfSecret;
this.tradeForeignPublicKey = tradeForeignPublicKey;
this.tradeForeignPublicKeyHash = tradeForeignPublicKeyHash;
this.bitcoinAmount = bitcoinAmount;
this.lastTransactionSignature = lastTransactionSignature;
this.lockTimeA = lockTimeA;
}
public byte[] getTradePrivateKey() {
@ -101,8 +104,8 @@ public class TradeBotData {
return this.secret;
}
public byte[] getSecretHash() {
return this.secretHash;
public byte[] getHashOfSecret() {
return this.hashOfSecret;
}
public byte[] getTradeForeignPublicKey() {
@ -125,4 +128,12 @@ public class TradeBotData {
this.lastTransactionSignature = lastTransactionSignature;
}
public Integer getLockTimeA() {
return this.lockTimeA;
}
public void setLockTimeA(Integer lockTimeA) {
this.lockTimeA = lockTimeA;
}
}

16
src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java

@ -21,9 +21,9 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
public List<TradeBotData> getAllTradeBotData() throws DataException {
String sql = "SELECT trade_private_key, trade_state, at_address, "
+ "trade_native_public_key, trade_native_public_key_hash, "
+ "secret, secret_hash, "
+ "secret, hash_of_secret, "
+ "trade_foreign_public_key, trade_foreign_public_key_hash, "
+ "bitcoin_amount, last_transaction_signature "
+ "bitcoin_amount, last_transaction_signature, locktime_a "
+ "FROM TradeBotStates";
List<TradeBotData> allTradeBotData = new ArrayList<>();
@ -43,17 +43,20 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
byte[] tradeNativePublicKey = resultSet.getBytes(4);
byte[] tradeNativePublicKeyHash = resultSet.getBytes(5);
byte[] secret = resultSet.getBytes(6);
byte[] secretHash = resultSet.getBytes(7);
byte[] hashOfSecret = resultSet.getBytes(7);
byte[] tradeForeignPublicKey = resultSet.getBytes(8);
byte[] tradeForeignPublicKeyHash = resultSet.getBytes(9);
long bitcoinAmount = resultSet.getLong(10);
byte[] lastTransactionSignature = resultSet.getBytes(11);
Integer lockTimeA = resultSet.getInt(12);
if (lockTimeA == 0 && resultSet.wasNull())
lockTimeA = null;
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState,
atAddress,
tradeNativePublicKey, tradeNativePublicKeyHash, secret, secretHash,
tradeNativePublicKey, tradeNativePublicKeyHash, secret, hashOfSecret,
tradeForeignPublicKey, tradeForeignPublicKeyHash,
bitcoinAmount, lastTransactionSignature);
bitcoinAmount, lastTransactionSignature, lockTimeA);
allTradeBotData.add(tradeBotData);
} while (resultSet.next());
@ -70,9 +73,10 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository {
saveHelper.bind("trade_private_key", tradeBotData.getTradePrivateKey())
.bind("trade_state", tradeBotData.getState().value)
.bind("at_address", tradeBotData.getAtAddress())
.bind("locktime_a", tradeBotData.getLockTimeA())
.bind("trade_native_public_key", tradeBotData.getTradeNativePublicKey())
.bind("trade_native_public_key_hash", tradeBotData.getTradeNativePublicKeyHash())
.bind("secret", tradeBotData.getSecret()).bind("secret_hash", tradeBotData.getSecretHash())
.bind("secret", tradeBotData.getSecret()).bind("hash_of_secret", tradeBotData.getHashOfSecret())
.bind("trade_foreign_public_key", tradeBotData.getTradeForeignPublicKey())
.bind("trade_foreign_public_key_hash", tradeBotData.getTradeForeignPublicKeyHash())
.bind("bitcoin_amount", tradeBotData.getBitcoinAmount())

7
src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java

@ -620,12 +620,13 @@ public class HSQLDBDatabaseUpdates {
case 20:
// Trade bot
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state SMALLINT NOT NULL, "
stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state TINYINT NOT NULL, "
+ "at_address QortalAddress, "
+ "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, "
+ "secret VARBINARY(32) NOT NULL, secret_hash VARBINARY(32) 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, last_transaction_signature Signature, PRIMARY KEY (trade_private_key))");
+ "bitcoin_amount BIGINT NOT NULL, last_transaction_signature Signature, locktime_a BIGINT, "
+ "PRIMARY KEY (trade_private_key))");
break;
default:

4
src/test/java/org/qortal/test/btcacct/AtTests.java

@ -63,7 +63,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);
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
}
@ -526,7 +526,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);
byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = deployer.getLastReference();

14
src/test/java/org/qortal/test/btcacct/DeployAT.java

@ -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> <your Bitcoin PKH> <HASH160-of-secret> <AT funding amount>"));
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"
+ "\t0.00864200 \\\n"
+ "\t750b06757a2448b8a4abebaa6e4662833fd5ddbb \\\n"
+ "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n"
+ "\t123.456"));
+ "\t123.456 \\\n"
+ "\t10080"));
System.exit(1);
}
public static void main(String[] args) {
if (args.length != 6)
if (args.length != 7)
usage(null);
Security.insertProviderAt(new BouncyCastleProvider(), 0);
@ -58,6 +59,7 @@ public class DeployAT {
byte[] bitcoinPublicKeyHash = null;
byte[] secretHash = null;
long fundingAmount = 0;
int tradeTimeout = 0;
int argIndex = 0;
try {
@ -84,6 +86,10 @@ public class DeployAT {
fundingAmount = Long.parseLong(args[argIndex++]);
if (fundingAmount <= redeemAmount)
usage("AT funding amount must be greater than QORT redeem amount");
tradeTimeout = Integer.parseInt(args[argIndex++]);
if (tradeTimeout < 60 || tradeTimeout > 50000)
usage("Trade timeout (minutes) must be between 60 and 50000");
} catch (IllegalArgumentException e) {
usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage()));
}
@ -108,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);
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…
Cancel
Save