forked from Qortal/qortal
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d3b6c5f052 | ||
|
f48eb27f00 | ||
|
b02ac2561f | ||
|
1b2f66b201 | ||
|
e992f6b683 | ||
|
8b3f9db497 | ||
|
0eebfe4a8c | ||
|
12b3fc257b | ||
|
66a3322ea6 | ||
|
4965cb7121 | ||
|
b92b1fecb0 | ||
|
43a75420d0 | ||
|
e85026f866 | ||
|
ba7b9f3ad8 | ||
|
987446cf7f | ||
|
d2fc705846 | ||
|
e393150e9c | ||
|
43bfd28bcd | ||
|
ca8f8a59f4 | ||
|
d086ade91f | ||
|
64d4c458ec | ||
|
9f19a042e6 | ||
|
922ffcc0be | ||
|
f887fcafe3 |
@@ -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:{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="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="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="3.3.1" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.3.3" 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="{BD915701-2111-4453-8154-1E88163F5548}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{28DC06DF-3CBA-4275-A907-3BD3F8E6AD61}" 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"/>
|
||||
|
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.3.5</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -3,9 +3,12 @@ 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.*;
|
||||
@@ -118,6 +121,8 @@ 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 */
|
||||
@@ -255,7 +260,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
|
||||
@@ -281,6 +286,35 @@ 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.
|
||||
*
|
||||
@@ -1194,7 +1228,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 == this.getATStates()) // Note object reference compare
|
||||
if (this.ourAtStates != null && this.ourAtStates == this.atStates) // Note object reference compare
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Generate local AT states for comparison
|
||||
@@ -1208,8 +1242,33 @@ public class Block {
|
||||
if (this.ourAtFees != this.blockData.getATFees())
|
||||
return ValidationResult.AT_STATES_MISMATCH;
|
||||
|
||||
// Note: this.atStates fully loaded thanks to this.getATStates() call above
|
||||
for (int s = 0; s < this.atStates.size(); ++s) {
|
||||
// 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) {
|
||||
ATStateData ourAtState = this.ourAtStates.get(s);
|
||||
ATStateData theirAtState = this.atStates.get(s);
|
||||
|
||||
|
@@ -70,7 +70,8 @@ public class BlockChain {
|
||||
shareBinFix,
|
||||
calcChainWeightTimestamp,
|
||||
transactionV5Timestamp,
|
||||
transactionV6Timestamp;
|
||||
transactionV6Timestamp,
|
||||
disableReferenceTimestamp
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@@ -410,6 +411,10 @@ 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) {
|
||||
|
@@ -1362,6 +1362,18 @@ 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());
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -26,14 +26,7 @@ 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.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.network.message.*;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
@@ -1579,12 +1572,23 @@ public class Synchronizer extends Thread {
|
||||
Message getBlockMessage = new GetBlockMessage(signature);
|
||||
|
||||
Message message = peer.getResponse(getBlockMessage);
|
||||
if (message == null || message.getType() != MessageType.BLOCK)
|
||||
if (message == null)
|
||||
return null;
|
||||
|
||||
BlockMessage blockMessage = (BlockMessage) message;
|
||||
switch (message.getType()) {
|
||||
case BLOCK: {
|
||||
BlockMessage blockMessage = (BlockMessage) message;
|
||||
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
|
||||
}
|
||||
|
||||
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
|
||||
case BLOCK_V2: {
|
||||
BlockV2Message blockMessage = (BlockV2Message) message;
|
||||
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStatesHash());
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {
|
||||
|
@@ -12,6 +12,7 @@ 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;
|
||||
@@ -546,6 +547,10 @@ 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
|
||||
|
@@ -9,6 +9,7 @@ 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;
|
||||
|
||||
@@ -46,12 +47,12 @@ public class BlockMessage extends Message {
|
||||
try {
|
||||
int height = byteBuffer.getInt();
|
||||
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = BlockTransformer.fromByteBuffer(byteBuffer);
|
||||
BlockTransformation blockTransformation = BlockTransformer.fromByteBuffer(byteBuffer);
|
||||
|
||||
BlockData blockData = blockInfo.getA();
|
||||
BlockData blockData = blockTransformation.getBlockData();
|
||||
blockData.setHeight(height);
|
||||
|
||||
return new BlockMessage(id, blockData, blockInfo.getB(), blockInfo.getC());
|
||||
return new BlockMessage(id, blockData, blockTransformation.getTransactions(), blockTransformation.getAtStates());
|
||||
} catch (TransformationException e) {
|
||||
LOGGER.info(String.format("Received garbled BLOCK message: %s", e.getMessage()));
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
|
87
src/main/java/org/qortal/network/message/BlockV2Message.java
Normal file
87
src/main/java/org/qortal/network/message/BlockV2Message.java
Normal file
@@ -0,0 +1,87 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -103,8 +103,7 @@ public abstract class Message {
|
||||
int typeValue = readOnlyBuffer.getInt();
|
||||
MessageType messageType = MessageType.valueOf(typeValue);
|
||||
if (messageType == null)
|
||||
// Unrecognised message type
|
||||
throw new MessageException(String.format("Received unknown message type [%d]", typeValue));
|
||||
messageType = MessageType.UNSUPPORTED;
|
||||
|
||||
// Optional message ID
|
||||
byte hasId = readOnlyBuffer.get();
|
||||
|
@@ -8,6 +8,9 @@ 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),
|
||||
@@ -31,6 +34,7 @@ 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),
|
||||
|
@@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -9,6 +9,7 @@ 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;
|
||||
|
||||
@@ -66,7 +67,7 @@ public class BlockArchiveReader {
|
||||
this.fileListCache = Map.copyOf(map);
|
||||
}
|
||||
|
||||
public Triple<BlockData, List<TransactionData>, List<ATStateData>> fetchBlockAtHeight(int height) {
|
||||
public BlockTransformation fetchBlockAtHeight(int height) {
|
||||
if (this.fileListCache == null) {
|
||||
this.fetchFileList();
|
||||
}
|
||||
@@ -77,13 +78,13 @@ public class BlockArchiveReader {
|
||||
}
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(serializedBytes);
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = null;
|
||||
BlockTransformation blockInfo = null;
|
||||
try {
|
||||
blockInfo = BlockTransformer.fromByteBuffer(byteBuffer);
|
||||
if (blockInfo != null && blockInfo.getA() != null) {
|
||||
if (blockInfo != null && blockInfo.getBlockData() != null) {
|
||||
// Block height is stored outside of the main serialized bytes, so it
|
||||
// won't be set automatically.
|
||||
blockInfo.getA().setHeight(height);
|
||||
blockInfo.getBlockData().setHeight(height);
|
||||
}
|
||||
} catch (TransformationException e) {
|
||||
return null;
|
||||
@@ -91,8 +92,7 @@ public class BlockArchiveReader {
|
||||
return blockInfo;
|
||||
}
|
||||
|
||||
public Triple<BlockData, List<TransactionData>, List<ATStateData>> fetchBlockWithSignature(
|
||||
byte[] signature, Repository repository) {
|
||||
public BlockTransformation fetchBlockWithSignature(byte[] signature, Repository repository) {
|
||||
|
||||
if (this.fileListCache == null) {
|
||||
this.fetchFileList();
|
||||
@@ -105,13 +105,12 @@ public class BlockArchiveReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> fetchBlocksFromRange(
|
||||
int startHeight, int endHeight) {
|
||||
public List<BlockTransformation> fetchBlocksFromRange(int startHeight, int endHeight) {
|
||||
|
||||
List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> blockInfoList = new ArrayList<>();
|
||||
List<BlockTransformation> blockInfoList = new ArrayList<>();
|
||||
|
||||
for (int height = startHeight; height <= endHeight; height++) {
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = this.fetchBlockAtHeight(height);
|
||||
BlockTransformation blockInfo = this.fetchBlockAtHeight(height);
|
||||
if (blockInfo == null) {
|
||||
return blockInfoList;
|
||||
}
|
||||
|
@@ -1,16 +1,13 @@
|
||||
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.utils.Triple;
|
||||
import org.qortal.transform.block.BlockTransformation;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
@@ -29,11 +26,11 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
|
||||
|
||||
@Override
|
||||
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockWithSignature(signature, this.repository);
|
||||
if (blockInfo != null) {
|
||||
return (BlockData) blockInfo.getA();
|
||||
}
|
||||
return null;
|
||||
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockWithSignature(signature, this.repository);
|
||||
if (blockInfo == null)
|
||||
return null;
|
||||
|
||||
return blockInfo.getBlockData();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,11 +44,11 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
|
||||
|
||||
@Override
|
||||
public BlockData fromHeight(int height) throws DataException {
|
||||
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
|
||||
if (blockInfo != null) {
|
||||
return (BlockData) blockInfo.getA();
|
||||
}
|
||||
return null;
|
||||
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
|
||||
if (blockInfo == null)
|
||||
return null;
|
||||
|
||||
return blockInfo.getBlockData();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,9 +76,9 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
|
||||
int height = referenceBlock.getHeight();
|
||||
if (height > 0) {
|
||||
// Request the block at height + 1
|
||||
Triple blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height + 1);
|
||||
BlockTransformation blockInfo = BlockArchiveReader.getInstance().fetchBlockAtHeight(height + 1);
|
||||
if (blockInfo != null) {
|
||||
return (BlockData) blockInfo.getA();
|
||||
return blockInfo.getBlockData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -964,6 +964,11 @@ 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;
|
||||
|
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -19,6 +20,7 @@ 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;
|
||||
@@ -86,6 +88,14 @@ 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;
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -12,6 +13,7 @@ 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;
|
||||
|
||||
@@ -75,6 +77,13 @@ 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());
|
||||
|
@@ -8,6 +8,7 @@ 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;
|
||||
@@ -20,6 +21,7 @@ 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;
|
||||
@@ -163,6 +165,14 @@ 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;
|
||||
|
||||
|
@@ -31,6 +31,7 @@ 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;
|
||||
|
||||
@@ -905,6 +906,13 @@ 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());
|
||||
|
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -3,12 +3,14 @@ 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;
|
||||
@@ -20,7 +22,6 @@ 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;
|
||||
@@ -45,14 +46,13 @@ 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;
|
||||
|
||||
protected static final int AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + AMOUNT_LENGTH;
|
||||
public 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 Triple<BlockData, List<TransactionData>, List<ATStateData>> fromBytes(byte[] bytes) throws TransformationException {
|
||||
public static BlockTransformation fromBytes(byte[] bytes) throws TransformationException {
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
@@ -76,28 +76,40 @@ public class BlockTransformer extends Transformer {
|
||||
/**
|
||||
* Extract block data and transaction data from serialized bytes containing a single block.
|
||||
*
|
||||
* @param bytes
|
||||
* @param byteBuffer source of serialized block bytes
|
||||
* @return BlockData and a List of transactions.
|
||||
* @throws TransformationException
|
||||
*/
|
||||
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer) 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 {
|
||||
return BlockTransformer.fromByteBuffer(byteBuffer, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract block data and transaction data from serialized bytes containing one or more blocks.
|
||||
*
|
||||
* @param bytes
|
||||
* 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
|
||||
* @return the next block's BlockData and a List of transactions.
|
||||
* @throws TransformationException
|
||||
*/
|
||||
public static Triple<BlockData, List<TransactionData>, List<ATStateData>> fromByteBuffer(ByteBuffer byteBuffer, boolean finalBlockInBuffer) throws TransformationException {
|
||||
private static BlockTransformation fromByteBuffer(ByteBuffer byteBuffer, boolean isV2) throws TransformationException {
|
||||
int version = byteBuffer.getInt();
|
||||
|
||||
if (finalBlockInBuffer && byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
|
||||
if (byteBuffer.remaining() < BASE_LENGTH + AT_BYTES_LENGTH - VERSION_LENGTH)
|
||||
throw new TransformationException("Byte data too short for Block");
|
||||
|
||||
if (finalBlockInBuffer && byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
|
||||
if (byteBuffer.remaining() > BlockChain.getInstance().getMaxBlockSize())
|
||||
throw new TransformationException("Byte data too long for Block");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
@@ -117,42 +129,52 @@ public class BlockTransformer extends Transformer {
|
||||
|
||||
int atCount = 0;
|
||||
long atFees = 0;
|
||||
List<ATStateData> atStates = new ArrayList<>();
|
||||
byte[] atStatesHash = null;
|
||||
List<ATStateData> atStates = null;
|
||||
|
||||
int atBytesLength = byteBuffer.getInt();
|
||||
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");
|
||||
|
||||
if (atBytesLength > BlockChain.getInstance().getMaxBlockSize())
|
||||
throw new TransformationException("Byte data too long for Block's AT info");
|
||||
// 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");
|
||||
|
||||
ByteBuffer atByteBuffer = byteBuffer.slice();
|
||||
atByteBuffer.limit(atBytesLength);
|
||||
ByteBuffer atByteBuffer = byteBuffer.slice();
|
||||
atByteBuffer.limit(atBytesLength);
|
||||
|
||||
// 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");
|
||||
atStates = new ArrayList<>();
|
||||
while (atByteBuffer.hasRemaining()) {
|
||||
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
|
||||
atByteBuffer.get(atAddressBytes);
|
||||
String atAddress = Base58.encode(atAddressBytes);
|
||||
|
||||
while (atByteBuffer.hasRemaining()) {
|
||||
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
|
||||
atByteBuffer.get(atAddressBytes);
|
||||
String atAddress = Base58.encode(atAddressBytes);
|
||||
byte[] stateHash = new byte[SHA256_LENGTH];
|
||||
atByteBuffer.get(stateHash);
|
||||
|
||||
byte[] stateHash = new byte[SHA256_LENGTH];
|
||||
atByteBuffer.get(stateHash);
|
||||
long fees = atByteBuffer.getLong();
|
||||
|
||||
long fees = atByteBuffer.getLong();
|
||||
// Add this AT's fees to our total
|
||||
atFees += fees;
|
||||
|
||||
// Add this AT's fees to our total
|
||||
atFees += fees;
|
||||
atStates.add(new ATStateData(atAddress, stateHash, fees));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -221,16 +243,15 @@ 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);
|
||||
|
||||
return new Triple<>(blockData, transactions, atStates);
|
||||
if (isV2)
|
||||
return new BlockTransformation(blockData, transactions, atStatesHash);
|
||||
else
|
||||
return new BlockTransformation(blockData, transactions, atStates);
|
||||
}
|
||||
|
||||
public static int getDataLength(Block block) throws TransformationException {
|
||||
@@ -266,6 +287,14 @@ 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 {
|
||||
@@ -279,16 +308,37 @@ public class BlockTransformer extends Transformer {
|
||||
bytes.write(blockData.getMinterSignature());
|
||||
|
||||
int atBytesLength = blockData.getATCount() * AT_ENTRY_LENGTH;
|
||||
bytes.write(Ints.toByteArray(atBytesLength));
|
||||
if (isV2) {
|
||||
ByteArrayOutputStream atHashBytes = new ByteArrayOutputStream(atBytesLength);
|
||||
long atFees = 0;
|
||||
|
||||
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;
|
||||
|
||||
bytes.write(Base58.decode(atStateData.getATAddress()));
|
||||
bytes.write(atStateData.getStateHash());
|
||||
bytes.write(Longs.toByteArray(atStateData.getFees()));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
// Transactions
|
||||
|
@@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@@ -33,8 +34,7 @@ public class BlockArchiveUtils {
|
||||
repository.discardChanges();
|
||||
final int requestedRange = endHeight+1-startHeight;
|
||||
|
||||
List<Triple<BlockData, List<TransactionData>, List<ATStateData>>> blockInfoList =
|
||||
BlockArchiveReader.getInstance().fetchBlocksFromRange(startHeight, endHeight);
|
||||
List<BlockTransformation> blockInfoList = BlockArchiveReader.getInstance().fetchBlocksFromRange(startHeight, endHeight);
|
||||
|
||||
// Ensure that we have received all of the requested blocks
|
||||
if (blockInfoList == null || blockInfoList.isEmpty()) {
|
||||
@@ -43,27 +43,26 @@ public class BlockArchiveUtils {
|
||||
if (blockInfoList.size() != requestedRange) {
|
||||
throw new IllegalStateException("Non matching block count when importing from archive");
|
||||
}
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> firstBlock = blockInfoList.get(0);
|
||||
if (firstBlock == null || firstBlock.getA().getHeight() != startHeight) {
|
||||
BlockTransformation firstBlock = blockInfoList.get(0);
|
||||
if (firstBlock == null || firstBlock.getBlockData().getHeight() != startHeight) {
|
||||
throw new IllegalStateException("Non matching first block when importing from archive");
|
||||
}
|
||||
if (blockInfoList.size() > 0) {
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> lastBlock =
|
||||
blockInfoList.get(blockInfoList.size() - 1);
|
||||
if (lastBlock == null || lastBlock.getA().getHeight() != endHeight) {
|
||||
BlockTransformation lastBlock = blockInfoList.get(blockInfoList.size() - 1);
|
||||
if (lastBlock == null || lastBlock.getBlockData().getHeight() != endHeight) {
|
||||
throw new IllegalStateException("Non matching last block when importing from archive");
|
||||
}
|
||||
}
|
||||
|
||||
// Everything seems okay, so go ahead with the import
|
||||
for (Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo : blockInfoList) {
|
||||
for (BlockTransformation blockInfo : blockInfoList) {
|
||||
try {
|
||||
// Save block
|
||||
repository.getBlockRepository().save(blockInfo.getA());
|
||||
repository.getBlockRepository().save(blockInfo.getBlockData());
|
||||
|
||||
// Save AT state data hashes
|
||||
for (ATStateData atStateData : blockInfo.getC()) {
|
||||
atStateData.setHeight(blockInfo.getA().getHeight());
|
||||
for (ATStateData atStateData : blockInfo.getAtStates()) {
|
||||
atStateData.setHeight(blockInfo.getBlockData().getHeight());
|
||||
repository.getATRepository().save(atStateData);
|
||||
}
|
||||
|
||||
|
@@ -59,7 +59,8 @@
|
||||
"shareBinFix": 399000,
|
||||
"calcChainWeightTimestamp": 1620579600000,
|
||||
"transactionV5Timestamp": 1642176000000,
|
||||
"transactionV6Timestamp": 9999999999999
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 1655222400000
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -123,8 +124,8 @@ public class BlockArchiveTests extends Common {
|
||||
|
||||
// Read block 2 from the archive
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> block2Info = reader.fetchBlockAtHeight(2);
|
||||
BlockData block2ArchiveData = block2Info.getA();
|
||||
BlockTransformation block2Info = reader.fetchBlockAtHeight(2);
|
||||
BlockData block2ArchiveData = block2Info.getBlockData();
|
||||
|
||||
// Read block 2 from the repository
|
||||
BlockData block2RepositoryData = repository.getBlockRepository().fromHeight(2);
|
||||
@@ -137,8 +138,8 @@ public class BlockArchiveTests extends Common {
|
||||
assertEquals(1, block2ArchiveData.getOnlineAccountsCount());
|
||||
|
||||
// Read block 900 from the archive
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> block900Info = reader.fetchBlockAtHeight(900);
|
||||
BlockData block900ArchiveData = block900Info.getA();
|
||||
BlockTransformation block900Info = reader.fetchBlockAtHeight(900);
|
||||
BlockData block900ArchiveData = block900Info.getBlockData();
|
||||
|
||||
// Read block 900 from the repository
|
||||
BlockData block900RepositoryData = repository.getBlockRepository().fromHeight(900);
|
||||
@@ -200,10 +201,10 @@ public class BlockArchiveTests extends Common {
|
||||
|
||||
// Read a block from the archive
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
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();
|
||||
BlockTransformation blockInfo = reader.fetchBlockAtHeight(testHeight);
|
||||
BlockData archivedBlockData = blockInfo.getBlockData();
|
||||
ATStateData archivedAtStateData = blockInfo.getAtStates().isEmpty() ? null : blockInfo.getAtStates().get(0);
|
||||
List<TransactionData> archivedTransactions = blockInfo.getTransactions();
|
||||
|
||||
// Read the same block from the repository
|
||||
BlockData repositoryBlockData = repository.getBlockRepository().fromHeight(testHeight);
|
||||
@@ -255,7 +256,7 @@ public class BlockArchiveTests extends Common {
|
||||
|
||||
// Check block 10 (unarchived)
|
||||
BlockArchiveReader reader = BlockArchiveReader.getInstance();
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = reader.fetchBlockAtHeight(10);
|
||||
BlockTransformation blockInfo = reader.fetchBlockAtHeight(10);
|
||||
assertNull(blockInfo);
|
||||
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ 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;
|
||||
@@ -121,10 +122,10 @@ public class BlockTests extends Common {
|
||||
|
||||
assertEquals(BlockTransformer.getDataLength(block), bytes.length);
|
||||
|
||||
Triple<BlockData, List<TransactionData>, List<ATStateData>> blockInfo = BlockTransformer.fromBytes(bytes);
|
||||
BlockTransformation blockInfo = BlockTransformer.fromBytes(bytes);
|
||||
|
||||
// Compare transactions
|
||||
List<TransactionData> deserializedTransactions = blockInfo.getB();
|
||||
List<TransactionData> deserializedTransactions = blockInfo.getTransactions();
|
||||
assertEquals("Transaction count differs", blockData.getTransactionCount(), deserializedTransactions.size());
|
||||
|
||||
for (int i = 0; i < blockData.getTransactionCount(); ++i) {
|
||||
|
165
src/test/java/org/qortal/test/TransactionReferenceTests.java
Normal file
165
src/test/java/org/qortal/test/TransactionReferenceTests.java
Normal file
@@ -0,0 +1,165 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -61,6 +61,7 @@ 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.
|
||||
@@ -126,6 +127,7 @@ public class Common {
|
||||
|
||||
public static void useSettings(String settingsFilename) throws DataException {
|
||||
Common.useSettingsAndDb(settingsFilename, true);
|
||||
setShouldRetainRepositoryAfterTest(false);
|
||||
}
|
||||
|
||||
public static void useDefaultSettings() throws DataException {
|
||||
@@ -207,7 +209,16 @@ 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 {
|
||||
|
@@ -509,6 +509,7 @@ 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";
|
||||
|
84
src/test/resources/test-chain-v2-disable-reference.json
Normal file
84
src/test/resources/test-chain-v2-disable-reference.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"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 }
|
||||
]
|
||||
}
|
||||
}
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 6,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
11
src/test/resources/test-settings-v2-disable-reference.json
Normal file
11
src/test/resources/test-settings-v2-disable-reference.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"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
|
||||
}
|
Reference in New Issue
Block a user