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:
CalDescent 2022-04-01 12:06:02 +01:00
parent f993f938f4
commit eb876e12c8
3 changed files with 194 additions and 28 deletions

View File

@ -85,7 +85,8 @@ public class Block {
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),
ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74); ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74),
ONLINE_ACCOUNT_NONCE_INCORRECT(75);
public final int value; public final int value;
@ -313,6 +314,15 @@ public class Block {
int version = parentBlock.getNextBlockVersion(); int version = parentBlock.getNextBlockVersion();
byte[] reference = parentBlockData.getSignature(); 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 // Fetch our list of online accounts
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts(); List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts();
if (onlineAccounts.isEmpty()) { if (onlineAccounts.isEmpty()) {
@ -355,26 +365,13 @@ public class Block {
byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet); byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet);
int onlineAccountsCount = onlineAccountsSet.size(); int onlineAccountsCount = onlineAccountsSet.size();
// Concatenate online account timestamp signatures (in correct order) // Build the onlineAccountsSignatures byte array
byte[] onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH]; byte[] onlineAccountsSignatures = BlockTransformer.encodeOnlineAccountSignatures(indexedOnlineAccounts,
for (int i = 0; i < onlineAccountsCount; ++i) { accountIndexes, onlineAccountsCount, timestamp);
Integer accountIndex = accountIndexes.get(i);
OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex);
System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH);
}
byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData, byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData,
minter.getPublicKey(), encodedOnlineAccounts)); 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; int transactionCount = 0;
byte[] transactionsSignature = null; byte[] transactionsSignature = null;
int height = parentBlockData.getHeight() + 1; int height = parentBlockData.getHeight() + 1;
@ -979,7 +976,14 @@ public class Block {
if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0) if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0)
return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MISSING; 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; return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED;
// Check signatures // Check signatures
@ -993,17 +997,25 @@ public class Block {
List<OnlineAccountData> latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts(); List<OnlineAccountData> latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts();
// Extract online accounts' timestamp signatures from block data // 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 // 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... // and this will become latestBlocksOnlineAccounts (above) to reduce CPU load when we process next block...
List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>(); List<OnlineAccountData> ourOnlineAccounts = new ArrayList<>();
for (int i = 0; i < onlineAccountsSignatures.size(); ++i) { 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(); 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); ourOnlineAccounts.add(onlineAccountData);
// If signature is still current then no need to perform Ed25519 verify // 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)) if (!Crypto.verify(publicKey, signature, onlineTimestampBytes))
return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; 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 // All online accounts valid, so save our list of online accounts for potential later use

View File

@ -6,11 +6,13 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import org.qortal.block.Block; import org.qortal.block.Block;
import org.qortal.block.BlockChain; import org.qortal.block.BlockChain;
import org.qortal.data.at.ATStateData; import org.qortal.data.at.ATStateData;
import org.qortal.data.block.BlockData; import org.qortal.data.block.BlockData;
import org.qortal.data.network.OnlineAccountData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
@ -27,6 +29,8 @@ import com.google.common.primitives.Longs;
import io.druid.extendedset.intset.ConciseSet; import io.druid.extendedset.intset.ConciseSet;
import static org.qortal.controller.OnlineAccountsManager.MAX_NONCE_COUNT;
public class BlockTransformer extends Transformer { public class BlockTransformer extends Transformer {
private static final int VERSION_LENGTH = INT_LENGTH; private static final int VERSION_LENGTH = INT_LENGTH;
@ -416,16 +420,101 @@ public class BlockTransformer extends Transformer {
return encodedSignatures; return encodedSignatures;
} }
public static List<byte[]> decodeTimestampSignatures(byte[] encodedSignatures) { public static byte[] encodeOnlineAccountSignatures(Map<Integer, OnlineAccountData> indexedOnlineAccounts,
List<byte[]> signatures = new ArrayList<>(); List<Integer> accountIndexes,
int onlineAccountsCount,
long timestamp) {
byte[] onlineAccountsSignatures;
for (int i = 0; i < encodedSignatures.length; i += Transformer.SIGNATURE_LENGTH) { if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH]; // Online accounts must include at least one nonce and a reduced block signature from this time onwards
System.arraycopy(encodedSignatures, i, signature, 0, Transformer.SIGNATURE_LENGTH);
signatures.add(signature); 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;
} }
} }

View File

@ -178,4 +178,65 @@ public class OnlineAccountsTests extends Common {
assertTrue(onlineAccountSignatures.size() >= 1 && onlineAccountSignatures.size() <= 3); 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);
}
}
} }