forked from Qortal/qortal
Added mempow support in OnlineAccountsManager.
- Removed OnlineAccountsMessage and GetOnlineAccountsMessage (no longer any need for backwards support). - Added OnlineAccountsV3Message and GetOnlineAccountsV3Message. - Mempow computations can be opted in early via the "onlineAccountsMemPoWEnabled" setting (although they won't be validated). - Feature trigger timestamp set to unknown future date. - Still needs calibration on a testnet. - Still need to uncomment/finalize code to calculate "next" signature ahead of time.
This commit is contained in:
parent
20d45955e5
commit
f993f938f4
@ -162,6 +162,10 @@ public class BlockChain {
|
|||||||
* featureTriggers because unit tests need to set this value via Reflection. */
|
* featureTriggers because unit tests need to set this value via Reflection. */
|
||||||
private long onlineAccountsModulusV2Timestamp;
|
private long onlineAccountsModulusV2Timestamp;
|
||||||
|
|
||||||
|
/** Feature trigger timestamp for online accounts mempow verification. Can't use featureTriggers
|
||||||
|
* because unit tests need to set this value via Reflection. */
|
||||||
|
private long onlineAccountsMemoryPoWTimestamp;
|
||||||
|
|
||||||
/** Settings relating to CIYAM AT feature. */
|
/** Settings relating to CIYAM AT feature. */
|
||||||
public static class CiyamAtSettings {
|
public static class CiyamAtSettings {
|
||||||
/** Fee per step/op-code executed. */
|
/** Fee per step/op-code executed. */
|
||||||
@ -325,6 +329,10 @@ public class BlockChain {
|
|||||||
return this.onlineAccountsModulusV2Timestamp;
|
return this.onlineAccountsModulusV2Timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getOnlineAccountsMemoryPoWTimestamp() {
|
||||||
|
return this.onlineAccountsMemoryPoWTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||||
public boolean getRequireGroupForApproval() {
|
public boolean getRequireGroupForApproval() {
|
||||||
return this.requireGroupForApproval;
|
return this.requireGroupForApproval;
|
||||||
|
@ -1140,14 +1140,6 @@ public class Controller extends Thread {
|
|||||||
TransactionImporter.getInstance().onNetworkTransactionSignaturesMessage(peer, message);
|
TransactionImporter.getInstance().onNetworkTransactionSignaturesMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GET_ONLINE_ACCOUNTS:
|
|
||||||
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ONLINE_ACCOUNTS:
|
|
||||||
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsMessage(peer, message);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_ONLINE_ACCOUNTS_V2:
|
case GET_ONLINE_ACCOUNTS_V2:
|
||||||
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV2Message(peer, message);
|
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV2Message(peer, message);
|
||||||
break;
|
break;
|
||||||
@ -1156,6 +1148,14 @@ public class Controller extends Thread {
|
|||||||
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV2Message(peer, message);
|
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV2Message(peer, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case GET_ONLINE_ACCOUNTS_V3:
|
||||||
|
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV3Message(peer, message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ONLINE_ACCOUNTS_V3:
|
||||||
|
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV3Message(peer, message);
|
||||||
|
break;
|
||||||
|
|
||||||
case GET_ARBITRARY_DATA:
|
case GET_ARBITRARY_DATA:
|
||||||
// Not currently supported
|
// Not currently supported
|
||||||
break;
|
break;
|
||||||
|
@ -7,8 +7,10 @@ import org.qortal.account.Account;
|
|||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.account.PublicKeyAccount;
|
import org.qortal.account.PublicKeyAccount;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
|
import org.qortal.crypto.MemoryPoW;
|
||||||
import org.qortal.data.account.MintingAccountData;
|
import org.qortal.data.account.MintingAccountData;
|
||||||
import org.qortal.data.account.RewardShareData;
|
import org.qortal.data.account.RewardShareData;
|
||||||
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.network.OnlineAccountData;
|
import org.qortal.data.network.OnlineAccountData;
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
@ -16,10 +18,14 @@ import org.qortal.network.message.*;
|
|||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class OnlineAccountsManager extends Thread {
|
public class OnlineAccountsManager extends Thread {
|
||||||
@ -47,6 +53,11 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
private static OnlineAccountsManager instance;
|
private static OnlineAccountsManager instance;
|
||||||
private volatile boolean isStopping = false;
|
private volatile boolean isStopping = false;
|
||||||
|
|
||||||
|
// MemoryPoW
|
||||||
|
public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes
|
||||||
|
public int POW_DIFFICULTY = 18; // leading zero bits
|
||||||
|
public static final int MAX_NONCE_COUNT = 1; // Maximum number of nonces to verify
|
||||||
|
|
||||||
// To do with online accounts list
|
// To do with online accounts list
|
||||||
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms
|
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms
|
||||||
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms
|
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms
|
||||||
@ -54,7 +65,7 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
public static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L;
|
public static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L;
|
||||||
/** How many (latest) blocks' worth of online accounts we cache */
|
/** How many (latest) blocks' worth of online accounts we cache */
|
||||||
private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2;
|
private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2;
|
||||||
private static final long ONLINE_ACCOUNTS_V2_PEER_VERSION = 0x0300020000L;
|
private static final long ONLINE_ACCOUNTS_V3_PEER_VERSION = 0x0300030000L;
|
||||||
|
|
||||||
private long onlineAccountsTasksTimestamp = Controller.startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
|
private long onlineAccountsTasksTimestamp = Controller.startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
|
||||||
|
|
||||||
@ -193,8 +204,16 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate mempow if feature trigger is active
|
||||||
|
if (now >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||||
|
if (!this.verifyMemoryPoW(onlineAccountData)) {
|
||||||
|
LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (this.onlineAccounts) {
|
synchronized (this.onlineAccounts) {
|
||||||
OnlineAccountData existingAccountData = this.onlineAccounts.stream().filter(account -> Arrays.equals(account.getPublicKey(), onlineAccountData.getPublicKey())).findFirst().orElse(null);
|
OnlineAccountData existingAccountData = this.onlineAccounts.stream().filter(account -> Arrays.equals(account.getPublicKey(), onlineAccountData.getPublicKey())).findFirst().orElse(null); // CME??
|
||||||
|
|
||||||
if (existingAccountData != null) {
|
if (existingAccountData != null) {
|
||||||
if (existingAccountData.getTimestamp() < onlineAccountData.getTimestamp()) {
|
if (existingAccountData.getTimestamp() < onlineAccountData.getTimestamp()) {
|
||||||
@ -225,21 +244,21 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now);
|
final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now);
|
||||||
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
|
|
||||||
|
List<MintingAccountData> mintingAccounts = new ArrayList<>();
|
||||||
|
|
||||||
synchronized (this.onlineAccounts) {
|
synchronized (this.onlineAccounts) {
|
||||||
this.onlineAccounts.clear();
|
this.onlineAccounts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
for (PrivateKeyAccount onlineAccount : onlineAccounts) {
|
for (PrivateKeyAccount onlineAccount : onlineAccounts) {
|
||||||
// Check mintingAccount is actually reward-share?
|
// Check mintingAccount is actually reward-share?
|
||||||
|
|
||||||
byte[] signature = onlineAccount.sign(timestampBytes);
|
MintingAccountData mintingAccountData = new MintingAccountData(onlineAccount.getPrivateKey(), onlineAccount.getPublicKey());
|
||||||
byte[] publicKey = onlineAccount.getPublicKey();
|
mintingAccounts.add(mintingAccountData);
|
||||||
|
}
|
||||||
|
|
||||||
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
|
computeOurAccountsForTimestamp(mintingAccounts, onlineAccountsTimestamp);
|
||||||
this.onlineAccounts.add(ourOnlineAccountData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performOnlineAccountsTasks() {
|
private void performOnlineAccountsTasks() {
|
||||||
@ -273,11 +292,11 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
safeOnlineAccounts = new ArrayList<>(this.onlineAccounts);
|
safeOnlineAccounts = new ArrayList<>(this.onlineAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
Message messageV1 = new GetOnlineAccountsMessage(safeOnlineAccounts);
|
|
||||||
Message messageV2 = new GetOnlineAccountsV2Message(safeOnlineAccounts);
|
Message messageV2 = new GetOnlineAccountsV2Message(safeOnlineAccounts);
|
||||||
|
Message messageV3 = new GetOnlineAccountsV3Message(safeOnlineAccounts);
|
||||||
|
|
||||||
Network.getInstance().broadcast(peer ->
|
Network.getInstance().broadcast(peer ->
|
||||||
peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION ? messageV2 : messageV1
|
peer.getPeersVersion() >= ONLINE_ACCOUNTS_V3_PEER_VERSION ? messageV3 : messageV2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,6 +307,11 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're not up-to-date, then there's no point in computing anything yet
|
||||||
|
if (!Controller.getInstance().isUpToDate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<MintingAccountData> mintingAccounts;
|
List<MintingAccountData> mintingAccounts;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
mintingAccounts = repository.getAccountRepository().getMintingAccounts();
|
mintingAccounts = repository.getAccountRepository().getMintingAccounts();
|
||||||
@ -328,38 +352,86 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
|
|
||||||
// 'current' timestamp
|
// 'current' timestamp
|
||||||
final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now);
|
final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now);
|
||||||
|
computeOurAccountsForTimestamp(mintingAccounts, onlineAccountsTimestamp);
|
||||||
|
|
||||||
|
// 'next' timestamp // TODO
|
||||||
|
// final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(now) + getOnlineTimestampModulus();
|
||||||
|
// computeOurAccountsForTimestamp(mintingAccounts, nextOnlineAccountsTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a mempow nonce and signature for a given set of accounts and timestamp
|
||||||
|
* @param mintingAccounts - the online accounts
|
||||||
|
* @param onlineAccountsTimestamp - the online accounts timestamp
|
||||||
|
*/
|
||||||
|
private void computeOurAccountsForTimestamp(List<MintingAccountData> mintingAccounts, long onlineAccountsTimestamp) {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
boolean hasInfoChanged = false;
|
boolean hasInfoChanged = false;
|
||||||
|
|
||||||
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
|
final long currentOnlineAccountsTimestamp = toOnlineAccountTimestamp(NTP.getTime());
|
||||||
|
|
||||||
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
|
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
|
||||||
|
|
||||||
MINTING_ACCOUNTS:
|
MINTING_ACCOUNTS:
|
||||||
for (MintingAccountData mintingAccountData : mintingAccounts) {
|
for (MintingAccountData mintingAccountData : mintingAccounts) {
|
||||||
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey());
|
PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey());
|
||||||
|
|
||||||
byte[] signature = mintingAccount.sign(timestampBytes);
|
|
||||||
byte[] publicKey = mintingAccount.getPublicKey();
|
byte[] publicKey = mintingAccount.getPublicKey();
|
||||||
|
|
||||||
// Our account is online
|
// Our account is online
|
||||||
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey);
|
List<OnlineAccountData> safeOnlineAccounts;
|
||||||
synchronized (this.onlineAccounts) {
|
synchronized (this.onlineAccounts) {
|
||||||
Iterator<OnlineAccountData> iterator = this.onlineAccounts.iterator();
|
safeOnlineAccounts = new ArrayList<>(this.onlineAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<OnlineAccountData> iterator = safeOnlineAccounts.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
OnlineAccountData existingOnlineAccountData = iterator.next();
|
OnlineAccountData existingOnlineAccountData = iterator.next();
|
||||||
|
|
||||||
if (Arrays.equals(existingOnlineAccountData.getPublicKey(), ourOnlineAccountData.getPublicKey())) {
|
if (Arrays.equals(existingOnlineAccountData.getPublicKey(), publicKey)) {
|
||||||
// If our online account is already present, with same timestamp, then move on to next mintingAccount
|
// If our online account is already present, with same timestamp, then move on to next mintingAccount
|
||||||
if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp)
|
if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp)
|
||||||
continue MINTING_ACCOUNTS;
|
continue MINTING_ACCOUNTS;
|
||||||
|
|
||||||
// If our online account is already present, but with older timestamp, then remove it
|
// If our online account is already present, but with older timestamp, then remove it
|
||||||
iterator.remove();
|
if (existingOnlineAccountData.getTimestamp() < currentOnlineAccountsTimestamp) {
|
||||||
break;
|
this.onlineAccounts.remove(existingOnlineAccountData); // Safe because we are iterating through a copy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onlineAccounts.add(ourOnlineAccountData);
|
// We need to add a new account
|
||||||
|
|
||||||
|
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
|
||||||
|
int chainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
int referenceHeight = Math.max(1, chainHeight - 10);
|
||||||
|
BlockData recentBlockData = repository.getBlockRepository().fromHeight(referenceHeight);
|
||||||
|
if (recentBlockData == null || recentBlockData.getSignature() == null) {
|
||||||
|
LOGGER.info("Unable to compute online accounts without having a recent block");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
byte[] reducedRecentBlockSignature = Arrays.copyOfRange(recentBlockData.getSignature(), 0, 8);
|
||||||
|
|
||||||
|
byte[] mempowBytes;
|
||||||
|
try {
|
||||||
|
mempowBytes = this.getMemoryPoWBytes(publicKey, onlineAccountsTimestamp, reducedRecentBlockSignature);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
LOGGER.info("Unable to create bytes for MemoryPoW. Moving on to next account...");
|
||||||
|
continue MINTING_ACCOUNTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer nonce = this.computeMemoryPoW(mempowBytes, publicKey);
|
||||||
|
if (nonce == null) {
|
||||||
|
// Send zero if we haven't computed a nonce due to feature trigger timestamp
|
||||||
|
nonce = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signature = mintingAccount.sign(timestampBytes); // TODO: include nonce and block signature?
|
||||||
|
|
||||||
|
OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, Arrays.asList(nonce), reducedRecentBlockSignature);
|
||||||
|
|
||||||
|
this.onlineAccounts.add(ourOnlineAccountData);
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp));
|
LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp));
|
||||||
ourOnlineAccounts.add(ourOnlineAccountData);
|
ourOnlineAccounts.add(ourOnlineAccountData);
|
||||||
@ -369,14 +441,94 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
if (!hasInfoChanged)
|
if (!hasInfoChanged)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Message messageV1 = new OnlineAccountsMessage(ourOnlineAccounts);
|
|
||||||
Message messageV2 = new OnlineAccountsV2Message(ourOnlineAccounts);
|
Message messageV2 = new OnlineAccountsV2Message(ourOnlineAccounts);
|
||||||
|
Message messageV3 = new OnlineAccountsV3Message(ourOnlineAccounts);
|
||||||
|
|
||||||
Network.getInstance().broadcast(peer ->
|
Network.getInstance().broadcast(peer ->
|
||||||
peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION ? messageV2 : messageV1
|
peer.getPeersVersion() >= ONLINE_ACCOUNTS_V3_PEER_VERSION ? messageV3 : messageV2
|
||||||
);
|
);
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Broadcasted %d online account%s with timestamp %d", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp));
|
LOGGER.trace(() -> String.format("Broadcasted %d online account%s with timestamp %d", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp));
|
||||||
|
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.error(String.format("Repository issue while computing online accounts"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getMemoryPoWBytes(byte[] publicKey, long onlineAccountsTimestamp, byte[] reducedRecentBlockSignature) throws IOException {
|
||||||
|
byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp);
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
outputStream.write(publicKey);
|
||||||
|
outputStream.write(timestampBytes);
|
||||||
|
outputStream.write(reducedRecentBlockSignature);
|
||||||
|
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer computeMemoryPoW(byte[] bytes, byte[] publicKey) {
|
||||||
|
Long startTime = NTP.getTime();
|
||||||
|
if (startTime < BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp() || Settings.getInstance().isOnlineAccountsMemPoWEnabled()) {
|
||||||
|
LOGGER.info("Mempow start timestamp not yet reached, and onlineAccountsMemPoWEnabled not enabled in settings");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Computing nonce for account %.8s...", Base58.encode(publicKey)));
|
||||||
|
|
||||||
|
// Calculate the time until the next online timestamp and use it as a timeout when computing the nonce
|
||||||
|
final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(startTime) + getOnlineTimestampModulus();
|
||||||
|
long timeUntilNextTimestamp = nextOnlineAccountsTimestamp - startTime;
|
||||||
|
|
||||||
|
Integer nonce;
|
||||||
|
try {
|
||||||
|
nonce = MemoryPoW.compute2(bytes, POW_BUFFER_SIZE, POW_DIFFICULTY, timeUntilNextTimestamp);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
LOGGER.info("Timed out computing nonce for account %.8s", Base58.encode(publicKey));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalSeconds = (NTP.getTime() - startTime) / 1000.0f;
|
||||||
|
int minutes = (int) ((totalSeconds % 3600) / 60);
|
||||||
|
int seconds = (int) (totalSeconds % 60);
|
||||||
|
double hashRate = nonce / totalSeconds;
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Computed nonce for account %.8s: %d. Buffer size: %d. Difficulty: %d. " +
|
||||||
|
"Time taken: %02d:%02d. Hashrate: %f", Base58.encode(publicKey), nonce,
|
||||||
|
POW_BUFFER_SIZE, POW_DIFFICULTY, minutes, seconds, hashRate));
|
||||||
|
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData) {
|
||||||
|
List<Integer> nonces = onlineAccountData.getNonces();
|
||||||
|
if (nonces == null || nonces.isEmpty()) {
|
||||||
|
// Missing required nonce value(s)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonces.size() > MAX_NONCE_COUNT) {
|
||||||
|
// More than the allowed nonce count
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] reducedBlockSignature = onlineAccountData.getReducedBlockSignature();
|
||||||
|
if (reducedBlockSignature == null) {
|
||||||
|
// Missing required block signature
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] mempowBytes;
|
||||||
|
try {
|
||||||
|
mempowBytes = this.getMemoryPoWBytes(onlineAccountData.getPublicKey(), onlineAccountData.getTimestamp(), reducedBlockSignature);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we will only require a single nonce
|
||||||
|
int nonce = nonces.get(0);
|
||||||
|
|
||||||
|
// Verify the nonce
|
||||||
|
return MemoryPoW.verify2(mempowBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long toOnlineAccountTimestamp(long timestamp) {
|
public static long toOnlineAccountTimestamp(long timestamp) {
|
||||||
@ -422,53 +574,6 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
|
|
||||||
// Network handlers
|
// Network handlers
|
||||||
|
|
||||||
public void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) {
|
|
||||||
GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message;
|
|
||||||
|
|
||||||
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
|
|
||||||
|
|
||||||
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
|
|
||||||
List<OnlineAccountData> accountsToSend;
|
|
||||||
synchronized (this.onlineAccounts) {
|
|
||||||
accountsToSend = new ArrayList<>(this.onlineAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
|
|
||||||
|
|
||||||
SEND_ITERATOR:
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
OnlineAccountData onlineAccountData = iterator.next();
|
|
||||||
|
|
||||||
for (int i = 0; i < excludeAccounts.size(); ++i) {
|
|
||||||
OnlineAccountData excludeAccountData = excludeAccounts.get(i);
|
|
||||||
|
|
||||||
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
|
|
||||||
iterator.remove();
|
|
||||||
continue SEND_ITERATOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend);
|
|
||||||
peer.sendMessage(onlineAccountsMessage);
|
|
||||||
|
|
||||||
LOGGER.trace(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNetworkOnlineAccountsMessage(Peer peer, Message message) {
|
|
||||||
OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message;
|
|
||||||
|
|
||||||
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
|
|
||||||
LOGGER.trace(() -> String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer));
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
for (OnlineAccountData onlineAccountData : peersOnlineAccounts)
|
|
||||||
this.verifyAndAddAccount(repository, onlineAccountData);
|
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.error(String.format("Repository issue while verifying online accounts from peer %s", peer), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNetworkGetOnlineAccountsV2Message(Peer peer, Message message) {
|
public void onNetworkGetOnlineAccountsV2Message(Peer peer, Message message) {
|
||||||
GetOnlineAccountsV2Message getOnlineAccountsMessage = (GetOnlineAccountsV2Message) message;
|
GetOnlineAccountsV2Message getOnlineAccountsMessage = (GetOnlineAccountsV2Message) message;
|
||||||
|
|
||||||
@ -529,4 +634,65 @@ public class OnlineAccountsManager extends Thread {
|
|||||||
|
|
||||||
LOGGER.debug(String.format("Added %d online accounts to queue", importCount));
|
LOGGER.debug(String.format("Added %d online accounts to queue", importCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onNetworkGetOnlineAccountsV3Message(Peer peer, Message message) {
|
||||||
|
GetOnlineAccountsV3Message getOnlineAccountsMessage = (GetOnlineAccountsV3Message) message;
|
||||||
|
|
||||||
|
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
|
||||||
|
|
||||||
|
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
|
||||||
|
List<OnlineAccountData> accountsToSend;
|
||||||
|
synchronized (this.onlineAccounts) {
|
||||||
|
accountsToSend = new ArrayList<>(this.onlineAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
|
||||||
|
|
||||||
|
SEND_ITERATOR:
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
OnlineAccountData onlineAccountData = iterator.next();
|
||||||
|
|
||||||
|
for (int i = 0; i < excludeAccounts.size(); ++i) {
|
||||||
|
OnlineAccountData excludeAccountData = excludeAccounts.get(i);
|
||||||
|
|
||||||
|
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
|
||||||
|
iterator.remove();
|
||||||
|
continue SEND_ITERATOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message onlineAccountsMessage = new OnlineAccountsV3Message(accountsToSend);
|
||||||
|
peer.sendMessage(onlineAccountsMessage);
|
||||||
|
|
||||||
|
LOGGER.trace(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNetworkOnlineAccountsV3Message(Peer peer, Message message) {
|
||||||
|
OnlineAccountsV3Message onlineAccountsMessage = (OnlineAccountsV3Message) message;
|
||||||
|
|
||||||
|
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
|
||||||
|
LOGGER.debug(String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer));
|
||||||
|
|
||||||
|
int importCount = 0;
|
||||||
|
|
||||||
|
// Add any online accounts to the queue that aren't already present
|
||||||
|
for (OnlineAccountData onlineAccountData : peersOnlineAccounts) {
|
||||||
|
|
||||||
|
// Do we already know about this online account data?
|
||||||
|
if (onlineAccounts.contains(onlineAccountData)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it already in the import queue?
|
||||||
|
if (onlineAccountsImportQueue.contains(onlineAccountData)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineAccountsImportQueue.add(onlineAccountData);
|
||||||
|
importCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug(String.format("Added %d online accounts to queue", importCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.qortal.data.network;
|
package org.qortal.data.network;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@ -15,6 +16,8 @@ public class OnlineAccountData {
|
|||||||
protected long timestamp;
|
protected long timestamp;
|
||||||
protected byte[] signature;
|
protected byte[] signature;
|
||||||
protected byte[] publicKey;
|
protected byte[] publicKey;
|
||||||
|
protected List<Integer> nonces;
|
||||||
|
protected byte[] reducedBlockSignature;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -22,10 +25,16 @@ public class OnlineAccountData {
|
|||||||
protected OnlineAccountData() {
|
protected OnlineAccountData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) {
|
public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey, List<Integer> nonces, byte[] reducedBlockSignature) {
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
this.nonces = nonces;
|
||||||
|
this.reducedBlockSignature = reducedBlockSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) {
|
||||||
|
this(timestamp, signature, publicKey, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
@ -40,6 +49,14 @@ public class OnlineAccountData {
|
|||||||
return this.publicKey;
|
return this.publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Integer> getNonces() {
|
||||||
|
return this.nonces;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getReducedBlockSignature() {
|
||||||
|
return this.reducedBlockSignature;
|
||||||
|
}
|
||||||
|
|
||||||
// For JAXB
|
// For JAXB
|
||||||
@XmlElement(name = "address")
|
@XmlElement(name = "address")
|
||||||
protected String getAddress() {
|
protected String getAddress() {
|
||||||
@ -69,6 +86,8 @@ public class OnlineAccountData {
|
|||||||
if (!Arrays.equals(otherOnlineAccountData.publicKey, this.publicKey))
|
if (!Arrays.equals(otherOnlineAccountData.publicKey, this.publicKey))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Best not to consider additional properties for the purposes of uniqueness
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.qortal.data.network.OnlineAccountData;
|
|
||||||
import org.qortal.transform.Transformer;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class GetOnlineAccountsMessage extends Message {
|
|
||||||
private static final int MAX_ACCOUNT_COUNT = 5000;
|
|
||||||
|
|
||||||
private List<OnlineAccountData> onlineAccounts;
|
|
||||||
|
|
||||||
public GetOnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
|
||||||
this(-1, onlineAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GetOnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
|
||||||
super(id, MessageType.GET_ONLINE_ACCOUNTS);
|
|
||||||
|
|
||||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OnlineAccountData> getOnlineAccounts() {
|
|
||||||
return this.onlineAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
final int accountCount = bytes.getInt();
|
|
||||||
|
|
||||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
|
||||||
long timestamp = bytes.getLong();
|
|
||||||
|
|
||||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
|
||||||
bytes.get(publicKey);
|
|
||||||
|
|
||||||
onlineAccounts.add(new OnlineAccountData(timestamp, null, publicKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GetOnlineAccountsMessage(id, onlineAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(this.onlineAccounts.size()));
|
|
||||||
|
|
||||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
|
||||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
|
||||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
|
||||||
|
|
||||||
bytes.write(onlineAccountData.getPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,113 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* Identical to V2, but added for consistency, since V2 will ultimately be phased out
|
||||||
|
*/
|
||||||
|
public class GetOnlineAccountsV3Message extends Message {
|
||||||
|
private List<OnlineAccountData> onlineAccounts;
|
||||||
|
private byte[] cachedData;
|
||||||
|
|
||||||
|
public GetOnlineAccountsV3Message(List<OnlineAccountData> onlineAccounts) {
|
||||||
|
this(-1, onlineAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetOnlineAccountsV3Message(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 GetOnlineAccountsV3Message(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -76,10 +76,10 @@ public abstract class Message {
|
|||||||
BLOCK_SUMMARIES(70),
|
BLOCK_SUMMARIES(70),
|
||||||
GET_BLOCK_SUMMARIES(71),
|
GET_BLOCK_SUMMARIES(71),
|
||||||
|
|
||||||
ONLINE_ACCOUNTS(80),
|
|
||||||
GET_ONLINE_ACCOUNTS(81),
|
|
||||||
ONLINE_ACCOUNTS_V2(82),
|
ONLINE_ACCOUNTS_V2(82),
|
||||||
GET_ONLINE_ACCOUNTS_V2(83),
|
GET_ONLINE_ACCOUNTS_V2(83),
|
||||||
|
ONLINE_ACCOUNTS_V3(84),
|
||||||
|
GET_ONLINE_ACCOUNTS_V3(85),
|
||||||
|
|
||||||
ARBITRARY_DATA(90),
|
ARBITRARY_DATA(90),
|
||||||
GET_ARBITRARY_DATA(91),
|
GET_ARBITRARY_DATA(91),
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
package org.qortal.network.message;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.qortal.data.network.OnlineAccountData;
|
|
||||||
import org.qortal.transform.Transformer;
|
|
||||||
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class OnlineAccountsMessage extends Message {
|
|
||||||
private static final int MAX_ACCOUNT_COUNT = 5000;
|
|
||||||
|
|
||||||
private List<OnlineAccountData> onlineAccounts;
|
|
||||||
|
|
||||||
public OnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
|
||||||
this(-1, onlineAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
|
||||||
super(id, MessageType.ONLINE_ACCOUNTS);
|
|
||||||
|
|
||||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<OnlineAccountData> getOnlineAccounts() {
|
|
||||||
return this.onlineAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException {
|
|
||||||
final int accountCount = bytes.getInt();
|
|
||||||
|
|
||||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
|
||||||
long timestamp = bytes.getLong();
|
|
||||||
|
|
||||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
|
||||||
bytes.get(signature);
|
|
||||||
|
|
||||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
|
||||||
bytes.get(publicKey);
|
|
||||||
|
|
||||||
OnlineAccountData onlineAccountData = new OnlineAccountData(timestamp, signature, publicKey);
|
|
||||||
onlineAccounts.add(onlineAccountData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OnlineAccountsMessage(id, onlineAccounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] toData() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(this.onlineAccounts.size()));
|
|
||||||
|
|
||||||
for (int i = 0; i < this.onlineAccounts.size(); ++i) {
|
|
||||||
OnlineAccountData onlineAccountData = this.onlineAccounts.get(i);
|
|
||||||
|
|
||||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
|
||||||
|
|
||||||
bytes.write(onlineAccountData.getSignature());
|
|
||||||
|
|
||||||
bytes.write(onlineAccountData.getPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,139 @@
|
|||||||
|
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 sending online accounts info to remote peer.
|
||||||
|
*
|
||||||
|
* Same format as V2, but with added support for mempow nonce values and a recent block signature
|
||||||
|
*/
|
||||||
|
public class OnlineAccountsV3Message extends Message {
|
||||||
|
private List<OnlineAccountData> onlineAccounts;
|
||||||
|
private byte[] cachedData;
|
||||||
|
|
||||||
|
public OnlineAccountsV3Message(List<OnlineAccountData> onlineAccounts) {
|
||||||
|
this(-1, onlineAccounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnlineAccountsV3Message(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);
|
||||||
|
|
||||||
|
byte[] reducedBlockSignature = new byte[Transformer.REDUCED_SIGNATURE_LENGTH];
|
||||||
|
bytes.get(reducedBlockSignature);
|
||||||
|
|
||||||
|
int nonceCount = bytes.getInt();
|
||||||
|
List<Integer> nonces = new ArrayList<>();
|
||||||
|
for (int n = 0; n < nonceCount; ++n) {
|
||||||
|
Integer nonce = bytes.getInt();
|
||||||
|
nonces.add(nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonces, reducedBlockSignature));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.hasRemaining()) {
|
||||||
|
accountCount = bytes.getInt();
|
||||||
|
} else {
|
||||||
|
// we've finished
|
||||||
|
accountCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OnlineAccountsV3Message(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());
|
||||||
|
|
||||||
|
bytes.write(onlineAccountData.getReducedBlockSignature());
|
||||||
|
|
||||||
|
int nonceCount = onlineAccountData.getNonces() != null ? onlineAccountData.getNonces().size() : 0;
|
||||||
|
bytes.write(Ints.toByteArray(nonceCount));
|
||||||
|
|
||||||
|
for (int n = 0; n < nonceCount; ++n) {
|
||||||
|
int nonce = onlineAccountData.getNonces().get(i);
|
||||||
|
bytes.write(Ints.toByteArray(nonce));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedData = bytes.toByteArray();
|
||||||
|
return this.cachedData;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -273,6 +273,11 @@ public class Settings {
|
|||||||
/** Additional offset added to values returned by NTP.getTime() */
|
/** Additional offset added to values returned by NTP.getTime() */
|
||||||
private Long testNtpOffset = null;
|
private Long testNtpOffset = null;
|
||||||
|
|
||||||
|
// Online accounts
|
||||||
|
|
||||||
|
/** Whether to opt-in to mempow computations for online accounts, ahead of general release */
|
||||||
|
private boolean onlineAccountsMemPoWEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
// Data storage (QDN)
|
// Data storage (QDN)
|
||||||
|
|
||||||
@ -740,6 +745,10 @@ public class Settings {
|
|||||||
return this.testNtpOffset;
|
return this.testNtpOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOnlineAccountsMemPoWEnabled() {
|
||||||
|
return this.onlineAccountsMemPoWEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public long getRepositoryBackupInterval() {
|
public long getRepositoryBackupInterval() {
|
||||||
return this.repositoryBackupInterval;
|
return this.repositoryBackupInterval;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ public abstract class Transformer {
|
|||||||
public static final int SIGNATURE_LENGTH = 64;
|
public static final int SIGNATURE_LENGTH = 64;
|
||||||
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
|
||||||
|
|
||||||
|
public static final int REDUCED_SIGNATURE_LENGTH = 8;
|
||||||
|
|
||||||
public static final int MD5_LENGTH = 16;
|
public static final int MD5_LENGTH = 16;
|
||||||
public static final int SHA256_LENGTH = 32;
|
public static final int SHA256_LENGTH = 32;
|
||||||
public static final int AES256_LENGTH = 32;
|
public static final int AES256_LENGTH = 32;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
|
@ -63,18 +63,6 @@ public class OnlineAccountsTests extends Common {
|
|||||||
|
|
||||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
||||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut));
|
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
|
@Test
|
||||||
@ -92,18 +80,6 @@ public class OnlineAccountsTests extends Common {
|
|||||||
|
|
||||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
||||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut));
|
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) {
|
private List<OnlineAccountData> generateOnlineAccounts(boolean withSignatures) {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
|
"onlineAccountsMemoryPoWTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user