Browse Source

Added trade-bot websocket

split-DB
catbref 4 years ago
parent
commit
cac68ccc14
  1. 2
      src/main/java/org/qortal/api/ApiService.java
  2. 119
      src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java
  3. 42
      src/main/java/org/qortal/controller/TradeBot.java

2
src/main/java/org/qortal/api/ApiService.java

@ -43,6 +43,7 @@ import org.qortal.api.websocket.ActiveChatsWebSocket;
import org.qortal.api.websocket.AdminStatusWebSocket;
import org.qortal.api.websocket.BlocksWebSocket;
import org.qortal.api.websocket.ChatMessagesWebSocket;
import org.qortal.api.websocket.TradeBotWebSocket;
import org.qortal.api.websocket.TradeOffersWebSocket;
import org.qortal.settings.Settings;
@ -199,6 +200,7 @@ public class ApiService {
context.addServlet(ActiveChatsWebSocket.class, "/websockets/chat/active/*");
context.addServlet(ChatMessagesWebSocket.class, "/websockets/chat/messages");
context.addServlet(TradeOffersWebSocket.class, "/websockets/crosschain/tradeoffers");
context.addServlet(TradeBotWebSocket.class, "/websockets/crosschain/tradebot");
// Start server
this.server.start();

119
src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java

@ -0,0 +1,119 @@
package org.qortal.api.websocket;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.qortal.controller.TradeBot;
import org.qortal.data.crosschain.TradeBotData;
import org.qortal.event.Event;
import org.qortal.event.EventBus;
import org.qortal.event.Listener;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.utils.Base58;
@WebSocket
@SuppressWarnings("serial")
public class TradeBotWebSocket extends ApiWebSocket implements Listener {
/** Cache of trade-bot entry states, keyed by trade-bot entry's "trade private key" (base58) */
private static final Map<String, TradeBotData.State> PREVIOUS_STATES = new HashMap<>();
@Override
public void configure(WebSocketServletFactory factory) {
factory.register(TradeBotWebSocket.class);
try (final Repository repository = RepositoryManager.getRepository()) {
List<TradeBotData> tradeBotEntries = repository.getCrossChainRepository().getAllTradeBotData();
if (tradeBotEntries == null)
// How do we properly fail here?
return;
PREVIOUS_STATES.putAll(tradeBotEntries.stream().collect(Collectors.toMap(entry -> Base58.encode(entry.getTradePrivateKey()), TradeBotData::getState)));
} catch (DataException e) {
// No output this time
}
EventBus.INSTANCE.addListener(this::listen);
}
@Override
public void listen(Event event) {
if (!(event instanceof TradeBot.StateChangeEvent))
return;
TradeBotData tradeBotData = ((TradeBot.StateChangeEvent) event).getTradeBotData();
String tradePrivateKey58 = Base58.encode(tradeBotData.getTradePrivateKey());
synchronized (PREVIOUS_STATES) {
if (PREVIOUS_STATES.get(tradePrivateKey58) == tradeBotData.getState())
// Not changed
return;
PREVIOUS_STATES.put(tradePrivateKey58, tradeBotData.getState());
}
List<TradeBotData> tradeBotEntries = Collections.singletonList(tradeBotData);
for (Session session : getSessions())
sendEntries(session, tradeBotEntries);
}
@OnWebSocketConnect
public void onWebSocketConnect(Session session) {
// Send all known trade-bot entries
try (final Repository repository = RepositoryManager.getRepository()) {
List<TradeBotData> tradeBotEntries = repository.getCrossChainRepository().getAllTradeBotData();
if (tradeBotEntries == null) {
session.close(4001, "repository issue fetching trade-bot entries");
return;
}
if (!sendEntries(session, tradeBotEntries)) {
session.close(4002, "websocket issue");
return;
}
} catch (DataException e) {
// No output this time
}
super.onWebSocketConnect(session);
}
@OnWebSocketClose
public void onWebSocketClose(Session session, int statusCode, String reason) {
super.onWebSocketClose(session, statusCode, reason);
}
@OnWebSocketMessage
public void onWebSocketMessage(Session session, String message) {
/* ignored */
}
private boolean sendEntries(Session session, List<TradeBotData> tradeBotEntries) {
try {
StringWriter stringWriter = new StringWriter();
marshall(stringWriter, tradeBotEntries);
String output = stringWriter.toString();
session.getRemote().sendStringByFuture(output);
} catch (IOException e) {
// No output this time?
return false;
}
return true;
}
}

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

@ -30,6 +30,8 @@ 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.event.Event;
import org.qortal.event.EventBus;
import org.qortal.group.Group;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
@ -47,6 +49,18 @@ public class TradeBot {
public enum ResponseResult { OK, INSUFFICIENT_FUNDS, BTC_BALANCE_ISSUE, BTC_NETWORK_ISSUE }
public static class StateChangeEvent implements Event {
private final TradeBotData tradeBotData;
public StateChangeEvent(TradeBotData tradeBotData) {
this.tradeBotData = tradeBotData;
}
public TradeBotData getTradeBotData() {
return this.tradeBotData;
}
}
private static final Logger LOGGER = LogManager.getLogger(TradeBot.class);
private static final Random RANDOM = new SecureRandom();
private static final long FEE_AMOUNT = 1000L;
@ -158,6 +172,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("Built AT %s. Waiting for deployment", atAddress));
notifyStateChange(tradeBotData);
// Return to user for signing and broadcast as we don't have their Qortal private key
try {
@ -258,6 +273,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("Funding P2SH-A %s. Waiting for confirmation", p2shAddress));
notifyStateChange(tradeBotData);
return ResponseResult.OK;
}
@ -368,6 +384,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("AT %s confirmed ready. Waiting for trade message", tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
}
/**
@ -405,6 +422,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddress));
notifyStateChange(tradeBotData);
return;
}
@ -443,6 +461,7 @@ public class TradeBot {
LOGGER.info(() -> String.format("P2SH-A %s funding confirmed. Messaged %s. Waiting for AT %s to lock to us",
p2shAddress, crossChainTradeData.qortalCreatorTradeAddress, tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
}
/**
@ -478,6 +497,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("AT %s cancelled - trading aborted", tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
return;
}
@ -550,6 +570,7 @@ public class TradeBot {
String p2shBAddress = BTC.getInstance().deriveP2shAddress(redeemScriptBytes);
LOGGER.info(() -> String.format("Locked AT %s to %s. Waiting for P2SH-B %s", tradeBotData.getAtAddress(), aliceNativeAddress, p2shBAddress));
notifyStateChange(tradeBotData);
return;
}
@ -558,6 +579,7 @@ public class TradeBot {
if (tradeBotData.getLastTransactionSignature() != originalLastTransactionSignature) {
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
notifyStateChange(tradeBotData);
}
}
@ -597,6 +619,8 @@ public class TradeBot {
else
LOGGER.info(() -> String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddress));
notifyStateChange(tradeBotData);
return;
}
@ -622,6 +646,8 @@ public class TradeBot {
repository.getCrossChainRepository().save(tradeBotData);
repository.saveChanges();
notifyStateChange(tradeBotData);
return;
}
@ -679,6 +705,8 @@ public class TradeBot {
LOGGER.info(() -> String.format("AT %s locked to us (%s). P2SH-B %s funded. Watching P2SH-B for secret-B",
tradeBotData.getAtAddress(), tradeBotData.getTradeNativeAddress(), p2shAddress));
notifyStateChange(tradeBotData);
}
/**
@ -706,6 +734,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
return;
}
@ -746,6 +775,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("P2SH-B %s redeemed (exposing secret-B). Watching AT %s for secret-A", p2shAddress, tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
}
/**
@ -782,6 +812,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("LockTime-B reached, refunding P2SH-B %s - aborting trade", p2shAddress));
notifyStateChange(tradeBotData);
return;
}
@ -825,6 +856,8 @@ public class TradeBot {
LOGGER.info(() -> String.format("P2SH-B %s redeemed, using secrets to redeem AT %s. Funds should arrive at %s",
p2shAddress, tradeBotData.getAtAddress(), receivingAddress));
notifyStateChange(tradeBotData);
}
/**
@ -867,6 +900,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
notifyStateChange(tradeBotData);
return;
}
@ -902,6 +936,7 @@ public class TradeBot {
String receivingAddress = BTC.getInstance().pkhToAddress(receivingAccountInfo);
LOGGER.info(() -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receivingAddress));
notifyStateChange(tradeBotData);
}
/**
@ -943,6 +978,7 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("Refunded P2SH-B %s. Waiting for LockTime-A", p2shAddress));
notifyStateChange(tradeBotData);
}
/** Trade-bot is attempting to refund P2SH-A. */
@ -987,6 +1023,12 @@ public class TradeBot {
repository.saveChanges();
LOGGER.info(() -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddress));
notifyStateChange(tradeBotData);
}
private static void notifyStateChange(TradeBotData tradeBotData) {
StateChangeEvent stateChangeEvent = new StateChangeEvent(tradeBotData);
EventBus.INSTANCE.notify(stateChangeEvent);
}
}

Loading…
Cancel
Save