diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index cde965c1..b333bd34 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1245,6 +1245,10 @@ public class Controller extends Thread { OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV3Message(peer, message); break; + case ONLINE_ACCOUNTS_V3: + OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV3Message(peer, message); + break; + case GET_ARBITRARY_DATA: // Not currently supported break; diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 529cc853..c1f58b43 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -445,10 +445,10 @@ public class OnlineAccountsManager { Message messageV1 = new OnlineAccountsMessage(ourOnlineAccounts); Message messageV2 = new OnlineAccountsV2Message(ourOnlineAccounts); - Message messageV3 = new OnlineAccountsV2Message(ourOnlineAccounts); // TODO: V3 message + Message messageV3 = new OnlineAccountsV3Message(ourOnlineAccounts); Network.getInstance().broadcast(peer -> - peer.getPeersVersion() >= ONLINE_ACCOUNTS_V3_PEER_VERSION + peer.getPeersVersion() >= OnlineAccountsV3Message.MIN_PEER_VERSION ? messageV3 : peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION ? messageV2 @@ -715,9 +715,32 @@ public class OnlineAccountsManager { } } - Message onlineAccountsMessage = new OnlineAccountsV2Message(outgoingOnlineAccounts); // TODO: V3 message - peer.sendMessage(onlineAccountsMessage); + peer.sendMessage( + peer.getPeersVersion() >= OnlineAccountsV3Message.MIN_PEER_VERSION ? + new OnlineAccountsV3Message(outgoingOnlineAccounts) : + new OnlineAccountsV2Message(outgoingOnlineAccounts) + ); LOGGER.debug("Sent {} online accounts to {}", outgoingOnlineAccounts.size(), peer); } + + public void onNetworkOnlineAccountsV3Message(Peer peer, Message message) { + OnlineAccountsV3Message onlineAccountsMessage = (OnlineAccountsV3Message) message; + + List peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts(); + LOGGER.debug("Received {} online accounts from {}", peersOnlineAccounts.size(), peer); + + int importCount = 0; + + // Add any online accounts to the queue that aren't already present + for (OnlineAccountData onlineAccountData : peersOnlineAccounts) { + boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData); + + if (isNewEntry) + importCount++; + } + + if (importCount > 0) + LOGGER.debug("Added {} online accounts to queue", importCount); + } } diff --git a/src/main/java/org/qortal/data/network/OnlineAccountData.java b/src/main/java/org/qortal/data/network/OnlineAccountData.java index 28c454b5..bd4842db 100644 --- a/src/main/java/org/qortal/data/network/OnlineAccountData.java +++ b/src/main/java/org/qortal/data/network/OnlineAccountData.java @@ -16,6 +16,7 @@ public class OnlineAccountData { protected long timestamp; protected byte[] signature; protected byte[] publicKey; + protected Integer nonce; @XmlTransient private int hash; @@ -26,10 +27,15 @@ public class OnlineAccountData { protected OnlineAccountData() { } - public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) { + public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey, Integer nonce) { this.timestamp = timestamp; this.signature = signature; this.publicKey = publicKey; + this.nonce = nonce; + } + + public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) { + this(timestamp, signature, publicKey, null); } public long getTimestamp() { @@ -44,6 +50,10 @@ public class OnlineAccountData { return this.publicKey; } + public Integer getNonce() { + return this.nonce; + } + // For JAXB @XmlElement(name = "address") protected String getAddress() { diff --git a/src/main/java/org/qortal/network/message/MessageType.java b/src/main/java/org/qortal/network/message/MessageType.java index de711dc3..087e7fbf 100644 --- a/src/main/java/org/qortal/network/message/MessageType.java +++ b/src/main/java/org/qortal/network/message/MessageType.java @@ -46,7 +46,7 @@ public enum MessageType { GET_ONLINE_ACCOUNTS(81, GetOnlineAccountsMessage::fromByteBuffer), ONLINE_ACCOUNTS_V2(82, OnlineAccountsV2Message::fromByteBuffer), GET_ONLINE_ACCOUNTS_V2(83, GetOnlineAccountsV2Message::fromByteBuffer), - // ONLINE_ACCOUNTS_V3(84, OnlineAccountsV3Message::fromByteBuffer), + ONLINE_ACCOUNTS_V3(84, OnlineAccountsV3Message::fromByteBuffer), GET_ONLINE_ACCOUNTS_V3(85, GetOnlineAccountsV3Message::fromByteBuffer), ARBITRARY_DATA(90, ArbitraryDataMessage::fromByteBuffer), diff --git a/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java new file mode 100644 index 00000000..cdd52939 --- /dev/null +++ b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java @@ -0,0 +1,121 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import org.qortal.data.network.OnlineAccountData; +import org.qortal.transform.Transformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * For sending online accounts info to remote peer. + * + * Same format as V2, but with added support for a mempow nonce. + */ +public class OnlineAccountsV3Message extends Message { + + public static final long MIN_PEER_VERSION = 0x300050000L; // 3.5.0 + + private List onlineAccounts; + + public OnlineAccountsV3Message(List onlineAccounts) { + super(MessageType.ONLINE_ACCOUNTS_V3); + + // Shortcut in case we have no online accounts + if (onlineAccounts.isEmpty()) { + this.dataBytes = Ints.toByteArray(0); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + return; + } + + // How many of each timestamp + Map countByTimestamp = new HashMap<>(); + + for (OnlineAccountData onlineAccountData : onlineAccounts) { + Long timestamp = onlineAccountData.getTimestamp(); + countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v); + } + + // We should know exactly how many bytes to allocate now + int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH) + + onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize); + + try { + for (long timestamp : countByTimestamp.keySet()) { + bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp))); + + bytes.write(Longs.toByteArray(timestamp)); + + for (OnlineAccountData onlineAccountData : onlineAccounts) { + if (onlineAccountData.getTimestamp() == timestamp) { + bytes.write(onlineAccountData.getSignature()); + bytes.write(onlineAccountData.getPublicKey()); + + // Nonce is optional; use -1 as placeholder if missing + int nonce = onlineAccountData.getNonce() != null ? onlineAccountData.getNonce() : -1; + bytes.write(Ints.toByteArray(nonce)); + } + } + } + } catch (IOException e) { + throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); + } + + this.dataBytes = bytes.toByteArray(); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + } + + private OnlineAccountsV3Message(int id, List onlineAccounts) { + super(id, MessageType.ONLINE_ACCOUNTS_V3); + + this.onlineAccounts = onlineAccounts; + } + + public List getOnlineAccounts() { + return this.onlineAccounts; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException { + int accountCount = bytes.getInt(); + + List onlineAccounts = new ArrayList<>(accountCount); + + while (accountCount > 0) { + long timestamp = bytes.getLong(); + + for (int i = 0; i < accountCount; ++i) { + byte[] signature = new byte[Transformer.SIGNATURE_LENGTH]; + bytes.get(signature); + + byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + bytes.get(publicKey); + + // Nonce is optional - will be -1 if missing + Integer nonce = bytes.getInt(); + if (nonce < 0) { + nonce = null; + } + + onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonce)); + } + + if (bytes.hasRemaining()) { + accountCount = bytes.getInt(); + } else { + // we've finished + accountCount = 0; + } + } + + return new OnlineAccountsV3Message(id, onlineAccounts); + } + +}