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;