mirror of https://github.com/qortal/qortal
CalDescent
3 years ago
committed by
GitHub
5 changed files with 429 additions and 5 deletions
@ -0,0 +1,117 @@
|
||||
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.io.UnsupportedEncodingException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* For requesting online accounts info from remote peer, given our list of online accounts. |
||||
* |
||||
* Different format to V1: |
||||
* V1 is: number of entries, then timestamp + pubkey for each entry |
||||
* V2 is: groups of: number of entries, timestamp, then pubkey for each entry |
||||
* |
||||
* Also V2 only builds online accounts message once! |
||||
*/ |
||||
public class GetOnlineAccountsV2Message extends Message { |
||||
private List<OnlineAccountData> onlineAccounts; |
||||
private byte[] cachedData; |
||||
|
||||
public GetOnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) { |
||||
this(-1, onlineAccounts); |
||||
} |
||||
|
||||
private GetOnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) { |
||||
super(id, MessageType.GET_ONLINE_ACCOUNTS_V2); |
||||
|
||||
this.onlineAccounts = onlineAccounts; |
||||
} |
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() { |
||||
return this.onlineAccounts; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { |
||||
int accountCount = bytes.getInt(); |
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount); |
||||
|
||||
while (accountCount > 0) { |
||||
long timestamp = bytes.getLong(); |
||||
|
||||
for (int i = 0; i < accountCount; ++i) { |
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; |
||||
bytes.get(publicKey); |
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, null, publicKey)); |
||||
} |
||||
|
||||
if (bytes.hasRemaining()) { |
||||
accountCount = bytes.getInt(); |
||||
} else { |
||||
// we've finished
|
||||
accountCount = 0; |
||||
} |
||||
} |
||||
|
||||
return new GetOnlineAccountsV2Message(id, onlineAccounts); |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized byte[] toData() { |
||||
if (this.cachedData != null) |
||||
return this.cachedData; |
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (this.onlineAccounts.isEmpty()) { |
||||
this.cachedData = Ints.toByteArray(0); |
||||
return this.cachedData; |
||||
} |
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>(); |
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) { |
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i); |
||||
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) |
||||
+ this.onlineAccounts.size() * Transformer.PUBLIC_KEY_LENGTH; |
||||
|
||||
try { |
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize); |
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) { |
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp))); |
||||
|
||||
bytes.write(Longs.toByteArray(timestamp)); |
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) { |
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i); |
||||
|
||||
if (onlineAccountData.getTimestamp() == timestamp) |
||||
bytes.write(onlineAccountData.getPublicKey()); |
||||
} |
||||
} |
||||
|
||||
this.cachedData = bytes.toByteArray(); |
||||
return this.cachedData; |
||||
} catch (IOException e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,124 @@
|
||||
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.io.UnsupportedEncodingException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* For sending online accounts info to remote peer. |
||||
* |
||||
* Different format to V1: |
||||
* V1 is: number of entries, then timestamp + sig + pubkey for each entry |
||||
* V2 is: groups of: number of entries, timestamp, then sig + pubkey for each entry |
||||
* |
||||
* Also V2 only builds online accounts message once! |
||||
*/ |
||||
public class OnlineAccountsV2Message extends Message { |
||||
private List<OnlineAccountData> onlineAccounts; |
||||
private byte[] cachedData; |
||||
|
||||
public OnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) { |
||||
this(-1, onlineAccounts); |
||||
} |
||||
|
||||
private OnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) { |
||||
super(id, MessageType.ONLINE_ACCOUNTS_V2); |
||||
|
||||
this.onlineAccounts = onlineAccounts; |
||||
} |
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() { |
||||
return this.onlineAccounts; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { |
||||
int accountCount = bytes.getInt(); |
||||
|
||||
List<OnlineAccountData> 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); |
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey)); |
||||
} |
||||
|
||||
if (bytes.hasRemaining()) { |
||||
accountCount = bytes.getInt(); |
||||
} else { |
||||
// we've finished
|
||||
accountCount = 0; |
||||
} |
||||
} |
||||
|
||||
return new OnlineAccountsV2Message(id, onlineAccounts); |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized byte[] toData() { |
||||
if (this.cachedData != null) |
||||
return this.cachedData; |
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (this.onlineAccounts.isEmpty()) { |
||||
this.cachedData = Ints.toByteArray(0); |
||||
return this.cachedData; |
||||
} |
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>(); |
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) { |
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i); |
||||
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) |
||||
+ this.onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH); |
||||
|
||||
try { |
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize); |
||||
|
||||
for (long timestamp : countByTimestamp.keySet()) { |
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp))); |
||||
|
||||
bytes.write(Longs.toByteArray(timestamp)); |
||||
|
||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) { |
||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i); |
||||
|
||||
if (onlineAccountData.getTimestamp() == timestamp) { |
||||
bytes.write(onlineAccountData.getSignature()); |
||||
|
||||
bytes.write(onlineAccountData.getPublicKey()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
this.cachedData = bytes.toByteArray(); |
||||
return this.cachedData; |
||||
} catch (IOException e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,114 @@
|
||||
package org.qortal.test.network; |
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; |
||||
import org.junit.Test; |
||||
import org.qortal.data.network.OnlineAccountData; |
||||
import org.qortal.network.message.*; |
||||
import org.qortal.transform.Transformer; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.security.Security; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Random; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
public class OnlineAccountsTests { |
||||
|
||||
private static final Random RANDOM = new Random(); |
||||
static { |
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); |
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0); |
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testGetOnlineAccountsV2() throws Message.MessageException { |
||||
List<OnlineAccountData> onlineAccountsOut = generateOnlineAccounts(false); |
||||
|
||||
Message messageOut = new GetOnlineAccountsV2Message(onlineAccountsOut); |
||||
|
||||
byte[] messageBytes = messageOut.toBytes(); |
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(messageBytes); |
||||
|
||||
GetOnlineAccountsV2Message messageIn = (GetOnlineAccountsV2Message) Message.fromByteBuffer(byteBuffer); |
||||
|
||||
List<OnlineAccountData> onlineAccountsIn = messageIn.getOnlineAccounts(); |
||||
|
||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size()); |
||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut)); |
||||
|
||||
Message oldMessageOut = new GetOnlineAccountsMessage(onlineAccountsOut); |
||||
byte[] oldMessageBytes = oldMessageOut.toBytes(); |
||||
|
||||
long numTimestamps = onlineAccountsOut.stream().mapToLong(OnlineAccountData::getTimestamp).sorted().distinct().count(); |
||||
|
||||
System.out.println(String.format("For %d accounts split across %d timestamp%s: old size %d vs new size %d", |
||||
onlineAccountsOut.size(), |
||||
numTimestamps, |
||||
numTimestamps != 1 ? "s" : "", |
||||
oldMessageBytes.length, |
||||
messageBytes.length)); |
||||
} |
||||
|
||||
@Test |
||||
public void testOnlineAccountsV2() throws Message.MessageException { |
||||
List<OnlineAccountData> onlineAccountsOut = generateOnlineAccounts(true); |
||||
|
||||
Message messageOut = new OnlineAccountsV2Message(onlineAccountsOut); |
||||
|
||||
byte[] messageBytes = messageOut.toBytes(); |
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(messageBytes); |
||||
|
||||
OnlineAccountsV2Message messageIn = (OnlineAccountsV2Message) Message.fromByteBuffer(byteBuffer); |
||||
|
||||
List<OnlineAccountData> onlineAccountsIn = messageIn.getOnlineAccounts(); |
||||
|
||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size()); |
||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut)); |
||||
|
||||
Message oldMessageOut = new OnlineAccountsMessage(onlineAccountsOut); |
||||
byte[] oldMessageBytes = oldMessageOut.toBytes(); |
||||
|
||||
long numTimestamps = onlineAccountsOut.stream().mapToLong(OnlineAccountData::getTimestamp).sorted().distinct().count(); |
||||
|
||||
System.out.println(String.format("For %d accounts split across %d timestamp%s: old size %d vs new size %d", |
||||
onlineAccountsOut.size(), |
||||
numTimestamps, |
||||
numTimestamps != 1 ? "s" : "", |
||||
oldMessageBytes.length, |
||||
messageBytes.length)); |
||||
} |
||||
|
||||
private List<OnlineAccountData> generateOnlineAccounts(boolean withSignatures) { |
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(); |
||||
|
||||
int numTimestamps = RANDOM.nextInt(2) + 1; // 1 or 2
|
||||
|
||||
for (int t = 0; t < numTimestamps; ++t) { |
||||
int numAccounts = RANDOM.nextInt(3000); |
||||
|
||||
for (int a = 0; a < numAccounts; ++a) { |
||||
byte[] sig = null; |
||||
if (withSignatures) { |
||||
sig = new byte[Transformer.SIGNATURE_LENGTH]; |
||||
RANDOM.nextBytes(sig); |
||||
} |
||||
|
||||
byte[] pubkey = new byte[Transformer.PUBLIC_KEY_LENGTH]; |
||||
RANDOM.nextBytes(pubkey); |
||||
|
||||
onlineAccounts.add(new OnlineAccountData(t << 32, sig, pubkey)); |
||||
} |
||||
} |
||||
|
||||
return onlineAccounts; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue