forked from Qortal/qortal
More work on online accounts / blocks
Block data now includes number of online accounts, as encoded online account indexes can't be validated by ConciseSet it seems. Corresponding changes to repository, transformer, block validation, data object, block summaries... Block timestamps are now calculated using parent block data and generator's public key, instead of old qora1 generating balance code. Generators are valid to forge if they have forging flag enabled. This will probably change to an account-level check in the near future. Added trimming of old online accounts signatures from blocks. Tidied up SysTray/BlockGenerator generation enabled/possible flag. Although we perform online accounts tasks (currently) every 10 seconds, only broadcast our online accounts every 60 seconds. In Controller.main(), if args are present then use first as a filename to settings JSON file (overriding the default filename). Still to do: change Block/BlockChain/Synchronizer to prefer blocks with more online accounts, failing that use generator nearest 'ideal', etc.
This commit is contained in:
parent
aa81c86cf1
commit
0dd5b1e65a
@ -16,11 +16,13 @@ import java.util.stream.Collectors;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qora.account.Account;
|
import org.qora.account.Account;
|
||||||
|
import org.qora.account.Forging;
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.at.AT;
|
import org.qora.at.AT;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.block.BlockChain.BlockTimingByHeight;
|
||||||
import org.qora.controller.Controller;
|
import org.qora.controller.Controller;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.ProxyForgerData;
|
import org.qora.data.account.ProxyForgerData;
|
||||||
@ -99,7 +101,7 @@ public class Block {
|
|||||||
TRANSACTION_ALREADY_PROCESSED(54),
|
TRANSACTION_ALREADY_PROCESSED(54),
|
||||||
TRANSACTION_NEEDS_APPROVAL(55),
|
TRANSACTION_NEEDS_APPROVAL(55),
|
||||||
AT_STATES_MISMATCH(61),
|
AT_STATES_MISMATCH(61),
|
||||||
ONLINE_ACCOUNT_LEVEL_ZERO(70),
|
ONLINE_ACCOUNTS_INVALID(70),
|
||||||
ONLINE_ACCOUNT_UNKNOWN(71),
|
ONLINE_ACCOUNT_UNKNOWN(71),
|
||||||
ONLINE_ACCOUNT_SIGNATURES_MISSING(72),
|
ONLINE_ACCOUNT_SIGNATURES_MISSING(72),
|
||||||
ONLINE_ACCOUNT_SIGNATURES_MALFORMED(73),
|
ONLINE_ACCOUNT_SIGNATURES_MALFORMED(73),
|
||||||
@ -147,6 +149,13 @@ public class Block {
|
|||||||
// TODO push this out to blockchain config file
|
// TODO push this out to blockchain config file
|
||||||
public static final int MAX_BLOCK_BYTES = 1048576;
|
public static final int MAX_BLOCK_BYTES = 1048576;
|
||||||
|
|
||||||
|
private static final BigInteger MAX_DISTANCE;
|
||||||
|
static {
|
||||||
|
byte[] maxValue = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||||
|
Arrays.fill(maxValue, (byte) 0xFF);
|
||||||
|
MAX_DISTANCE = new BigInteger(1, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
public static final ConciseSet EMPTY_ONLINE_ACCOUNTS = new ConciseSet();
|
public static final ConciseSet EMPTY_ONLINE_ACCOUNTS = new ConciseSet();
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
@ -251,10 +260,11 @@ public class Block {
|
|||||||
ConciseSet onlineAccountsSet = new ConciseSet();
|
ConciseSet onlineAccountsSet = new ConciseSet();
|
||||||
onlineAccountsSet = onlineAccountsSet.convert(accountIndexes);
|
onlineAccountsSet = onlineAccountsSet.convert(accountIndexes);
|
||||||
byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet);
|
byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet);
|
||||||
|
int onlineAccountsCount = onlineAccountsSet.size();
|
||||||
|
|
||||||
// Concatenate online account timestamp signatures (in correct order)
|
// Concatenate online account timestamp signatures (in correct order)
|
||||||
byte[] onlineAccountsSignatures = new byte[accountIndexes.size() * Transformer.SIGNATURE_LENGTH];
|
byte[] onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH];
|
||||||
for (int i = 0; i < accountIndexes.size(); ++i) {
|
for (int i = 0; i < onlineAccountsCount; ++i) {
|
||||||
Integer accountIndex = accountIndexes.get(i);
|
Integer accountIndex = accountIndexes.get(i);
|
||||||
OnlineAccount onlineAccount = indexedOnlineAccounts.get(accountIndex);
|
OnlineAccount onlineAccount = indexedOnlineAccounts.get(accountIndex);
|
||||||
System.arraycopy(onlineAccount.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
|
System.arraycopy(onlineAccount.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
|
||||||
@ -268,7 +278,7 @@ public class Block {
|
|||||||
throw new DataException("Unable to calculate next block generator signature", e);
|
throw new DataException("Unable to calculate next block generator signature", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
long timestamp = parentBlock.calcNextBlockTimestamp(version, generatorSignature, generator);
|
long timestamp = calcTimestamp(parentBlockData, generator.getPublicKey());
|
||||||
|
|
||||||
int transactionCount = 0;
|
int transactionCount = 0;
|
||||||
byte[] transactionsSignature = null;
|
byte[] transactionsSignature = null;
|
||||||
@ -282,7 +292,8 @@ public class Block {
|
|||||||
|
|
||||||
// This instance used for AT processing
|
// This instance used for AT processing
|
||||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
generator.getPublicKey(), generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsTimestamp, onlineAccountsSignatures);
|
generator.getPublicKey(), generatorSignature, atCount, atFees,
|
||||||
|
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
|
|
||||||
// Requires this.blockData and this.transactions, sets this.ourAtStates and this.ourAtFees
|
// Requires this.blockData and this.transactions, sets this.ourAtStates and this.ourAtFees
|
||||||
this.executeATs();
|
this.executeATs();
|
||||||
@ -294,7 +305,8 @@ public class Block {
|
|||||||
|
|
||||||
// Rebuild blockData using post-AT-execute data
|
// Rebuild blockData using post-AT-execute data
|
||||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
generator.getPublicKey(), generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsTimestamp, onlineAccountsSignatures);
|
generator.getPublicKey(), generatorSignature, atCount, atFees,
|
||||||
|
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -311,7 +323,6 @@ public class Block {
|
|||||||
newBlock.generator = generator;
|
newBlock.generator = generator;
|
||||||
|
|
||||||
BlockData parentBlockData = this.getParent();
|
BlockData parentBlockData = this.getParent();
|
||||||
Block parentBlock = new Block(repository, parentBlockData);
|
|
||||||
|
|
||||||
// Copy AT state data
|
// Copy AT state data
|
||||||
newBlock.ourAtStates = this.ourAtStates;
|
newBlock.ourAtStates = this.ourAtStates;
|
||||||
@ -331,7 +342,7 @@ public class Block {
|
|||||||
throw new DataException("Unable to calculate next block generator signature", e);
|
throw new DataException("Unable to calculate next block generator signature", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
long timestamp = parentBlock.calcNextBlockTimestamp(version, generatorSignature, generator);
|
long timestamp = calcTimestamp(parentBlockData, generator.getPublicKey());
|
||||||
|
|
||||||
newBlock.transactions = this.transactions;
|
newBlock.transactions = this.transactions;
|
||||||
int transactionCount = this.blockData.getTransactionCount();
|
int transactionCount = this.blockData.getTransactionCount();
|
||||||
@ -343,11 +354,12 @@ public class Block {
|
|||||||
BigDecimal atFees = newBlock.ourAtFees;
|
BigDecimal atFees = newBlock.ourAtFees;
|
||||||
|
|
||||||
byte[] encodedOnlineAccounts = this.blockData.getEncodedOnlineAccounts();
|
byte[] encodedOnlineAccounts = this.blockData.getEncodedOnlineAccounts();
|
||||||
|
int onlineAccountsCount = this.blockData.getOnlineAccountsCount();
|
||||||
Long onlineAccountsTimestamp = this.blockData.getOnlineAccountsTimestamp();
|
Long onlineAccountsTimestamp = this.blockData.getOnlineAccountsTimestamp();
|
||||||
byte[] onlineAccountsSignatures = this.blockData.getOnlineAccountsSignatures();
|
byte[] onlineAccountsSignatures = this.blockData.getOnlineAccountsSignatures();
|
||||||
|
|
||||||
newBlock.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
newBlock.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
generator.getPublicKey(), generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsTimestamp, onlineAccountsSignatures);
|
generator.getPublicKey(), generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
|
|
||||||
// Resign to update transactions signature
|
// Resign to update transactions signature
|
||||||
newBlock.sign();
|
newBlock.sign();
|
||||||
@ -474,86 +486,6 @@ public class Block {
|
|||||||
return actualBlockTime;
|
return actualBlockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigInteger calcGeneratorsTarget(PublicKeyAccount nextBlockGenerator) throws DataException {
|
|
||||||
// Start with 32-byte maximum integer representing all possible correct "guesses"
|
|
||||||
// Where a "correct guess" is an integer greater than the threshold represented by calcBlockHash()
|
|
||||||
byte[] targetBytes = new byte[32];
|
|
||||||
Arrays.fill(targetBytes, Byte.MAX_VALUE);
|
|
||||||
BigInteger target = new BigInteger(1, targetBytes);
|
|
||||||
|
|
||||||
// Divide by next block's base target
|
|
||||||
// So if next block requires a higher generating balance then there are fewer remaining "correct guesses"
|
|
||||||
BigInteger baseTarget = BigInteger.valueOf(calcBaseTarget(calcNextBlockGeneratingBalance()));
|
|
||||||
target = target.divide(baseTarget);
|
|
||||||
|
|
||||||
// If generator is actually proxy account then use forger's account to calculate target.
|
|
||||||
BigDecimal generatingBalance;
|
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(nextBlockGenerator.getPublicKey());
|
|
||||||
if (proxyForgerData != null)
|
|
||||||
generatingBalance = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()).getGeneratingBalance();
|
|
||||||
else
|
|
||||||
generatingBalance = nextBlockGenerator.getGeneratingBalance();
|
|
||||||
|
|
||||||
// Multiply by account's generating balance
|
|
||||||
// So the greater the account's generating balance then the greater the remaining "correct guesses"
|
|
||||||
target = target.multiply(generatingBalance.toBigInteger());
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns pseudo-random, but deterministic, integer for this block (and block's generator for v3+ blocks) */
|
|
||||||
private BigInteger calcBlockHash() {
|
|
||||||
byte[] hashData;
|
|
||||||
|
|
||||||
if (this.blockData.getVersion() < 3)
|
|
||||||
hashData = this.blockData.getGeneratorSignature();
|
|
||||||
else
|
|
||||||
hashData = Bytes.concat(this.blockData.getReference(), generator.getPublicKey());
|
|
||||||
|
|
||||||
// Calculate 32-byte hash as pseudo-random, but deterministic, integer (unique to this generator for v3+ blocks)
|
|
||||||
byte[] hash = Crypto.digest(hashData);
|
|
||||||
|
|
||||||
// Convert hash to BigInteger form
|
|
||||||
return new BigInteger(1, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns pseudo-random, but deterministic, integer for next block (and next block's generator for v3+ blocks) */
|
|
||||||
private BigInteger calcNextBlockHash(int nextBlockVersion, byte[] preVersion3GeneratorSignature, PublicKeyAccount nextBlockGenerator) {
|
|
||||||
byte[] hashData;
|
|
||||||
|
|
||||||
if (nextBlockVersion < 3)
|
|
||||||
hashData = preVersion3GeneratorSignature;
|
|
||||||
else
|
|
||||||
hashData = Bytes.concat(this.blockData.getSignature(), nextBlockGenerator.getPublicKey());
|
|
||||||
|
|
||||||
// Calculate 32-byte hash as pseudo-random, but deterministic, integer (unique to this generator for v3+ blocks)
|
|
||||||
byte[] hash = Crypto.digest(hashData);
|
|
||||||
|
|
||||||
// Convert hash to BigInteger form
|
|
||||||
return new BigInteger(1, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calculate next block's timestamp, given next block's version, generator signature and generator's public key */
|
|
||||||
private long calcNextBlockTimestamp(int nextBlockVersion, byte[] nextBlockGeneratorSignature, PublicKeyAccount nextBlockGenerator) throws DataException {
|
|
||||||
BigInteger hashValue = calcNextBlockHash(nextBlockVersion, nextBlockGeneratorSignature, nextBlockGenerator);
|
|
||||||
BigInteger target = calcGeneratorsTarget(nextBlockGenerator);
|
|
||||||
|
|
||||||
// If target is zero then generator has no balance so return longest value
|
|
||||||
if (target.compareTo(BigInteger.ZERO) == 0)
|
|
||||||
return Long.MAX_VALUE;
|
|
||||||
|
|
||||||
// Use ratio of "correct guesses" to calculate minimum delay until this generator can forge a block
|
|
||||||
BigInteger seconds = hashValue.divide(target).add(BigInteger.ONE);
|
|
||||||
|
|
||||||
// Calculate next block timestamp using delay
|
|
||||||
BigInteger timestamp = seconds.multiply(BigInteger.valueOf(1000)).add(BigInteger.valueOf(this.blockData.getTimestamp()));
|
|
||||||
|
|
||||||
// Limit timestamp to maximum long value
|
|
||||||
timestamp = timestamp.min(BigInteger.valueOf(Long.MAX_VALUE));
|
|
||||||
|
|
||||||
return timestamp.longValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return block's transactions.
|
* Return block's transactions.
|
||||||
* <p>
|
* <p>
|
||||||
@ -787,14 +719,55 @@ public class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] calcIdealGeneratorPublicKey(int parentBlockHeight, byte[] parentBlockSignature) {
|
||||||
|
return Crypto.digest(Bytes.concat(Longs.toByteArray(parentBlockHeight), parentBlockSignature));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] calcHeightPerturbedPublicKey(int height, byte[] publicKey) {
|
||||||
|
return Crypto.digest(Bytes.concat(Longs.toByteArray(height), publicKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger calcGeneratorDistance(BlockData parentBlockData, byte[] generatorPublicKey) {
|
||||||
|
final int parentHeight = parentBlockData.getHeight();
|
||||||
|
final int thisHeight = parentHeight + 1;
|
||||||
|
|
||||||
|
// Convert all bits into unsigned BigInteger
|
||||||
|
BigInteger idealBI = new BigInteger(1, calcIdealGeneratorPublicKey(parentHeight, parentBlockData.getSignature()));
|
||||||
|
BigInteger generatorBI = new BigInteger(1, calcHeightPerturbedPublicKey(thisHeight, generatorPublicKey));
|
||||||
|
return idealBI.subtract(generatorBI).abs();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns timestamp based on previous block.
|
* Returns timestamp based on previous block and this block's generator.
|
||||||
* <p>
|
* <p>
|
||||||
* For qora-core, we'll using the minimum from BlockChain config.
|
* Uses same proportion of this block's generator from 'ideal' generator
|
||||||
|
* with min to max target block periods, added to previous block's timestamp.
|
||||||
|
* <p>
|
||||||
|
* Example:<br>
|
||||||
|
* This block's generator is 20% of max distance from 'ideal' generator.<br>
|
||||||
|
* Min/Max block periods are 30s and 90s respectively.<br>
|
||||||
|
* 20% of (90s - 30s) is 12s<br>
|
||||||
|
* So this block's timestamp is previous block's timestamp + 30s + 12s.
|
||||||
*/
|
*/
|
||||||
|
public static long calcTimestamp(BlockData parentBlockData, byte[] generatorPublicKey) {
|
||||||
|
BigInteger distance = calcGeneratorDistance(parentBlockData, generatorPublicKey);
|
||||||
|
final int thisHeight = parentBlockData.getHeight() + 1;
|
||||||
|
BlockTimingByHeight blockTiming = BlockChain.getInstance().getBlockTimingByHeight(thisHeight);
|
||||||
|
|
||||||
|
double ratio = new BigDecimal(distance).divide(new BigDecimal(MAX_DISTANCE), 40, RoundingMode.DOWN).doubleValue();
|
||||||
|
|
||||||
|
// Use power transform on ratio to spread out smaller values for bigger effect
|
||||||
|
double transformed = Math.pow(ratio, blockTiming.power);
|
||||||
|
|
||||||
|
long timeOffset = Double.valueOf(blockTiming.deviation * 2.0 * transformed).longValue();
|
||||||
|
|
||||||
|
return parentBlockData.getTimestamp() + blockTiming.target - blockTiming.deviation + timeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
public static long calcMinimumTimestamp(BlockData parentBlockData) {
|
public static long calcMinimumTimestamp(BlockData parentBlockData) {
|
||||||
long minBlockTime = BlockChain.getInstance().getMinBlockTime(); // seconds
|
final int thisHeight = parentBlockData.getHeight() + 1;
|
||||||
return parentBlockData.getTimestamp() + (minBlockTime * 1000L);
|
BlockTimingByHeight blockTiming = BlockChain.getInstance().getBlockTimingByHeight(thisHeight);
|
||||||
|
return parentBlockData.getTimestamp() + blockTiming.target - blockTiming.deviation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -856,14 +829,13 @@ public class Block {
|
|||||||
if (this.blockData.getTimestamp() - BlockChain.getInstance().getBlockTimestampMargin() > NTP.getTime())
|
if (this.blockData.getTimestamp() - BlockChain.getInstance().getBlockTimestampMargin() > NTP.getTime())
|
||||||
return ValidationResult.TIMESTAMP_IN_FUTURE;
|
return ValidationResult.TIMESTAMP_IN_FUTURE;
|
||||||
|
|
||||||
// Legacy gen1 test: check timestamp milliseconds is the same as parent timestamp milliseconds?
|
// Check timestamp is at least minimum based on parent block
|
||||||
if (this.blockData.getTimestamp() % 1000 != parentBlockData.getTimestamp() % 1000)
|
if (this.blockData.getTimestamp() < Block.calcMinimumTimestamp(parentBlockData))
|
||||||
return ValidationResult.TIMESTAMP_MS_INCORRECT;
|
return ValidationResult.TIMESTAMP_TOO_SOON;
|
||||||
|
|
||||||
// Too early to forge block?
|
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getGeneratorPublicKey());
|
||||||
// XXX DISABLED as it doesn't work - but why?
|
if (this.blockData.getTimestamp() != expectedTimestamp)
|
||||||
// if (this.blockData.getTimestamp() < Block.calcMinimumTimestamp(parentBlockData))
|
return ValidationResult.TIMESTAMP_INCORRECT;
|
||||||
// return ValidationResult.TIMESTAMP_TOO_SOON;
|
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
@ -875,6 +847,9 @@ public class Block {
|
|||||||
|
|
||||||
// Expand block's online accounts indexes into actual accounts
|
// Expand block's online accounts indexes into actual accounts
|
||||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||||
|
// We use count of online accounts to validate decoded account indexes
|
||||||
|
if (accountIndexes.size() != this.blockData.getOnlineAccountsCount())
|
||||||
|
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
||||||
|
|
||||||
List<ProxyForgerData> expandedAccounts = new ArrayList<>();
|
List<ProxyForgerData> expandedAccounts = new ArrayList<>();
|
||||||
|
|
||||||
@ -1148,28 +1123,21 @@ public class Block {
|
|||||||
|
|
||||||
/** Returns whether block's generator is actually allowed to forge this block. */
|
/** Returns whether block's generator is actually allowed to forge this block. */
|
||||||
protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException {
|
protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException {
|
||||||
BlockData parentBlockData = parentBlock.getBlockData();
|
// Generator must have forging flag enabled
|
||||||
|
Account generator = new PublicKeyAccount(repository, this.blockData.getGeneratorPublicKey());
|
||||||
|
if (Forging.canForge(generator))
|
||||||
|
return true;
|
||||||
|
|
||||||
BigInteger hashValue = this.calcBlockHash();
|
// Check whether generator public key could be a proxy forge account
|
||||||
|
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
||||||
|
if (proxyForgerData != null) {
|
||||||
|
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
||||||
|
|
||||||
// calcGeneratorsTarget handles proxy forging aspect
|
if (Forging.canForge(forger))
|
||||||
BigInteger target = parentBlock.calcGeneratorsTarget(this.generator);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Multiply target by guesses
|
return false;
|
||||||
long guesses = (this.blockData.getTimestamp() - parentBlockData.getTimestamp()) / 1000;
|
|
||||||
BigInteger lowerTarget = target.multiply(BigInteger.valueOf(guesses - 1));
|
|
||||||
target = target.multiply(BigInteger.valueOf(guesses));
|
|
||||||
|
|
||||||
// Generator's target must exceed block's hashValue threshold
|
|
||||||
if (hashValue.compareTo(target) >= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Odd gen1 comment: "CHECK IF FIRST BLOCK OF USER"
|
|
||||||
// Each second elapsed allows generator to test a new "target" window against hashValue
|
|
||||||
if (hashValue.compareTo(lowerTarget) < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,6 +32,7 @@ import org.qora.repository.DataException;
|
|||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.settings.Settings;
|
import org.qora.settings.Settings;
|
||||||
|
import org.qora.utils.NTP;
|
||||||
import org.qora.utils.StringLongMapXmlAdapter;
|
import org.qora.utils.StringLongMapXmlAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +100,15 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
List<RewardByHeight> rewardsByHeight;
|
List<RewardByHeight> rewardsByHeight;
|
||||||
|
|
||||||
|
/** Block times by block height */
|
||||||
|
public static class BlockTimingByHeight {
|
||||||
|
public int height;
|
||||||
|
public long target; // ms
|
||||||
|
public long deviation; // ms
|
||||||
|
public double power;
|
||||||
|
}
|
||||||
|
List<BlockTimingByHeight> blockTimingsByHeight;
|
||||||
|
|
||||||
/** Forging right tiers */
|
/** Forging right tiers */
|
||||||
public static class ForgingTier {
|
public static class ForgingTier {
|
||||||
/** Minimum number of blocks forged before account can enable minting on other accounts. */
|
/** Minimum number of blocks forged before account can enable minting on other accounts. */
|
||||||
@ -329,6 +339,14 @@ public class BlockChain {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlockTimingByHeight getBlockTimingByHeight(int ourHeight) {
|
||||||
|
for (int i = blockTimingsByHeight.size() - 1; i >= 0; --i)
|
||||||
|
if (blockTimingsByHeight.get(i).height <= ourHeight)
|
||||||
|
return blockTimingsByHeight.get(i);
|
||||||
|
|
||||||
|
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
||||||
|
}
|
||||||
|
|
||||||
/** Validate blockchain config read from JSON */
|
/** Validate blockchain config read from JSON */
|
||||||
private void validateConfig() {
|
private void validateConfig() {
|
||||||
if (this.genesisInfo == null) {
|
if (this.genesisInfo == null) {
|
||||||
@ -440,4 +458,25 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void trimOldOnlineAccountsSignatures() {
|
||||||
|
final Long now = NTP.getTime();
|
||||||
|
if (now == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.tryRepository()) {
|
||||||
|
if (repository == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int numBlocksTrimmed = repository.getBlockRepository().trimOldOnlineAccountsSignatures(now - BlockChain.getInstance().getOnlineAccountSignaturesMaxLifetime());
|
||||||
|
|
||||||
|
if (numBlocksTrimmed > 0)
|
||||||
|
LOGGER.debug(String.format("Trimmed old online accounts signatures from %d block%s", numBlocksTrimmed, (numBlocksTrimmed != 1 ? "s" : "")));
|
||||||
|
|
||||||
|
repository.saveChanges();
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.warn("Repository issue trying to trim old online accounts signatures: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,28 +70,36 @@ public class BlockGenerator extends Thread {
|
|||||||
|
|
||||||
List<Block> newBlocks = new ArrayList<>();
|
List<Block> newBlocks = new ArrayList<>();
|
||||||
|
|
||||||
|
// Flags that allow us to track whether generating is possible changes,
|
||||||
|
// so we can notify Controller, and further update SysTray, etc.
|
||||||
|
boolean isGenerationPossible = false;
|
||||||
|
boolean wasGenerationPossible = isGenerationPossible;
|
||||||
while (running) {
|
while (running) {
|
||||||
// Sleep for a while
|
// Sleep for a while
|
||||||
try {
|
try {
|
||||||
repository.discardChanges(); // Free repository locks, if any
|
repository.discardChanges(); // Free repository locks, if any
|
||||||
|
|
||||||
|
if (isGenerationPossible != wasGenerationPossible)
|
||||||
|
Controller.getInstance().onGenerationPossibleChange(isGenerationPossible);
|
||||||
|
|
||||||
|
wasGenerationPossible = isGenerationPossible;
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// We've been interrupted - time to exit
|
// We've been interrupted - time to exit
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Controller says we can't generate, then don't...
|
isGenerationPossible = false;
|
||||||
if (!Controller.getInstance().isGenerationAllowed())
|
|
||||||
|
final Long now = NTP.getTime();
|
||||||
|
if (now == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||||
if (minLatestBlockTimestamp == null)
|
if (minLatestBlockTimestamp == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
final Long now = NTP.getTime();
|
|
||||||
if (now == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// No online accounts? (e.g. during startup)
|
// No online accounts? (e.g. during startup)
|
||||||
if (Controller.getInstance().getOnlineAccounts().isEmpty())
|
if (Controller.getInstance().getOnlineAccounts().isEmpty())
|
||||||
continue;
|
continue;
|
||||||
@ -121,6 +129,7 @@ public class BlockGenerator extends Thread {
|
|||||||
|
|
||||||
// There are no peers with a recent block and/or our latest block is recent
|
// There are no peers with a recent block and/or our latest block is recent
|
||||||
// so go ahead and generate a block if possible.
|
// so go ahead and generate a block if possible.
|
||||||
|
isGenerationPossible = true;
|
||||||
|
|
||||||
// Check blockchain hasn't changed
|
// Check blockchain hasn't changed
|
||||||
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
if (previousBlock == null || !Arrays.equals(previousBlock.getSignature(), lastBlockData.getSignature())) {
|
||||||
|
@ -106,6 +106,7 @@ public class Controller extends Thread {
|
|||||||
|
|
||||||
// To do with online accounts list
|
// To do with online accounts list
|
||||||
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000; // ms
|
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000; // ms
|
||||||
|
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000; // ms
|
||||||
private static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000;
|
private static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000;
|
||||||
private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000);
|
private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000);
|
||||||
|
|
||||||
@ -125,8 +126,8 @@ public class Controller extends Thread {
|
|||||||
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
|
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
|
||||||
private long onlineAccountsTasksTimestamp = startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
|
private long onlineAccountsTasksTimestamp = startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms
|
||||||
|
|
||||||
/** Whether BlockGenerator is allowed to generate blocks. Mostly determined by system clock accuracy. */
|
/** Whether we can generate new blocks, as reported by BlockGenerator. */
|
||||||
private volatile boolean isGenerationAllowed = false;
|
private volatile boolean isGenerationPossible = false;
|
||||||
|
|
||||||
/** Signature of peer's latest block that will result in no sync action needed (e.g. INFERIOR_CHAIN, NOTHING_TO_DO, OK). */
|
/** Signature of peer's latest block that will result in no sync action needed (e.g. INFERIOR_CHAIN, NOTHING_TO_DO, OK). */
|
||||||
private byte[] noSyncPeerBlockSignature = null;
|
private byte[] noSyncPeerBlockSignature = null;
|
||||||
@ -227,10 +228,6 @@ public class Controller extends Thread {
|
|||||||
return this.blockchainLock;
|
return this.blockchainLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGenerationAllowed() {
|
|
||||||
return this.isGenerationAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
@ -243,7 +240,10 @@ public class Controller extends Thread {
|
|||||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||||
|
|
||||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||||
Settings.getInstance();
|
if (args.length > 0)
|
||||||
|
Settings.fileInstance(args[0]);
|
||||||
|
else
|
||||||
|
Settings.getInstance();
|
||||||
|
|
||||||
LOGGER.info("Starting NTP");
|
LOGGER.info("Starting NTP");
|
||||||
NTP.start();
|
NTP.start();
|
||||||
@ -373,7 +373,6 @@ public class Controller extends Thread {
|
|||||||
ntpCheckTimestamp = now + NTP_PRE_SYNC_CHECK_PERIOD;
|
ntpCheckTimestamp = now + NTP_PRE_SYNC_CHECK_PERIOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
isGenerationAllowed = ntpTime != null;
|
|
||||||
requestSysTrayUpdate = true;
|
requestSysTrayUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +524,7 @@ public class Controller extends Thread {
|
|||||||
|
|
||||||
String connectionsText = Translator.INSTANCE.translate("SysTray", numberOfPeers != 1 ? "CONNECTIONS" : "CONNECTION");
|
String connectionsText = Translator.INSTANCE.translate("SysTray", numberOfPeers != 1 ? "CONNECTIONS" : "CONNECTION");
|
||||||
String heightText = Translator.INSTANCE.translate("SysTray", "BLOCK_HEIGHT");
|
String heightText = Translator.INSTANCE.translate("SysTray", "BLOCK_HEIGHT");
|
||||||
String generatingText = Translator.INSTANCE.translate("SysTray", isGenerationAllowed ? "GENERATING_ENABLED" : "GENERATING_DISABLED");
|
String generatingText = Translator.INSTANCE.translate("SysTray", isGenerationPossible ? "GENERATING_ENABLED" : "GENERATING_DISABLED");
|
||||||
|
|
||||||
String tooltip = String.format("%s - %d %s - %s %d", generatingText, numberOfPeers, connectionsText, heightText, height);
|
String tooltip = String.format("%s - %d %s - %s %d", generatingText, numberOfPeers, connectionsText, heightText, height);
|
||||||
SysTray.getInstance().setToolTipText(tooltip);
|
SysTray.getInstance().setToolTipText(tooltip);
|
||||||
@ -632,6 +631,11 @@ public class Controller extends Thread {
|
|||||||
network.broadcast(peer -> network.buildGetUnconfirmedTransactionsMessage(peer));
|
network.broadcast(peer -> network.buildGetUnconfirmedTransactionsMessage(peer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onGenerationPossibleChange(boolean isGenerationPossible) {
|
||||||
|
this.isGenerationPossible = isGenerationPossible;
|
||||||
|
requestSysTrayUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
public void onGeneratedBlock() {
|
public void onGeneratedBlock() {
|
||||||
// Broadcast our new height info
|
// Broadcast our new height info
|
||||||
BlockData latestBlockData;
|
BlockData latestBlockData;
|
||||||
@ -646,6 +650,8 @@ public class Controller extends Thread {
|
|||||||
|
|
||||||
Network network = Network.getInstance();
|
Network network = Network.getInstance();
|
||||||
network.broadcast(peer -> network.buildHeightMessage(peer, latestBlockData));
|
network.broadcast(peer -> network.buildHeightMessage(peer, latestBlockData));
|
||||||
|
|
||||||
|
requestSysTrayUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onNewTransaction(TransactionData transactionData) {
|
public void onNewTransaction(TransactionData transactionData) {
|
||||||
@ -1247,7 +1253,9 @@ public class Controller extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void performOnlineAccountsTasks() {
|
private void performOnlineAccountsTasks() {
|
||||||
final long now = System.currentTimeMillis();
|
final Long now = NTP.getTime();
|
||||||
|
if (now == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Expire old entries
|
// Expire old entries
|
||||||
final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD;
|
final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD;
|
||||||
@ -1267,15 +1275,20 @@ public class Controller extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request data from another peer
|
// Request data from other peers?
|
||||||
Message message;
|
if ((this.onlineAccountsTasksTimestamp % ONLINE_ACCOUNTS_BROADCAST_INTERVAL) < ONLINE_ACCOUNTS_TASKS_INTERVAL) {
|
||||||
synchronized (this.onlineAccounts) {
|
Message message;
|
||||||
message = new GetOnlineAccountsMessage(this.onlineAccounts);
|
synchronized (this.onlineAccounts) {
|
||||||
|
message = new GetOnlineAccountsMessage(this.onlineAccounts);
|
||||||
|
}
|
||||||
|
Network.getInstance().broadcast((peer) -> message);
|
||||||
}
|
}
|
||||||
Network.getInstance().broadcast((peer) -> message);
|
|
||||||
|
|
||||||
// Refresh our onlineness?
|
// Refresh our online accounts signatures?
|
||||||
sendOurOnlineAccountsInfo();
|
sendOurOnlineAccountsInfo();
|
||||||
|
|
||||||
|
// Trim blockchain by removing 'old' online accounts signatures
|
||||||
|
BlockChain.trimOldOnlineAccountsSignatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendOurOnlineAccountsInfo() {
|
private void sendOurOnlineAccountsInfo() {
|
||||||
@ -1304,7 +1317,6 @@ public class Controller extends Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 'current' timestamp
|
// 'current' timestamp
|
||||||
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
|
final long onlineAccountsTimestamp = Controller.toOnlineAccountTimestamp(now);
|
||||||
boolean hasInfoChanged = false;
|
boolean hasInfoChanged = false;
|
||||||
@ -1351,7 +1363,7 @@ public class Controller extends Thread {
|
|||||||
Message message = new OnlineAccountsMessage(ourOnlineAccounts);
|
Message message = new OnlineAccountsMessage(ourOnlineAccounts);
|
||||||
Network.getInstance().broadcast((peer) -> message);
|
Network.getInstance().broadcast((peer) -> message);
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long toOnlineAccountTimestamp(long timestamp) {
|
public static long toOnlineAccountTimestamp(long timestamp) {
|
||||||
|
@ -99,7 +99,6 @@ public class Synchronizer {
|
|||||||
if (peerHeight == 1)
|
if (peerHeight == 1)
|
||||||
return SynchronizationResult.GENESIS_ONLY;
|
return SynchronizationResult.GENESIS_ONLY;
|
||||||
|
|
||||||
// XXX this may well be obsolete now
|
|
||||||
// If peer is too far behind us then don't them.
|
// If peer is too far behind us then don't them.
|
||||||
int minHeight = ourHeight - MAXIMUM_HEIGHT_DELTA;
|
int minHeight = ourHeight - MAXIMUM_HEIGHT_DELTA;
|
||||||
if (!force && peerHeight < minHeight) {
|
if (!force && peerHeight < minHeight) {
|
||||||
@ -136,7 +135,6 @@ public class Synchronizer {
|
|||||||
peerHeight = commonBlockHeight;
|
peerHeight = commonBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX This may well be obsolete now
|
|
||||||
// If common block is peer's latest block then we simply have the same, or longer, chain to peer, so exit now
|
// If common block is peer's latest block then we simply have the same, or longer, chain to peer, so exit now
|
||||||
if (commonBlockHeight == peerHeight) {
|
if (commonBlockHeight == peerHeight) {
|
||||||
if (peerHeight == ourHeight)
|
if (peerHeight == ourHeight)
|
||||||
@ -154,7 +152,7 @@ public class Synchronizer {
|
|||||||
return SynchronizationResult.TOO_DIVERGENT;
|
return SynchronizationResult.TOO_DIVERGENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have blocks after common block then decide whether we want to sync (lowest block signature wins)
|
// If we both have blocks after common block then decide whether we want to sync
|
||||||
int highestMutualHeight = Math.min(peerHeight, ourHeight);
|
int highestMutualHeight = Math.min(peerHeight, ourHeight);
|
||||||
|
|
||||||
// If our latest block is very old, we're very behind and should ditch our fork.
|
// If our latest block is very old, we're very behind and should ditch our fork.
|
||||||
|
@ -33,6 +33,7 @@ public class BlockData implements Serializable {
|
|||||||
private int atCount;
|
private int atCount;
|
||||||
private BigDecimal atFees;
|
private BigDecimal atFees;
|
||||||
private byte[] encodedOnlineAccounts;
|
private byte[] encodedOnlineAccounts;
|
||||||
|
private int onlineAccountsCount;
|
||||||
private Long onlineAccountsTimestamp;
|
private Long onlineAccountsTimestamp;
|
||||||
private byte[] onlineAccountsSignatures;
|
private byte[] onlineAccountsSignatures;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public class BlockData implements Serializable {
|
|||||||
|
|
||||||
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
||||||
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees,
|
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees,
|
||||||
byte[] encodedOnlineAccounts, Long onlineAccountsTimestamp, byte[] onlineAccountsSignatures) {
|
byte[] encodedOnlineAccounts, int onlineAccountsCount, Long onlineAccountsTimestamp, byte[] onlineAccountsSignatures) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
this.transactionCount = transactionCount;
|
this.transactionCount = transactionCount;
|
||||||
@ -58,6 +59,7 @@ public class BlockData implements Serializable {
|
|||||||
this.atCount = atCount;
|
this.atCount = atCount;
|
||||||
this.atFees = atFees;
|
this.atFees = atFees;
|
||||||
this.encodedOnlineAccounts = encodedOnlineAccounts;
|
this.encodedOnlineAccounts = encodedOnlineAccounts;
|
||||||
|
this.onlineAccountsCount = onlineAccountsCount;
|
||||||
this.onlineAccountsTimestamp = onlineAccountsTimestamp;
|
this.onlineAccountsTimestamp = onlineAccountsTimestamp;
|
||||||
this.onlineAccountsSignatures = onlineAccountsSignatures;
|
this.onlineAccountsSignatures = onlineAccountsSignatures;
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ public class BlockData implements Serializable {
|
|||||||
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
||||||
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees) {
|
BigDecimal generatingBalance, byte[] generatorPublicKey, byte[] generatorSignature, int atCount, BigDecimal atFees) {
|
||||||
this(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey, generatorSignature, atCount, atFees,
|
this(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey, generatorSignature, atCount, atFees,
|
||||||
null, null, null);
|
null, 0, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/setters
|
// Getters/setters
|
||||||
@ -167,8 +169,12 @@ public class BlockData implements Serializable {
|
|||||||
return this.encodedOnlineAccounts;
|
return this.encodedOnlineAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getOnlineAccountsCount() {
|
||||||
|
return this.onlineAccountsCount;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getOnlineAccountsTimestamp() {
|
public Long getOnlineAccountsTimestamp() {
|
||||||
return onlineAccountsTimestamp;
|
return this.onlineAccountsTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnlineAccountsTimestamp(Long onlineAccountsTimestamp) {
|
public void setOnlineAccountsTimestamp(Long onlineAccountsTimestamp) {
|
||||||
|
@ -1,21 +1,34 @@
|
|||||||
package org.qora.data.block;
|
package org.qora.data.block;
|
||||||
|
|
||||||
|
import org.qora.transform.block.BlockTransformer;
|
||||||
|
|
||||||
public class BlockSummaryData {
|
public class BlockSummaryData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private int height;
|
private int height;
|
||||||
private byte[] signature;
|
private byte[] signature;
|
||||||
private byte[] generatorPublicKey;
|
private byte[] generatorPublicKey;
|
||||||
|
private int onlineAccountsCount;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
public BlockSummaryData(int height, byte[] signature, byte[] generatorPublicKey) {
|
public BlockSummaryData(int height, byte[] signature, byte[] generatorPublicKey, int onlineAccountsCount) {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
this.generatorPublicKey = generatorPublicKey;
|
this.generatorPublicKey = generatorPublicKey;
|
||||||
|
this.onlineAccountsCount = onlineAccountsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockSummaryData(BlockData blockData) {
|
public BlockSummaryData(BlockData blockData) {
|
||||||
this(blockData.getHeight(), blockData.getSignature(), blockData.getGeneratorPublicKey());
|
this.height = blockData.getHeight();
|
||||||
|
this.signature = blockData.getSignature();
|
||||||
|
this.generatorPublicKey = blockData.getGeneratorPublicKey();
|
||||||
|
|
||||||
|
byte[] encodedOnlineAccounts = blockData.getEncodedOnlineAccounts();
|
||||||
|
if (encodedOnlineAccounts != null) {
|
||||||
|
this.onlineAccountsCount = BlockTransformer.decodeOnlineAccounts(encodedOnlineAccounts).size();
|
||||||
|
} else {
|
||||||
|
this.onlineAccountsCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
@ -32,4 +45,8 @@ public class BlockSummaryData {
|
|||||||
return this.generatorPublicKey;
|
return this.generatorPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getOnlineAccountsCount() {
|
||||||
|
return this.onlineAccountsCount;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import com.google.common.primitives.Ints;
|
|||||||
|
|
||||||
public class BlockSummariesMessage extends Message {
|
public class BlockSummariesMessage extends Message {
|
||||||
|
|
||||||
private static final int BLOCK_SUMMARY_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH + Transformer.INT_LENGTH + Transformer.PUBLIC_KEY_LENGTH;
|
private static final int BLOCK_SUMMARY_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH + Transformer.INT_LENGTH + Transformer.PUBLIC_KEY_LENGTH + Transformer.INT_LENGTH;
|
||||||
|
|
||||||
private List<BlockSummaryData> blockSummaries;
|
private List<BlockSummaryData> blockSummaries;
|
||||||
|
|
||||||
@ -49,7 +49,9 @@ public class BlockSummariesMessage extends Message {
|
|||||||
byte[] generatorPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
byte[] generatorPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||||
bytes.get(generatorPublicKey);
|
bytes.get(generatorPublicKey);
|
||||||
|
|
||||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey);
|
int onlineAccountsCount = bytes.getInt();
|
||||||
|
|
||||||
|
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey, onlineAccountsCount);
|
||||||
blockSummaries.add(blockSummary);
|
blockSummaries.add(blockSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ public class BlockSummariesMessage extends Message {
|
|||||||
bytes.write(Ints.toByteArray(blockSummary.getHeight()));
|
bytes.write(Ints.toByteArray(blockSummary.getHeight()));
|
||||||
bytes.write(blockSummary.getSignature());
|
bytes.write(blockSummary.getSignature());
|
||||||
bytes.write(blockSummary.getGeneratorPublicKey());
|
bytes.write(blockSummary.getGeneratorPublicKey());
|
||||||
|
bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes.toByteArray();
|
return bytes.toByteArray();
|
||||||
|
@ -123,6 +123,14 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException;
|
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim online accounts signatures from blocks older than passed timestamp.
|
||||||
|
*
|
||||||
|
* @param timestamp
|
||||||
|
* @return number of blocks trimmed
|
||||||
|
*/
|
||||||
|
public int trimOldOnlineAccountsSignatures(long timestamp) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves block into repository.
|
* Saves block into repository.
|
||||||
*
|
*
|
||||||
|
@ -23,7 +23,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
|
|
||||||
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
|
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
|
||||||
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, "
|
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, "
|
||||||
+ "AT_count, AT_fees, online_accounts, online_accounts_timestamp, online_accounts_signatures";
|
+ "AT_count, AT_fees, online_accounts, online_accounts_count, online_accounts_timestamp, online_accounts_signatures";
|
||||||
|
|
||||||
protected HSQLDBRepository repository;
|
protected HSQLDBRepository repository;
|
||||||
|
|
||||||
@ -49,11 +49,13 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
int atCount = resultSet.getInt(11);
|
int atCount = resultSet.getInt(11);
|
||||||
BigDecimal atFees = resultSet.getBigDecimal(12);
|
BigDecimal atFees = resultSet.getBigDecimal(12);
|
||||||
byte[] encodedOnlineAccounts = resultSet.getBytes(13);
|
byte[] encodedOnlineAccounts = resultSet.getBytes(13);
|
||||||
Long onlineAccountsTimestamp = getZonedTimestampMilli(resultSet, 14);
|
int onlineAccountsCount = resultSet.getInt(14);
|
||||||
byte[] onlineAccountsSignatures = resultSet.getBytes(15);
|
Long onlineAccountsTimestamp = getZonedTimestampMilli(resultSet, 15);
|
||||||
|
byte[] onlineAccountsSignatures = resultSet.getBytes(16);
|
||||||
|
|
||||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
generatorPublicKey, generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsTimestamp, onlineAccountsSignatures);
|
generatorPublicKey, generatorSignature, atCount, atFees,
|
||||||
|
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Error extracting data from result set", e);
|
throw new DataException("Error extracting data from result set", e);
|
||||||
}
|
}
|
||||||
@ -288,7 +290,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException {
|
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException {
|
||||||
String sql = "SELECT signature, height, generator FROM Blocks WHERE height BETWEEN ? AND ?";
|
String sql = "SELECT signature, height, generator, online_accounts_count FROM Blocks WHERE height BETWEEN ? AND ?";
|
||||||
|
|
||||||
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
||||||
|
|
||||||
@ -300,8 +302,9 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
byte[] signature = resultSet.getBytes(1);
|
byte[] signature = resultSet.getBytes(1);
|
||||||
int height = resultSet.getInt(2);
|
int height = resultSet.getInt(2);
|
||||||
byte[] generatorPublicKey = resultSet.getBytes(3);
|
byte[] generatorPublicKey = resultSet.getBytes(3);
|
||||||
|
int onlineAccountsCount = resultSet.getInt(4);
|
||||||
|
|
||||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey);
|
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, generatorPublicKey, onlineAccountsCount);
|
||||||
blockSummaries.add(blockSummary);
|
blockSummaries.add(blockSummary);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -311,6 +314,17 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int trimOldOnlineAccountsSignatures(long timestamp) throws DataException {
|
||||||
|
String sql = "UPDATE Blocks set online_accounts_signatures = NULL WHERE generation < ? AND online_accounts_signatures IS NOT NULL";
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.repository.checkedExecuteUpdateCount(sql, toOffsetDateTime(timestamp));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to trim old online accounts signatures in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(BlockData blockData) throws DataException {
|
public void save(BlockData blockData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
||||||
@ -321,7 +335,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
.bind("generation", toOffsetDateTime(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
|
.bind("generation", toOffsetDateTime(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
|
||||||
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature())
|
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature())
|
||||||
.bind("AT_count", blockData.getATCount()).bind("AT_fees", blockData.getATFees())
|
.bind("AT_count", blockData.getATCount()).bind("AT_fees", blockData.getATFees())
|
||||||
.bind("online_accounts", blockData.getEncodedOnlineAccounts())
|
.bind("online_accounts", blockData.getEncodedOnlineAccounts()).bind("online_accounts_count", blockData.getOnlineAccountsCount())
|
||||||
.bind("online_accounts_timestamp", toOffsetDateTime(blockData.getOnlineAccountsTimestamp()))
|
.bind("online_accounts_timestamp", toOffsetDateTime(blockData.getOnlineAccountsTimestamp()))
|
||||||
.bind("online_accounts_signatures", blockData.getOnlineAccountsSignatures());
|
.bind("online_accounts_signatures", blockData.getOnlineAccountsSignatures());
|
||||||
|
|
||||||
|
@ -787,6 +787,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
case 55:
|
case 55:
|
||||||
// Storage of which level 1+ accounts were 'online' for a particular block. Used to distribute block rewards.
|
// Storage of which level 1+ accounts were 'online' for a particular block. Used to distribute block rewards.
|
||||||
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts VARBINARY(1048576)");
|
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts VARBINARY(1048576)");
|
||||||
|
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_count INT NOT NULL DEFAULT 0");
|
||||||
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_timestamp TIMESTAMP WITH TIME ZONE");
|
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_timestamp TIMESTAMP WITH TIME ZONE");
|
||||||
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_signatures BLOB");
|
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_signatures BLOB");
|
||||||
break;
|
break;
|
||||||
|
@ -460,17 +460,19 @@ public class HSQLDBRepository implements Repository {
|
|||||||
* @return number of changed rows
|
* @return number of changed rows
|
||||||
* @throws SQLException
|
* @throws SQLException
|
||||||
*/
|
*/
|
||||||
private int checkedExecuteUpdateCount(PreparedStatement preparedStatement, Object... objects) throws SQLException {
|
/* package */ int checkedExecuteUpdateCount(String sql, Object... objects) throws SQLException {
|
||||||
prepareExecute(preparedStatement, objects);
|
try (PreparedStatement preparedStatement = this.prepareStatement(sql)) {
|
||||||
|
prepareExecute(preparedStatement, objects);
|
||||||
|
|
||||||
if (preparedStatement.execute())
|
if (preparedStatement.execute())
|
||||||
throw new SQLException("Database produced results, not row count");
|
throw new SQLException("Database produced results, not row count");
|
||||||
|
|
||||||
int rowCount = preparedStatement.getUpdateCount();
|
int rowCount = preparedStatement.getUpdateCount();
|
||||||
if (rowCount == -1)
|
if (rowCount == -1)
|
||||||
throw new SQLException("Database returned invalid row count");
|
throw new SQLException("Database returned invalid row count");
|
||||||
|
|
||||||
return rowCount;
|
return rowCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -543,9 +545,7 @@ public class HSQLDBRepository implements Repository {
|
|||||||
sql.append(" WHERE ");
|
sql.append(" WHERE ");
|
||||||
sql.append(whereClause);
|
sql.append(whereClause);
|
||||||
|
|
||||||
try (PreparedStatement preparedStatement = this.prepareStatement(sql.toString())) {
|
return this.checkedExecuteUpdateCount(sql.toString(), objects);
|
||||||
return this.checkedExecuteUpdateCount(preparedStatement, objects);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -559,9 +559,7 @@ public class HSQLDBRepository implements Repository {
|
|||||||
sql.append("DELETE FROM ");
|
sql.append("DELETE FROM ");
|
||||||
sql.append(tableName);
|
sql.append(tableName);
|
||||||
|
|
||||||
try (PreparedStatement preparedStatement = this.prepareStatement(sql.toString())) {
|
return this.checkedExecuteUpdateCount(sql.toString());
|
||||||
return this.checkedExecuteUpdateCount(preparedStatement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +49,7 @@ public class BlockTransformer extends Transformer {
|
|||||||
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
|
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
|
||||||
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
|
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
|
||||||
|
|
||||||
|
protected static final int ONLINE_ACCOUNTS_COUNT_LENGTH = INT_LENGTH;
|
||||||
protected static final int ONLINE_ACCOUNTS_SIZE_LENGTH = INT_LENGTH;
|
protected static final int ONLINE_ACCOUNTS_SIZE_LENGTH = INT_LENGTH;
|
||||||
protected static final int ONLINE_ACCOUNTS_TIMESTAMP_LENGTH = LONG_LENGTH;
|
protected static final int ONLINE_ACCOUNTS_TIMESTAMP_LENGTH = LONG_LENGTH;
|
||||||
protected static final int ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH = INT_LENGTH;
|
protected static final int ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH = INT_LENGTH;
|
||||||
@ -194,11 +195,14 @@ public class BlockTransformer extends Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Online accounts info?
|
// Online accounts info?
|
||||||
byte[] onlineAccounts = null;
|
byte[] encodedOnlineAccounts = null;
|
||||||
|
int onlineAccountsCount = 0;
|
||||||
byte[] onlineAccountsSignatures = null;
|
byte[] onlineAccountsSignatures = null;
|
||||||
Long onlineAccountsTimestamp = null;
|
Long onlineAccountsTimestamp = null;
|
||||||
|
|
||||||
if (version >= 4) {
|
if (version >= 4) {
|
||||||
|
onlineAccountsCount = byteBuffer.getInt();
|
||||||
|
|
||||||
int conciseSetLength = byteBuffer.getInt();
|
int conciseSetLength = byteBuffer.getInt();
|
||||||
|
|
||||||
if (conciseSetLength > Block.MAX_BLOCK_BYTES)
|
if (conciseSetLength > Block.MAX_BLOCK_BYTES)
|
||||||
@ -207,8 +211,13 @@ public class BlockTransformer extends Transformer {
|
|||||||
if ((conciseSetLength & 3) != 0)
|
if ((conciseSetLength & 3) != 0)
|
||||||
throw new TransformationException("Byte data length not multiple of 4 for online account info");
|
throw new TransformationException("Byte data length not multiple of 4 for online account info");
|
||||||
|
|
||||||
onlineAccounts = new byte[conciseSetLength];
|
encodedOnlineAccounts = new byte[conciseSetLength];
|
||||||
byteBuffer.get(onlineAccounts);
|
byteBuffer.get(encodedOnlineAccounts);
|
||||||
|
|
||||||
|
// Try to decode to ConciseSet
|
||||||
|
ConciseSet accountsIndexes = BlockTransformer.decodeOnlineAccounts(encodedOnlineAccounts);
|
||||||
|
if (accountsIndexes.size() != onlineAccountsCount)
|
||||||
|
throw new TransformationException("Block's online account data malformed");
|
||||||
|
|
||||||
// Note: number of signatures, not byte length
|
// Note: number of signatures, not byte length
|
||||||
int onlineAccountsSignaturesCount = byteBuffer.getInt();
|
int onlineAccountsSignaturesCount = byteBuffer.getInt();
|
||||||
@ -228,7 +237,7 @@ public class BlockTransformer extends Transformer {
|
|||||||
// We don't have a height!
|
// We don't have a height!
|
||||||
Integer height = null;
|
Integer height = null;
|
||||||
BlockData blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
BlockData blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||||
generatorPublicKey, generatorSignature, atCount, atFees, onlineAccounts, onlineAccountsTimestamp, onlineAccountsSignatures);
|
generatorPublicKey, generatorSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
|
|
||||||
return new Triple<BlockData, List<TransactionData>, List<ATStateData>>(blockData, transactions, atStates);
|
return new Triple<BlockData, List<TransactionData>, List<ATStateData>>(blockData, transactions, atStates);
|
||||||
}
|
}
|
||||||
@ -239,9 +248,11 @@ public class BlockTransformer extends Transformer {
|
|||||||
|
|
||||||
if (blockData.getVersion() >= 4) {
|
if (blockData.getVersion() >= 4) {
|
||||||
blockLength += AT_BYTES_LENGTH + blockData.getATCount() * V4_AT_ENTRY_LENGTH;
|
blockLength += AT_BYTES_LENGTH + blockData.getATCount() * V4_AT_ENTRY_LENGTH;
|
||||||
blockLength += ONLINE_ACCOUNTS_SIZE_LENGTH + blockData.getEncodedOnlineAccounts().length;
|
blockLength += ONLINE_ACCOUNTS_COUNT_LENGTH + ONLINE_ACCOUNTS_SIZE_LENGTH + blockData.getEncodedOnlineAccounts().length;
|
||||||
blockLength += ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH;
|
blockLength += ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH;
|
||||||
if (blockData.getOnlineAccountsSignatures().length > 0)
|
|
||||||
|
byte[] onlineAccountsSignatures = blockData.getOnlineAccountsSignatures();
|
||||||
|
if (onlineAccountsSignatures != null && onlineAccountsSignatures.length > 0)
|
||||||
blockLength += ONLINE_ACCOUNTS_TIMESTAMP_LENGTH + blockData.getOnlineAccountsSignatures().length;
|
blockLength += ONLINE_ACCOUNTS_TIMESTAMP_LENGTH + blockData.getOnlineAccountsSignatures().length;
|
||||||
} else if (blockData.getVersion() >= 2)
|
} else if (blockData.getVersion() >= 2)
|
||||||
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getATCount() * V2_AT_ENTRY_LENGTH;
|
blockLength += AT_FEES_LENGTH + AT_BYTES_LENGTH + blockData.getATCount() * V2_AT_ENTRY_LENGTH;
|
||||||
@ -315,25 +326,27 @@ public class BlockTransformer extends Transformer {
|
|||||||
byte[] encodedOnlineAccounts = blockData.getEncodedOnlineAccounts();
|
byte[] encodedOnlineAccounts = blockData.getEncodedOnlineAccounts();
|
||||||
|
|
||||||
if (encodedOnlineAccounts != null) {
|
if (encodedOnlineAccounts != null) {
|
||||||
|
bytes.write(Ints.toByteArray(blockData.getOnlineAccountsCount()));
|
||||||
|
|
||||||
bytes.write(Ints.toByteArray(encodedOnlineAccounts.length));
|
bytes.write(Ints.toByteArray(encodedOnlineAccounts.length));
|
||||||
bytes.write(encodedOnlineAccounts);
|
bytes.write(encodedOnlineAccounts);
|
||||||
} else {
|
} else {
|
||||||
bytes.write(Ints.toByteArray(0));
|
bytes.write(Ints.toByteArray(0)); // onlineAccountsCount
|
||||||
|
bytes.write(Ints.toByteArray(0)); // encodedOnlineAccounts length
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] onlineAccountsSignatures = blockData.getOnlineAccountsSignatures();
|
byte[] onlineAccountsSignatures = blockData.getOnlineAccountsSignatures();
|
||||||
|
|
||||||
if (onlineAccountsSignatures != null) {
|
if (onlineAccountsSignatures != null && onlineAccountsSignatures.length > 0) {
|
||||||
// Note: we write the number of signatures, not the number of bytes
|
// Note: we write the number of signatures, not the number of bytes
|
||||||
bytes.write(Ints.toByteArray(onlineAccountsSignatures.length / Transformer.SIGNATURE_LENGTH));
|
bytes.write(Ints.toByteArray(onlineAccountsSignatures.length / Transformer.SIGNATURE_LENGTH));
|
||||||
|
|
||||||
if (onlineAccountsSignatures.length > 0) {
|
// We only write online accounts timestamp if we have signatures
|
||||||
// Only write online accounts timestamp if we have signatures
|
bytes.write(Longs.toByteArray(blockData.getOnlineAccountsTimestamp()));
|
||||||
bytes.write(Longs.toByteArray(blockData.getOnlineAccountsTimestamp()));
|
|
||||||
|
|
||||||
bytes.write(onlineAccountsSignatures);
|
bytes.write(onlineAccountsSignatures);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// Zero online accounts signatures (timestamp omitted also)
|
||||||
bytes.write(Ints.toByteArray(0));
|
bytes.write(Ints.toByteArray(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 3196800000,
|
"onlineAccountSignaturesMaxLifetime": 3196800000,
|
||||||
"genesisInfo": {
|
"genesisInfo": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"timestamp": "1568360000000",
|
"timestamp": "1568720000000",
|
||||||
"generatingBalance": "100000",
|
"generatingBalance": "100000",
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 10000000, "isDivisible": true, "fee": 0, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 10000000, "isDivisible": true, "fee": 0, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
|
||||||
@ -48,6 +48,9 @@
|
|||||||
{ "minBlocks": 50, "maxSubAccounts": 1 },
|
{ "minBlocks": 50, "maxSubAccounts": 1 },
|
||||||
{ "minBlocks": 0, "maxSubAccounts": 0 }
|
{ "minBlocks": 0, "maxSubAccounts": 0 }
|
||||||
],
|
],
|
||||||
|
"blockTimingsByHeight": [
|
||||||
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
|
],
|
||||||
"featureTriggers": {
|
"featureTriggers": {
|
||||||
"messageHeight": 0,
|
"messageHeight": 0,
|
||||||
"atHeight": 0,
|
"atHeight": 0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user