forked from Qortal/qortal
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9d81ea7744 | ||
|
688acd466c | ||
|
81cf46f5dd | ||
|
4c52d6f0fc | ||
|
c03f271825 | ||
|
dfe3754afc | ||
|
30105199a2 | ||
|
e91e612b55 | ||
|
2a55eba1f7 | ||
|
39e59cbcf8 | ||
|
016191bdb0 | ||
|
0596a07c7d | ||
|
c62c59b445 | ||
|
f78101e9cc | ||
|
476fdcb31d | ||
|
02d5043ef7 | ||
|
0ad9e2f65b | ||
|
4dc0033a5a | ||
|
745cfe8ea1 | ||
|
6284a4691c | ||
|
41f88be55e | ||
|
ba95f8376f | ||
|
8e97c05b56 | ||
|
eb569304ba | ||
|
b0486f44bb | ||
|
cecf28ab7b | ||
|
98b92a5bf1 | ||
|
6b45901c47 | ||
|
166f9bd079 | ||
|
2f8f896077 | ||
|
9a77aff0a6 | ||
|
c6d65a88dc | ||
|
4aea29a91b | ||
|
0e81665a36 | ||
|
2a4ac1ed24 | ||
|
bb74b2d4f6 | ||
|
758a02d71a | ||
|
7ae142fa64 | ||
|
a75ed0e634 | ||
|
e40dc4af59 | ||
|
e678ea22e0 | ||
|
cf3195cb83 | ||
|
80048208d1 | ||
|
08de1fb4ec | ||
|
99d5bf9103 | ||
|
1dc7f056f9 | ||
|
cdeb2052b0 | ||
|
5c9109aca9 | ||
|
ccc1976d00 | ||
|
12fb6cd0ad | ||
|
ae991dda4d | ||
|
2b6ae57a27 | ||
|
9d74f0eec0 | ||
|
09014d07e0 | ||
|
1dd039fb2d | ||
|
23a5c5f9b4 | ||
|
a4759a0ef4 | ||
|
910191b074 |
@@ -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:{ADE0C9E9-F7D9-4829-8626-8571C735C4D7} 1049:{F5230C0A-9D8C-4C70-AC72-17CECC8273B8} 2052:{D5A0760C-E5B3-4C4C-97B0-81CC445F07B9} 2057:{EF5EF0BE-0B00-4F5C-A2A0-DF2CB82FF20D} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{6C93A96C-E3AF-42FD-BE11-7EC3734905C6} 1049:{754F5347-82E5-4251-AED0-F4141CDD11F5} 2052:{413BD7B3-A3F8-47D0-BCA4-5C7694A40936} 2057:{71450AC8-1E6F-4469-852D-0591FA693680} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="3.6.3" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.8.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="{F4F774B9-18DC-4740-9552-EA16B98801C9}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{EC7B4AD9-F2D9-48C4-A586-C4697D9C380C}" 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.8.0</version>
|
||||
<version>3.8.4</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -52,6 +52,11 @@ public class SelfSponsorshipAlgoV1 {
|
||||
|
||||
|
||||
public void run() throws DataException {
|
||||
if (this.accountData == null) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetchSponsorshipRewardShares();
|
||||
if (this.sponsorshipRewardShares.isEmpty()) {
|
||||
// Nothing to do
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.network.Handshake;
|
||||
@@ -36,6 +37,7 @@ public class ConnectedPeer {
|
||||
public Long lastBlockTimestamp;
|
||||
public UUID connectionId;
|
||||
public String age;
|
||||
public Boolean isTooDivergent;
|
||||
|
||||
protected ConnectedPeer() {
|
||||
}
|
||||
@@ -69,6 +71,11 @@ public class ConnectedPeer {
|
||||
this.lastBlockSignature = peerChainTipData.getSignature();
|
||||
this.lastBlockTimestamp = peerChainTipData.getTimestamp();
|
||||
}
|
||||
|
||||
// Only include isTooDivergent decision if we've had the opportunity to request block summaries this peer
|
||||
if (peer.getLastTooDivergentTime() != null) {
|
||||
this.isTooDivergent = Controller.wasRecentlyTooDivergent.test(peer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -70,6 +70,8 @@ public class ChatResource {
|
||||
@QueryParam("txGroupId") Integer txGroupId,
|
||||
@QueryParam("involving") List<String> involvingAddresses,
|
||||
@QueryParam("reference") String reference,
|
||||
@QueryParam("chatreference") String chatReference,
|
||||
@QueryParam("haschatreference") Boolean hasChatReference,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
||||
@Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
|
||||
@@ -92,12 +94,18 @@ public class ChatResource {
|
||||
if (reference != null)
|
||||
referenceBytes = Base58.decode(reference);
|
||||
|
||||
byte[] chatReferenceBytes = null;
|
||||
if (chatReference != null)
|
||||
chatReferenceBytes = Base58.decode(chatReference);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getChatRepository().getMessagesMatchingCriteria(
|
||||
before,
|
||||
after,
|
||||
txGroupId,
|
||||
referenceBytes,
|
||||
chatReferenceBytes,
|
||||
hasChatReference,
|
||||
involvingAddresses,
|
||||
limit, offset, reverse);
|
||||
} catch (DataException e) {
|
||||
|
@@ -8,11 +8,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.*;
|
||||
@@ -25,7 +24,6 @@ import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.qortal.api.*;
|
||||
import org.qortal.api.model.CrossChainBitcoinyHTLCStatus;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.crosschain.*;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.at.ATData;
|
||||
@@ -586,98 +584,103 @@ public class CrossChainHtlcResource {
|
||||
}
|
||||
|
||||
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData();
|
||||
TradeBotData tradeBotData = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).findFirst().orElse(null);
|
||||
if (tradeBotData == null)
|
||||
List<TradeBotData> tradeBotDataList = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).collect(Collectors.toList());
|
||||
if (tradeBotDataList == null || tradeBotDataList.isEmpty())
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||
int lockTime = tradeBotData.getLockTimeA();
|
||||
// Loop through all matching entries for this AT address, as there might be more than one
|
||||
for (TradeBotData tradeBotData : tradeBotDataList) {
|
||||
|
||||
// We can't refund P2SH-A until lockTime-A has passed
|
||||
if (NTP.getTime() <= lockTime * 1000L)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
||||
if (tradeBotData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
// We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113)
|
||||
int medianBlockTime = bitcoiny.getMedianBlockTime();
|
||||
if (medianBlockTime <= lockTime)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||
int lockTime = tradeBotData.getLockTimeA();
|
||||
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = bitcoiny.getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
// We can't refund P2SH-A until lockTime-A has passed
|
||||
if (NTP.getTime() <= lockTime * 1000L)
|
||||
continue;
|
||||
|
||||
// Create redeem script based on destination chain
|
||||
byte[] redeemScriptA;
|
||||
String p2shAddressA;
|
||||
BitcoinyHTLC.Status htlcStatusA;
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA);
|
||||
htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
}
|
||||
else {
|
||||
redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
||||
htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
}
|
||||
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
||||
// We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113)
|
||||
int medianBlockTime = bitcoiny.getMedianBlockTime();
|
||||
if (medianBlockTime <= lockTime)
|
||||
continue;
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
// Still waiting for P2SH-A to be funded...
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = bitcoiny.getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
// Too late!
|
||||
return false;
|
||||
// Create redeem script based on destination chain
|
||||
byte[] redeemScriptA;
|
||||
String p2shAddressA;
|
||||
BitcoinyHTLC.Status htlcStatusA;
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA);
|
||||
htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
} else {
|
||||
redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
||||
htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
}
|
||||
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
||||
|
||||
case FUNDED:{
|
||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
// Still waiting for P2SH-A to be funded...
|
||||
continue;
|
||||
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
// Pirate Chain custom integration
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
// Too late!
|
||||
continue;
|
||||
|
||||
PirateChain pirateChain = PirateChain.getInstance();
|
||||
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA);
|
||||
case FUNDED: {
|
||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||
|
||||
// Get funding txid
|
||||
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
if (fundingTxidHex == null) {
|
||||
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
// Pirate Chain custom integration
|
||||
|
||||
PirateChain pirateChain = PirateChain.getInstance();
|
||||
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
// Get funding txid
|
||||
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
if (fundingTxidHex == null) {
|
||||
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
|
||||
}
|
||||
String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes());
|
||||
|
||||
byte[] privateKey = tradeBotData.getTradePrivateKey();
|
||||
String privateKey58 = Base58.encode(privateKey);
|
||||
String redeemScript58 = Base58.encode(redeemScriptA);
|
||||
|
||||
String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3,
|
||||
receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58);
|
||||
LOGGER.info("Refund txid: {}", txid);
|
||||
} else {
|
||||
// ElectrumX coins
|
||||
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
// Validate the destination foreign blockchain address
|
||||
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
||||
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
||||
|
||||
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
||||
}
|
||||
String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes());
|
||||
|
||||
byte[] privateKey = tradeBotData.getTradePrivateKey();
|
||||
String privateKey58 = Base58.encode(privateKey);
|
||||
String redeemScript58 = Base58.encode(redeemScriptA);
|
||||
|
||||
String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3,
|
||||
receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58);
|
||||
LOGGER.info("Refund txid: {}", txid);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// ElectrumX coins
|
||||
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
// Validate the destination foreign blockchain address
|
||||
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
||||
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
||||
|
||||
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -47,6 +47,8 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
|
||||
txGroupId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null, null, null);
|
||||
|
||||
sendMessages(session, chatMessages);
|
||||
@@ -74,6 +76,8 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
involvingAddresses,
|
||||
null, null, null);
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package org.qortal.arbitrary;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.metadata.ArbitraryDataMetadataCache;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
@@ -88,7 +89,7 @@ public class ArbitraryDataBuilder {
|
||||
if (latestPut == null) {
|
||||
String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s",
|
||||
this.name, this.service, this.identifierString());
|
||||
throw new DataException(message);
|
||||
throw new DataNotPublishedException(message);
|
||||
}
|
||||
this.latestPutTransaction = latestPut;
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||
@@ -169,10 +170,18 @@ public class ArbitraryDataReader {
|
||||
this.uncompress();
|
||||
this.validate();
|
||||
|
||||
} catch (DataNotPublishedException e) {
|
||||
if (e.getMessage() != null) {
|
||||
// Log the message only, to avoid spamming the logs with a full stack trace
|
||||
LOGGER.debug("DataNotPublishedException when trying to load QDN resource: {}", e.getMessage());
|
||||
}
|
||||
this.deleteWorkingDirectory();
|
||||
throw e;
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.info("DataException when trying to load QDN resource", e);
|
||||
this.deleteWorkingDirectory();
|
||||
throw new DataException(e.getMessage());
|
||||
throw e;
|
||||
|
||||
} finally {
|
||||
this.postExecute();
|
||||
|
@@ -3,6 +3,7 @@ package org.qortal.arbitrary;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType;
|
||||
import org.qortal.arbitrary.exception.DataNotPublishedException;
|
||||
import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||
@@ -325,7 +326,7 @@ public class ArbitraryDataResource {
|
||||
if (latestPut == null) {
|
||||
String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s",
|
||||
this.resourceId, this.service, this.identifierString());
|
||||
throw new DataException(message);
|
||||
throw new DataNotPublishedException(message);
|
||||
}
|
||||
this.latestPutTransaction = latestPut;
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
package org.qortal.arbitrary.exception;
|
||||
|
||||
import org.qortal.repository.DataException;
|
||||
|
||||
public class DataNotPublishedException extends DataException {
|
||||
|
||||
public DataNotPublishedException() {
|
||||
}
|
||||
|
||||
public DataNotPublishedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DataNotPublishedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DataNotPublishedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@@ -10,9 +10,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
@@ -20,6 +18,31 @@ import static java.util.stream.Collectors.toMap;
|
||||
public enum Service {
|
||||
AUTO_UPDATE(1, false, null, null),
|
||||
ARBITRARY_DATA(100, false, null, null),
|
||||
QCHAT_ATTACHMENT(120, true, 1024*1024L, null) {
|
||||
@Override
|
||||
public ValidationResult validate(Path path) {
|
||||
// Custom validation function to require a single file, with a whitelisted extension
|
||||
int fileCount = 0;
|
||||
File[] files = path.toFile().listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
return ValidationResult.DIRECTORIES_NOT_ALLOWED;
|
||||
}
|
||||
final String extension = FilenameUtils.getExtension(file.getName()).toLowerCase();
|
||||
final List<String> allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx");
|
||||
if (extension == null || !allowedExtensions.contains(extension)) {
|
||||
return ValidationResult.INVALID_FILE_EXTENSION;
|
||||
}
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
if (fileCount != 1) {
|
||||
return ValidationResult.INVALID_FILE_COUNT;
|
||||
}
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
},
|
||||
WEBSITE(200, true, null, null) {
|
||||
@Override
|
||||
public ValidationResult validate(Path path) {
|
||||
@@ -143,7 +166,8 @@ public enum Service {
|
||||
MISSING_INDEX_FILE(4),
|
||||
DIRECTORIES_NOT_ALLOWED(5),
|
||||
INVALID_FILE_EXTENSION(6),
|
||||
MISSING_DATA(7);
|
||||
MISSING_DATA(7),
|
||||
INVALID_FILE_COUNT(8);
|
||||
|
||||
public final int value;
|
||||
|
||||
|
@@ -1522,6 +1522,9 @@ public class Block {
|
||||
// Batch update in repository
|
||||
repository.getAccountRepository().modifyMintedBlockCounts(allUniqueExpandedAccounts.stream().map(AccountData::getAddress).collect(Collectors.toList()), +1);
|
||||
|
||||
// Keep track of level bumps in case we need to apply to other entries
|
||||
Map<String, Integer> bumpedAccounts = new HashMap<>();
|
||||
|
||||
// Local changes and also checks for level bump
|
||||
for (AccountData accountData : allUniqueExpandedAccounts) {
|
||||
// Adjust count locally (in Java)
|
||||
@@ -1535,6 +1538,7 @@ public class Block {
|
||||
if (newLevel > accountData.getLevel()) {
|
||||
// Account has increased in level!
|
||||
accountData.setLevel(newLevel);
|
||||
bumpedAccounts.put(accountData.getAddress(), newLevel);
|
||||
repository.getAccountRepository().setLevel(accountData);
|
||||
LOGGER.trace(() -> String.format("Block minter %s bumped to level %d", accountData.getAddress(), accountData.getLevel()));
|
||||
}
|
||||
@@ -1542,6 +1546,25 @@ public class Block {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Also bump other entries if need be
|
||||
if (!bumpedAccounts.isEmpty()) {
|
||||
for (ExpandedAccount expandedAccount : expandedAccounts) {
|
||||
Integer newLevel = bumpedAccounts.get(expandedAccount.mintingAccountData.getAddress());
|
||||
if (newLevel != null && expandedAccount.mintingAccountData.getLevel() != newLevel) {
|
||||
expandedAccount.mintingAccountData.setLevel(newLevel);
|
||||
LOGGER.trace("Also bumped {} to level {}", expandedAccount.mintingAccountData.getAddress(), newLevel);
|
||||
}
|
||||
|
||||
if (!expandedAccount.isRecipientAlsoMinter) {
|
||||
newLevel = bumpedAccounts.get(expandedAccount.recipientAccountData.getAddress());
|
||||
if (newLevel != null && expandedAccount.recipientAccountData.getLevel() != newLevel) {
|
||||
expandedAccount.recipientAccountData.setLevel(newLevel);
|
||||
LOGGER.trace("Also bumped {} to level {}", expandedAccount.recipientAccountData.getAddress(), newLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processBlockRewards() throws DataException {
|
||||
|
@@ -76,7 +76,9 @@ public class BlockChain {
|
||||
disableReferenceTimestamp,
|
||||
increaseOnlineAccountsDifficultyTimestamp,
|
||||
onlineAccountMinterLevelValidationHeight,
|
||||
selfSponsorshipAlgoV1Height;
|
||||
selfSponsorshipAlgoV1Height,
|
||||
feeValidationFixTimestamp,
|
||||
chatReferenceTimestamp;
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@@ -98,6 +100,13 @@ public class BlockChain {
|
||||
/** Whether only one registered name is allowed per account. */
|
||||
private boolean oneNamePerAccount = false;
|
||||
|
||||
/** Checkpoints */
|
||||
public static class Checkpoint {
|
||||
public int height;
|
||||
public String signature;
|
||||
}
|
||||
private List<Checkpoint> checkpoints;
|
||||
|
||||
/** Block rewards by block height */
|
||||
public static class RewardByHeight {
|
||||
public int height;
|
||||
@@ -379,6 +388,10 @@ public class BlockChain {
|
||||
return this.oneNamePerAccount;
|
||||
}
|
||||
|
||||
public List<Checkpoint> getCheckpoints() {
|
||||
return this.checkpoints;
|
||||
}
|
||||
|
||||
public List<RewardByHeight> getBlockRewardsByHeight() {
|
||||
return this.rewardsByHeight;
|
||||
}
|
||||
@@ -501,6 +514,14 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.onlineAccountMinterLevelValidationHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public long getFeeValidationFixTimestamp() {
|
||||
return this.featureTriggers.get(FeatureTrigger.feeValidationFixTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
public long getChatReferenceTimestamp() {
|
||||
return this.featureTriggers.get(FeatureTrigger.chatReferenceTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
@@ -669,6 +690,7 @@ public class BlockChain {
|
||||
|
||||
boolean isTopOnly = Settings.getInstance().isTopOnly();
|
||||
boolean archiveEnabled = Settings.getInstance().isArchiveEnabled();
|
||||
boolean isLite = Settings.getInstance().isLite();
|
||||
boolean canBootstrap = Settings.getInstance().getBootstrap();
|
||||
boolean needsArchiveRebuild = false;
|
||||
BlockData chainTip;
|
||||
@@ -689,22 +711,44 @@ public class BlockChain {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checkpoints
|
||||
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
|
||||
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
|
||||
if (isTopOnly && !isLite) {
|
||||
List<Checkpoint> checkpoints = BlockChain.getInstance().getCheckpoints();
|
||||
for (Checkpoint checkpoint : checkpoints) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(checkpoint.height);
|
||||
if (blockData == null) {
|
||||
// Try the archive
|
||||
blockData = repository.getBlockArchiveRepository().fromHeight(checkpoint.height);
|
||||
}
|
||||
if (blockData == null) {
|
||||
LOGGER.trace("Couldn't find block for height {}", checkpoint.height);
|
||||
// This is likely due to the block being pruned, so is safe to ignore.
|
||||
// Continue, as there might be other blocks we can check more definitively.
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] signature = Base58.decode(checkpoint.signature);
|
||||
if (!Arrays.equals(signature, blockData.getSignature())) {
|
||||
LOGGER.info("Error: block at height {} with signature {} doesn't match checkpoint sig: {}. Bootstrapping...", checkpoint.height, Base58.encode(blockData.getSignature()), checkpoint.signature);
|
||||
needsArchiveRebuild = true;
|
||||
break;
|
||||
}
|
||||
LOGGER.info("Block at height {} matches checkpoint signature", blockData.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean hasBlocks = (chainTip != null && chainTip.getHeight() > 1);
|
||||
// Check first block is Genesis Block
|
||||
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
||||
try {
|
||||
rebuildBlockchain();
|
||||
|
||||
if (isTopOnly && hasBlocks) {
|
||||
// Top-only mode is enabled and we have blocks, so it's possible that the genesis block has been pruned
|
||||
// It's best not to validate it, and there's no real need to
|
||||
} else {
|
||||
// Check first block is Genesis Block
|
||||
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
||||
try {
|
||||
rebuildBlockchain();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,9 +757,7 @@ public class BlockChain {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
repository.checkConsistency();
|
||||
|
||||
// Set the number of blocks to validate based on the pruned state of the chain
|
||||
// If pruned, subtract an extra 10 to allow room for error
|
||||
int blocksToValidate = (isTopOnly || archiveEnabled) ? Settings.getInstance().getPruneBlockLimit() - 10 : 1440;
|
||||
int blocksToValidate = Math.min(Settings.getInstance().getPruneBlockLimit() - 10, 1440);
|
||||
|
||||
int startHeight = Math.max(repository.getBlockRepository().getBlockchainHeight() - blocksToValidate, 1);
|
||||
BlockData detachedBlockData = repository.getBlockRepository().getDetachedBlockSignature(startHeight);
|
||||
|
@@ -63,8 +63,8 @@ public class BlockMinter extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("BlockMinter");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes do not mint
|
||||
if (Settings.getInstance().isTopOnly() || Settings.getInstance().isLite()) {
|
||||
// Top only and lite nodes do not sign blocks
|
||||
return;
|
||||
}
|
||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||
|
@@ -769,6 +769,16 @@ public class Controller extends Thread {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> wasRecentlyTooDivergent = peer -> {
|
||||
Long now = NTP.getTime();
|
||||
Long peerLastTooDivergentTime = peer.getLastTooDivergentTime();
|
||||
if (now == null || peerLastTooDivergentTime == null)
|
||||
return false;
|
||||
|
||||
// Exclude any peers that were TOO_DIVERGENT in the last 5 mins
|
||||
return (now - peerLastTooDivergentTime < 5 * 60 * 1000L);
|
||||
};
|
||||
|
||||
private long getRandomRepositoryMaintenanceInterval() {
|
||||
final long minInterval = Settings.getInstance().getRepositoryMaintenanceMinInterval();
|
||||
final long maxInterval = Settings.getInstance().getRepositoryMaintenanceMaxInterval();
|
||||
|
@@ -1121,6 +1121,7 @@ public class Synchronizer extends Thread {
|
||||
// If common block is too far behind us then we're on massively different forks so give up.
|
||||
if (!force && testHeight < ourHeight - MAXIMUM_COMMON_DELTA) {
|
||||
LOGGER.info(String.format("Blockchain too divergent with peer %s", peer));
|
||||
peer.setLastTooDivergentTime(NTP.getTime());
|
||||
return SynchronizationResult.TOO_DIVERGENT;
|
||||
}
|
||||
|
||||
@@ -1130,6 +1131,9 @@ public class Synchronizer extends Thread {
|
||||
testHeight = Math.max(testHeight - step, 1);
|
||||
}
|
||||
|
||||
// Peer not considered too divergent
|
||||
peer.setLastTooDivergentTime(0L);
|
||||
|
||||
// Prepend test block's summary as first block summary, as summaries returned are *after* test block
|
||||
BlockSummaryData testBlockSummary = new BlockSummaryData(testBlockData);
|
||||
blockSummariesFromCommon.add(0, testBlockSummary);
|
||||
|
@@ -82,7 +82,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
|
||||
try {
|
||||
// Use a fixed thread pool to execute the arbitrary data file requests
|
||||
int threadCount = 10;
|
||||
int threadCount = 5;
|
||||
ExecutorService arbitraryDataFileRequestExecutor = Executors.newFixedThreadPool(threadCount);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
arbitraryDataFileRequestExecutor.execute(new ArbitraryDataFileRequestThread());
|
||||
@@ -288,7 +288,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
// The ID needs to match that of the original request
|
||||
message.setId(originalMessage.getId());
|
||||
|
||||
if (!requestingPeer.sendMessage(message)) {
|
||||
if (!requestingPeer.sendMessageWithTimeout(message, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) {
|
||||
LOGGER.debug("Failed to forward arbitrary data file to peer {}", requestingPeer);
|
||||
requestingPeer.disconnect("failed to forward arbitrary data file");
|
||||
}
|
||||
@@ -564,13 +564,16 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
LOGGER.trace("Hash {} exists", hash58);
|
||||
|
||||
// We can serve the file directly as we already have it
|
||||
LOGGER.debug("Sending file {}...", arbitraryDataFile);
|
||||
ArbitraryDataFileMessage arbitraryDataFileMessage = new ArbitraryDataFileMessage(signature, arbitraryDataFile);
|
||||
arbitraryDataFileMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(arbitraryDataFileMessage)) {
|
||||
LOGGER.debug("Couldn't sent file");
|
||||
if (!peer.sendMessageWithTimeout(arbitraryDataFileMessage, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) {
|
||||
LOGGER.debug("Couldn't send file {}", arbitraryDataFile);
|
||||
peer.disconnect("failed to send file");
|
||||
}
|
||||
LOGGER.debug("Sent file {}", arbitraryDataFile);
|
||||
else {
|
||||
LOGGER.debug("Sent file {}", arbitraryDataFile);
|
||||
}
|
||||
}
|
||||
else if (relayInfo != null) {
|
||||
LOGGER.debug("We have relay info for hash {}", Base58.encode(hash));
|
||||
|
@@ -42,6 +42,7 @@ public class AtStatesPruner implements Runnable {
|
||||
|
||||
repository.discardChanges();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.saveChanges();
|
||||
|
||||
while (!Controller.isStopping()) {
|
||||
repository.discardChanges();
|
||||
|
@@ -29,6 +29,7 @@ public class AtStatesTrimmer implements Runnable {
|
||||
|
||||
repository.discardChanges();
|
||||
repository.getATRepository().rebuildLatestAtStates();
|
||||
repository.saveChanges();
|
||||
|
||||
while (!Controller.isStopping()) {
|
||||
repository.discardChanges();
|
||||
|
@@ -134,6 +134,8 @@ public class Digibyte extends Bitcoiny {
|
||||
Context bitcoinjContext = new Context(digibyteNet.getParams());
|
||||
|
||||
instance = new Digibyte(digibyteNet, electrumX, bitcoinjContext, CURRENCY_CODE);
|
||||
|
||||
electrumX.setBlockchain(instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
@@ -40,7 +40,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
||||
private static final String VERBOSE_TRANSACTIONS_UNSUPPORTED_MESSAGE = "verbose transactions are currently unsupported";
|
||||
|
||||
private static final int RESPONSE_TIME_READINGS = 5;
|
||||
private static final long MAX_AVG_RESPONSE_TIME = 500L; // ms
|
||||
private static final long MAX_AVG_RESPONSE_TIME = 1000L; // ms
|
||||
|
||||
public static class Server {
|
||||
String hostname;
|
||||
|
@@ -117,7 +117,7 @@ public class PirateWallet {
|
||||
// Restore existing wallet
|
||||
String response = LiteWalletJni.initfromb64(serverUri, params, wallet, saplingOutput64, saplingSpend64);
|
||||
if (response != null && !response.contains("\"initalized\":true")) {
|
||||
LOGGER.info("Unable to initialize Pirate Chain wallet: {}", response);
|
||||
LOGGER.info("Unable to initialize Pirate Chain wallet at {}: {}", serverUri, response);
|
||||
return false;
|
||||
}
|
||||
this.seedPhrase = inputSeedPhrase;
|
||||
|
@@ -138,6 +138,8 @@ public class Ravencoin extends Bitcoiny {
|
||||
Context bitcoinjContext = new Context(ravencoinNet.getParams());
|
||||
|
||||
instance = new Ravencoin(ravencoinNet, electrumX, bitcoinjContext, CURRENCY_CODE);
|
||||
|
||||
electrumX.setBlockchain(instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
|
@@ -27,6 +27,8 @@ public class ChatMessage {
|
||||
|
||||
private String recipientName;
|
||||
|
||||
private byte[] chatReference;
|
||||
|
||||
private byte[] data;
|
||||
|
||||
private boolean isText;
|
||||
@@ -42,8 +44,8 @@ public class ChatMessage {
|
||||
|
||||
// For repository use
|
||||
public ChatMessage(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String sender,
|
||||
String senderName, String recipient, String recipientName, byte[] data, boolean isText,
|
||||
boolean isEncrypted, byte[] signature) {
|
||||
String senderName, String recipient, String recipientName, byte[] chatReference, byte[] data,
|
||||
boolean isText, boolean isEncrypted, byte[] signature) {
|
||||
this.timestamp = timestamp;
|
||||
this.txGroupId = txGroupId;
|
||||
this.reference = reference;
|
||||
@@ -52,6 +54,7 @@ public class ChatMessage {
|
||||
this.senderName = senderName;
|
||||
this.recipient = recipient;
|
||||
this.recipientName = recipientName;
|
||||
this.chatReference = chatReference;
|
||||
this.data = data;
|
||||
this.isText = isText;
|
||||
this.isEncrypted = isEncrypted;
|
||||
@@ -90,6 +93,10 @@ public class ChatMessage {
|
||||
return this.recipientName;
|
||||
}
|
||||
|
||||
public byte[] getChatReference() {
|
||||
return this.chatReference;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@ public class ChatTransactionData extends TransactionData {
|
||||
|
||||
private String recipient; // can be null
|
||||
|
||||
private byte[] chatReference; // can be null
|
||||
|
||||
@Schema(description = "raw message data, possibly UTF8 text", example = "2yGEbwRFyhPZZckKA")
|
||||
private byte[] data;
|
||||
|
||||
@@ -44,13 +46,14 @@ public class ChatTransactionData extends TransactionData {
|
||||
}
|
||||
|
||||
public ChatTransactionData(BaseTransactionData baseTransactionData,
|
||||
String sender, int nonce, String recipient, byte[] data, boolean isText, boolean isEncrypted) {
|
||||
String sender, int nonce, String recipient, byte[] chatReference, byte[] data, boolean isText, boolean isEncrypted) {
|
||||
super(TransactionType.CHAT, baseTransactionData);
|
||||
|
||||
this.senderPublicKey = baseTransactionData.creatorPublicKey;
|
||||
this.sender = sender;
|
||||
this.nonce = nonce;
|
||||
this.recipient = recipient;
|
||||
this.chatReference = chatReference;
|
||||
this.data = data;
|
||||
this.isText = isText;
|
||||
this.isEncrypted = isEncrypted;
|
||||
@@ -78,6 +81,14 @@ public class ChatTransactionData extends TransactionData {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public byte[] getChatReference() {
|
||||
return this.chatReference;
|
||||
}
|
||||
|
||||
public void setChatReference(byte[] chatReference) {
|
||||
this.chatReference = chatReference;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package org.qortal.data.transaction;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
@@ -90,4 +91,17 @@ public class DeployAtTransactionData extends TransactionData {
|
||||
this.aTAddress = AtAddress;
|
||||
}
|
||||
|
||||
// Re-expose creatorPublicKey for this transaction type for JAXB
|
||||
@XmlElement(name = "creatorPublicKey")
|
||||
@Schema(name = "creatorPublicKey", description = "AT creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
public byte[] getAtCreatorPublicKey() {
|
||||
return this.creatorPublicKey;
|
||||
}
|
||||
|
||||
@XmlElement(name = "creatorPublicKey")
|
||||
@Schema(name = "creatorPublicKey", description = "AT creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
public void setAtCreatorPublicKey(byte[] creatorPublicKey) {
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -265,7 +265,7 @@ public enum Handshake {
|
||||
private static final long PEER_VERSION_131 = 0x0100030001L;
|
||||
|
||||
/** Minimum peer version that we are allowed to communicate with */
|
||||
private static final String MIN_PEER_VERSION = "3.7.0";
|
||||
private static final String MIN_PEER_VERSION = "3.8.2";
|
||||
|
||||
private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes
|
||||
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
||||
|
@@ -155,6 +155,11 @@ public class Peer {
|
||||
*/
|
||||
private CommonBlockData commonBlockData;
|
||||
|
||||
/**
|
||||
* Last time we detected this peer as TOO_DIVERGENT
|
||||
*/
|
||||
private Long lastTooDivergentTime;
|
||||
|
||||
// Message stats
|
||||
|
||||
private static class MessageStats {
|
||||
@@ -383,6 +388,14 @@ public class Peer {
|
||||
this.commonBlockData = commonBlockData;
|
||||
}
|
||||
|
||||
public Long getLastTooDivergentTime() {
|
||||
return this.lastTooDivergentTime;
|
||||
}
|
||||
|
||||
public void setLastTooDivergentTime(Long lastTooDivergentTime) {
|
||||
this.lastTooDivergentTime = lastTooDivergentTime;
|
||||
}
|
||||
|
||||
public boolean isSyncInProgress() {
|
||||
return this.syncInProgress;
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ public interface ChatRepository {
|
||||
* Expects EITHER non-null txGroupID OR non-null sender and recipient addresses.
|
||||
*/
|
||||
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after,
|
||||
Integer txGroupId, byte[] reference, List<String> involving,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, Boolean hasChatReference,
|
||||
List<String> involving, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException;
|
||||
|
||||
|
@@ -131,7 +131,14 @@ public interface GroupRepository {
|
||||
|
||||
public GroupBanData getBan(int groupId, String member) throws DataException;
|
||||
|
||||
public boolean banExists(int groupId, String offender) throws DataException;
|
||||
/**
|
||||
* IMPORTANT: when using banExists() as part of validation, the timestamp must be that of the transaction that
|
||||
* is calling banExists() as part of its validation. It must NOT be the current time, unless this is being
|
||||
* called outside of validation, as part of an on demand check for a ban existing (such as via an API call).
|
||||
* This is because we need to evaluate a ban's status based on the time of the subsequent transaction, as
|
||||
* validation will not occur at a fixed time for every node. For some, it could be months into the future.
|
||||
*/
|
||||
public boolean banExists(int groupId, String offender, long timestamp) throws DataException;
|
||||
|
||||
public List<GroupBanData> getGroupBans(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
|
@@ -24,8 +24,8 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
|
||||
@Override
|
||||
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes,
|
||||
List<String> involving, Integer limit, Integer offset, Boolean reverse)
|
||||
throws DataException {
|
||||
byte[] chatReferenceBytes, Boolean hasChatReference, List<String> involving,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
// Check args meet expectations
|
||||
if ((txGroupId != null && involving != null && !involving.isEmpty())
|
||||
|| (txGroupId == null && (involving == null || involving.size() != 2)))
|
||||
@@ -35,7 +35,7 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
|
||||
sql.append("SELECT created_when, tx_group_id, Transactions.reference, creator, "
|
||||
+ "sender, SenderNames.name, recipient, RecipientNames.name, "
|
||||
+ "data, is_text, is_encrypted, signature "
|
||||
+ "chat_reference, data, is_text, is_encrypted, signature "
|
||||
+ "FROM ChatTransactions "
|
||||
+ "JOIN Transactions USING (signature) "
|
||||
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
|
||||
@@ -62,6 +62,18 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
bindParams.add(referenceBytes);
|
||||
}
|
||||
|
||||
if (chatReferenceBytes != null) {
|
||||
whereClauses.add("chat_reference = ?");
|
||||
bindParams.add(chatReferenceBytes);
|
||||
}
|
||||
|
||||
if (hasChatReference != null && hasChatReference == true) {
|
||||
whereClauses.add("chat_reference IS NOT NULL");
|
||||
}
|
||||
else if (hasChatReference != null && hasChatReference == false) {
|
||||
whereClauses.add("chat_reference IS NULL");
|
||||
}
|
||||
|
||||
if (txGroupId != null) {
|
||||
whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally
|
||||
whereClauses.add("recipient IS NULL");
|
||||
@@ -103,13 +115,14 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
String senderName = resultSet.getString(6);
|
||||
String recipient = resultSet.getString(7);
|
||||
String recipientName = resultSet.getString(8);
|
||||
byte[] data = resultSet.getBytes(9);
|
||||
boolean isText = resultSet.getBoolean(10);
|
||||
boolean isEncrypted = resultSet.getBoolean(11);
|
||||
byte[] signature = resultSet.getBytes(12);
|
||||
byte[] chatReference = resultSet.getBytes(9);
|
||||
byte[] data = resultSet.getBytes(10);
|
||||
boolean isText = resultSet.getBoolean(11);
|
||||
boolean isEncrypted = resultSet.getBoolean(12);
|
||||
byte[] signature = resultSet.getBytes(13);
|
||||
|
||||
ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
|
||||
senderName, recipient, recipientName, data, isText, isEncrypted, signature);
|
||||
senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature);
|
||||
|
||||
chatMessages.add(chatMessage);
|
||||
} while (resultSet.next());
|
||||
@@ -141,13 +154,14 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
byte[] senderPublicKey = chatTransactionData.getSenderPublicKey();
|
||||
String sender = chatTransactionData.getSender();
|
||||
String recipient = chatTransactionData.getRecipient();
|
||||
byte[] chatReference = chatTransactionData.getChatReference();
|
||||
byte[] data = chatTransactionData.getData();
|
||||
boolean isText = chatTransactionData.getIsText();
|
||||
boolean isEncrypted = chatTransactionData.getIsEncrypted();
|
||||
byte[] signature = chatTransactionData.getSignature();
|
||||
|
||||
return new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
|
||||
senderName, recipient, recipientName, data, isText, isEncrypted, signature);
|
||||
senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch convert chat transaction from repository", e);
|
||||
}
|
||||
|
@@ -980,6 +980,14 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("ALTER TABLE Accounts ADD blocks_minted_penalty INTEGER NOT NULL DEFAULT 0");
|
||||
break;
|
||||
|
||||
case 45:
|
||||
// Add a chat reference, to allow one message to reference another, and for this to be easily
|
||||
// searchable. Null values are allowed as most transactions won't have a reference.
|
||||
stmt.execute("ALTER TABLE ChatTransactions ADD chat_reference Signature");
|
||||
// For finding chat messages by reference
|
||||
stmt.execute("CREATE INDEX ChatTransactionsChatReferenceIndex ON ChatTransactions (chat_reference)");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@@ -777,9 +777,9 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean banExists(int groupId, String offender) throws DataException {
|
||||
public boolean banExists(int groupId, String offender, long timestamp) throws DataException {
|
||||
try {
|
||||
return this.repository.exists("GroupBans", "group_id = ? AND offender = ?", groupId, offender);
|
||||
return this.repository.exists("GroupBans", "group_id = ? AND offender = ? AND (expires_when IS NULL OR expires_when > ?)", groupId, offender, timestamp);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to check for group ban in repository", e);
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository
|
||||
}
|
||||
|
||||
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
||||
String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data FROM ChatTransactions WHERE signature = ?";
|
||||
String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data, chat_reference FROM ChatTransactions WHERE signature = ?";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
||||
if (resultSet == null)
|
||||
@@ -29,8 +29,9 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository
|
||||
boolean isText = resultSet.getBoolean(4);
|
||||
boolean isEncrypted = resultSet.getBoolean(5);
|
||||
byte[] data = resultSet.getBytes(6);
|
||||
byte[] chatReference = resultSet.getBytes(7);
|
||||
|
||||
return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted);
|
||||
return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch chat transaction from repository", e);
|
||||
}
|
||||
@@ -45,7 +46,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository
|
||||
saveHelper.bind("signature", chatTransactionData.getSignature()).bind("nonce", chatTransactionData.getNonce())
|
||||
.bind("sender", chatTransactionData.getSender()).bind("recipient", chatTransactionData.getRecipient())
|
||||
.bind("is_text", chatTransactionData.getIsText()).bind("is_encrypted", chatTransactionData.getIsEncrypted())
|
||||
.bind("data", chatTransactionData.getData());
|
||||
.bind("data", chatTransactionData.getData()).bind("chat_reference", chatTransactionData.getChatReference());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
|
@@ -110,7 +110,13 @@ public class Settings {
|
||||
/** Maximum number of unconfirmed transactions allowed per account */
|
||||
private int maxUnconfirmedPerAccount = 25;
|
||||
/** Max milliseconds into future for accepting new, unconfirmed transactions */
|
||||
private int maxTransactionTimestampFuture = 24 * 60 * 60 * 1000; // milliseconds
|
||||
private int maxTransactionTimestampFuture = 30 * 60 * 1000; // milliseconds
|
||||
|
||||
/** Maximum number of CHAT transactions allowed per account in recent timeframe */
|
||||
private int maxRecentChatMessagesPerAccount = 250;
|
||||
/** Maximum age of a CHAT transaction to be considered 'recent' */
|
||||
private long recentChatMessagesMaxAge = 60 * 60 * 1000L; // milliseconds
|
||||
|
||||
/** Whether we check, fetch and install auto-updates */
|
||||
private boolean autoUpdateEnabled = true;
|
||||
/** How long between repository backups (ms), or 0 if disabled. */
|
||||
@@ -153,7 +159,7 @@ public class Settings {
|
||||
* This prevents the node from being able to serve older blocks */
|
||||
private boolean topOnly = false;
|
||||
/** The amount of recent blocks we should keep when pruning */
|
||||
private int pruneBlockLimit = 1450;
|
||||
private int pruneBlockLimit = 6000;
|
||||
|
||||
/** How often to attempt AT state pruning (ms). */
|
||||
private long atStatesPruneInterval = 3219L; // milliseconds
|
||||
@@ -209,7 +215,7 @@ public class Settings {
|
||||
public long recoveryModeTimeout = 10 * 60 * 1000L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "3.7.0";
|
||||
private String minPeerVersion = "3.8.2";
|
||||
/** Whether to allow connections with peers below minPeerVersion
|
||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||
@@ -267,7 +273,7 @@ public class Settings {
|
||||
private String[] bootstrapHosts = new String[] {
|
||||
"http://bootstrap.qortal.org",
|
||||
"http://bootstrap2.qortal.org",
|
||||
"http://62.171.190.193"
|
||||
"http://bootstrap.qortal.online"
|
||||
};
|
||||
|
||||
// Auto-update sources
|
||||
@@ -640,6 +646,14 @@ public class Settings {
|
||||
return this.maxTransactionTimestampFuture;
|
||||
}
|
||||
|
||||
public int getMaxRecentChatMessagesPerAccount() {
|
||||
return this.maxRecentChatMessagesPerAccount;
|
||||
}
|
||||
|
||||
public long getRecentChatMessagesMaxAge() {
|
||||
return recentChatMessagesMaxAge;
|
||||
}
|
||||
|
||||
public int getBlockCacheSize() {
|
||||
return this.blockCacheSize;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.transaction.ArbitraryTransactionTransformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.ArbitraryTransactionUtils;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class ArbitraryTransaction extends Transaction {
|
||||
|
||||
@@ -34,9 +35,13 @@ public class ArbitraryTransaction extends Transaction {
|
||||
public static final int MAX_DATA_SIZE = 4000;
|
||||
public static final int MAX_METADATA_LENGTH = 32;
|
||||
public static final int HASH_LENGTH = TransactionTransformer.SHA256_LENGTH;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int MAX_IDENTIFIER_LENGTH = 64;
|
||||
|
||||
/** If time difference between transaction and now is greater than this then we don't verify proof-of-work. */
|
||||
public static final long HISTORIC_THRESHOLD = 2 * 7 * 24 * 60 * 60 * 1000L;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
|
||||
|
||||
// Constructors
|
||||
|
||||
public ArbitraryTransaction(Repository repository, TransactionData transactionData) {
|
||||
@@ -202,9 +207,11 @@ public class ArbitraryTransaction extends Transaction {
|
||||
// Clear nonce from transactionBytes
|
||||
ArbitraryTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Check nonce
|
||||
int difficulty = ArbitraryDataManager.getInstance().getPowDifficulty();
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce);
|
||||
// We only need to check nonce for recent transactions due to PoW verification overhead
|
||||
if (NTP.getTime() - this.arbitraryTransactionData.getTimestamp() < HISTORIC_THRESHOLD) {
|
||||
int difficulty = ArbitraryDataManager.getInstance().getPowDifficulty();
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -73,7 +73,7 @@ public class CancelGroupBanTransaction extends Transaction {
|
||||
Account member = getMember();
|
||||
|
||||
// Check ban actually exists
|
||||
if (!this.repository.getGroupRepository().banExists(groupId, member.getAddress()))
|
||||
if (!this.repository.getGroupRepository().banExists(groupId, member.getAddress(), this.groupUnbanTransactionData.getTimestamp()))
|
||||
return ValidationResult.BAN_UNKNOWN;
|
||||
|
||||
// Check admin has enough funds
|
||||
|
@@ -1,7 +1,9 @@
|
||||
package org.qortal.transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
@@ -16,9 +18,11 @@ import org.qortal.list.ResourceListManager;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.GroupRepository;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.ChatTransactionTransformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class ChatTransaction extends Transaction {
|
||||
|
||||
@@ -26,10 +30,11 @@ public class ChatTransaction extends Transaction {
|
||||
private ChatTransactionData chatTransactionData;
|
||||
|
||||
// Other useful constants
|
||||
public static final int MAX_DATA_SIZE = 1024;
|
||||
public static final int MAX_DATA_SIZE = 4000;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 12; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_ABOVE_QORT_THRESHOLD = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_BELOW_QORT_THRESHOLD = 18; // leading zero bits
|
||||
public static final long POW_QORT_THRESHOLD = 400000000L;
|
||||
|
||||
// Constructors
|
||||
|
||||
@@ -78,7 +83,7 @@ public class ChatTransaction extends Transaction {
|
||||
// Clear nonce from transactionBytes
|
||||
ChatTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT;
|
||||
int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) >= POW_QORT_THRESHOLD ? POW_DIFFICULTY_ABOVE_QORT_THRESHOLD : POW_DIFFICULTY_BELOW_QORT_THRESHOLD;
|
||||
|
||||
// Calculate nonce
|
||||
this.chatTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, difficulty));
|
||||
@@ -145,6 +150,11 @@ public class ChatTransaction extends Transaction {
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
||||
// Disregard messages with timestamp too far in the future (we have stricter limits for CHAT transactions)
|
||||
if (this.chatTransactionData.getTimestamp() > NTP.getTime() + (5 * 60 * 1000L)) {
|
||||
return ValidationResult.TIMESTAMP_TOO_NEW;
|
||||
}
|
||||
|
||||
// Check for blocked author by address
|
||||
ResourceListManager listManager = ResourceListManager.getInstance();
|
||||
if (listManager.listContains("blockedAddresses", this.chatTransactionData.getSender(), true)) {
|
||||
@@ -163,6 +173,14 @@ public class ChatTransaction extends Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
PublicKeyAccount creator = this.getCreator();
|
||||
if (creator == null)
|
||||
return ValidationResult.MISSING_CREATOR;
|
||||
|
||||
// Reject if unconfirmed pile already has X recent CHAT transactions from same creator
|
||||
if (countRecentChatTransactionsByCreator(creator) >= Settings.getInstance().getMaxRecentChatMessagesPerAccount())
|
||||
return ValidationResult.TOO_MANY_UNCONFIRMED;
|
||||
|
||||
// If we exist in the repository then we've been imported as unconfirmed,
|
||||
// but we don't want to make it into a block, so return fake non-OK result.
|
||||
if (this.repository.getTransactionRepository().exists(this.chatTransactionData.getSignature()))
|
||||
@@ -204,7 +222,7 @@ public class ChatTransaction extends Transaction {
|
||||
|
||||
int difficulty;
|
||||
try {
|
||||
difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT;
|
||||
difficulty = this.getSender().getConfirmedBalance(Asset.QORT) >= POW_QORT_THRESHOLD ? POW_DIFFICULTY_ABOVE_QORT_THRESHOLD : POW_DIFFICULTY_BELOW_QORT_THRESHOLD;
|
||||
} catch (DataException e) {
|
||||
return false;
|
||||
}
|
||||
@@ -213,6 +231,26 @@ public class ChatTransaction extends Transaction {
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce);
|
||||
}
|
||||
|
||||
private int countRecentChatTransactionsByCreator(PublicKeyAccount creator) throws DataException {
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
final Long now = NTP.getTime();
|
||||
long recentThreshold = Settings.getInstance().getRecentChatMessagesMaxAge();
|
||||
|
||||
// We only care about chat transactions, and only those that are considered 'recent'
|
||||
Predicate<TransactionData> hasSameCreatorAndIsRecentChat = transactionData -> {
|
||||
if (transactionData.getType() != TransactionType.CHAT)
|
||||
return false;
|
||||
|
||||
if (transactionData.getTimestamp() < now - recentThreshold)
|
||||
return false;
|
||||
|
||||
return Arrays.equals(creator.getPublicKey(), transactionData.getCreatorPublicKey());
|
||||
};
|
||||
|
||||
return (int) unconfirmedTransactions.stream().filter(hasSameCreatorAndIsRecentChat).count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure there's at least a skeleton account so people
|
||||
* can retrieve sender's public key using address, even if all their messages
|
||||
|
@@ -78,7 +78,7 @@ public class GroupInviteTransaction extends Transaction {
|
||||
return ValidationResult.ALREADY_GROUP_MEMBER;
|
||||
|
||||
// Check invitee is not banned
|
||||
if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress()))
|
||||
if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress(), this.groupInviteTransactionData.getTimestamp()))
|
||||
return ValidationResult.BANNED_FROM_GROUP;
|
||||
|
||||
// Check creator has enough funds
|
||||
|
@@ -53,7 +53,7 @@ public class JoinGroupTransaction extends Transaction {
|
||||
return ValidationResult.ALREADY_GROUP_MEMBER;
|
||||
|
||||
// Check member is not banned
|
||||
if (this.repository.getGroupRepository().banExists(groupId, joiner.getAddress()))
|
||||
if (this.repository.getGroupRepository().banExists(groupId, joiner.getAddress(), this.joinGroupTransactionData.getTimestamp()))
|
||||
return ValidationResult.BANNED_FROM_GROUP;
|
||||
|
||||
// Check join request doesn't already exist
|
||||
|
@@ -4,7 +4,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@@ -26,7 +28,7 @@ public class PublicizeTransaction extends Transaction {
|
||||
/** If time difference between transaction and now is greater than this then we don't verify proof-of-work. */
|
||||
public static final long HISTORIC_THRESHOLD = 2 * 7 * 24 * 60 * 60 * 1000L;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY = 15; // leading zero bits
|
||||
public static final int POW_DIFFICULTY = 14; // leading zero bits
|
||||
|
||||
// Constructors
|
||||
|
||||
@@ -102,6 +104,12 @@ public class PublicizeTransaction extends Transaction {
|
||||
if (!verifyNonce())
|
||||
return ValidationResult.INCORRECT_NONCE;
|
||||
|
||||
// Validate fee if one has been included
|
||||
PublicKeyAccount creator = this.getCreator();
|
||||
if (this.transactionData.getFee() > 0)
|
||||
if (creator.getConfirmedBalance(Asset.QORT) < this.transactionData.getFee())
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
|
@@ -163,9 +163,12 @@ public class RewardShareTransaction extends Transaction {
|
||||
return ValidationResult.SELF_SHARE_EXISTS;
|
||||
}
|
||||
|
||||
// Fee checking needed if not setting up new self-share
|
||||
if (!(isRecipientAlsoMinter && existingRewardShareData == null))
|
||||
// Check creator has enough funds
|
||||
// Check creator has enough funds
|
||||
if (this.rewardShareTransactionData.getTimestamp() >= BlockChain.getInstance().getFeeValidationFixTimestamp())
|
||||
if (creator.getConfirmedBalance(Asset.QORT) < this.rewardShareTransactionData.getFee())
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
else if (!(isRecipientAlsoMinter && existingRewardShareData == null))
|
||||
if (creator.getConfirmedBalance(Asset.QORT) < this.rewardShareTransactionData.getFee())
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
|
@@ -103,7 +103,7 @@ public class UpdateGroupTransaction extends Transaction {
|
||||
Account newOwner = getNewOwner();
|
||||
|
||||
// Check new owner is not banned
|
||||
if (this.repository.getGroupRepository().banExists(this.updateGroupTransactionData.getGroupId(), newOwner.getAddress()))
|
||||
if (this.repository.getGroupRepository().banExists(this.updateGroupTransactionData.getGroupId(), newOwner.getAddress(), this.updateGroupTransactionData.getTimestamp()))
|
||||
return ValidationResult.BANNED_FROM_GROUP;
|
||||
|
||||
return ValidationResult.OK;
|
||||
|
@@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
@@ -22,11 +23,13 @@ public class ChatTransactionTransformer extends TransactionTransformer {
|
||||
private static final int NONCE_LENGTH = INT_LENGTH;
|
||||
private static final int HAS_RECIPIENT_LENGTH = BOOLEAN_LENGTH;
|
||||
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int HAS_CHAT_REFERENCE_LENGTH = BOOLEAN_LENGTH;
|
||||
private static final int CHAT_REFERENCE_LENGTH = SIGNATURE_LENGTH;
|
||||
private static final int DATA_SIZE_LENGTH = INT_LENGTH;
|
||||
private static final int IS_TEXT_LENGTH = BOOLEAN_LENGTH;
|
||||
private static final int IS_ENCRYPTED_LENGTH = BOOLEAN_LENGTH;
|
||||
|
||||
private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH;
|
||||
private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH + HAS_CHAT_REFERENCE_LENGTH;
|
||||
|
||||
protected static final TransactionLayout layout;
|
||||
|
||||
@@ -77,13 +80,24 @@ public class ChatTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
long fee = byteBuffer.getLong();
|
||||
|
||||
byte[] chatReference = null;
|
||||
|
||||
if (timestamp >= BlockChain.getInstance().getChatReferenceTimestamp()) {
|
||||
boolean hasChatReference = byteBuffer.get() != 0;
|
||||
|
||||
if (hasChatReference) {
|
||||
chatReference = new byte[CHAT_REFERENCE_LENGTH];
|
||||
byteBuffer.get(chatReference);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, signature);
|
||||
|
||||
String sender = Crypto.toAddress(senderPublicKey);
|
||||
return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted);
|
||||
return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted);
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) {
|
||||
@@ -94,6 +108,9 @@ public class ChatTransactionTransformer extends TransactionTransformer {
|
||||
if (chatTransactionData.getRecipient() != null)
|
||||
dataLength += RECIPIENT_LENGTH;
|
||||
|
||||
if (chatTransactionData.getChatReference() != null)
|
||||
dataLength += CHAT_REFERENCE_LENGTH;
|
||||
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
@@ -124,6 +141,16 @@ public class ChatTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
bytes.write(Longs.toByteArray(chatTransactionData.getFee()));
|
||||
|
||||
if (transactionData.getTimestamp() >= BlockChain.getInstance().getChatReferenceTimestamp()) {
|
||||
// Include chat reference if it's not null
|
||||
if (chatTransactionData.getChatReference() != null) {
|
||||
bytes.write((byte) 1);
|
||||
bytes.write(chatTransactionData.getChatReference());
|
||||
} else {
|
||||
bytes.write((byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (chatTransactionData.getSignature() != null)
|
||||
bytes.write(chatTransactionData.getSignature());
|
||||
|
||||
|
@@ -82,9 +82,14 @@
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 1655222400000,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 1093400,
|
||||
"selfSponsorshipAlgoV1Height": 1092400
|
||||
"onlineAccountMinterLevelValidationHeight": 1092000,
|
||||
"selfSponsorshipAlgoV1Height": 1092400,
|
||||
"feeValidationFixTimestamp": 1671918000000,
|
||||
"chatReferenceTimestamp": 1674316800000
|
||||
},
|
||||
"checkpoints": [
|
||||
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
||||
],
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": "1593450000000",
|
||||
|
@@ -50,8 +50,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Bob self sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -73,7 +73,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -139,18 +139,18 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Chloe sponsors 10 accounts
|
||||
List<PrivateKeyAccount> chloeSponsees = generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
List<PrivateKeyAccount> chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
onlineAccounts.addAll(chloeSponseesOnlineAccounts);
|
||||
|
||||
// Dilbert sponsors 5 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -172,7 +172,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -270,20 +270,20 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts);
|
||||
onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Chloe sponsors 10 accounts
|
||||
List<PrivateKeyAccount> chloeSponsees = generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
List<PrivateKeyAccount> chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
onlineAccountsAliceSigner.addAll(chloeSponseesOnlineAccounts);
|
||||
onlineAccountsBobSigner.addAll(chloeSponseesOnlineAccounts);
|
||||
|
||||
// Dilbert sponsors 5 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccountsAliceSigner.addAll(dilbertSponseesOnlineAccounts);
|
||||
onlineAccountsBobSigner.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
@@ -306,7 +306,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccountsAliceSigner.addAll(bobSponseeSelfShares);
|
||||
onlineAccountsBobSigner.addAll(bobSponseeSelfShares);
|
||||
|
||||
@@ -382,14 +382,14 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Alice sponsors 10 accounts
|
||||
List<PrivateKeyAccount> aliceSponsees = generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
onlineAccountsAliceSigner.addAll(aliceSponseesOnlineAccounts);
|
||||
onlineAccountsBobSigner.addAll(aliceSponseesOnlineAccounts);
|
||||
|
||||
// Bob sponsors 9 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 9);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts);
|
||||
onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
@@ -412,7 +412,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = generateSelfShares(repository, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees);
|
||||
onlineAccountsAliceSigner.addAll(aliceSponseeSelfShares);
|
||||
onlineAccountsBobSigner.addAll(aliceSponseeSelfShares);
|
||||
|
||||
@@ -483,18 +483,18 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Chloe sponsors 10 accounts
|
||||
List<PrivateKeyAccount> chloeSponsees = generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
List<PrivateKeyAccount> chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
onlineAccounts.addAll(chloeSponseesOnlineAccounts);
|
||||
|
||||
// Dilbert sponsors 5 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -516,7 +516,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -597,14 +597,14 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Alice sponsors 10 accounts
|
||||
List<PrivateKeyAccount> aliceSponsees = generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
onlineAccounts.addAll(aliceSponseesOnlineAccounts);
|
||||
onlineAccounts.addAll(aliceSponseesOnlineAccounts);
|
||||
|
||||
// Bob sponsors 9 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 9);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
@@ -627,7 +627,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = generateSelfShares(repository, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees);
|
||||
onlineAccounts.addAll(aliceSponseeSelfShares);
|
||||
|
||||
// Mint blocks (Bob is the signer)
|
||||
@@ -706,13 +706,13 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Chloe sponsors 10 accounts
|
||||
List<PrivateKeyAccount> chloeSponsees = generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
List<PrivateKeyAccount> chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
onlineAccounts.addAll(chloeSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -728,7 +728,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -744,22 +744,22 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertEquals(19, (int) block.getBlockData().getHeight());
|
||||
|
||||
// Bob creates a valid reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, bobAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount));
|
||||
|
||||
// Mint a block, so the algo runs
|
||||
block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Bob can no longer create a reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, createRandomRewardShare(repository, bobAccount));
|
||||
assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, bobAccount));
|
||||
|
||||
// ... but Chloe still can
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, chloeAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, chloeAccount));
|
||||
|
||||
// Orphan last block
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Bob creates another valid reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, bobAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount));
|
||||
|
||||
// Run orphan check - this can't be in afterTest() because some tests access the live db
|
||||
Common.orphanCheck();
|
||||
@@ -780,13 +780,13 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Alice sponsors 10 accounts
|
||||
List<PrivateKeyAccount> aliceSponsees = generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10);
|
||||
List<PrivateKeyAccount> aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees);
|
||||
onlineAccounts.addAll(aliceSponseesOnlineAccounts);
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -802,7 +802,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = generateSelfShares(repository, aliceSponsees);
|
||||
List<PrivateKeyAccount> aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees);
|
||||
onlineAccounts.addAll(aliceSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -818,7 +818,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertEquals(19, (int) block.getBlockData().getHeight());
|
||||
|
||||
// Alice creates a valid reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, aliceAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount));
|
||||
|
||||
// Mint a block, so the algo runs
|
||||
block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
@@ -830,16 +830,16 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertEquals(0, (int) new Account(repository, aliceAccount.getAddress()).getLevel());
|
||||
|
||||
// Alice can no longer create a reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, createRandomRewardShare(repository, aliceAccount));
|
||||
assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, aliceAccount));
|
||||
|
||||
// ... but Bob still can
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, bobAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount));
|
||||
|
||||
// Orphan last block
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Alice creates another valid reward share transaction
|
||||
assertEquals(Transaction.ValidationResult.OK, createRandomRewardShare(repository, aliceAccount));
|
||||
assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount));
|
||||
|
||||
// Run orphan check - this can't be in afterTest() because some tests access the live db
|
||||
Common.orphanCheck();
|
||||
@@ -867,8 +867,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Dilbert sponsors 10 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 10);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -921,8 +921,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Dilbert sponsors 10 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 10);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -935,7 +935,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0]));
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> dilbertSponseeSelfShares = generateSelfShares(repository, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponseeSelfShares = AccountUtils.generateSelfShares(repository, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -985,8 +985,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Chloe sponsors THE SAME 10 accounts
|
||||
@@ -996,12 +996,12 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
TransactionUtils.signAndImportValid(repository, transactionData, chloeAccount);
|
||||
}
|
||||
List<PrivateKeyAccount> chloeSponsees = new ArrayList<>(bobSponsees);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
List<PrivateKeyAccount> chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees);
|
||||
onlineAccounts.addAll(chloeSponseesOnlineAccounts);
|
||||
|
||||
// Dilbert sponsors 5 accounts
|
||||
List<PrivateKeyAccount> dilbertSponsees = generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
List<PrivateKeyAccount> dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5);
|
||||
List<PrivateKeyAccount> dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees);
|
||||
onlineAccounts.addAll(dilbertSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1023,7 +1023,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1108,8 +1108,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1128,7 +1128,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1220,8 +1220,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1240,7 +1240,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1316,8 +1316,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1336,7 +1336,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1392,8 +1392,8 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Bob sponsors 10 accounts
|
||||
List<PrivateKeyAccount> bobSponsees = generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = toRewardShares(repository, bobAccount, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10);
|
||||
List<PrivateKeyAccount> bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseesOnlineAccounts);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1412,7 +1412,7 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0);
|
||||
|
||||
// Generate self shares so the sponsees can start minting
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = generateSelfShares(repository, bobSponsees);
|
||||
List<PrivateKeyAccount> bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees);
|
||||
onlineAccounts.addAll(bobSponseeSelfShares);
|
||||
|
||||
// Mint blocks
|
||||
@@ -1543,61 +1543,6 @@ public class SelfSponsorshipAlgoV1Tests extends Common {
|
||||
return new TransferPrivsTransaction(repository, transactionData);
|
||||
}
|
||||
|
||||
private static List<PrivateKeyAccount> generateSponsorshipRewardShares(Repository repository, PrivateKeyAccount sponsorAccount, int accountsCount) throws DataException {
|
||||
final int sharePercent = 0;
|
||||
Random random = new Random();
|
||||
|
||||
List<PrivateKeyAccount> sponsees = new ArrayList<>();
|
||||
for (int i = 0; i < accountsCount; i++) {
|
||||
|
||||
// Generate random sponsee account
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
sponsees.add(sponseeAccount);
|
||||
|
||||
// Create reward-share
|
||||
TransactionData transactionData = AccountUtils.createRewardShare(repository, sponsorAccount, sponseeAccount, sharePercent, fee);
|
||||
TransactionUtils.signAndImportValid(repository, transactionData, sponsorAccount);
|
||||
}
|
||||
|
||||
return sponsees;
|
||||
}
|
||||
|
||||
private static Transaction.ValidationResult createRandomRewardShare(Repository repository, PrivateKeyAccount account) throws DataException {
|
||||
// Bob attempts to create a reward share transaction
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
new Random().nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
TransactionData transactionData = AccountUtils.createRewardShare(repository, account, sponseeAccount, 0, fee);
|
||||
return TransactionUtils.signAndImport(repository, transactionData, account);
|
||||
}
|
||||
|
||||
private static List<PrivateKeyAccount> generateSelfShares(Repository repository, List<PrivateKeyAccount> accounts) throws DataException {
|
||||
final int sharePercent = 0;
|
||||
|
||||
for (PrivateKeyAccount account : accounts) {
|
||||
// Create reward-share
|
||||
TransactionData transactionData = AccountUtils.createRewardShare(repository, account, account, sharePercent, 0L);
|
||||
TransactionUtils.signAndImportValid(repository, transactionData, account);
|
||||
}
|
||||
|
||||
return toRewardShares(repository, null, accounts);
|
||||
}
|
||||
|
||||
private static List<PrivateKeyAccount> toRewardShares(Repository repository, PrivateKeyAccount parentAccount, List<PrivateKeyAccount> accounts) {
|
||||
List<PrivateKeyAccount> rewardShares = new ArrayList<>();
|
||||
|
||||
for (PrivateKeyAccount account : accounts) {
|
||||
PrivateKeyAccount sponsor = (parentAccount != null) ? parentAccount : account;
|
||||
byte[] rewardSharePrivateKey = sponsor.getRewardSharePrivateKey(account.getPublicKey());
|
||||
PrivateKeyAccount rewardShareAccount = new PrivateKeyAccount(repository, rewardSharePrivateKey);
|
||||
rewardShares.add(rewardShareAccount);
|
||||
}
|
||||
|
||||
return rewardShares;
|
||||
}
|
||||
|
||||
private boolean areAllAccountsPresentInBlock(List<PrivateKeyAccount> accounts, Block block) throws DataException {
|
||||
for (PrivateKeyAccount bobSponsee : accounts) {
|
||||
boolean foundOnlineAccountInBlock = false;
|
||||
|
@@ -175,4 +175,93 @@ public class ArbitraryServiceTests extends Common {
|
||||
assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path));
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void testValidateQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "document.pdf"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.OK, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateInvalidQChatAttachmentFileExtension() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateInvalidQChatAttachmentFileExtension");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "application.exe"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateEmptyQChatAttachment() throws IOException {
|
||||
Path path = Files.createTempDirectory("testValidateEmptyQChatAttachment");
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMultiLayerQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Path subdirectory = Paths.get(path.toString(), "subdirectory");
|
||||
Files.createDirectories(subdirectory);
|
||||
Files.write(Paths.get(subdirectory.toString(), "file2.txt"), data, StandardOpenOption.CREATE);
|
||||
Files.write(Paths.get(subdirectory.toString(), "file3.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMultiFileQChatAttachment() throws IOException {
|
||||
// Generate some random data
|
||||
byte[] data = new byte[1024];
|
||||
new Random().nextBytes(data);
|
||||
|
||||
// Write the data to several files in a temp path
|
||||
Path path = Files.createTempDirectory("testValidateMultiFileQChatAttachment");
|
||||
path.toFile().deleteOnExit();
|
||||
Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE);
|
||||
Files.write(Paths.get(path.toString(), "file2.txt"), data, StandardOpenOption.CREATE);
|
||||
|
||||
Service service = Service.QCHAT_ATTACHMENT;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
// There is an index file in the root
|
||||
assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path));
|
||||
}
|
||||
|
||||
}
|
@@ -8,7 +8,6 @@ import java.util.*;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crypto.Qortal25519Extras;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
@@ -19,6 +18,7 @@ import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
@@ -86,6 +86,61 @@ public class AccountUtils {
|
||||
return rewardSharePrivateKey;
|
||||
}
|
||||
|
||||
public static List<PrivateKeyAccount> generateSponsorshipRewardShares(Repository repository, PrivateKeyAccount sponsorAccount, int accountsCount) throws DataException {
|
||||
final int sharePercent = 0;
|
||||
Random random = new Random();
|
||||
|
||||
List<PrivateKeyAccount> sponsees = new ArrayList<>();
|
||||
for (int i = 0; i < accountsCount; i++) {
|
||||
|
||||
// Generate random sponsee account
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
sponsees.add(sponseeAccount);
|
||||
|
||||
// Create reward-share
|
||||
TransactionData transactionData = AccountUtils.createRewardShare(repository, sponsorAccount, sponseeAccount, sharePercent, fee);
|
||||
TransactionUtils.signAndImportValid(repository, transactionData, sponsorAccount);
|
||||
}
|
||||
|
||||
return sponsees;
|
||||
}
|
||||
|
||||
public static Transaction.ValidationResult createRandomRewardShare(Repository repository, PrivateKeyAccount account) throws DataException {
|
||||
// Bob attempts to create a reward share transaction
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
new Random().nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
TransactionData transactionData = createRewardShare(repository, account, sponseeAccount, 0, fee);
|
||||
return TransactionUtils.signAndImport(repository, transactionData, account);
|
||||
}
|
||||
|
||||
public static List<PrivateKeyAccount> generateSelfShares(Repository repository, List<PrivateKeyAccount> accounts) throws DataException {
|
||||
final int sharePercent = 0;
|
||||
|
||||
for (PrivateKeyAccount account : accounts) {
|
||||
// Create reward-share
|
||||
TransactionData transactionData = createRewardShare(repository, account, account, sharePercent, 0L);
|
||||
TransactionUtils.signAndImportValid(repository, transactionData, account);
|
||||
}
|
||||
|
||||
return toRewardShares(repository, null, accounts);
|
||||
}
|
||||
|
||||
public static List<PrivateKeyAccount> toRewardShares(Repository repository, PrivateKeyAccount parentAccount, List<PrivateKeyAccount> accounts) {
|
||||
List<PrivateKeyAccount> rewardShares = new ArrayList<>();
|
||||
|
||||
for (PrivateKeyAccount account : accounts) {
|
||||
PrivateKeyAccount sponsor = (parentAccount != null) ? parentAccount : account;
|
||||
byte[] rewardSharePrivateKey = sponsor.getRewardSharePrivateKey(account.getPublicKey());
|
||||
PrivateKeyAccount rewardShareAccount = new PrivateKeyAccount(repository, rewardSharePrivateKey);
|
||||
rewardShares.add(rewardShareAccount);
|
||||
}
|
||||
|
||||
return rewardShares;
|
||||
}
|
||||
|
||||
public static Map<String, Map<Long, Long>> getBalances(Repository repository, long... assetIds) throws DataException {
|
||||
Map<String, Map<Long, Long>> balances = new HashMap<>();
|
||||
|
||||
|
@@ -120,7 +120,9 @@ public class Common {
|
||||
}
|
||||
|
||||
public static void useSettingsAndDb(String settingsFilename, boolean dbInMemory) throws DataException {
|
||||
closeRepository();
|
||||
if (RepositoryManager.getRepositoryFactory() != null) {
|
||||
closeRepository();
|
||||
}
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
LOGGER.debug(String.format("Using setting file: %s", settingsFilename));
|
||||
|
@@ -0,0 +1,40 @@
|
||||
package org.qortal.test.common.transaction;
|
||||
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class ChatTestTransaction extends TestTransaction {
|
||||
|
||||
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
|
||||
Random random = new Random();
|
||||
byte[] orderId = new byte[64];
|
||||
random.nextBytes(orderId);
|
||||
|
||||
String sender = Crypto.toAddress(account.getPublicKey());
|
||||
int nonce = 1234567;
|
||||
|
||||
// Generate random recipient
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
String recipient = Crypto.toAddress(recipientAccount.getPublicKey());
|
||||
|
||||
byte[] chatReference = new byte[64];
|
||||
random.nextBytes(chatReference);
|
||||
|
||||
byte[] data = new byte[4000];
|
||||
random.nextBytes(data);
|
||||
|
||||
boolean isText = true;
|
||||
boolean isEncrypted = true;
|
||||
|
||||
return new ChatTransactionData(generateBase(account), sender, nonce, recipient, chatReference, data, isText, isEncrypted);
|
||||
}
|
||||
|
||||
}
|
@@ -135,7 +135,8 @@ public class AdminTests extends Common {
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Attempt to ban Bob
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress());
|
||||
int timeToLive = 0;
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
@@ -158,7 +159,7 @@ public class AdminTests extends Common {
|
||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Attempt to ban Bob
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress());
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
@@ -205,6 +206,144 @@ public class AdminTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupBanMemberWithExpiry() throws DataException, InterruptedException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
// Create group
|
||||
int groupId = createGroup(repository, alice, "open-group", true);
|
||||
|
||||
// Confirm Bob is not a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Attempt to cancel non-existent Bob ban
|
||||
ValidationResult result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
|
||||
// Should NOT be OK
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Attempt to ban Bob for 2 seconds
|
||||
int timeToLive = 2;
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob no longer a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Bob attempts to rejoin
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should NOT be OK
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Wait for 2 seconds to pass
|
||||
Thread.sleep(2000L);
|
||||
|
||||
// Bob attempts to rejoin again
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should be OK, as the ban has expired
|
||||
assertSame(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob is now a member
|
||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Orphan last block (Bob join)
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Confirm Bob is not a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Orphan last block (Bob ban)
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
// Delete unconfirmed group-ban transaction
|
||||
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||
|
||||
// Bob to join
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob now a member
|
||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
|
||||
// Attempt to ban Bob for 2 seconds
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), 2);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob no longer a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Wait for 2 seconds to pass
|
||||
Thread.sleep(2000L);
|
||||
|
||||
// Cancel Bob's ban
|
||||
result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
|
||||
// Should NOT be OK, as ban has already expired
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob still not a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Bob attempts to rejoin
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should be OK, as no longer banned
|
||||
assertSame(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob is now a member
|
||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
|
||||
// Attempt to ban Bob for 10 seconds
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), 10);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Confirm Bob no longer a member
|
||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Bob attempts to rejoin
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should NOT be OK, as ban still exists
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Cancel Bob's ban
|
||||
result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
|
||||
// Should be OK, as ban still exists
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Bob attempts to rejoin
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should be OK, as no longer banned
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
// Orphan last block (Bob join)
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
// Delete unconfirmed join-group transaction
|
||||
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||
|
||||
// Orphan last block (Cancel Bob ban)
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
// Delete unconfirmed cancel-ban transaction
|
||||
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||
|
||||
// Bob attempts to rejoin
|
||||
result = joinGroup(repository, bob, groupId);
|
||||
// Should NOT be OK
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Orphan last block (Bob ban)
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
// Delete unconfirmed group-ban transaction
|
||||
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||
|
||||
// Confirm Bob now a member
|
||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupBanAdmin() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
@@ -226,7 +365,8 @@ public class AdminTests extends Common {
|
||||
assertTrue(isAdmin(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Attempt to ban Bob
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress());
|
||||
int timeToLive = 0;
|
||||
result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive);
|
||||
// Should be OK
|
||||
assertEquals(ValidationResult.OK, result);
|
||||
|
||||
@@ -272,12 +412,12 @@ public class AdminTests extends Common {
|
||||
assertTrue(isAdmin(repository, bob.getAddress(), groupId));
|
||||
|
||||
// Have Alice (owner) try to ban herself!
|
||||
result = groupBan(repository, alice, groupId, alice.getAddress());
|
||||
result = groupBan(repository, alice, groupId, alice.getAddress(), timeToLive);
|
||||
// Should NOT be OK
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
|
||||
// Have Bob try to ban Alice (owner)
|
||||
result = groupBan(repository, bob, groupId, alice.getAddress());
|
||||
result = groupBan(repository, bob, groupId, alice.getAddress(), timeToLive);
|
||||
// Should NOT be OK
|
||||
assertNotSame(ValidationResult.OK, result);
|
||||
}
|
||||
@@ -316,8 +456,8 @@ public class AdminTests extends Common {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
||||
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0);
|
||||
private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member, int timeToLive) throws DataException {
|
||||
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", timeToLive);
|
||||
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
||||
|
||||
if (result == ValidationResult.OK)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package org.qortal.test.at;
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import org.junit.After;
|
@@ -0,0 +1,102 @@
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
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.transaction.ChatTestTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ChatSerializationTests {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testChatSerializationWithChatReference() throws DataException, TransformationException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Build MESSAGE-type AT transaction with chatReference
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice");
|
||||
ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
|
||||
assertNotNull(transactionData.getChatReference());
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
||||
assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length);
|
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction);
|
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData);
|
||||
deserializedTransaction.sign(signingAccount);
|
||||
assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature()));
|
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength);
|
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString());
|
||||
|
||||
// Deserialized chat reference must match initial chat reference
|
||||
ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData;
|
||||
assertNotNull(deserializedChatTransactionData.getChatReference());
|
||||
assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializationWithoutChatReference() throws DataException, TransformationException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Build MESSAGE-type AT transaction without chatReference
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice");
|
||||
ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true);
|
||||
transactionData.setChatReference(null);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
|
||||
assertNull(transactionData.getChatReference());
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
||||
assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length);
|
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction);
|
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData);
|
||||
deserializedTransaction.sign(signingAccount);
|
||||
assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature()));
|
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength);
|
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData);
|
||||
assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString());
|
||||
|
||||
// Deserialized chat reference must match initial chat reference
|
||||
ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData;
|
||||
assertNull(deserializedChatTransactionData.getChatReference());
|
||||
assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package org.qortal.test;
|
||||
package org.qortal.test.serialization;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -47,7 +47,6 @@ public class SerializationTests extends Common {
|
||||
switch (txType) {
|
||||
case GENESIS:
|
||||
case ACCOUNT_FLAGS:
|
||||
case CHAT:
|
||||
case PUBLICIZE:
|
||||
case AIRDROP:
|
||||
case ENABLE_FORGING:
|
||||
@@ -60,6 +59,7 @@ public class SerializationTests extends Common {
|
||||
TransactionData transactionData = TransactionUtils.randomTransaction(repository, signingAccount, txType, true);
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(signingAccount);
|
||||
transaction.importAsUnconfirmed();
|
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData);
|
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData);
|
@@ -73,7 +73,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -76,7 +76,9 @@
|
||||
"disableReferenceTimestamp": 0,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -78,7 +78,9 @@
|
||||
"aggregateSignatureTimestamp": 0,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -44,7 +44,7 @@
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"minAccountsToActivateShareBin": 30,
|
||||
"minAccountsToActivateShareBin": 0,
|
||||
"shareBinActivationMinLevel": 7,
|
||||
"blocksNeededByLevel": [ 5, 20, 30, 40, 50, 60, 18, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 20
|
||||
"selfSponsorshipAlgoV1Height": 20,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@@ -77,7 +77,9 @@
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"onlineAccountMinterLevelValidationHeight": 0,
|
||||
"selfSponsorshipAlgoV1Height": 999999999
|
||||
"selfSponsorshipAlgoV1Height": 999999999,
|
||||
"feeValidationFixTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
Reference in New Issue
Block a user