Compare commits

..

2 Commits

Author SHA1 Message Date
CalDescent
958fb299b2 Simplified buildHeightMessage() 2022-05-30 14:38:05 +02:00
CalDescent
16ba426932 Added HEIGHT_V3 message with the following additions:
- latest block's reference
- latest block's online accounts count
- latest blocks transaction count

These are then retained locally in PeerChainTipData (as optional values, which will be null if peer doesn't support HEIGHT_V3 yet).
2022-05-30 14:36:42 +02:00
42 changed files with 312 additions and 756 deletions

View File

@@ -17,10 +17,10 @@
<ROW Property="Manufacturer" Value="Qortal"/>
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
<ROW Property="NTP_GOOD" Value="false"/>
<ROW Property="ProductCode" Value="1033:{4FED6490-EFD9-4670-A497-4E0778B72720} 1049:{059E82DD-E2D3-40EC-AF0C-259FB68F7BEE} 2052:{0571ADAA-B818-4AA9-9320-57267B44E5C7} 2057:{92A305F9-3C4A-4800-9977-10DBB6B8F30A} " Type="16"/>
<ROW Property="ProductCode" Value="1033:{BAC69595-0A3F-4B9F-AB21-5DAC9F288F27} 1049:{4ADB3829-2ECB-4DB0-B114-4068417B6F01} 2052:{B830ACAC-079B-49A9-8271-25D89719826C} 2057:{3F18FD2A-1F0F-4A0A-96B7-21DB829CE7EB} " Type="16"/>
<ROW Property="ProductLanguage" Value="2057"/>
<ROW Property="ProductName" Value="Qortal"/>
<ROW Property="ProductVersion" Value="3.3.3" Type="32"/>
<ROW Property="ProductVersion" Value="3.3.1" Type="32"/>
<ROW Property="RECONFIG_NTP" Value="true"/>
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
@@ -212,7 +212,7 @@
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
<ROW Component="AI_CustomARPName" ComponentId="{28DC06DF-3CBA-4275-A907-3BD3F8E6AD61}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
<ROW Component="AI_CustomARPName" ComponentId="{BD915701-2111-4453-8154-1E88163F5548}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>3.3.4</version>
<version>3.3.2</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>

View File

@@ -3,12 +3,9 @@ package org.qortal.block;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;
@@ -121,8 +118,6 @@ public class Block {
/** Remote/imported/loaded AT states */
protected List<ATStateData> atStates;
/** Remote hash of AT states - in lieu of full AT state data in {@code atStates} */
protected byte[] atStatesHash;
/** Locally-generated AT states */
protected List<ATStateData> ourAtStates;
/** Locally-generated AT fees */
@@ -260,7 +255,7 @@ public class Block {
* Constructs new Block using passed transaction and AT states.
* <p>
* This constructor typically used when receiving a serialized block over the network.
*
*
* @param repository
* @param blockData
* @param transactions
@@ -286,35 +281,6 @@ public class Block {
this.blockData.setTotalFees(totalFees);
}
/**
* Constructs new Block using passed transaction and minimal AT state info.
* <p>
* This constructor typically used when receiving a serialized block over the network.
*
* @param repository
* @param blockData
* @param transactions
* @param atStatesHash
*/
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions, byte[] atStatesHash) {
this(repository, blockData);
this.transactions = new ArrayList<>();
long totalFees = 0;
// We have to sum fees too
for (TransactionData transactionData : transactions) {
this.transactions.add(Transaction.fromData(repository, transactionData));
totalFees += transactionData.getFee();
}
this.atStatesHash = atStatesHash;
totalFees += this.blockData.getATFees();
this.blockData.setTotalFees(totalFees);
}
/**
* Constructs new Block with empty transaction list, using passed minter account.
*
@@ -378,10 +344,6 @@ public class Block {
// Online account (reward-share) with current timestamp but reward-share cancelled
continue;
if (accountIndex >= 7850)
// Temporary limitation as first stage of multipart minting fix
continue;
indexedOnlineAccounts.put(accountIndex, onlineAccountData);
}
List<Integer> accountIndexes = new ArrayList<>(indexedOnlineAccounts.keySet());
@@ -393,10 +355,6 @@ public class Block {
byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet);
int onlineAccountsCount = onlineAccountsSet.size();
if (encodedOnlineAccounts.length > 1024)
// Safety check - to be removed along with above temporary limitation
return null;
// Concatenate online account timestamp signatures (in correct order)
byte[] onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH];
for (int i = 0; i < onlineAccountsCount; ++i) {
@@ -1236,7 +1194,7 @@ public class Block {
*/
private ValidationResult areAtsValid() throws DataException {
// Locally generated AT states should be valid so no need to re-execute them
if (this.ourAtStates != null && this.ourAtStates == this.atStates) // Note object reference compare
if (this.ourAtStates == this.getATStates()) // Note object reference compare
return ValidationResult.OK;
// Generate local AT states for comparison
@@ -1250,33 +1208,8 @@ public class Block {
if (this.ourAtFees != this.blockData.getATFees())
return ValidationResult.AT_STATES_MISMATCH;
// If we have a single AT states hash then compare that in preference
if (this.atStatesHash != null) {
int atBytesLength = blockData.getATCount() * BlockTransformer.AT_ENTRY_LENGTH;
ByteArrayOutputStream atHashBytes = new ByteArrayOutputStream(atBytesLength);
try {
for (ATStateData atStateData : this.ourAtStates) {
atHashBytes.write(atStateData.getATAddress().getBytes(StandardCharsets.UTF_8));
atHashBytes.write(atStateData.getStateHash());
atHashBytes.write(Longs.toByteArray(atStateData.getFees()));
}
} catch (IOException e) {
throw new DataException("Couldn't validate AT states hash due to serialization issue?", e);
}
byte[] ourAtStatesHash = Crypto.digest(atHashBytes.toByteArray());
if (!Arrays.equals(ourAtStatesHash, this.atStatesHash))
return ValidationResult.AT_STATES_MISMATCH;
// Use our AT state data from now on
this.atStates = this.ourAtStates;
return ValidationResult.OK;
}
// Note: this.atStates fully loaded thanks to this.getATStates() call:
this.getATStates();
for (int s = 0; s < this.ourAtStates.size(); ++s) {
// Note: this.atStates fully loaded thanks to this.getATStates() call above
for (int s = 0; s < this.atStates.size(); ++s) {
ATStateData ourAtState = this.ourAtStates.get(s);
ATStateData theirAtState = this.atStates.get(s);

View File

@@ -70,8 +70,7 @@ public class BlockChain {
shareBinFix,
calcChainWeightTimestamp,
transactionV5Timestamp,
transactionV6Timestamp,
disableReferenceTimestamp
transactionV6Timestamp;
}
// Custom transaction fees
@@ -411,10 +410,6 @@ public class BlockChain {
return this.featureTriggers.get(FeatureTrigger.transactionV6Timestamp.name()).longValue();
}
public long getDisableReferenceTimestamp() {
return this.featureTriggers.get(FeatureTrigger.disableReferenceTimestamp.name()).longValue();
}
// More complex getters for aspects that change by height or timestamp
public long getRewardAtHeight(int ourHeight) {

View File

@@ -1197,6 +1197,10 @@ public class Controller extends Thread {
onNetworkHeightV2Message(peer, message);
break;
case HEIGHT_V3:
onNetworkHeightV3Message(peer, message);
break;
case GET_TRANSACTION:
TransactionImporter.getInstance().onNetworkGetTransactionMessage(peer, message);
break;
@@ -1362,18 +1366,6 @@ public class Controller extends Thread {
Block block = new Block(repository, blockData);
// V2 support
if (peer.getPeersVersion() >= BlockV2Message.MIN_PEER_VERSION) {
Message blockMessage = new BlockV2Message(block);
blockMessage.setId(message.getId());
if (!peer.sendMessage(blockMessage)) {
peer.disconnect("failed to send block");
// Don't fall-through to caching because failure to send might be from failure to build message
return;
}
return;
}
CachedBlockMessage blockMessage = new CachedBlockMessage(block);
blockMessage.setId(message.getId());
@@ -1549,6 +1541,27 @@ public class Controller extends Thread {
Synchronizer.getInstance().requestSync();
}
private void onNetworkHeightV3Message(Peer peer, Message message) {
HeightV3Message heightV3Message = (HeightV3Message) message;
if (!Settings.getInstance().isLite()) {
// If peer is inbound and we've not updated their height
// then this is probably their initial HEIGHT_V3 message
// so they need a corresponding HEIGHT_V3 message from us
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
}
// Update peer chain tip data
PeerChainTipData newChainTipData = new PeerChainTipData(heightV3Message.getHeight(), heightV3Message.getSignature(),
heightV3Message.getReference(), heightV3Message.getTimestamp(), heightV3Message.getMinterPublicKey(),
heightV3Message.getOnlineAccountsCount(), heightV3Message.getTransactionCount());
peer.setChainTipData(newChainTipData);
// Potentially synchronize
Synchronizer.getInstance().requestSync();
}
private void onNetworkGetAccountMessage(Peer peer, Message message) {
GetAccountMessage getAccountMessage = (GetAccountMessage) message;
String address = getAccountMessage.getAddress();

View File

@@ -173,7 +173,7 @@ public class LiteNode {
}
if (responseMessage == null) {
LOGGER.info("Peer {} didn't respond to {} message", peer, message.getType());
LOGGER.info("Peer didn't respond to {} message", peer, message.getType());
return null;
}
else if (responseMessage.getType() != expectedResponseMessageType) {

View File

@@ -26,7 +26,14 @@ import org.qortal.event.Event;
import org.qortal.event.EventBus;
import org.qortal.network.Network;
import org.qortal.network.Peer;
import org.qortal.network.message.*;
import org.qortal.network.message.BlockMessage;
import org.qortal.network.message.BlockSummariesMessage;
import org.qortal.network.message.GetBlockMessage;
import org.qortal.network.message.GetBlockSummariesMessage;
import org.qortal.network.message.GetSignaturesV2Message;
import org.qortal.network.message.Message;
import org.qortal.network.message.SignaturesMessage;
import org.qortal.network.message.MessageType;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
@@ -1572,23 +1579,12 @@ public class Synchronizer extends Thread {
Message getBlockMessage = new GetBlockMessage(signature);
Message message = peer.getResponse(getBlockMessage);
if (message == null)
if (message == null || message.getType() != MessageType.BLOCK)
return null;
switch (message.getType()) {
case BLOCK: {
BlockMessage blockMessage = (BlockMessage) message;
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
}
BlockMessage blockMessage = (BlockMessage) message;
case BLOCK_V2: {
BlockV2Message blockMessage = (BlockV2Message) message;
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStatesHash());
}
default:
return null;
}
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
}
public void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {

View File

@@ -6,19 +6,34 @@ public class PeerChainTipData {
private Integer lastHeight;
/** Latest block signature as reported by peer. */
private byte[] lastBlockSignature;
/** Latest block reference as reported by peer. */
private byte[] lastBlockReference;
/** Latest block timestamp as reported by peer. */
private Long lastBlockTimestamp;
/** Latest block minter public key as reported by peer. */
private byte[] lastBlockMinter;
/** Latest block's online accounts count as reported by peer. */
private Integer lastBlockOnlineAccountsCount;
/** Latest block's transaction count as reported by peer. */
private Integer lastBlockTransactionCount;
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockMinter) {
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, byte[] lastBlockReference,
Long lastBlockTimestamp, byte[] lastBlockMinter, Integer lastBlockOnlineAccountsCount,
Integer lastBlockTransactionCount) {
this.lastHeight = lastHeight;
this.lastBlockSignature = lastBlockSignature;
this.lastBlockReference = lastBlockReference;
this.lastBlockTimestamp = lastBlockTimestamp;
this.lastBlockMinter = lastBlockMinter;
this.lastBlockOnlineAccountsCount = lastBlockOnlineAccountsCount;
this.lastBlockTransactionCount = lastBlockTransactionCount;
}
public Integer getLastHeight() {
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockMinter) {
this(lastHeight, lastBlockSignature, null, lastBlockTimestamp, lastBlockMinter, null, null);
}
public Integer getLastHeight() {
return this.lastHeight;
}
@@ -26,6 +41,10 @@ public class PeerChainTipData {
return this.lastBlockSignature;
}
public byte[] getLastBlockReference() {
return this.lastBlockReference;
}
public Long getLastBlockTimestamp() {
return this.lastBlockTimestamp;
}
@@ -34,4 +53,12 @@ public class PeerChainTipData {
return this.lastBlockMinter;
}
public Integer getLastBlockOnlineAccountsCount() {
return this.lastBlockOnlineAccountsCount;
}
public Integer getLastBlockTransactionCount() {
return this.lastBlockTransactionCount;
}
}

View File

@@ -1163,9 +1163,17 @@ public class Network {
}
public Message buildHeightMessage(Peer peer, BlockData blockData) {
// HEIGHT_V2 contains way more useful info
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(),
blockData.getTimestamp(), blockData.getMinterPublicKey());
final long HEIGHT_V3_PEER_VERSION = 0x0300030003L;
if (peer.getPeersVersion() >= HEIGHT_V3_PEER_VERSION) {
// HEIGHT_V3 contains even more useful info
return new HeightV3Message(blockData.getHeight(), blockData.getSignature(), blockData.getReference(),
blockData.getTimestamp(), blockData.getMinterPublicKey(), blockData.getOnlineAccountsCount(),
blockData.getTransactionCount());
} else {
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(),
blockData.getTimestamp(), blockData.getMinterPublicKey());
}
}
public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) {

View File

@@ -12,7 +12,6 @@ import org.qortal.data.network.PeerData;
import org.qortal.network.message.ChallengeMessage;
import org.qortal.network.message.Message;
import org.qortal.network.message.MessageException;
import org.qortal.network.message.MessageType;
import org.qortal.network.task.MessageTask;
import org.qortal.network.task.PingTask;
import org.qortal.settings.Settings;
@@ -547,10 +546,6 @@ public class Peer {
// adjusting position accordingly, reset limit to capacity
this.byteBuffer.compact();
// Unsupported message type? Discard with no further processing
if (message.getType() == MessageType.UNSUPPORTED)
continue;
BlockingQueue<Message> queue = this.replyQueues.get(message.getId());
if (queue != null) {
// Adding message to queue will unblock thread waiting for response

View File

@@ -9,7 +9,6 @@ import org.qortal.data.at.ATStateData;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.transform.TransformationException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.transform.block.BlockTransformer;
import org.qortal.utils.Triple;
@@ -47,12 +46,12 @@ public class BlockMessage extends Message {
try {
int height = byteBuffer.getInt();
BlockTransformation blockTransformation = BlockTransformer.fromByteBuffer(byteBuffer);
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = BlockTransformer.fromByteBuffer(byteBuffer);
BlockData blockData = blockTransformation.getBlockData();
BlockData blockData = blockInfo.getA();
blockData.setHeight(height);
return new BlockMessage(id, blockData, blockTransformation.getTransactions(), blockTransformation.getAtStates());
return new BlockMessage(id, blockData, blockInfo.getB(), blockInfo.getC());
} catch (TransformationException e) {
LOGGER.info(String.format("Received garbled BLOCK message: %s", e.getMessage()));
throw new MessageException(e.getMessage(), e);

View File

@@ -1,87 +0,0 @@
package org.qortal.network.message;
import com.google.common.primitives.Ints;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.block.Block;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.transform.TransformationException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.transform.block.BlockTransformer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
public class BlockV2Message extends Message {
private static final Logger LOGGER = LogManager.getLogger(BlockV2Message.class);
public static final long MIN_PEER_VERSION = 0x300030003L; // 3.3.3
private BlockData blockData;
private List<TransactionData> transactions;
private byte[] atStatesHash;
public BlockV2Message(Block block) throws TransformationException {
super(MessageType.BLOCK_V2);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try {
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
bytes.write(BlockTransformer.toBytesV2(block));
} catch (IOException e) {
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
}
this.dataBytes = bytes.toByteArray();
this.checksumBytes = Message.generateChecksum(this.dataBytes);
}
public BlockV2Message(byte[] cachedBytes) {
super(MessageType.BLOCK_V2);
this.dataBytes = cachedBytes;
this.checksumBytes = Message.generateChecksum(this.dataBytes);
}
private BlockV2Message(int id, BlockData blockData, List<TransactionData> transactions, byte[] atStatesHash) {
super(id, MessageType.BLOCK_V2);
this.blockData = blockData;
this.transactions = transactions;
this.atStatesHash = atStatesHash;
}
public BlockData getBlockData() {
return this.blockData;
}
public List<TransactionData> getTransactions() {
return this.transactions;
}
public byte[] getAtStatesHash() {
return this.atStatesHash;
}
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
try {
int height = byteBuffer.getInt();
BlockTransformation blockTransformation = BlockTransformer.fromByteBufferV2(byteBuffer);
BlockData blockData = blockTransformation.getBlockData();
blockData.setHeight(height);
return new BlockV2Message(id, blockData, blockTransformation.getTransactions(), blockTransformation.getAtStatesHash());
} catch (TransformationException e) {
LOGGER.info(String.format("Received garbled BLOCK_V2 message: %s", e.getMessage()));
throw new MessageException(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,113 @@
package org.qortal.network.message;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import org.qortal.transform.Transformer;
import org.qortal.transform.block.BlockTransformer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class HeightV3Message extends Message {
private int height;
private byte[] signature;
private byte[] reference;
private long timestamp;
private byte[] minterPublicKey;
private int onlineAccountsCount;
private int transactionCount;
public HeightV3Message(int height, byte[] signature, byte[] reference, long timestamp, byte[] minterPublicKey,
int onlineAccountsCount, int transactionCount) {
super(MessageType.HEIGHT_V3);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try {
bytes.write(Ints.toByteArray(height));
bytes.write(signature);
bytes.write(reference);
bytes.write(Longs.toByteArray(timestamp));
bytes.write(minterPublicKey);
bytes.write(Ints.toByteArray(onlineAccountsCount));
bytes.write(Ints.toByteArray(transactionCount));
} catch (IOException e) {
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
}
this.dataBytes = bytes.toByteArray();
this.checksumBytes = Message.generateChecksum(this.dataBytes);
}
private HeightV3Message(int id, int height, byte[] signature, byte[] reference, long timestamp, byte[] minterPublicKey,
int onlineAccountsCount, int transactionCount) {
super(id, MessageType.HEIGHT_V3);
this.height = height;
this.signature = signature;
this.reference = reference;
this.timestamp = timestamp;
this.minterPublicKey = minterPublicKey;
this.onlineAccountsCount = onlineAccountsCount;
this.transactionCount = transactionCount;
}
public int getHeight() {
return this.height;
}
public byte[] getSignature() {
return this.signature;
}
public byte[] getReference() {
return this.reference;
}
public long getTimestamp() {
return this.timestamp;
}
public byte[] getMinterPublicKey() {
return this.minterPublicKey;
}
public int getOnlineAccountsCount() {
return this.onlineAccountsCount;
}
public int getTransactionCount() {
return this.transactionCount;
}
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
int height = bytes.getInt();
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
bytes.get(signature);
byte[] reference = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
bytes.get(reference);
long timestamp = bytes.getLong();
byte[] minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
bytes.get(minterPublicKey);
int onlineAccountsCount = bytes.getInt();
int transactionCount = bytes.getInt();
return new HeightV3Message(id, height, signature, reference, timestamp, minterPublicKey, onlineAccountsCount,
transactionCount);
}
}

View File

@@ -103,7 +103,8 @@ public abstract class Message {
int typeValue = readOnlyBuffer.getInt();
MessageType messageType = MessageType.valueOf(typeValue);
if (messageType == null)
messageType = MessageType.UNSUPPORTED;
// Unrecognised message type
throw new MessageException(String.format("Received unknown message type [%d]", typeValue));
// Optional message ID
byte hasId = readOnlyBuffer.get();

View File

@@ -8,9 +8,6 @@ import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
public enum MessageType {
// Pseudo-message, not sent over the wire
UNSUPPORTED(-1, UnsupportedMessage::fromByteBuffer),
// Handshaking
HELLO(0, HelloMessage::fromByteBuffer),
GOODBYE(1, GoodbyeMessage::fromByteBuffer),
@@ -21,6 +18,7 @@ public enum MessageType {
HEIGHT_V2(10, HeightV2Message::fromByteBuffer),
PING(11, PingMessage::fromByteBuffer),
PONG(12, PongMessage::fromByteBuffer),
HEIGHT_V3(13, HeightV3Message::fromByteBuffer),
// Requesting data
PEERS_V2(20, PeersV2Message::fromByteBuffer),
@@ -34,7 +32,6 @@ public enum MessageType {
BLOCK(50, BlockMessage::fromByteBuffer),
GET_BLOCK(51, GetBlockMessage::fromByteBuffer),
BLOCK_V2(52, BlockV2Message::fromByteBuffer),
SIGNATURES(60, SignaturesMessage::fromByteBuffer),
GET_SIGNATURES_V2(61, GetSignaturesV2Message::fromByteBuffer),

View File

@@ -1,20 +0,0 @@
package org.qortal.network.message;
import java.nio.ByteBuffer;
public class UnsupportedMessage extends Message {
public UnsupportedMessage() {
super(MessageType.UNSUPPORTED);
throw new UnsupportedOperationException("Unsupported message is unsupported!");
}
private UnsupportedMessage(int id) {
super(id, MessageType.UNSUPPORTED);
}
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException {
return new UnsupportedMessage(id);
}
}

View File

@@ -9,7 +9,6 @@ import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.settings.Settings;
import org.qortal.transform.TransformationException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.transform.block.BlockTransformer;
import org.qortal.utils.Triple;
@@ -67,7 +66,7 @@ public class BlockArchiveReader {
this.fileListCache = Map.copyOf(map);
}
public BlockTransformation fetchBlockAtHeight(int height) {
public Triple<BlockData, List<TransactionData>, List<ATStateData>> fetchBlockAtHeight(int height) {
if (this.fileListCache == null) {
this.fetchFileList();
}
@@ -78,13 +77,13 @@ public class BlockArchiveReader {
}
ByteBuffer byteBuffer = ByteBuffer.wrap(serializedBytes);
BlockTransformation blockInfo = null;
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = null;
try {
blockInfo = BlockTransformer.fromByteBuffer(byteBuffer);
if (blockInfo != null && blockInfo.getBlockData() != null) {
if (blockInfo != null && blockInfo.getA() != null) {
// Block height is stored outside of the main serialized bytes, so it
// won't be set automatically.
blockInfo.getBlockData().setHeight(height);
blockInfo.getA().setHeight(height);
}
} catch (TransformationException e) {
return null;
@@ -92,7 +91,8 @@ public class BlockArchiveReader {
return blockInfo;
}
public BlockTransformation fetchBlockWithSignature(byte[] signature, Repository repository) {
public Triple<BlockData, List<TransactionData>, List<ATStateData>> fetchBlockWithSignature(
byte[] signature, Repository repository) {
if (this.fileListCache == null) {
this.fetchFileList();
@@ -105,12 +105,13 @@ public class BlockArchiveReader {
return null;
}
public List<BlockTransformation> fetchBlocksFromRange(int startHeight, int endHeight) {
public List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> fetchBlocksFromRange(
int startHeight, int endHeight) {
List<BlockTransformation> blockInfoList = new ArrayList<>();
List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> blockInfoList = new ArrayList<>();
for (int height = startHeight; height <= endHeight; height++) {
BlockTransformation blockInfo = this.fetchBlockAtHeight(height);
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = this.fetchBlockAtHeight(height);
if (blockInfo == null) {
return blockInfoList;
}

View File

@@ -1,13 +1,16 @@
package org.qortal.repository.hsqldb;
import org.qortal.api.ApiError;
import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.model.BlockSignerSummary;
import org.qortal.block.Block;
import org.qortal.data.block.BlockArchiveData;
import org.qortal.data.block.BlockData;
import org.qortal.data.block.BlockSummaryData;
import org.qortal.repository.BlockArchiveReader;
import org.qortal.repository.BlockArchiveRepository;
import org.qortal.repository.DataException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.utils.Triple;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -26,11 +29,11 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
@Override
public BlockData fromSignature(byte[] signature) throws DataException {
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockWithSignature(signature, this.repository);
if (blockInfo == null)
return null;
return blockInfo.getBlockData();
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockWithSignature(signature, this.repository);
if (blockInfo != null) {
return (BlockData) blockInfo.getA();
}
return null;
}
@Override
@@ -44,11 +47,11 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
@Override
public BlockData fromHeight(int height) throws DataException {
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
if (blockInfo == null)
return null;
return blockInfo.getBlockData();
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
if (blockInfo != null) {
return (BlockData) blockInfo.getA();
}
return null;
}
@Override
@@ -76,9 +79,9 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
int height = referenceBlock.getHeight();
if (height > 0) {
// Request the block at height + 1
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height + 1);
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height + 1);
if (blockInfo != null) {
return blockInfo.getBlockData();
return (BlockData) blockInfo.getA();
}
}
}

View File

@@ -964,11 +964,6 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("DROP TABLE ArbitraryPeers");
break;
case 42:
// We need more space for online accounts
stmt.execute("ALTER TABLE Blocks ALTER COLUMN online_accounts SET DATA TYPE VARBINARY(10240)");
break;
default:
// nothing to do
return false;

View File

@@ -6,7 +6,6 @@ import java.util.Objects;
import java.util.stream.Collectors;
import org.qortal.account.Account;
import org.qortal.block.BlockChain;
import org.qortal.controller.arbitrary.ArbitraryDataManager;
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
import org.qortal.crypto.Crypto;
@@ -20,7 +19,6 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.arbitrary.ArbitraryDataFile;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.ArbitraryTransactionTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.ArbitraryTransactionUtils;
@@ -88,14 +86,6 @@ public class ArbitraryTransaction extends Transaction {
@Override
public boolean hasValidReference() throws DataException {
// We shouldn't really get this far, but just in case:
// Disable reference checking after feature trigger timestamp
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.arbitraryTransactionData.getReference() != null &&
this.arbitraryTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}
if (this.arbitraryTransactionData.getReference() == null) {
return false;
}

View File

@@ -5,7 +5,6 @@ import java.util.List;
import org.qortal.account.Account;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.data.asset.AssetData;
import org.qortal.data.transaction.ATTransactionData;
@@ -13,7 +12,6 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.AtTransactionTransformer;
import org.qortal.utils.Amounts;
@@ -77,13 +75,6 @@ public class AtTransaction extends Transaction {
@Override
public boolean hasValidReference() throws DataException {
// Disable reference checking after feature trigger timestamp
if (this.atTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.atTransactionData.getReference() != null &&
this.atTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}
// Check reference is correct, using AT account, not transaction creator which is null account
Account atAccount = getATAccount();
return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference());

View File

@@ -8,7 +8,6 @@ import org.qortal.account.Account;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.crypto.MemoryPoW;
import org.qortal.data.PaymentData;
@@ -21,7 +20,6 @@ import org.qortal.repository.DataException;
import org.qortal.repository.GroupRepository;
import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.ChatTransactionTransformer;
import org.qortal.transform.transaction.MessageTransactionTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
@@ -165,14 +163,6 @@ public class MessageTransaction extends Transaction {
@Override
public boolean hasValidReference() throws DataException {
// We shouldn't really get this far, but just in case:
// Disable reference checking after feature trigger timestamp
if (this.messageTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.messageTransactionData.getReference() != null &&
this.messageTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}
if (this.messageTransactionData.getReference() == null)
return false;

View File

@@ -31,7 +31,6 @@ import org.qortal.repository.GroupRepository;
import org.qortal.repository.Repository;
import org.qortal.settings.Settings;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.NTP;
@@ -906,13 +905,6 @@ public abstract class Transaction {
* @throws DataException
*/
public boolean hasValidReference() throws DataException {
// Disable reference checking after feature trigger timestamp
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.transactionData.getReference() != null &&
this.transactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}
Account creator = getCreator();
return Arrays.equals(transactionData.getReference(), creator.getLastReference());

View File

@@ -1,44 +0,0 @@
package org.qortal.transform.block;
import org.qortal.data.at.ATStateData;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
import java.util.List;
public class BlockTransformation {
private final BlockData blockData;
private final List<TransactionData> transactions;
private final List<ATStateData> atStates;
private final byte[] atStatesHash;
/*package*/ BlockTransformation(BlockData blockData, List<TransactionData> transactions, List<ATStateData> atStates) {
this.blockData = blockData;
this.transactions = transactions;
this.atStates = atStates;
this.atStatesHash = null;
}
/*package*/ BlockTransformation(BlockData blockData, List<TransactionData> transactions, byte[] atStatesHash) {
this.blockData = blockData;
this.transactions = transactions;
this.atStates = null;
this.atStatesHash = atStatesHash;
}
public BlockData getBlockData() {
return blockData;
}
public List<TransactionData> getTransactions() {
return transactions;
}
public List<ATStateData> getAtStates() {
return atStates;
}
public byte[] getAtStatesHash() {
return atStatesHash;
}
}

View File

@@ -3,14 +3,12 @@ package org.qortal.transform.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.qortal.block.Block;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.data.at.ATStateData;
import org.qortal.data.block.BlockData;
import org.qortal.data.transaction.TransactionData;
@@ -22,6 +20,7 @@ import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.Base58;
import org.qortal.utils.Serialization;
import org.qortal.utils.Triple;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
@@ -46,13 +45,14 @@ public class BlockTransformer extends Transformer {
protected static final int AT_BYTES_LENGTH = INT_LENGTH;
protected static final int AT_FEES_LENGTH = AMOUNT_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_TIMESTAMP_LENGTH = TIMESTAMP_LENGTH;
protected static final int ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH = INT_LENGTH;
public static final int AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + AMOUNT_LENGTH;
protected static final int AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + AMOUNT_LENGTH;
/**
* Extract block data and transaction data from serialized bytes.
@@ -61,7 +61,7 @@ public class BlockTransformer extends Transformer {
* @return BlockData and a List of transactions.
* @throws TransformationException
*/
public static BlockTransformation fromBytes(byte[] bytes) throws TransformationException {
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromBytes(byte[] bytes) throws TransformationException {
if (bytes == null)
return null;
@@ -76,40 +76,28 @@ public class BlockTransformer extends Transformer {
/**
* Extract block data and transaction data from serialized bytes containing a single block.
*
* @param byteBuffer source of serialized block bytes
* @param bytes
* @return BlockData and a List of transactions.
* @throws TransformationException
*/
public static BlockTransformation fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
return BlockTransformer.fromByteBuffer(byteBuffer, false);
}
/**
* Extract block data and transaction data from serialized bytes containing a single block.
*
* @param byteBuffer source of serialized block bytes
* @return BlockData and a List of transactions.
* @throws TransformationException
*/
public static BlockTransformation fromByteBufferV2(ByteBuffer byteBuffer) throws TransformationException {
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
return BlockTransformer.fromByteBuffer(byteBuffer, true);
}
/**
* Extract block data and transaction data from serialized bytes containing a single block, in one of two forms.
*
* @param byteBuffer source of serialized block bytes
* @param isV2 set to true if AT state info is represented by a single hash, false if serialized as per-AT address+state hash+fees
* Extract block data and transaction data from serialized bytes containing one or more blocks.
*
* @param bytes
* @return the next block's BlockData and a List of transactions.
* @throws TransformationException
*/
private static BlockTransformation fromByteBuffer(ByteBuffer byteBuffer, boolean isV2) throws TransformationException {
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer, boolean finalBlockInBuffer) throws TransformationException {
int version = byteBuffer.getInt();
if (byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
if (finalBlockInBuffer && byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
throw new TransformationException("Byte data too short for Block");
if (byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
if (finalBlockInBuffer && byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
throw new TransformationException("Byte data too long for Block");
long timestamp = byteBuffer.getLong();
@@ -129,52 +117,42 @@ public class BlockTransformer extends Transformer {
int atCount = 0;
long atFees = 0;
byte[] atStatesHash = null;
List<ATStateData> atStates = null;
List<ATStateData> atStates = new ArrayList<>();
if (isV2) {
// Simply: AT count, AT total fees, hash(all AT states)
atCount = byteBuffer.getInt();
atFees = byteBuffer.getLong();
atStatesHash = new byte[Transformer.SHA256_LENGTH];
byteBuffer.get(atStatesHash);
} else {
// V1: AT info byte length, then per-AT entries of AT address + state hash + fees
int atBytesLength = byteBuffer.getInt();
if (atBytesLength > BlockChain.getInstance().getMaxBlockSize())
throw new TransformationException("Byte data too long for Block's AT info");
int atBytesLength = byteBuffer.getInt();
// Read AT-address, SHA256 hash and fees
if (atBytesLength % AT_ENTRY_LENGTH != 0)
throw new TransformationException("AT byte data not a multiple of AT entry length");
if (atBytesLength > BlockChain.getInstance().getMaxBlockSize())
throw new TransformationException("Byte data too long for Block's AT info");
ByteBuffer atByteBuffer = byteBuffer.slice();
atByteBuffer.limit(atBytesLength);
ByteBuffer atByteBuffer = byteBuffer.slice();
atByteBuffer.limit(atBytesLength);
atStates = new ArrayList<>();
while (atByteBuffer.hasRemaining()) {
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
atByteBuffer.get(atAddressBytes);
String atAddress = Base58.encode(atAddressBytes);
// Read AT-address, SHA256 hash and fees
if (atBytesLength % AT_ENTRY_LENGTH != 0)
throw new TransformationException("AT byte data not a multiple of AT entry length");
byte[] stateHash = new byte[SHA256_LENGTH];
atByteBuffer.get(stateHash);
while (atByteBuffer.hasRemaining()) {
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
atByteBuffer.get(atAddressBytes);
String atAddress = Base58.encode(atAddressBytes);
long fees = atByteBuffer.getLong();
byte[] stateHash = new byte[SHA256_LENGTH];
atByteBuffer.get(stateHash);
// Add this AT's fees to our total
atFees += fees;
long fees = atByteBuffer.getLong();
atStates.add(new ATStateData(atAddress, stateHash, fees));
}
// Add this AT's fees to our total
atFees += fees;
// Bump byteBuffer over AT states just read in slice
byteBuffer.position(byteBuffer.position() + atBytesLength);
// AT count to reflect the number of states we have
atCount = atStates.size();
atStates.add(new ATStateData(atAddress, stateHash, fees));
}
// Bump byteBuffer over AT states just read in slice
byteBuffer.position(byteBuffer.position() + atBytesLength);
// AT count to reflect the number of states we have
atCount = atStates.size();
// Add AT fees to totalFees
totalFees += atFees;
@@ -243,15 +221,16 @@ public class BlockTransformer extends Transformer {
byteBuffer.get(onlineAccountsSignatures);
}
// We should only complain about excess byte data if we aren't expecting more blocks in this ByteBuffer
if (finalBlockInBuffer && byteBuffer.hasRemaining())
throw new TransformationException("Excess byte data found after parsing Block");
// We don't have a height!
Integer height = null;
BlockData blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
minterPublicKey, minterSignature, atCount, atFees, encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
if (isV2)
return new BlockTransformation(blockData, transactions, atStatesHash);
else
return new BlockTransformation(blockData, transactions, atStates);
return new Triple<>(blockData, transactions, atStates);
}
public static int getDataLength(Block block) throws TransformationException {
@@ -287,14 +266,6 @@ public class BlockTransformer extends Transformer {
}
public static byte[] toBytes(Block block) throws TransformationException {
return toBytes(block, false);
}
public static byte[] toBytesV2(Block block) throws TransformationException {
return toBytes(block, true);
}
private static byte[] toBytes(Block block, boolean isV2) throws TransformationException {
BlockData blockData = block.getBlockData();
try {
@@ -308,37 +279,16 @@ public class BlockTransformer extends Transformer {
bytes.write(blockData.getMinterSignature());
int atBytesLength = blockData.getATCount() * AT_ENTRY_LENGTH;
if (isV2) {
ByteArrayOutputStream atHashBytes = new ByteArrayOutputStream(atBytesLength);
long atFees = 0;
bytes.write(Ints.toByteArray(atBytesLength));
for (ATStateData atStateData : block.getATStates()) {
// Skip initial states generated by DEPLOY_AT transactions in the same block
if (atStateData.isInitial())
continue;
for (ATStateData atStateData : block.getATStates()) {
// Skip initial states generated by DEPLOY_AT transactions in the same block
if (atStateData.isInitial())
continue;
atHashBytes.write(atStateData.getATAddress().getBytes(StandardCharsets.UTF_8));
atHashBytes.write(atStateData.getStateHash());
atHashBytes.write(Longs.toByteArray(atStateData.getFees()));
atFees += atStateData.getFees();
}
bytes.write(Ints.toByteArray(blockData.getATCount()));
bytes.write(Longs.toByteArray(atFees));
bytes.write(Crypto.digest(atHashBytes.toByteArray()));
} else {
bytes.write(Ints.toByteArray(atBytesLength));
for (ATStateData atStateData : block.getATStates()) {
// Skip initial states generated by DEPLOY_AT transactions in the same block
if (atStateData.isInitial())
continue;
bytes.write(Base58.decode(atStateData.getATAddress()));
bytes.write(atStateData.getStateHash());
bytes.write(Longs.toByteArray(atStateData.getFees()));
}
bytes.write(Base58.decode(atStateData.getATAddress()));
bytes.write(atStateData.getStateHash());
bytes.write(Longs.toByteArray(atStateData.getFees()));
}
// Transactions

View File

@@ -6,7 +6,6 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.BlockArchiveReader;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transform.block.BlockTransformation;
import java.util.List;
@@ -34,7 +33,8 @@ public class BlockArchiveUtils {
repository.discardChanges();
final int requestedRange = endHeight+1-startHeight;
List<BlockTransformation> blockInfoList = BlockArchiveReader.getInstance().fetchBlocksFromRange(startHeight, endHeight);
List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> blockInfoList =
BlockArchiveReader.getInstance().fetchBlocksFromRange(startHeight, endHeight);
// Ensure that we have received all of the requested blocks
if (blockInfoList == null || blockInfoList.isEmpty()) {
@@ -43,26 +43,27 @@ public class BlockArchiveUtils {
if (blockInfoList.size() != requestedRange) {
throw new IllegalStateException("Non matching block count when importing from archive");
}
BlockTransformation firstBlock = blockInfoList.get(0);
if (firstBlock == null || firstBlock.getBlockData().getHeight() != startHeight) {
Triple<BlockData, List<TransactionData>, List<ATStateData>> firstBlock = blockInfoList.get(0);
if (firstBlock == null || firstBlock.getA().getHeight() != startHeight) {
throw new IllegalStateException("Non matching first block when importing from archive");
}
if (blockInfoList.size() > 0) {
BlockTransformation lastBlock = blockInfoList.get(blockInfoList.size() - 1);
if (lastBlock == null || lastBlock.getBlockData().getHeight() != endHeight) {
Triple<BlockData, List<TransactionData>, List<ATStateData>> lastBlock =
blockInfoList.get(blockInfoList.size() - 1);
if (lastBlock == null || lastBlock.getA().getHeight() != endHeight) {
throw new IllegalStateException("Non matching last block when importing from archive");
}
}
// Everything seems okay, so go ahead with the import
for (BlockTransformation blockInfo : blockInfoList) {
for (Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo : blockInfoList) {
try {
// Save block
repository.getBlockRepository().save(blockInfo.getBlockData());
repository.getBlockRepository().save(blockInfo.getA());
// Save AT state data hashes
for (ATStateData atStateData : blockInfo.getAtStates()) {
atStateData.setHeight(blockInfo.getBlockData().getHeight());
for (ATStateData atStateData : blockInfo.getC()) {
atStateData.setHeight(blockInfo.getA().getHeight());
repository.getATRepository().save(atStateData);
}

View File

@@ -59,8 +59,7 @@
"shareBinFix": 399000,
"calcChainWeightTimestamp": 1620579600000,
"transactionV5Timestamp": 1642176000000,
"transactionV6Timestamp": 9999999999999,
"disableReferenceTimestamp": 1655222400000
"transactionV6Timestamp": 9999999999999
},
"genesisInfo": {
"version": 4,

View File

@@ -20,7 +20,6 @@ import org.qortal.test.common.Common;
import org.qortal.transaction.DeployAtTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transform.TransformationException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.utils.BlockArchiveUtils;
import org.qortal.utils.NTP;
import org.qortal.utils.Triple;
@@ -124,8 +123,8 @@ public class BlockArchiveTests extends Common {
// Read block 2 from the archive
BlockArchiveReader reader = BlockArchiveReader.getInstance();
BlockTransformation block2Info = reader.fetchBlockAtHeight(2);
BlockData block2ArchiveData = block2Info.getBlockData();
Triple<BlockData, List<TransactionData>, List<ATStateData>> block2Info = reader.fetchBlockAtHeight(2);
BlockData block2ArchiveData = block2Info.getA();
// Read block 2 from the repository
BlockData block2RepositoryData = repository.getBlockRepository().fromHeight(2);
@@ -138,8 +137,8 @@ public class BlockArchiveTests extends Common {
assertEquals(1, block2ArchiveData.getOnlineAccountsCount());
// Read block 900 from the archive
BlockTransformation block900Info = reader.fetchBlockAtHeight(900);
BlockData block900ArchiveData = block900Info.getBlockData();
Triple<BlockData, List<TransactionData>, List<ATStateData>> block900Info = reader.fetchBlockAtHeight(900);
BlockData block900ArchiveData = block900Info.getA();
// Read block 900 from the repository
BlockData block900RepositoryData = repository.getBlockRepository().fromHeight(900);
@@ -201,10 +200,10 @@ public class BlockArchiveTests extends Common {
// Read a block from the archive
BlockArchiveReader reader = BlockArchiveReader.getInstance();
BlockTransformation blockInfo = reader.fetchBlockAtHeight(testHeight);
BlockData archivedBlockData = blockInfo.getBlockData();
ATStateData archivedAtStateData = blockInfo.getAtStates().isEmpty() ? null : blockInfo.getAtStates().get(0);
List<TransactionData> archivedTransactions = blockInfo.getTransactions();
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = reader.fetchBlockAtHeight(testHeight);
BlockData archivedBlockData = blockInfo.getA();
ATStateData archivedAtStateData = blockInfo.getC().isEmpty() ? null : blockInfo.getC().get(0);
List<TransactionData> archivedTransactions = blockInfo.getB();
// Read the same block from the repository
BlockData repositoryBlockData = repository.getBlockRepository().fromHeight(testHeight);
@@ -256,7 +255,7 @@ public class BlockArchiveTests extends Common {
// Check block 10 (unarchived)
BlockArchiveReader reader = BlockArchiveReader.getInstance();
BlockTransformation blockInfo = reader.fetchBlockAtHeight(10);
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = reader.fetchBlockAtHeight(10);
assertNull(blockInfo);
}

View File

@@ -24,7 +24,6 @@ import org.qortal.test.common.TransactionUtils;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.transform.TransformationException;
import org.qortal.transform.block.BlockTransformation;
import org.qortal.transform.block.BlockTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.Base58;
@@ -122,10 +121,10 @@ public class BlockTests extends Common {
assertEquals(BlockTransformer.getDataLength(block), bytes.length);
BlockTransformation blockInfo = BlockTransformer.fromBytes(bytes);
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = BlockTransformer.fromBytes(bytes);
// Compare transactions
List<TransactionData> deserializedTransactions = blockInfo.getTransactions();
List<TransactionData> deserializedTransactions = blockInfo.getB();
assertEquals("Transaction count differs", blockData.getTransactionCount(), deserializedTransactions.size());
for (int i = 0; i < blockData.getTransactionCount(); ++i) {

View File

@@ -1,165 +0,0 @@
package org.qortal.test;
import org.junit.Before;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.data.transaction.PaymentTransactionData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.transaction.Transaction;
import java.util.Random;
import static org.junit.Assert.assertEquals;
public class TransactionReferenceTests extends Common {
@Before
public void beforeTest() throws DataException {
Common.useDefaultSettings();
}
@Test
public void testInvalidRandomReferenceBeforeFeatureTrigger() throws DataException {
Random random = new Random();
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
byte[] randomPrivateKey = new byte[32];
random.nextBytes(randomPrivateKey);
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
// Create payment transaction data
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
// Set random reference
byte[] randomReference = new byte[64];
random.nextBytes(randomReference);
paymentTransactionData.setReference(randomReference);
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
// Transaction should be invalid due to random reference
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}
@Test
public void testValidRandomReferenceAfterFeatureTrigger() throws DataException {
Common.useSettings("test-settings-v2-disable-reference.json");
Random random = new Random();
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
byte[] randomPrivateKey = new byte[32];
random.nextBytes(randomPrivateKey);
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
// Create payment transaction data
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
// Set random reference
byte[] randomReference = new byte[64];
random.nextBytes(randomReference);
paymentTransactionData.setReference(randomReference);
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
// Transaction should be valid, even with random reference, because reference checking is now disabled
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.OK, validationResult);
TransactionUtils.signAndImportValid(repository, paymentTransactionData, alice);
}
}
@Test
public void testNullReferenceAfterFeatureTrigger() throws DataException {
Common.useSettings("test-settings-v2-disable-reference.json");
Random random = new Random();
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
byte[] randomPrivateKey = new byte[32];
random.nextBytes(randomPrivateKey);
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
// Create payment transaction data
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
// Set null reference
paymentTransactionData.setReference(null);
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
// Transaction should be invalid, as we require a non-null reference
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}
@Test
public void testShortReferenceAfterFeatureTrigger() throws DataException {
Common.useSettings("test-settings-v2-disable-reference.json");
Random random = new Random();
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
byte[] randomPrivateKey = new byte[32];
random.nextBytes(randomPrivateKey);
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
// Create payment transaction data
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
// Set a 1-byte reference
byte[] randomByte = new byte[63];
random.nextBytes(randomByte);
paymentTransactionData.setReference(randomByte);
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
// Transaction should be invalid, as reference isn't long enough
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}
@Test
public void testLongReferenceAfterFeatureTrigger() throws DataException {
Common.useSettings("test-settings-v2-disable-reference.json");
Random random = new Random();
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
byte[] randomPrivateKey = new byte[32];
random.nextBytes(randomPrivateKey);
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
// Create payment transaction data
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
// Set a 1-byte reference
byte[] randomByte = new byte[65];
random.nextBytes(randomByte);
paymentTransactionData.setReference(randomByte);
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
// Transaction should be invalid, as reference is too long
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}
}

View File

@@ -61,7 +61,6 @@ public class Common {
public static final String testSettingsFilename = "test-settings-v2.json";
public static boolean shouldRetainRepositoryAfterTest = false;
static {
// Load/check settings, which potentially sets up blockchain config, etc.
@@ -127,7 +126,6 @@ public class Common {
public static void useSettings(String settingsFilename) throws DataException {
Common.useSettingsAndDb(settingsFilename, true);
setShouldRetainRepositoryAfterTest(false);
}
public static void useDefaultSettings() throws DataException {
@@ -209,16 +207,7 @@ public class Common {
RepositoryManager.setRepositoryFactory(repositoryFactory);
}
public static void setShouldRetainRepositoryAfterTest(boolean shouldRetain) {
shouldRetainRepositoryAfterTest = shouldRetain;
}
public static void deleteTestRepository() throws DataException {
if (shouldRetainRepositoryAfterTest) {
// Don't delete if we've requested to keep the db intact
return;
}
// Delete repository directory if exists
Path repositoryPath = Paths.get(Settings.getInstance().getRepositoryPath());
try {

View File

@@ -509,7 +509,6 @@ public class IntegrityTests extends Common {
@Ignore("Checks 'live' repository")
@Test
public void testRepository() throws DataException {
Common.setShouldRetainRepositoryAfterTest(true);
Settings.fileInstance("settings.json"); // use 'live' settings
String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=false;hsqldb.full_log_replay=true";

View File

@@ -1,84 +0,0 @@
{
"isTestChain": true,
"blockTimestampMargin": 500,
"transactionExpiryPeriod": 86400000,
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,
"founderEffectiveMintingLevel": 10,
"onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000,
"rewardsByHeight": [
{ "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 },
{ "height": 21, "reward": 1 }
],
"sharesByLevel": [
{ "levels": [ 1, 2 ], "share": 0.05 },
{ "levels": [ 3, 4 ], "share": 0.10 },
{ "levels": [ 5, 6 ], "share": 0.15 },
{ "levels": [ 7, 8 ], "share": 0.20 },
{ "levels": [ 9, 10 ], "share": 0.25 }
],
"qoraHoldersShare": 0.20,
"qoraPerQortReward": 250,
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
"blockTimingsByHeight": [
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
],
"ciyamAtSettings": {
"feePerStep": "0.0001",
"maxStepsPerRound": 500,
"stepsPerFunctionCall": 10,
"minutesPerBlock": 1
},
"featureTriggers": {
"messageHeight": 0,
"atHeight": 0,
"assetsTimestamp": 0,
"votingTimestamp": 0,
"arbitraryTimestamp": 0,
"powfixTimestamp": 0,
"qortalTimestamp": 0,
"newAssetPricingTimestamp": 0,
"groupApprovalTimestamp": 0,
"atFindNextTransactionFix": 0,
"newBlockSigHeight": 999999,
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 0
},
"genesisInfo": {
"version": 4,
"timestamp": 0,
"transactions": [
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" },
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }
]
}
}

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 6,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -53,8 +53,7 @@
"shareBinFix": 999999,
"calcChainWeightTimestamp": 0,
"transactionV5Timestamp": 0,
"transactionV6Timestamp": 0,
"disableReferenceTimestamp": 9999999999999
"transactionV6Timestamp": 0
},
"genesisInfo": {
"version": 4,

View File

@@ -1,11 +0,0 @@
{
"repositoryPath": "testdb",
"restrictedApi": false,
"blockchainConfig": "src/test/resources/test-chain-v2-disable-reference.json",
"exportPath": "qortal-backup-test",
"bootstrap": false,
"wipeUnconfirmedOnStart": false,
"testNtpOffset": 0,
"minPeers": 0,
"pruneBlockLimit": 100
}