diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 0c61c83b..40654179 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -1011,6 +1011,9 @@ public class CrossChainResource { if (tradeBotCreateRequest.tradeTimeout < 60) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + if (tradeBotCreateRequest.bitcoinAmount <= 0 || tradeBotCreateRequest.qortAmount <= 0 || tradeBotCreateRequest.fundingQortAmount <= 0) + 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); diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java index 74bec0bc..496125b4 100644 --- a/src/main/java/org/qortal/controller/TradeBot.java +++ b/src/main/java/org/qortal/controller/TradeBot.java @@ -163,7 +163,7 @@ public class TradeBot { String atAddress = deployAtTransactionData.getAtAddress(); TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.BOB_WAITING_FOR_AT_CONFIRM, - atAddress, + creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secretB, hashOfSecretB, tradeForeignPublicKey, tradeForeignPublicKeyHash, @@ -237,7 +237,7 @@ public class TradeBot { int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (NTP.getTime() / 1000L); TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, TradeBotData.State.ALICE_WAITING_FOR_P2SH_A, - crossChainTradeData.qortalAtAddress, + receivingAddress, crossChainTradeData.qortalAtAddress, NTP.getTime(), crossChainTradeData.qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secretA, hashOfSecretA, tradeForeignPublicKey, tradeForeignPublicKeyHash, @@ -380,6 +380,7 @@ public class TradeBot { return; tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_MESSAGE); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -418,6 +419,7 @@ public class TradeBot { if (atData.getIsFinished()) { // No point sending MESSAGE - might as well wait for refund tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -456,6 +458,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.ALICE_WAITING_FOR_AT_LOCK); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -493,6 +496,7 @@ public class TradeBot { // If AT has finished then Bob likely cancelled his trade offer if (atData.getIsFinished()) { tradeBotData.setState(TradeBotData.State.BOB_REFUNDED); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -563,6 +567,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_P2SH_B); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -577,6 +582,7 @@ public class TradeBot { // Don't resave if we don't need to if (tradeBotData.getLastTransactionSignature() != originalLastTransactionSignature) { + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); notifyStateChange(tradeBotData); @@ -608,6 +614,7 @@ public class TradeBot { // Refund P2SH-A if AT finished (i.e. Bob cancelled trade) or we've passed lockTime-A if (atData.getIsFinished() || NTP.getTime() >= tradeBotData.getLockTimeA() * 1000L) { tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -643,6 +650,7 @@ public class TradeBot { // There's no P2SH-B at this point, so jump straight to refunding P2SH-A tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -700,6 +708,7 @@ public class TradeBot { // P2SH-B funded, now we wait for Bob to redeem it tradeBotData.setState(TradeBotData.State.ALICE_WATCH_P2SH_B); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -730,6 +739,7 @@ public class TradeBot { // If we've passed AT refund timestamp then AT will have finished after auto-refunding if (atData.getIsFinished()) { tradeBotData.setState(TradeBotData.State.BOB_REFUNDED); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -771,6 +781,7 @@ public class TradeBot { // P2SH-B redeemed, now we wait for Alice to use secret-A to redeem AT tradeBotData.setState(TradeBotData.State.BOB_WAITING_FOR_AT_REDEEM); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -808,6 +819,7 @@ public class TradeBot { // Refund P2SH-B if we've passed lockTime-B if (NTP.getTime() >= crossChainTradeData.lockTimeB * 1000L) { tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_B); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -849,6 +861,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.ALICE_DONE); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -896,6 +909,7 @@ public class TradeBot { // We check variable in AT that is set when trade successfully completes if (crossChainTradeData.mode != BTCACCT.Mode.REDEEMED) { tradeBotData.setState(TradeBotData.State.BOB_REFUNDED); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -930,6 +944,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.BOB_DONE); + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -973,7 +988,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.ALICE_REFUNDING_A); - + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); @@ -1018,7 +1033,7 @@ public class TradeBot { } tradeBotData.setState(TradeBotData.State.ALICE_REFUNDED); - + tradeBotData.setTimestamp(NTP.getTime()); repository.getCrossChainRepository().save(tradeBotData); repository.saveChanges(); diff --git a/src/main/java/org/qortal/data/crosschain/TradeBotData.java b/src/main/java/org/qortal/data/crosschain/TradeBotData.java index 6544999b..0f57845d 100644 --- a/src/main/java/org/qortal/data/crosschain/TradeBotData.java +++ b/src/main/java/org/qortal/data/crosschain/TradeBotData.java @@ -35,8 +35,14 @@ public class TradeBotData { } private State tradeState; + private String creatorAddress; private String atAddress; + private long timestamp; + + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + private long qortAmount; + private byte[] tradeNativePublicKey; private byte[] tradeNativePublicKeyHash; String tradeNativeAddress; @@ -66,14 +72,18 @@ public class TradeBotData { /* JAXB */ } - public TradeBotData(byte[] tradePrivateKey, State tradeState, String atAddress, + public TradeBotData(byte[] tradePrivateKey, State tradeState, String creatorAddress, String atAddress, + long timestamp, long qortAmount, byte[] tradeNativePublicKey, byte[] tradeNativePublicKeyHash, String tradeNativeAddress, byte[] secret, byte[] hashOfSecret, byte[] tradeForeignPublicKey, byte[] tradeForeignPublicKeyHash, long bitcoinAmount, String xprv58, byte[] lastTransactionSignature, Integer lockTimeA, byte[] receivingAccountInfo) { this.tradePrivateKey = tradePrivateKey; this.tradeState = tradeState; + this.creatorAddress = creatorAddress; this.atAddress = atAddress; + this.timestamp = timestamp; + this.qortAmount = qortAmount; this.tradeNativePublicKey = tradeNativePublicKey; this.tradeNativePublicKeyHash = tradeNativePublicKeyHash; this.tradeNativeAddress = tradeNativeAddress; @@ -100,6 +110,10 @@ public class TradeBotData { this.tradeState = state; } + public String getCreatorAddress() { + return this.creatorAddress; + } + public String getAtAddress() { return this.atAddress; } @@ -108,6 +122,18 @@ public class TradeBotData { this.atAddress = atAddress; } + public long getTimestamp() { + return this.timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public long getQortAmount() { + return this.qortAmount; + } + public byte[] getTradeNativePublicKey() { return this.tradeNativePublicKey; } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java index 0eb1ef00..589ca0a4 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java @@ -19,7 +19,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { @Override public TradeBotData getTradeBotData(byte[] tradePrivateKey) throws DataException { - String sql = "SELECT trade_state, at_address, " + String sql = "SELECT trade_state, creator_address, at_address, " + + "updated_when, qort_amount, " + "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, " @@ -36,24 +37,27 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { if (tradeState == null) throw new DataException("Illegal trade-bot trade-state fetched from repository"); - String atAddress = resultSet.getString(2); - byte[] tradeNativePublicKey = resultSet.getBytes(3); - byte[] tradeNativePublicKeyHash = resultSet.getBytes(4); - String tradeNativeAddress = resultSet.getString(5); - byte[] secret = resultSet.getBytes(6); - byte[] hashOfSecret = resultSet.getBytes(7); - byte[] tradeForeignPublicKey = resultSet.getBytes(8); - byte[] tradeForeignPublicKeyHash = resultSet.getBytes(9); - long bitcoinAmount = resultSet.getLong(10); - String xprv58 = resultSet.getString(11); - byte[] lastTransactionSignature = resultSet.getBytes(12); - Integer lockTimeA = resultSet.getInt(13); + String creatorAddress = resultSet.getString(2); + String atAddress = resultSet.getString(3); + long timestamp = resultSet.getLong(4); + long qortAmount = resultSet.getLong(5); + byte[] tradeNativePublicKey = resultSet.getBytes(6); + byte[] tradeNativePublicKeyHash = resultSet.getBytes(7); + String tradeNativeAddress = resultSet.getString(8); + byte[] secret = resultSet.getBytes(9); + byte[] hashOfSecret = resultSet.getBytes(10); + byte[] tradeForeignPublicKey = resultSet.getBytes(11); + byte[] tradeForeignPublicKeyHash = resultSet.getBytes(12); + long bitcoinAmount = resultSet.getLong(13); + String xprv58 = resultSet.getString(14); + byte[] lastTransactionSignature = resultSet.getBytes(15); + Integer lockTimeA = resultSet.getInt(16); if (lockTimeA == 0 && resultSet.wasNull()) lockTimeA = null; - byte[] receivingAccountInfo = resultSet.getBytes(14); + byte[] receivingAccountInfo = resultSet.getBytes(17); return new TradeBotData(tradePrivateKey, tradeState, - atAddress, + creatorAddress, atAddress, timestamp, qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secret, hashOfSecret, tradeForeignPublicKey, tradeForeignPublicKeyHash, @@ -65,7 +69,8 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { @Override public List<TradeBotData> getAllTradeBotData() throws DataException { - String sql = "SELECT trade_private_key, trade_state, at_address, " + String sql = "SELECT trade_private_key, trade_state, creator_address, at_address, " + + "updated_when, qort_amount, " + "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, " @@ -85,24 +90,27 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { if (tradeState == null) throw new DataException("Illegal trade-bot trade-state fetched from repository"); - String atAddress = resultSet.getString(3); - byte[] tradeNativePublicKey = resultSet.getBytes(4); - byte[] tradeNativePublicKeyHash = resultSet.getBytes(5); - String tradeNativeAddress = resultSet.getString(6); - byte[] secret = resultSet.getBytes(7); - byte[] hashOfSecret = resultSet.getBytes(8); - byte[] tradeForeignPublicKey = resultSet.getBytes(9); - byte[] tradeForeignPublicKeyHash = resultSet.getBytes(10); - long bitcoinAmount = resultSet.getLong(11); - String xprv58 = resultSet.getString(12); - byte[] lastTransactionSignature = resultSet.getBytes(13); - Integer lockTimeA = resultSet.getInt(14); + String creatorAddress = resultSet.getString(3); + String atAddress = resultSet.getString(4); + long timestamp = resultSet.getLong(5); + long qortAmount = resultSet.getLong(6); + byte[] tradeNativePublicKey = resultSet.getBytes(7); + byte[] tradeNativePublicKeyHash = resultSet.getBytes(8); + String tradeNativeAddress = resultSet.getString(9); + byte[] secret = resultSet.getBytes(10); + byte[] hashOfSecret = resultSet.getBytes(11); + byte[] tradeForeignPublicKey = resultSet.getBytes(12); + byte[] tradeForeignPublicKeyHash = resultSet.getBytes(13); + long bitcoinAmount = resultSet.getLong(14); + String xprv58 = resultSet.getString(15); + byte[] lastTransactionSignature = resultSet.getBytes(16); + Integer lockTimeA = resultSet.getInt(17); if (lockTimeA == 0 && resultSet.wasNull()) lockTimeA = null; - byte[] receivingAccountInfo = resultSet.getBytes(15); + byte[] receivingAccountInfo = resultSet.getBytes(18); TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, tradeState, - atAddress, + creatorAddress, atAddress, timestamp, qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secret, hashOfSecret, tradeForeignPublicKey, tradeForeignPublicKeyHash, @@ -122,7 +130,10 @@ public class HSQLDBCrossChainRepository implements CrossChainRepository { saveHelper.bind("trade_private_key", tradeBotData.getTradePrivateKey()) .bind("trade_state", tradeBotData.getState().value) + .bind("creator_address", tradeBotData.getCreatorAddress()) .bind("at_address", tradeBotData.getAtAddress()) + .bind("updated_when", tradeBotData.getTimestamp()) + .bind("qort_amount", tradeBotData.getQortAmount()) .bind("trade_native_public_key", tradeBotData.getTradeNativePublicKey()) .bind("trade_native_public_key_hash", tradeBotData.getTradeNativePublicKeyHash()) .bind("trade_native_address", tradeBotData.getTradeNativeAddress()) diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 2706bb5d..4c4d5274 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -4,9 +4,14 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.utils.Base58; + +import com.google.common.hash.HashCode; public class HSQLDBDatabaseUpdates { @@ -621,7 +626,7 @@ public class HSQLDBDatabaseUpdates { case 20: // Trade bot stmt.execute("CREATE TABLE TradeBotStates (trade_private_key QortalKeySeed NOT NULL, trade_state TINYINT NOT NULL, " - + "at_address QortalAddress, " + + "creator_address QortalAddress NOT NULL, at_address QortalAddress, updated_when BIGINT NOT NULL, qort_amount QortalAmount NOT NULL, " + "trade_native_public_key QortalPublicKey NOT NULL, trade_native_public_key_hash VARBINARY(32) NOT NULL, " + "trade_native_address QortalAddress NOT NULL, secret VARBINARY(32) NOT NULL, hash_of_secret VARBINARY(32) NOT NULL, " + "trade_foreign_public_key VARBINARY(33) NOT NULL, trade_foreign_public_key_hash VARBINARY(32) NOT NULL, " @@ -641,6 +646,39 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS receiving_account_info VARBINARY(32)"); break; + case 23: + // XXX for testing/dev only - do not merge into 'master' + stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS creator_address QortalAddress BEFORE at_address"); + // Update Bob bot entries + stmt.execute("UPDATE TradeBotStates AS StatesToUpdate " + + "SET (trade_private_key, creator_address) = (" + + "SELECT trade_private_key, accounts.account " + + "FROM TradeBotStates " + + "JOIN ATs USING (at_address) " + + "JOIN Accounts ON Accounts.public_key = ATs.creator " + + "WHERE tradebotstates.trade_private_key = StatesToUpdate.trade_private_key" + + ") WHERE trade_state < 90"); + + stmt.execute("SELECT trade_private_key, receiving_account_info FROM TradeBotStates WHERE trade_state >= 90"); + Map<String, String> aliceAddresses = new HashMap<>(); + try (ResultSet resultSet = stmt.getResultSet()) { + while (resultSet.next()) { + byte[] tradePrivateKey = resultSet.getBytes(1); + byte[] receivingAccountInfo = resultSet.getBytes(2); + + aliceAddresses.put(HashCode.fromBytes(tradePrivateKey).toString(), Base58.encode(receivingAccountInfo)); + } + } + for (Map.Entry<String, String> entry : aliceAddresses.entrySet()) + stmt.execute("UPDATE TradeBotStates SET creator_address = '" + entry.getValue() + "' WHERE trade_private_key = HEXTORAW('" + entry.getKey() + "')"); + stmt.execute("COMMIT"); + + stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS updated_when BIGINT BEFORE trade_native_public_key"); + stmt.execute("UPDATE TradeBotStates SET updated_when = UNIX_TIMESTAMP() * 1000"); + + stmt.execute("ALTER TABLE TradeBotStates ADD COLUMN IF NOT EXISTS qort_amount QortalAmount NOT NULL DEFAULT 12345678 BEFORE trade_native_public_key"); + break; + default: // nothing to do return false;