diff --git a/src/main/java/org/qortal/controller/TradeBot.java b/src/main/java/org/qortal/controller/TradeBot.java index e5494675..85a84016 100644 --- a/src/main/java/org/qortal/controller/TradeBot.java +++ b/src/main/java/org/qortal/controller/TradeBot.java @@ -3,7 +3,10 @@ package org.qortal.controller; import java.awt.TrayIcon.MessageType; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Random; import org.apache.logging.log4j.LogManager; @@ -32,6 +35,7 @@ import org.qortal.data.crosschain.TradeBotData; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.PresenceTransactionData; import org.qortal.event.Event; import org.qortal.event.EventBus; import org.qortal.event.Listener; @@ -43,13 +47,18 @@ import org.qortal.repository.RepositoryManager; import org.qortal.settings.Settings; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; +import org.qortal.transaction.PresenceTransaction; +import org.qortal.transaction.PresenceTransaction.PresenceType; import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.DeployAtTransactionTransformer; +import org.qortal.transform.transaction.TransactionTransformer; import org.qortal.utils.Amounts; import org.qortal.utils.Base58; import org.qortal.utils.NTP; +import com.google.common.primitives.Longs; + /** * Performing cross-chain trading steps on behalf of user. *

@@ -86,6 +95,8 @@ public class TradeBot implements Listener { private static TradeBot instance; + private final Map presenceTimestampsByAtAddress = Collections.synchronizedMap(new HashMap<>()); + private TradeBot() { EventBus.INSTANCE.addListener(event -> TradeBot.getInstance().listen(event)); } @@ -348,26 +359,32 @@ public class TradeBot implements Listener { break; case ALICE_WAITING_FOR_P2SH_A: + updatePresence(repository, tradeBotData); handleAliceWaitingForP2shA(repository, tradeBotData); break; case BOB_WAITING_FOR_MESSAGE: + updatePresence(repository, tradeBotData); handleBobWaitingForMessage(repository, tradeBotData); break; case ALICE_WAITING_FOR_AT_LOCK: + updatePresence(repository, tradeBotData); handleAliceWaitingForAtLock(repository, tradeBotData); break; case BOB_WAITING_FOR_P2SH_B: + updatePresence(repository, tradeBotData); handleBobWaitingForP2shB(repository, tradeBotData); break; case ALICE_WATCH_P2SH_B: + updatePresence(repository, tradeBotData); handleAliceWatchingP2shB(repository, tradeBotData); break; case BOB_WAITING_FOR_AT_REDEEM: + updatePresence(repository, tradeBotData); handleBobWaitingForAtRedeem(repository, tradeBotData); break; @@ -376,10 +393,12 @@ public class TradeBot implements Listener { break; case ALICE_REFUNDING_B: + updatePresence(repository, tradeBotData); handleAliceRefundingP2shB(repository, tradeBotData); break; case ALICE_REFUNDING_A: + updatePresence(repository, tradeBotData); handleAliceRefundingP2shA(repository, tradeBotData); break; @@ -1249,4 +1268,41 @@ public class TradeBot implements Listener { EventBus.INSTANCE.notify(stateChangeEvent); } + // PRESENCE-related + private void updatePresence(Repository repository, TradeBotData tradeBotData) throws DataException { + String key = tradeBotData.getAtAddress(); + + long now = NTP.getTime(); + long threshold = now - PresenceType.TRADE_BOT.getLifetime(); + + long timestamp = presenceTimestampsByAtAddress.compute(key, (k, v) -> (v == null || v < threshold) ? now : v); + + // If timestamp hasn't been updated then nothing to do + if (timestamp != now) + return; + + PrivateKeyAccount tradeNativeAccount = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); + + int txGroupId = Group.NO_GROUP; + byte[] reference = new byte[TransactionTransformer.SIGNATURE_LENGTH]; + byte[] creatorPublicKey = tradeNativeAccount.getPublicKey(); + long fee = 0L; + + BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null); + + int nonce = 0; + byte[] timestampSignature = tradeNativeAccount.sign(Longs.toByteArray(timestamp)); + + PresenceTransactionData transactionData = new PresenceTransactionData(baseTransactionData, nonce, PresenceType.TRADE_BOT, timestampSignature); + + PresenceTransaction presenceTransaction = new PresenceTransaction(repository, transactionData); + presenceTransaction.computeNonce(); + + presenceTransaction.sign(tradeNativeAccount); + + ValidationResult result = presenceTransaction.importAsUnconfirmed(); + if (result != ValidationResult.OK) + LOGGER.debug(() -> String.format("Unable to build trade-bot PRESENCE transaction for %s: %s", tradeBotData.getAtAddress(), result.name())); + } + } diff --git a/src/main/java/org/qortal/data/transaction/PresenceTransactionData.java b/src/main/java/org/qortal/data/transaction/PresenceTransactionData.java index 2276f6ea..001bd5b4 100644 --- a/src/main/java/org/qortal/data/transaction/PresenceTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/PresenceTransactionData.java @@ -1,14 +1,10 @@ package org.qortal.data.transaction; -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toMap; - -import java.util.Map; - import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import org.qortal.transaction.PresenceTransaction.PresenceType; import org.qortal.transaction.Transaction.TransactionType; import io.swagger.v3.oas.annotations.media.Schema; @@ -26,20 +22,6 @@ public class PresenceTransactionData extends TransactionData { @Schema(accessMode = AccessMode.READ_ONLY) private int nonce; - public enum PresenceType { - REWARD_SHARE(0), TRADE_BOT(1); - - public final int value; - private static final Map map = stream(PresenceType.values()).collect(toMap(type -> type.value, type -> type)); - - PresenceType(int value) { - this.value = value; - } - - public static PresenceType valueOf(int value) { - return map.get(value); - } - } private PresenceType presenceType; @Schema(description = "timestamp signature", example = "2yGEbwRFyhPZZckKA") diff --git a/src/main/java/org/qortal/data/transaction/TransactionData.java b/src/main/java/org/qortal/data/transaction/TransactionData.java index 397693b8..060901f2 100644 --- a/src/main/java/org/qortal/data/transaction/TransactionData.java +++ b/src/main/java/org/qortal/data/transaction/TransactionData.java @@ -40,7 +40,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode; GroupApprovalTransactionData.class, SetGroupTransactionData.class, UpdateAssetTransactionData.class, AccountFlagsTransactionData.class, RewardShareTransactionData.class, - AccountLevelTransactionData.class, ChatTransactionData.class + AccountLevelTransactionData.class, ChatTransactionData.class, PresenceTransactionData.class }) //All properties to be converted to JSON via JAXB @XmlAccessorType(XmlAccessType.FIELD) diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBPresenceTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBPresenceTransactionRepository.java index cb2b3638..309ffcad 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBPresenceTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBPresenceTransactionRepository.java @@ -5,11 +5,11 @@ import java.sql.SQLException; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.PresenceTransactionData; -import org.qortal.data.transaction.PresenceTransactionData.PresenceType; import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; import org.qortal.repository.hsqldb.HSQLDBRepository; import org.qortal.repository.hsqldb.HSQLDBSaver; +import org.qortal.transaction.PresenceTransaction.PresenceType; public class HSQLDBPresenceTransactionRepository extends HSQLDBTransactionRepository { diff --git a/src/main/java/org/qortal/transaction/PresenceTransaction.java b/src/main/java/org/qortal/transaction/PresenceTransaction.java index 4a2ba67a..a1b12993 100644 --- a/src/main/java/org/qortal/transaction/PresenceTransaction.java +++ b/src/main/java/org/qortal/transaction/PresenceTransaction.java @@ -1,11 +1,16 @@ package org.qortal.transaction; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toMap; + import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.account.Account; +import org.qortal.controller.Controller; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; import org.qortal.crypto.MemoryPoW; @@ -13,7 +18,6 @@ import org.qortal.data.at.ATData; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.transaction.PresenceTransactionData; import org.qortal.data.transaction.TransactionData; -import org.qortal.data.transaction.PresenceTransactionData.PresenceType; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; @@ -35,6 +39,34 @@ public class PresenceTransaction extends Transaction { public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes public static final int POW_DIFFICULTY = 8; // leading zero bits + public enum PresenceType { + REWARD_SHARE(0) { + @Override + public long getLifetime() { + return Controller.ONLINE_TIMESTAMP_MODULUS; + } + }, + TRADE_BOT(1) { + @Override + public long getLifetime() { + return 30 * 60 * 1000L; // 30 minutes in milliseconds + } + }; + + public final int value; + private static final Map map = stream(PresenceType.values()).collect(toMap(type -> type.value, type -> type)); + + PresenceType(int value) { + this.value = value; + } + + public abstract long getLifetime(); + + public static PresenceType valueOf(int value) { + return map.get(value); + } + } + // Constructors public PresenceTransaction(Repository repository, TransactionData transactionData) { @@ -45,6 +77,11 @@ public class PresenceTransaction extends Transaction { // More information + @Override + public long getDeadline() { + return this.transactionData.getTimestamp() + this.presenceTransactionData.getPresenceType().getLifetime(); + } + @Override public List getRecipientAddresses() throws DataException { return Collections.emptyList(); diff --git a/src/main/java/org/qortal/transform/transaction/PresenceTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/PresenceTransactionTransformer.java index bac322a1..bf69d102 100644 --- a/src/main/java/org/qortal/transform/transaction/PresenceTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/PresenceTransactionTransformer.java @@ -6,8 +6,8 @@ import java.nio.ByteBuffer; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.PresenceTransactionData; -import org.qortal.data.transaction.PresenceTransactionData.PresenceType; import org.qortal.data.transaction.TransactionData; +import org.qortal.transaction.PresenceTransaction.PresenceType; import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transform.TransformationException; import org.qortal.utils.Serialization; diff --git a/src/test/java/org/qortal/test/PresenceTests.java b/src/test/java/org/qortal/test/PresenceTests.java index 228a543b..60d24e1b 100644 --- a/src/test/java/org/qortal/test/PresenceTests.java +++ b/src/test/java/org/qortal/test/PresenceTests.java @@ -10,7 +10,6 @@ import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.PresenceTransactionData; import org.qortal.data.transaction.TransactionData; -import org.qortal.data.transaction.PresenceTransactionData.PresenceType; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; @@ -20,6 +19,7 @@ import org.qortal.test.common.Common; import org.qortal.test.common.TransactionUtils; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.PresenceTransaction; +import org.qortal.transaction.PresenceTransaction.PresenceType; import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.utils.NTP;