forked from Qortal/qortal
Add preferred-blockchain filtering to /websockets/crosschain/tradeoffers via foreignBlockchain query param
This commit is contained in:
parent
31fa916156
commit
68e3d3b989
@ -3,6 +3,8 @@ package org.qortal.api.websocket;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -42,18 +44,23 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(TradeOffersWebSocket.class);
|
private static final Logger LOGGER = LogManager.getLogger(TradeOffersWebSocket.class);
|
||||||
|
|
||||||
private static final Map<String, AcctMode> previousAtModes = new HashMap<>();
|
private static class CachedOfferInfo {
|
||||||
|
public final Map<String, AcctMode> previousAtModes = new HashMap<>();
|
||||||
|
|
||||||
// OFFERING
|
// OFFERING
|
||||||
private static final Map<String, CrossChainOfferSummary> currentSummaries = new HashMap<>();
|
public final Map<String, CrossChainOfferSummary> currentSummaries = new HashMap<>();
|
||||||
// REDEEMED/REFUNDED/CANCELLED
|
// REDEEMED/REFUNDED/CANCELLED
|
||||||
private static final Map<String, CrossChainOfferSummary> historicSummaries = new HashMap<>();
|
public final Map<String, CrossChainOfferSummary> historicSummaries = new HashMap<>();
|
||||||
|
}
|
||||||
|
// Manual synchronization
|
||||||
|
private static final Map<String, CachedOfferInfo> cachedInfoByBlockchain = new HashMap<>();
|
||||||
|
|
||||||
private static final Predicate<CrossChainOfferSummary> isHistoric = offerSummary
|
private static final Predicate<CrossChainOfferSummary> isHistoric = offerSummary
|
||||||
-> offerSummary.getMode() == AcctMode.REDEEMED
|
-> offerSummary.getMode() == AcctMode.REDEEMED
|
||||||
|| offerSummary.getMode() == AcctMode.REFUNDED
|
|| offerSummary.getMode() == AcctMode.REFUNDED
|
||||||
|| offerSummary.getMode() == AcctMode.CANCELLED;
|
|| offerSummary.getMode() == AcctMode.CANCELLED;
|
||||||
|
|
||||||
|
private static final Map<Session, String> sessionBlockchain = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(WebSocketServletFactory factory) {
|
public void configure(WebSocketServletFactory factory) {
|
||||||
@ -79,7 +86,6 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
BlockData blockData = ((Controller.NewBlockEvent) event).getBlockData();
|
BlockData blockData = ((Controller.NewBlockEvent) event).getBlockData();
|
||||||
|
|
||||||
// Process any new info
|
// Process any new info
|
||||||
List<CrossChainOfferSummary> crossChainOfferSummaries = new ArrayList<>();
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
// Find any new/changed trade ATs since this block
|
// Find any new/changed trade ATs since this block
|
||||||
@ -88,8 +94,14 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
final Long expectedValue = null;
|
final Long expectedValue = null;
|
||||||
final Integer minimumFinalHeight = blockData.getHeight();
|
final Integer minimumFinalHeight = blockData.getHeight();
|
||||||
|
|
||||||
// Loop for all different types of trade offer?
|
for (SupportedBlockchain blockchain : SupportedBlockchain.values()) {
|
||||||
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getAcctMap();
|
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(blockchain);
|
||||||
|
|
||||||
|
List<CrossChainOfferSummary> crossChainOfferSummaries = new ArrayList<>();
|
||||||
|
|
||||||
|
synchronized (cachedInfoByBlockchain) {
|
||||||
|
CachedOfferInfo cachedInfo = cachedInfoByBlockchain.computeIfAbsent(blockchain.name(), k -> new CachedOfferInfo());
|
||||||
|
|
||||||
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
@ -100,67 +112,94 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
|
|
||||||
crossChainOfferSummaries.addAll(produceSummaries(repository, acct, atStates, blockData.getTimestamp()));
|
crossChainOfferSummaries.addAll(produceSummaries(repository, acct, atStates, blockData.getTimestamp()));
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
|
||||||
// No output this time
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (previousAtModes) {
|
|
||||||
// Remove any entries unchanged from last time
|
// Remove any entries unchanged from last time
|
||||||
crossChainOfferSummaries.removeIf(offerSummary -> previousAtModes.get(offerSummary.getQortalAtAddress()) == offerSummary.getMode());
|
crossChainOfferSummaries.removeIf(offerSummary -> cachedInfo.previousAtModes.get(offerSummary.getQortalAtAddress()) == offerSummary.getMode());
|
||||||
|
|
||||||
// Don't send anything if no results
|
// Skip to next blockchain if nothing has changed (for this blockchain)
|
||||||
if (crossChainOfferSummaries.isEmpty())
|
if (crossChainOfferSummaries.isEmpty())
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
for (CrossChainOfferSummary offerSummary : crossChainOfferSummaries) {
|
for (CrossChainOfferSummary offerSummary : crossChainOfferSummaries) {
|
||||||
previousAtModes.put(offerSummary.qortalAtAddress, offerSummary.getMode());
|
cachedInfo.previousAtModes.put(offerSummary.qortalAtAddress, offerSummary.getMode());
|
||||||
LOGGER.trace(() -> String.format("Block height: %d, AT: %s, mode: %s", blockData.getHeight(), offerSummary.qortalAtAddress, offerSummary.getMode().name()));
|
LOGGER.trace(() -> String.format("Block height: %d, AT: %s, mode: %s", blockData.getHeight(), offerSummary.qortalAtAddress, offerSummary.getMode().name()));
|
||||||
|
|
||||||
switch (offerSummary.getMode()) {
|
switch (offerSummary.getMode()) {
|
||||||
case OFFERING:
|
case OFFERING:
|
||||||
currentSummaries.put(offerSummary.qortalAtAddress, offerSummary);
|
cachedInfo.currentSummaries.put(offerSummary.qortalAtAddress, offerSummary);
|
||||||
historicSummaries.remove(offerSummary.qortalAtAddress);
|
cachedInfo.historicSummaries.remove(offerSummary.qortalAtAddress);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REDEEMED:
|
case REDEEMED:
|
||||||
case REFUNDED:
|
case REFUNDED:
|
||||||
case CANCELLED:
|
case CANCELLED:
|
||||||
currentSummaries.remove(offerSummary.qortalAtAddress);
|
cachedInfo.currentSummaries.remove(offerSummary.qortalAtAddress);
|
||||||
historicSummaries.put(offerSummary.qortalAtAddress, offerSummary);
|
cachedInfo.historicSummaries.put(offerSummary.qortalAtAddress, offerSummary);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TRADING:
|
case TRADING:
|
||||||
currentSummaries.remove(offerSummary.qortalAtAddress);
|
cachedInfo.currentSummaries.remove(offerSummary.qortalAtAddress);
|
||||||
historicSummaries.remove(offerSummary.qortalAtAddress);
|
cachedInfo.historicSummaries.remove(offerSummary.qortalAtAddress);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any historic offers that are over 24 hours old
|
// Remove any historic offers that are over 24 hours old
|
||||||
final long tooOldTimestamp = NTP.getTime() - 24 * 60 * 60 * 1000L;
|
final long tooOldTimestamp = NTP.getTime() - 24 * 60 * 60 * 1000L;
|
||||||
historicSummaries.values().removeIf(historicSummary -> historicSummary.getTimestamp() < tooOldTimestamp);
|
cachedInfo.historicSummaries.values().removeIf(historicSummary -> historicSummary.getTimestamp() < tooOldTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify sessions
|
// Notify sessions
|
||||||
for (Session session : getSessions())
|
for (Session session : getSessions()) {
|
||||||
|
// Only send if this session has this/no preferred blockchain
|
||||||
|
String preferredBlockchain = sessionBlockchain.get(session);
|
||||||
|
|
||||||
|
if (preferredBlockchain == null || preferredBlockchain.equals(blockchain.name()))
|
||||||
sendOfferSummaries(session, crossChainOfferSummaries);
|
sendOfferSummaries(session, crossChainOfferSummaries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
// No output this time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OnWebSocketConnect
|
@OnWebSocketConnect
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(Session session) {
|
public void onWebSocketConnect(Session session) {
|
||||||
Map<String, List<String>> queryParams = session.getUpgradeRequest().getParameterMap();
|
Map<String, List<String>> queryParams = session.getUpgradeRequest().getParameterMap();
|
||||||
final boolean includeHistoric = queryParams.get("includeHistoric") != null;
|
final boolean includeHistoric = queryParams.get("includeHistoric") != null;
|
||||||
|
|
||||||
|
List<String> foreignBlockchains = queryParams.get("foreignBlockchain");
|
||||||
|
final String foreignBlockchain = foreignBlockchains == null ? null : foreignBlockchains.get(0);
|
||||||
|
|
||||||
|
// Make sure blockchain (if any) is valid
|
||||||
|
if (foreignBlockchain != null && SupportedBlockchain.fromString(foreignBlockchain) == null) {
|
||||||
|
session.close(4003, "unknown blockchain: " + foreignBlockchain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save session's preferred blockchain (if any)
|
||||||
|
sessionBlockchain.put(session, foreignBlockchain);
|
||||||
|
|
||||||
List<CrossChainOfferSummary> crossChainOfferSummaries = new ArrayList<>();
|
List<CrossChainOfferSummary> crossChainOfferSummaries = new ArrayList<>();
|
||||||
|
|
||||||
synchronized (previousAtModes) {
|
synchronized (cachedInfoByBlockchain) {
|
||||||
crossChainOfferSummaries.addAll(currentSummaries.values());
|
Collection<CachedOfferInfo> cachedInfos;
|
||||||
|
|
||||||
|
if (foreignBlockchain == null)
|
||||||
|
// No preferred blockchain, so iterate through all of them
|
||||||
|
cachedInfos = cachedInfoByBlockchain.values();
|
||||||
|
else
|
||||||
|
cachedInfos = Collections.singleton(cachedInfoByBlockchain.computeIfAbsent(foreignBlockchain, k -> new CachedOfferInfo()));
|
||||||
|
|
||||||
|
for (CachedOfferInfo cachedInfo : cachedInfos) {
|
||||||
|
crossChainOfferSummaries.addAll(cachedInfo.currentSummaries.values());
|
||||||
|
|
||||||
if (includeHistoric)
|
if (includeHistoric)
|
||||||
crossChainOfferSummaries.addAll(historicSummaries.values());
|
crossChainOfferSummaries.addAll(cachedInfo.historicSummaries.values());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sendOfferSummaries(session, crossChainOfferSummaries)) {
|
if (!sendOfferSummaries(session, crossChainOfferSummaries)) {
|
||||||
@ -174,6 +213,9 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
@OnWebSocketClose
|
@OnWebSocketClose
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketClose(Session session, int statusCode, String reason) {
|
public void onWebSocketClose(Session session, int statusCode, String reason) {
|
||||||
|
// clean up
|
||||||
|
sessionBlockchain.remove(session);
|
||||||
|
|
||||||
super.onWebSocketClose(session, statusCode, reason);
|
super.onWebSocketClose(session, statusCode, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +250,11 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
Long expectedValue = (long) AcctMode.OFFERING.value;
|
Long expectedValue = (long) AcctMode.OFFERING.value;
|
||||||
Integer minimumFinalHeight = null;
|
Integer minimumFinalHeight = null;
|
||||||
|
|
||||||
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getAcctMap();
|
for (SupportedBlockchain blockchain : SupportedBlockchain.values()) {
|
||||||
|
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(blockchain);
|
||||||
|
|
||||||
|
CachedOfferInfo cachedInfo = cachedInfoByBlockchain.computeIfAbsent(blockchain.name(), k -> new CachedOfferInfo());
|
||||||
|
|
||||||
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
@ -222,13 +268,14 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
throw new DataException("Couldn't fetch current trades from repository");
|
throw new DataException("Couldn't fetch current trades from repository");
|
||||||
|
|
||||||
// Save initial AT modes
|
// Save initial AT modes
|
||||||
previousAtModes.putAll(initialAtStates.stream().collect(Collectors.toMap(ATStateData::getATAddress, atState -> AcctMode.OFFERING)));
|
cachedInfo.previousAtModes.putAll(initialAtStates.stream().collect(Collectors.toMap(ATStateData::getATAddress, atState -> AcctMode.OFFERING)));
|
||||||
|
|
||||||
// Convert to offer summaries
|
// Convert to offer summaries
|
||||||
currentSummaries.putAll(produceSummaries(repository, acct, initialAtStates, null).stream()
|
cachedInfo.currentSummaries.putAll(produceSummaries(repository, acct, initialAtStates, null).stream()
|
||||||
.collect(Collectors.toMap(CrossChainOfferSummary::getQortalAtAddress, offerSummary -> offerSummary)));
|
.collect(Collectors.toMap(CrossChainOfferSummary::getQortalAtAddress, offerSummary -> offerSummary)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void populateHistoricSummaries(Repository repository) throws DataException {
|
private static void populateHistoricSummaries(Repository repository) throws DataException {
|
||||||
// We want REDEEMED/REFUNDED/CANCELLED trades over the last 24 hours
|
// We want REDEEMED/REFUNDED/CANCELLED trades over the last 24 hours
|
||||||
@ -243,7 +290,11 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
Long expectedValue = null;
|
Long expectedValue = null;
|
||||||
++minimumFinalHeight; // because height is just *before* timestamp
|
++minimumFinalHeight; // because height is just *before* timestamp
|
||||||
|
|
||||||
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getAcctMap();
|
for (SupportedBlockchain blockchain : SupportedBlockchain.values()) {
|
||||||
|
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(blockchain);
|
||||||
|
|
||||||
|
CachedOfferInfo cachedInfo = cachedInfoByBlockchain.computeIfAbsent(blockchain.name(), k -> new CachedOfferInfo());
|
||||||
|
|
||||||
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
@ -262,10 +313,11 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Add summary to initial burst
|
// Add summary to initial burst
|
||||||
historicSummaries.put(historicOfferSummary.getQortalAtAddress(), historicOfferSummary);
|
cachedInfo.historicSummaries.put(historicOfferSummary.getQortalAtAddress(), historicOfferSummary);
|
||||||
|
|
||||||
// Save initial AT mode
|
// Save initial AT mode
|
||||||
previousAtModes.put(historicOfferSummary.getQortalAtAddress(), historicOfferSummary.getMode());
|
cachedInfo.previousAtModes.put(historicOfferSummary.getQortalAtAddress(), historicOfferSummary.getMode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@ public enum SupportedBlockchain {
|
|||||||
return supportedAcctsByCodeHash;
|
return supportedAcctsByCodeHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SupportedBlockchain fromString(String name) {
|
||||||
|
return blockchainsByName.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<ByteArray, Supplier<ACCT>> getFilteredAcctMap(SupportedBlockchain blockchain) {
|
public static Map<ByteArray, Supplier<ACCT>> getFilteredAcctMap(SupportedBlockchain blockchain) {
|
||||||
if (blockchain == null)
|
if (blockchain == null)
|
||||||
return getAcctMap();
|
return getAcctMap();
|
||||||
|
Loading…
Reference in New Issue
Block a user