mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-30 06:47:50 +00:00
Modified block minting and validation to support extended OnlineAccountData.
This doesn't require changes to the transformation of the outer Block components, since the "onlineAccountsSignatures" component is already variable length. It does however affect the encoding of the data within "onlineAccountsSignatures". New encoding becomes active once the block timestamp reaches onlineAccountsMemoryPoWTimestamp.
This commit is contained in:
parent
f993f938f4
commit
eb876e12c8
@ -85,7 +85,8 @@ public class Block {
|
||||
ONLINE_ACCOUNT_UNKNOWN(71),
|
||||
ONLINE_ACCOUNT_SIGNATURES_MISSING(72),
|
||||
ONLINE_ACCOUNT_SIGNATURES_MALFORMED(73),
|
||||
ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74);
|
||||
ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74),
|
||||
ONLINE_ACCOUNT_NONCE_INCORRECT(75);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -313,6 +314,15 @@ public class Block {
|
||||
int version = parentBlock.getNextBlockVersion();
|
||||
byte[] reference = parentBlockData.getSignature();
|
||||
|
||||
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey());
|
||||
if (minterLevel == 0) {
|
||||
LOGGER.error("Minter effective level returned zero?");
|
||||
return null;
|
||||
}
|
||||
|
||||
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel);
|
||||
|
||||
// Fetch our list of online accounts
|
||||
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts();
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
@ -355,26 +365,13 @@ public class Block {
|
||||
byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet);
|
||||
int onlineAccountsCount = onlineAccountsSet.size();
|
||||
|
||||
// Concatenate online account timestamp signatures (in correct order)
|
||||
byte[] onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH];
|
||||
for (int i = 0; i < onlineAccountsCount; ++i) {
|
||||
Integer accountIndex = accountIndexes.get(i);
|
||||
OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex);
|
||||
System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
|
||||
}
|
||||
// Build the onlineAccountsSignatures byte array
|
||||
byte[] onlineAccountsSignatures = BlockTransformer.encodeOnlineAccountSignatures(indexedOnlineAccounts,
|
||||
accountIndexes, onlineAccountsCount, timestamp);
|
||||
|
||||
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData,
|
||||
minter.getPublicKey(), encodedOnlineAccounts));
|
||||
|
||||
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey());
|
||||
if (minterLevel == 0) {
|
||||
LOGGER.error("Minter effective level returned zero?");
|
||||
return null;
|
||||
}
|
||||
|
||||
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel);
|
||||
|
||||
int transactionCount = 0;
|
||||
byte[] transactionsSignature = null;
|
||||
int height = parentBlockData.getHeight() + 1;
|
||||
@ -979,7 +976,14 @@ public class Block {
|
||||
if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0)
|
||||
return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MISSING;
|
||||
|
||||
if (this.blockData.getOnlineAccountsSignatures().length != onlineRewardShares.size() * Transformer.SIGNATURE_LENGTH)
|
||||
// Verify the online account signatures length
|
||||
int expectedLength;
|
||||
if (this.blockData.getTimestamp() >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp())
|
||||
expectedLength = onlineRewardShares.size() * (Transformer.SIGNATURE_LENGTH + Transformer.REDUCED_SIGNATURE_LENGTH + Transformer.INT_LENGTH + (OnlineAccountsManager.MAX_NONCE_COUNT * Transformer.INT_LENGTH));
|
||||
else
|
||||
expectedLength = onlineRewardShares.size() * Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
if (this.blockData.getOnlineAccountsSignatures().length != expectedLength)
|
||||
return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED;
|
||||
|
||||
// Check signatures
|
||||
@ -993,17 +997,25 @@ public class Block {
|
||||
List<OnlineAccountData> latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts();
|
||||
|
||||
// Extract online accounts' timestamp signatures from block data
|
||||
List<byte[]> onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures());
|
||||
List<OnlineAccountData> onlineAccountsSignatures = BlockTransformer.decodeOnlineAccountSignatures(
|
||||
this.blockData.getOnlineAccountsSignatures(), onlineRewardShares.size(), this.blockData.getTimestamp());
|
||||
|
||||
// We'll build up a list of online accounts to hand over to Controller if block is added to chain
|
||||
// and this will become latestBlocksOnlineAccounts (above) to reduce CPU load when we process next block...
|
||||
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < onlineAccountsSignatures.size(); ++i) {
|
||||
byte[] signature = onlineAccountsSignatures.get(i);
|
||||
// onlineAccountsSignatures will contain OnlineAccountData objects with at least a signature, and
|
||||
// also a reduced block signature and nonce(s) if the mempow feature is active.
|
||||
// It won't contain a public key or timestamp, so these must be added below.
|
||||
OnlineAccountData onlineAccountSignatureData = onlineAccountsSignatures.get(i);
|
||||
byte[] signature = onlineAccountSignatureData.getSignature();
|
||||
byte[] reducedBlockSignature = onlineAccountSignatureData.getReducedBlockSignature();
|
||||
List<Integer> nonces = onlineAccountSignatureData.getNonces();
|
||||
byte[] publicKey = onlineRewardShares.get(i).getRewardSharePublicKey();
|
||||
|
||||
OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, signature, publicKey);
|
||||
// It's simpler to create a new OnlineAccountData object rather than trying to modify the one we already have
|
||||
OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, signature, publicKey, nonces, reducedBlockSignature);
|
||||
ourOnlineAccounts.add(onlineAccountData);
|
||||
|
||||
// If signature is still current then no need to perform Ed25519 verify
|
||||
@ -1018,6 +1030,10 @@ public class Block {
|
||||
|
||||
if (!Crypto.verify(publicKey, signature, onlineTimestampBytes))
|
||||
return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT;
|
||||
|
||||
if (this.blockData.getTimestamp() >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp())
|
||||
if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccountData))
|
||||
return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT;
|
||||
}
|
||||
|
||||
// All online accounts valid, so save our list of online accounts for potential later use
|
||||
|
@ -6,11 +6,13 @@ import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.transaction.Transaction;
|
||||
@ -27,6 +29,8 @@ import com.google.common.primitives.Longs;
|
||||
|
||||
import io.druid.extendedset.intset.ConciseSet;
|
||||
|
||||
import static org.qortal.controller.OnlineAccountsManager.MAX_NONCE_COUNT;
|
||||
|
||||
public class BlockTransformer extends Transformer {
|
||||
|
||||
private static final int VERSION_LENGTH = INT_LENGTH;
|
||||
@ -416,16 +420,101 @@ public class BlockTransformer extends Transformer {
|
||||
return encodedSignatures;
|
||||
}
|
||||
|
||||
public static List<byte[]> decodeTimestampSignatures(byte[] encodedSignatures) {
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
public static byte[] encodeOnlineAccountSignatures(Map<Integer, OnlineAccountData> indexedOnlineAccounts,
|
||||
List<Integer> accountIndexes,
|
||||
int onlineAccountsCount,
|
||||
long timestamp) {
|
||||
byte[] onlineAccountsSignatures;
|
||||
|
||||
for (int i = 0; i < encodedSignatures.length; i += Transformer.SIGNATURE_LENGTH) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
System.arraycopy(encodedSignatures, i, signature, 0, Transformer.SIGNATURE_LENGTH);
|
||||
signatures.add(signature);
|
||||
if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||
// Online accounts must include at least one nonce and a reduced block signature from this time onwards
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < onlineAccountsCount; ++i) {
|
||||
Integer accountIndex = accountIndexes.get(i);
|
||||
OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex);
|
||||
|
||||
List<Integer> nonces = onlineAccountData.getNonces();
|
||||
byte[] reducedBlockSignature = onlineAccountData.getReducedBlockSignature();
|
||||
if (nonces == null || nonces.isEmpty() || nonces.size() > MAX_NONCE_COUNT || reducedBlockSignature == null) {
|
||||
// Missing or invalid data, so exclude this online account
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
outputStream.write(onlineAccountData.getSignature());
|
||||
|
||||
outputStream.write(reducedBlockSignature);
|
||||
|
||||
outputStream.write(Ints.toByteArray(nonces.size()));
|
||||
|
||||
for (int n = 0; n < nonces.size(); ++n) {
|
||||
Integer nonce = nonces.get(n);
|
||||
outputStream.write(Ints.toByteArray(nonce));
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// Couldn't serialize this online account, so exclude it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
onlineAccountsSignatures = outputStream.toByteArray();
|
||||
}
|
||||
else {
|
||||
// Exclude nonce and reference block signature from online accounts data
|
||||
// Concatenate online account timestamp signatures (in correct order)
|
||||
onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH];
|
||||
for (int i = 0; i < onlineAccountsCount; ++i) {
|
||||
Integer accountIndex = accountIndexes.get(i);
|
||||
OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex);
|
||||
System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
return signatures;
|
||||
return onlineAccountsSignatures;
|
||||
}
|
||||
|
||||
public static List<OnlineAccountData> decodeOnlineAccountSignatures(byte[] encodedSignatures, int count, long timestamp) {
|
||||
List<OnlineAccountData> onlineAccountSignatures = new ArrayList<>();
|
||||
|
||||
if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||
// byte array contains signatures, reduced signatures, and nonces
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(encodedSignatures);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
byte[] reducedBlockSignature = new byte[Transformer.REDUCED_SIGNATURE_LENGTH];
|
||||
byteBuffer.get(reducedBlockSignature);
|
||||
|
||||
int nonceCount = byteBuffer.getInt();
|
||||
|
||||
List<Integer> nonces = new ArrayList<>();
|
||||
for (int n = 0; n < nonceCount; ++n) { // TODO: check against NONCE_COUNT in block validation
|
||||
Integer nonce = byteBuffer.getInt();
|
||||
nonces.add(nonce);
|
||||
}
|
||||
|
||||
// Create an OnlineAccountData wrapper object containing the signature, nonce(s), and reduced block signature
|
||||
OnlineAccountData onlineAccountDataWrapper = new OnlineAccountData(0, signature, null, nonces, reducedBlockSignature);
|
||||
onlineAccountSignatures.add(onlineAccountDataWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// byte array contains signatures only
|
||||
for (int i = 0; i < encodedSignatures.length; i += Transformer.SIGNATURE_LENGTH) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
System.arraycopy(encodedSignatures, i, signature, 0, Transformer.SIGNATURE_LENGTH);
|
||||
|
||||
// Create an OnlineAccountData wrapper object containing only the signature
|
||||
OnlineAccountData onlineAccountDataWrapper = new OnlineAccountData(0, signature, null);
|
||||
onlineAccountSignatures.add(onlineAccountDataWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return onlineAccountSignatures;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -178,4 +178,65 @@ public class OnlineAccountsTests extends Common {
|
||||
assertTrue(onlineAccountSignatures.size() >= 1 && onlineAccountSignatures.size() <= 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeforeMemoryPoW() throws IllegalAccessException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set feature trigger timestamp to MAX long so that it is inactive
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsMemoryPoWTimestamp", Long.MAX_VALUE, true);
|
||||
|
||||
// Mint some blocks
|
||||
for (int i = 0; i < 10; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemoryPoW() throws IllegalAccessException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set feature trigger timestamp to 0 so that it is active
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsMemoryPoWTimestamp", 0L, true);
|
||||
|
||||
// Set difficulty to 5, to speed up test
|
||||
FieldUtils.writeField(OnlineAccountsManager.getInstance(), "POW_DIFFICULTY", 5, true);
|
||||
|
||||
// Mint some blocks
|
||||
for (int i = 0; i < 10; i++) {
|
||||
BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitionToMemoryPoW() throws IllegalAccessException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set feature trigger timestamp to now + 5 mins
|
||||
long featureTriggerTimestamp = NTP.getTime() + (5 * 60 * 1000L);
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsMemoryPoWTimestamp", featureTriggerTimestamp, true);
|
||||
|
||||
// Set difficulty to 5, to speed up test
|
||||
FieldUtils.writeField(OnlineAccountsManager.getInstance(), "POW_DIFFICULTY", 5, true);
|
||||
|
||||
// Mint a block
|
||||
Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
assertEquals(1, block.getBlockData().getOnlineAccountsCount());
|
||||
|
||||
// Ensure online accounts signatures are in legacy format (no nonce or reduced block signature)
|
||||
assertEquals(64, block.getBlockData().getOnlineAccountsSignatures().length);
|
||||
|
||||
// Mint some blocks (at least 5 minutes' worth, to allow mempow to kick in)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share"));
|
||||
assertEquals(1, block.getBlockData().getOnlineAccountsCount());
|
||||
}
|
||||
|
||||
// Ensure online accounts signatures are in new format (with 1 nonce and a reduced block signature)
|
||||
assertEquals(80, block.getBlockData().getOnlineAccountsSignatures().length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user