From 5e32b48776398927a7a3bf988a6e08fc94d3f2e7 Mon Sep 17 00:00:00 2001 From: QuickMythril Date: Thu, 8 Jan 2026 02:47:02 -0500 Subject: [PATCH] Allow same-node Alice tradebot entries for AT offers --- .../qortal/controller/tradebot/TradeBot.java | 4 +- .../repository/CrossChainRepository.java | 3 + .../hsqldb/HSQLDBCrossChainRepository.java | 33 +++++++- .../test/CrossChainRepositoryTests.java | 84 +++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/qortal/test/CrossChainRepositoryTests.java diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index c17e57589..f47ecaa86 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -218,7 +218,7 @@ public ResponseResult startResponse(Repository repository, ATData atData, ACCT a } // Check Alice doesn't already have an existing, on-going trade-bot entry for this AT. - if (repository.getCrossChainRepository().existsTradeWithAtExcludingStates(atData.getATAddress(), acctTradeBot.getEndStates())) + if (repository.getCrossChainRepository().existsAliceTradeWithAtExcludingStates(atData.getATAddress(), acctTradeBot.getEndStates())) return ResponseResult.TRADE_ALREADY_EXISTS; return acctTradeBot.startResponse(repository, atData, acct, crossChainTradeData, foreignKey, receivingAddress); @@ -253,7 +253,7 @@ public ResponseResult startResponseMultiple( for( CrossChainTradeData tradeData : crossChainTradeDataList) { // Check Alice doesn't already have an existing, on-going trade-bot entry for this AT. - if (repository.getCrossChainRepository().existsTradeWithAtExcludingStates(tradeData.qortalAtAddress, acctTradeBot.getEndStates())) + if (repository.getCrossChainRepository().existsAliceTradeWithAtExcludingStates(tradeData.qortalAtAddress, acctTradeBot.getEndStates())) return ResponseResult.TRADE_ALREADY_EXISTS; } return TradeBotUtils.startResponseMultiple(repository, acct, crossChainTradeDataList, receiveAddress, foreignKey, bitcoiny); diff --git a/src/main/java/org/qortal/repository/CrossChainRepository.java b/src/main/java/org/qortal/repository/CrossChainRepository.java index 405fdfb57..ac2be5a7d 100644 --- a/src/main/java/org/qortal/repository/CrossChainRepository.java +++ b/src/main/java/org/qortal/repository/CrossChainRepository.java @@ -11,6 +11,9 @@ public interface CrossChainRepository { /** Returns true if there is an existing trade-bot entry relating to given AT address, excluding trade-bot entries with given states. */ public boolean existsTradeWithAtExcludingStates(String atAddress, List excludeStates) throws DataException; + /** Returns true if there is an existing Alice-side trade-bot entry relating to given AT address, excluding trade-bot entries with given states. */ + public boolean existsAliceTradeWithAtExcludingStates(String atAddress, List excludeStates) throws DataException; + public List getAllTradeBotData() throws DataException; public void save(TradeBotData tradeBotData) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java index 6305a468a..02885277b 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCrossChainRepository.java @@ -99,6 +99,37 @@ public boolean existsTradeWithAtExcludingStates(String atAddress, List e } } + @Override + public boolean existsAliceTradeWithAtExcludingStates(String atAddress, List excludeStates) throws DataException { + if (excludeStates == null) + excludeStates = Collections.emptyList(); + + StringBuilder whereClause = new StringBuilder(256); + whereClause.append("at_address = ? AND trade_state LIKE ?"); + + Object[] bindParams = new Object[2 + excludeStates.size()]; + bindParams[0] = atAddress; + bindParams[1] = "ALICE_%"; + + if (!excludeStates.isEmpty()) { + whereClause.append(" AND trade_state NOT IN (?"); + bindParams[2] = excludeStates.get(0); + + for (int i = 1; i < excludeStates.size(); ++i) { + whereClause.append(", ?"); + bindParams[2 + i] = excludeStates.get(i); + } + + whereClause.append(")"); + } + + try { + return this.repository.exists("TradeBotStates", whereClause.toString(), bindParams); + } catch (SQLException e) { + throw new DataException("Unable to check for trade-bot state in repository", e); + } + } + @Override public List getAllTradeBotData() throws DataException { String sql = "SELECT trade_private_key, acct_name, trade_state, trade_state_value, " @@ -199,4 +230,4 @@ public int delete(byte[] tradePrivateKey) throws DataException { } } -} \ No newline at end of file +} diff --git a/src/test/java/org/qortal/test/CrossChainRepositoryTests.java b/src/test/java/org/qortal/test/CrossChainRepositoryTests.java new file mode 100644 index 000000000..418f760fd --- /dev/null +++ b/src/test/java/org/qortal/test/CrossChainRepositoryTests.java @@ -0,0 +1,84 @@ +package org.qortal.test; + +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.PublicKeyAccount; +import org.qortal.controller.tradebot.LitecoinACCTv1TradeBot; +import org.qortal.controller.tradebot.TradeBot; +import org.qortal.crosschain.LitecoinACCTv1; +import org.qortal.crosschain.SupportedBlockchain; +import org.qortal.crypto.Crypto; +import org.qortal.data.crosschain.TradeBotData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.Common; +import org.qortal.utils.NTP; + +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CrossChainRepositoryTests extends Common { + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @Test + public void testExistsAliceTradeWithAtExcludingStates() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + String atAddress = Crypto.toATAddress(new byte[64]); + List endStates = LitecoinACCTv1TradeBot.getInstance().getEndStates(); + + TradeBotData bobData = buildTradeBotData(repository, atAddress, LitecoinACCTv1TradeBot.State.BOB_WAITING_FOR_AT_CONFIRM); + repository.getCrossChainRepository().save(bobData); + repository.saveChanges(); + + assertFalse(repository.getCrossChainRepository().existsAliceTradeWithAtExcludingStates(atAddress, endStates)); + + TradeBotData aliceData = buildTradeBotData(repository, atAddress, LitecoinACCTv1TradeBot.State.ALICE_WAITING_FOR_AT_LOCK); + repository.getCrossChainRepository().save(aliceData); + repository.saveChanges(); + + assertTrue(repository.getCrossChainRepository().existsAliceTradeWithAtExcludingStates(atAddress, endStates)); + + aliceData.setState(LitecoinACCTv1TradeBot.State.ALICE_DONE.name()); + aliceData.setStateValue(LitecoinACCTv1TradeBot.State.ALICE_DONE.value); + repository.getCrossChainRepository().save(aliceData); + repository.saveChanges(); + + assertFalse(repository.getCrossChainRepository().existsAliceTradeWithAtExcludingStates(atAddress, endStates)); + } + } + + private TradeBotData buildTradeBotData(Repository repository, String atAddress, LitecoinACCTv1TradeBot.State state) { + byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); + + byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); + byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); + String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); + + byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); + byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); + + byte[] creatorPublicKey = new byte[32]; + PublicKeyAccount creator = new PublicKeyAccount(repository, creatorPublicKey); + + long timestamp = NTP.getTime(); + long foreignAmount = 1234L; + long qortAmount = 5678L; + byte[] receivingAccountInfo = new byte[20]; + + return new TradeBotData(tradePrivateKey, LitecoinACCTv1.NAME, + state.name(), state.value, + creator.getAddress(), atAddress, timestamp, qortAmount, + tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, + null, null, + SupportedBlockchain.LITECOIN.name(), + tradeForeignPublicKey, tradeForeignPublicKeyHash, + foreignAmount, null, null, null, receivingAccountInfo); + } +}