mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-30 21:51:26 +00:00
Compare commits
30 Commits
optimize_a
...
add-create
Author | SHA1 | Date | |
---|---|---|---|
|
3fbcc50503 | ||
b051f9be89 | |||
24ff3ab581 | |||
34382b6e69 | |||
|
9428f9688f | ||
|
9f4a0b7957 | ||
|
3c8574a466 | ||
|
c428d7ce2e | ||
|
4feb8f46c8 | ||
|
1f7a60dfd8 | ||
|
379b850bbd | ||
|
7bb61ec564 | ||
|
b0224651c2 | ||
|
6bf2b99913 | ||
|
fd9d0c4e51 | ||
|
c2756a5872 | ||
|
ecfb9a7d6d | ||
|
dd9d3fdb22 | ||
|
eea2884112 | ||
|
640a472876 | ||
|
e244a5f882 | ||
|
278dca75e8 | ||
|
897c44ffe8 | ||
|
d9147b3af3 | ||
|
fe840bbf02 | ||
|
e7901a391f | ||
|
18e880158f | ||
|
a3526d84bc | ||
|
ff7a87ab18 | ||
|
cc8cdcd93a |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>4.2.2</version>
|
||||
<version>4.2.4</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
102
src/main/java/org/qortal/api/model/AtCreationRequest.java
Normal file
102
src/main/java/org/qortal/api/model/AtCreationRequest.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.encoders.DecoderException;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AtCreationRequest {
|
||||
|
||||
@Schema(description = "CIYAM AT version", example = "2")
|
||||
private short ciyamAtVersion;
|
||||
|
||||
@Schema(description = "base64-encoded code bytes", example = "")
|
||||
private String codeBytesBase64;
|
||||
|
||||
@Schema(description = "base64-encoded data bytes", example = "")
|
||||
private String dataBytesBase64;
|
||||
|
||||
private short numCallStackPages;
|
||||
private short numUserStackPages;
|
||||
private long minActivationAmount;
|
||||
|
||||
// Default constructor for JSON deserialization
|
||||
public AtCreationRequest() {}
|
||||
|
||||
// Getters and setters
|
||||
public short getCiyamAtVersion() {
|
||||
return ciyamAtVersion;
|
||||
}
|
||||
|
||||
public void setCiyamAtVersion(short ciyamAtVersion) {
|
||||
this.ciyamAtVersion = ciyamAtVersion;
|
||||
}
|
||||
|
||||
|
||||
public String getCodeBytesBase64() {
|
||||
return this.codeBytesBase64;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
public byte[] getCodeBytes() {
|
||||
if (this.codeBytesBase64 != null) {
|
||||
try {
|
||||
return Base64.decode(this.codeBytesBase64);
|
||||
}
|
||||
catch (DecoderException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public String getDataBytesBase64() {
|
||||
return this.dataBytesBase64;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
public byte[] getDataBytes() {
|
||||
if (this.dataBytesBase64 != null) {
|
||||
try {
|
||||
return Base64.decode(this.dataBytesBase64);
|
||||
}
|
||||
catch (DecoderException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public short getNumCallStackPages() {
|
||||
return numCallStackPages;
|
||||
}
|
||||
|
||||
public void setNumCallStackPages(short numCallStackPages) {
|
||||
this.numCallStackPages = numCallStackPages;
|
||||
}
|
||||
|
||||
public short getNumUserStackPages() {
|
||||
return numUserStackPages;
|
||||
}
|
||||
|
||||
public void setNumUserStackPages(short numUserStackPages) {
|
||||
this.numUserStackPages = numUserStackPages;
|
||||
}
|
||||
|
||||
public long getMinActivationAmount() {
|
||||
return minActivationAmount;
|
||||
}
|
||||
|
||||
public void setMinActivationAmount(long minActivationAmount) {
|
||||
this.minActivationAmount = minActivationAmount;
|
||||
}
|
||||
}
|
@@ -724,7 +724,7 @@ public class ArbitraryResource {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier);
|
||||
|
||||
try {
|
||||
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, false);
|
||||
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, true);
|
||||
if (transactionMetadata != null) {
|
||||
ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true);
|
||||
if (resourceMetadata != null) {
|
||||
|
@@ -27,6 +27,7 @@ import org.qortal.api.ApiException;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.api.model.AtCreationRequest;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -38,9 +39,14 @@ import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@Path("/at")
|
||||
@Tag(name = "Automated Transactions")
|
||||
public class AtResource {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AtResource.class);
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
@@ -156,6 +162,52 @@ public class AtResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/create")
|
||||
@Operation(
|
||||
summary = "Create base58-encoded AT creation bytes from the provided parameters",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = AtCreationRequest.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "AT creation bytes suitable for use in a DEPLOY_AT transaction",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
public String create(AtCreationRequest atCreationRequest) {
|
||||
if (atCreationRequest.getCiyamAtVersion() < 2) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "ciyamAtVersion must be at least 2");
|
||||
}
|
||||
if (atCreationRequest.getCodeBytes() == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid codeBytesBase64 must be supplied");
|
||||
}
|
||||
if (atCreationRequest.getDataBytes() == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid dataBytesBase64 must be supplied");
|
||||
}
|
||||
|
||||
byte[] creationBytes = MachineState.toCreationBytes(
|
||||
atCreationRequest.getCiyamAtVersion(),
|
||||
atCreationRequest.getCodeBytes(),
|
||||
atCreationRequest.getDataBytes(),
|
||||
atCreationRequest.getNumCallStackPages(),
|
||||
atCreationRequest.getNumUserStackPages(),
|
||||
atCreationRequest.getMinActivationAmount()
|
||||
);
|
||||
return Base58.encode(creationBytes);
|
||||
}
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Build raw, unsigned, DEPLOY_AT transaction",
|
||||
|
@@ -127,6 +127,11 @@ public class ArbitraryDataRenderer {
|
||||
String filename = this.getFilename(unzippedPath, inPath);
|
||||
Path filePath = Paths.get(unzippedPath, filename);
|
||||
boolean usingCustomRouting = false;
|
||||
if (Files.isDirectory(filePath) && (!inPath.endsWith("/"))) {
|
||||
inPath = inPath + "/";
|
||||
filename = this.getFilename(unzippedPath, inPath);
|
||||
filePath = Paths.get(unzippedPath, filename);
|
||||
}
|
||||
|
||||
// If the file doesn't exist, we may need to route the request elsewhere, or cleanup
|
||||
if (!Files.exists(filePath)) {
|
||||
|
@@ -84,6 +84,7 @@ public class Block {
|
||||
TRANSACTION_PROCESSING_FAILED(53),
|
||||
TRANSACTION_ALREADY_PROCESSED(54),
|
||||
TRANSACTION_NEEDS_APPROVAL(55),
|
||||
TRANSACTION_NOT_CONFIRMABLE(56),
|
||||
AT_STATES_MISMATCH(61),
|
||||
ONLINE_ACCOUNTS_INVALID(70),
|
||||
ONLINE_ACCOUNT_UNKNOWN(71),
|
||||
@@ -130,6 +131,9 @@ public class Block {
|
||||
/** Locally-generated AT fees */
|
||||
protected long ourAtFees; // Generated locally
|
||||
|
||||
/** Cached online accounts validation decision, to avoid revalidating when true */
|
||||
private boolean onlineAccountsAlreadyValid = false;
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BlockRewardDistributor {
|
||||
long distribute(long amount, Map<String, Long> balanceChanges) throws DataException;
|
||||
@@ -563,6 +567,13 @@ public class Block {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Force online accounts to be revalidated, e.g. at final stage of block minting.
|
||||
*/
|
||||
public void clearOnlineAccountsValidationCache() {
|
||||
this.onlineAccountsAlreadyValid = false;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
/**
|
||||
@@ -1043,6 +1054,10 @@ public class Block {
|
||||
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Don't bother revalidating if accounts have already been validated in this block
|
||||
if (this.onlineAccountsAlreadyValid)
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Expand block's online accounts indexes into actual accounts
|
||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||
// We use count of online accounts to validate decoded account indexes
|
||||
@@ -1130,6 +1145,9 @@ public class Block {
|
||||
// All online accounts valid, so save our list of online accounts for potential later use
|
||||
this.cachedOnlineRewardShares = onlineRewardShares;
|
||||
|
||||
// Remember that the accounts are valid, to speed up subsequent checks
|
||||
this.onlineAccountsAlreadyValid = true;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@@ -1234,6 +1252,13 @@ public class Block {
|
||||
|| transaction.getDeadline() <= this.blockData.getTimestamp())
|
||||
return ValidationResult.TRANSACTION_TIMESTAMP_INVALID;
|
||||
|
||||
// After feature trigger, check that this transaction is confirmable
|
||||
if (transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
if (!transaction.isConfirmable()) {
|
||||
return ValidationResult.TRANSACTION_NOT_CONFIRMABLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check transaction isn't already included in a block
|
||||
if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature()))
|
||||
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
||||
|
@@ -48,9 +48,6 @@ public class BlockChain {
|
||||
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
||||
private long transactionExpiryPeriod;
|
||||
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long unitFee;
|
||||
|
||||
private int maxBytesPerUnitFee;
|
||||
|
||||
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
||||
@@ -89,6 +86,7 @@ public class BlockChain {
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long fee;
|
||||
}
|
||||
private List<UnitFeesByTimestamp> unitFees;
|
||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@@ -211,6 +209,9 @@ public class BlockChain {
|
||||
/** Snapshot timestamp for self sponsorship algo V1 */
|
||||
private long selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||
|
||||
/** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */
|
||||
private long mempowTransactionUpdatesTimestamp;
|
||||
|
||||
/** Max reward shares by block height */
|
||||
public static class MaxRewardSharesByTimestamp {
|
||||
public long timestamp;
|
||||
@@ -346,10 +347,6 @@ public class BlockChain {
|
||||
return this.isTestChain;
|
||||
}
|
||||
|
||||
public long getUnitFee() {
|
||||
return this.unitFee;
|
||||
}
|
||||
|
||||
public int getMaxBytesPerUnitFee() {
|
||||
return this.maxBytesPerUnitFee;
|
||||
}
|
||||
@@ -376,6 +373,11 @@ public class BlockChain {
|
||||
return this.selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||
}
|
||||
|
||||
// Feature-trigger timestamp to modify behaviour of various transactions that support mempow
|
||||
public long getMemPoWTransactionUpdatesTimestamp() {
|
||||
return this.mempowTransactionUpdatesTimestamp;
|
||||
}
|
||||
|
||||
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||
public boolean getRequireGroupForApproval() {
|
||||
return this.requireGroupForApproval;
|
||||
@@ -547,13 +549,22 @@ public class BlockChain {
|
||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
||||
}
|
||||
|
||||
public long getUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
for (int i = unitFees.size() - 1; i >= 0; --i)
|
||||
if (unitFees.get(i).timestamp <= ourTimestamp)
|
||||
return unitFees.get(i).fee;
|
||||
|
||||
// Shouldn't happen, but set a sensible default just in case
|
||||
return 100000;
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
||||
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
||||
return nameRegistrationUnitFees.get(i).fee;
|
||||
|
||||
// Default to system-wide unit fee
|
||||
return this.getUnitFee();
|
||||
// Shouldn't happen, but set a sensible default just in case
|
||||
return 100000;
|
||||
}
|
||||
|
||||
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
||||
|
@@ -562,6 +562,9 @@ public class BlockMinter extends Thread {
|
||||
// Sign to create block's signature
|
||||
newBlock.sign();
|
||||
|
||||
// Ensure online accounts are fully re-validated in this final check
|
||||
newBlock.clearOnlineAccountsValidationCache();
|
||||
|
||||
// Is newBlock still valid?
|
||||
ValidationResult validationResult = newBlock.isValid();
|
||||
if (validationResult != ValidationResult.OK)
|
||||
|
@@ -229,13 +229,6 @@ public class Synchronizer extends Thread {
|
||||
peers.removeIf(Controller.hasOldVersion);
|
||||
|
||||
checkRecoveryModeForPeers(peers);
|
||||
if (recoveryMode) {
|
||||
// Needs a mutable copy of the unmodifiableList
|
||||
peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||
peers.removeIf(Controller.hasOnlyGenesisBlock);
|
||||
peers.removeIf(Controller.hasMisbehaved);
|
||||
peers.removeIf(Controller.hasOldVersion);
|
||||
}
|
||||
|
||||
// Check we have enough peers to potentially synchronize
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
@@ -262,10 +255,7 @@ public class Synchronizer extends Thread {
|
||||
peers.removeIf(Controller.hasInferiorChainTip);
|
||||
|
||||
// Remove any peers that are no longer on a recent block since the last check
|
||||
// Except for times when we're in recovery mode, in which case we need to keep them
|
||||
if (!recoveryMode) {
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
}
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
|
||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||
if (peersRemoved > 0 && peers.size() > 0)
|
||||
@@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread {
|
||||
return SynchronizationResult.INVALID_DATA;
|
||||
}
|
||||
|
||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
||||
// Final check to make sure the peer isn't out of date
|
||||
if (peer.getChainTipData() != null) {
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||
|
@@ -47,6 +47,9 @@ public class TransactionImporter extends Thread {
|
||||
/** Map of recent invalid unconfirmed transactions. Key is base58 transaction signature, value is do-not-request expiry timestamp. */
|
||||
private final Map<String, Long> invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/** Cached list of unconfirmed transactions, used when counting per creator. This is replaced regularly */
|
||||
public static List<TransactionData> unconfirmedTransactionsCache = null;
|
||||
|
||||
|
||||
public static synchronized TransactionImporter getInstance() {
|
||||
if (instance == null) {
|
||||
@@ -215,12 +218,6 @@ public class TransactionImporter extends Thread {
|
||||
LOGGER.debug("Finished validating signatures in incoming transactions queue (valid this round: {}, total pending import: {})...", validatedCount, sigValidTransactions.size());
|
||||
}
|
||||
|
||||
if (!newlyValidSignatures.isEmpty()) {
|
||||
LOGGER.debug("Broadcasting {} newly valid signatures ahead of import", newlyValidSignatures.size());
|
||||
Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyValidSignatures);
|
||||
Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage);
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while processing incoming transactions", e);
|
||||
}
|
||||
@@ -254,6 +251,15 @@ public class TransactionImporter extends Thread {
|
||||
int processedCount = 0;
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Use a single copy of the unconfirmed transactions list for each cycle, to speed up constant lookups
|
||||
// when counting unconfirmed transactions by creator.
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
unconfirmedTransactions.removeIf(t -> t.getType() == Transaction.TransactionType.CHAT);
|
||||
unconfirmedTransactionsCache = unconfirmedTransactions;
|
||||
|
||||
// A list of signatures were imported in this round
|
||||
List<byte[]> newlyImportedSignatures = new ArrayList<>();
|
||||
|
||||
// Import transactions with valid signatures
|
||||
try {
|
||||
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
||||
@@ -286,6 +292,15 @@ public class TransactionImporter extends Thread {
|
||||
|
||||
case OK: {
|
||||
LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature())));
|
||||
|
||||
// Add to the unconfirmed transactions cache
|
||||
if (transactionData.getType() != Transaction.TransactionType.CHAT && unconfirmedTransactionsCache != null) {
|
||||
unconfirmedTransactionsCache.add(transactionData);
|
||||
}
|
||||
|
||||
// Signature imported in this round
|
||||
newlyImportedSignatures.add(transactionData.getSignature());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -314,9 +329,18 @@ public class TransactionImporter extends Thread {
|
||||
// Transaction has been processed, even if only to reject it
|
||||
removeIncomingTransaction(transactionData.getSignature());
|
||||
}
|
||||
|
||||
if (!newlyImportedSignatures.isEmpty()) {
|
||||
LOGGER.debug("Broadcasting {} newly imported signatures", newlyImportedSignatures.size());
|
||||
Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyImportedSignatures);
|
||||
Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage);
|
||||
}
|
||||
} finally {
|
||||
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
||||
blockchainLock.unlock();
|
||||
|
||||
// Clear the unconfirmed transaction cache so new data can be populated in the next cycle
|
||||
unconfirmedTransactionsCache = null;
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while importing incoming transactions", e);
|
||||
|
@@ -275,7 +275,10 @@ public class ArbitraryDataManager extends Thread {
|
||||
int offset = 0;
|
||||
|
||||
while (!isStopping) {
|
||||
Thread.sleep(1000L);
|
||||
final int minSeconds = 3;
|
||||
final int maxSeconds = 10;
|
||||
final int randomSleepTime = new Random().nextInt((maxSeconds - minSeconds + 1)) + minSeconds;
|
||||
Thread.sleep(randomSleepTime * 1000L);
|
||||
|
||||
// Any arbitrary transactions we want to fetch data for?
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -325,15 +325,24 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
@@ -556,15 +565,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -676,15 +695,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -320,20 +321,36 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddressT3));
|
||||
@@ -561,15 +578,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -681,15 +708,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -333,7 +333,7 @@ public class TradeBot implements Listener {
|
||||
SysTray.getInstance().showMessage("Trade-Bot", String.format("%s: %s", tradeBotData.getAtAddress(), newState), MessageType.INFO);
|
||||
|
||||
if (logMessageSupplier != null)
|
||||
LOGGER.info(logMessageSupplier);
|
||||
LOGGER.info(logMessageSupplier.get());
|
||||
|
||||
LOGGER.debug(() -> String.format("new state for trade-bot entry based on AT %s: %s", tradeBotData.getAtAddress(), newState));
|
||||
|
||||
|
@@ -147,7 +147,7 @@ public class Settings {
|
||||
private int blockCacheSize = 10;
|
||||
|
||||
/** Maximum number of transactions for the block minter to include in a block */
|
||||
private int maxTransactionsPerBlock = 25;
|
||||
private int maxTransactionsPerBlock = 50;
|
||||
|
||||
/** How long to keep old, full, AT state data (ms). */
|
||||
private long atStatesMaxLifetime = 5 * 24 * 60 * 60 * 1000L; // milliseconds
|
||||
@@ -227,7 +227,7 @@ public class Settings {
|
||||
private int maxRetries = 2;
|
||||
|
||||
/** The number of seconds of no activity before recovery mode begins */
|
||||
public long recoveryModeTimeout = 24 * 60 * 60 * 1000L;
|
||||
public long recoveryModeTimeout = 9999999999999L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "4.1.2";
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package org.qortal.transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -8,7 +7,6 @@ import java.util.stream.Collectors;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
|
||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
@@ -88,8 +86,14 @@ public class ArbitraryTransaction extends Transaction {
|
||||
if (this.transactionData.getFee() < 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// After the feature trigger, we require the fee to be sufficient if it's not 0.
|
||||
// If the fee is zero, then the nonce is validated in isSignatureValid() as an alternative to a fee
|
||||
// As of the mempow transaction updates timestamp, a nonce is no longer supported, so a valid fee must be included
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// Validate the fee
|
||||
return super.isFeeValid();
|
||||
}
|
||||
|
||||
// After the earlier "optional fee" feature trigger, we required the fee to be sufficient if it wasn't 0.
|
||||
// If the fee was zero, then the nonce was validated in isSignatureValid() as an alternative to a fee
|
||||
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) {
|
||||
return super.isFeeValid();
|
||||
}
|
||||
@@ -214,7 +218,13 @@ public class ArbitraryTransaction extends Transaction {
|
||||
// Clear nonce from transactionBytes
|
||||
ArbitraryTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// As of feature-trigger timestamp, we only require a nonce when the fee is zero
|
||||
// As of the mempow transaction updates timestamp, a nonce is no longer supported, so a fee must be included
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// Require that the fee is a positive number. Fee checking itself is performed in isFeeValid()
|
||||
return (this.arbitraryTransactionData.getFee() > 0L);
|
||||
}
|
||||
|
||||
// As of the earlier "optional fee" feature-trigger timestamp, we only required a nonce when the fee was zero
|
||||
boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp();
|
||||
if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) {
|
||||
// We only need to check nonce for recent transactions due to PoW verification overhead
|
||||
|
@@ -148,6 +148,12 @@ public class ChatTransaction extends Transaction {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// CHAT transactions can't go into blocks
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
@@ -33,7 +33,9 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
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 = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V1 = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V2_CONFIRMABLE = 16; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V2_UNCONFIRMABLE = 12; // leading zero bits
|
||||
|
||||
// Properties
|
||||
|
||||
@@ -109,7 +111,17 @@ public class MessageTransaction extends Transaction {
|
||||
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Calculate nonce
|
||||
this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY));
|
||||
this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty()));
|
||||
}
|
||||
|
||||
public int getPoWDifficulty() {
|
||||
// The difficulty changes at the "mempow transactions updates" timestamp
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// If this message is confirmable then require a higher difficulty
|
||||
return this.isConfirmable() ? POW_DIFFICULTY_V2_CONFIRMABLE : POW_DIFFICULTY_V2_UNCONFIRMABLE;
|
||||
}
|
||||
// Before feature trigger timestamp, so use existing difficulty value
|
||||
return POW_DIFFICULTY_V1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +195,18 @@ public class MessageTransaction extends Transaction {
|
||||
return super.hasValidReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// After feature trigger timestamp, only messages to an AT address can confirm
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
if (this.messageTransactionData.getRecipient() == null || !this.messageTransactionData.getRecipient().toUpperCase().startsWith("A")) {
|
||||
// Message isn't to an AT address, so this transaction is unconfirmable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
@@ -235,7 +259,7 @@ public class MessageTransaction extends Transaction {
|
||||
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Check nonce
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty(), nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,6 +280,11 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Only certain MESSAGE transactions are able to confirm
|
||||
if (!this.isConfirmable()) {
|
||||
throw new DataException("Unconfirmable MESSAGE transactions should never be processed");
|
||||
}
|
||||
|
||||
// If we have no amount then there's nothing to do
|
||||
if (this.messageTransactionData.getAmount() == 0L)
|
||||
return;
|
||||
@@ -280,6 +309,11 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Only certain MESSAGE transactions are able to confirm
|
||||
if (!this.isConfirmable()) {
|
||||
throw new DataException("Unconfirmable MESSAGE transactions should never be orphaned");
|
||||
}
|
||||
|
||||
// If we have no amount then there's nothing to do
|
||||
if (this.messageTransactionData.getAmount() == 0L)
|
||||
return;
|
||||
|
@@ -155,6 +155,12 @@ public class PresenceTransaction extends Transaction {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// PRESENCE transactions can't go into blocks
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
@@ -7,6 +7,7 @@ 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.block.BlockChain;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@@ -89,6 +90,12 @@ public class PublicizeTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Disable completely after feature-trigger timestamp, at the same time that mempow difficulties are being increased.
|
||||
// It could be enabled again in the future, but preferably with an enforced minimum fee instead of allowing a mempow nonce.
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
return ValidationResult.NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// There can be only one
|
||||
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
||||
TransactionType.PUBLICIZE,
|
||||
|
@@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.TransactionImporter;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.group.GroupApprovalData;
|
||||
@@ -247,7 +248,8 @@ public abstract class Transaction {
|
||||
GROUP_APPROVAL_REQUIRED(98),
|
||||
ACCOUNT_NOT_TRANSFERABLE(99),
|
||||
INVALID_BUT_OK(999),
|
||||
NOT_YET_RELEASED(1000);
|
||||
NOT_YET_RELEASED(1000),
|
||||
NOT_SUPPORTED(1001);
|
||||
|
||||
public final int value;
|
||||
|
||||
@@ -377,7 +379,7 @@ public abstract class Transaction {
|
||||
* @return
|
||||
*/
|
||||
public long getUnitFee(Long timestamp) {
|
||||
return BlockChain.getInstance().getUnitFee();
|
||||
return BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -617,7 +619,10 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
List<TransactionData> unconfirmedTransactions = TransactionImporter.getInstance().unconfirmedTransactionsCache;
|
||||
if (unconfirmedTransactions == null) {
|
||||
unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
}
|
||||
|
||||
// We exclude CHAT transactions as they never get included into blocks and
|
||||
// have spam/DoS prevention by requiring proof of work
|
||||
@@ -632,7 +637,7 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted, unconfirmed transactions, excluding invalid.
|
||||
* Returns sorted, unconfirmed transactions, excluding invalid and unconfirmable.
|
||||
*
|
||||
* @return sorted, unconfirmed transactions
|
||||
* @throws DataException
|
||||
@@ -650,7 +655,8 @@ public abstract class Transaction {
|
||||
TransactionData transactionData = unconfirmedTransactionsIterator.next();
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
|
||||
if (transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK)
|
||||
// Must be confirmable and valid
|
||||
if (!transaction.isConfirmable() || transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK)
|
||||
unconfirmedTransactionsIterator.remove();
|
||||
}
|
||||
|
||||
@@ -888,6 +894,17 @@ public abstract class Transaction {
|
||||
/* To be optionally overridden */
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether transaction is 'confirmable' - i.e. is of a type that
|
||||
* can be included in a block. Some transactions are 'unconfirmable'
|
||||
* and therefore must remain in the mempool until they expire.
|
||||
* @return
|
||||
*/
|
||||
public boolean isConfirmable() {
|
||||
/* To be optionally overridden */
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether transaction can be added to the blockchain.
|
||||
* <p>
|
||||
|
@@ -3,8 +3,12 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" },
|
||||
{ "timestamp": 1692118800000, "fee": "0.01" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" },
|
||||
{ "timestamp": 1651420800000, "fee": "1.25" }
|
||||
],
|
||||
@@ -25,6 +29,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
||||
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 5.00 },
|
||||
{ "height": 259201, "reward": 4.75 },
|
||||
|
@@ -3,6 +3,8 @@ package org.qortal.test;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -39,13 +41,14 @@ public class MemoryPoWTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleComputes() {
|
||||
public void testMultipleComputes() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
Random random = new Random();
|
||||
|
||||
final int sampleSize = 20;
|
||||
final int sampleSize = 10;
|
||||
final long stddevDivisor = sampleSize * (sampleSize - 1);
|
||||
|
||||
for (int difficulty = 8; difficulty < 16; difficulty += 2) {
|
||||
for (int difficulty = 8; difficulty <= 16; difficulty++) {
|
||||
byte[] data = new byte[256];
|
||||
long[] times = new long[sampleSize];
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -14,12 +15,9 @@ import org.qortal.group.Group.ApprovalThreshold;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.GroupUtils;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
@@ -31,6 +29,7 @@ import org.qortal.utils.NTP;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class MessageTests extends Common {
|
||||
@@ -85,7 +84,7 @@ public class MessageTests extends Common {
|
||||
byte[] randomReference = new byte[64];
|
||||
random.nextBytes(randomReference);
|
||||
|
||||
long minimumFee = BlockChain.getInstance().getUnitFee();
|
||||
long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis());
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
@@ -139,7 +138,7 @@ public class MessageTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withRecipentWithAmount() throws DataException {
|
||||
public void withRecipientWithAmount() throws DataException {
|
||||
testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT);
|
||||
}
|
||||
|
||||
@@ -153,6 +152,140 @@ public class MessageTests extends Common {
|
||||
testMessage(1, null, 0L, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(16, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
// Transaction should not be present in db yet
|
||||
List<MessageTransactionData> messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null);
|
||||
assertTrue(messageTransactionsData.isEmpty());
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
// Transaction should be found when trade bot searches for it
|
||||
messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null);
|
||||
assertEquals(1, messageTransactionsData.size());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, null, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientWithFeeNoNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, true, false, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(16, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientWithFeeNoNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, true, false, recipient, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
|
||||
|
||||
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction)); // All MESSAGE transactions would confirm before feature trigger
|
||||
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializationTests() throws DataException, TransformationException {
|
||||
// with recipient, with amount
|
||||
@@ -165,6 +298,24 @@ public class MessageTests extends Common {
|
||||
testSerialization(null, 0L, null);
|
||||
}
|
||||
|
||||
private String deployAt() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
String address = deployAtTransaction.getATAccount().getAddress();
|
||||
assertNotNull(address);
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTransactionConfirmed(Repository repository, MessageTransaction transaction) throws DataException {
|
||||
TransactionData queriedTransactionData = repository.getTransactionRepository().fromSignature(transaction.getTransactionData().getSignature());
|
||||
return queriedTransactionData.getBlockHeight() != null && queriedTransactionData.getBlockHeight() > 0;
|
||||
}
|
||||
|
||||
private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
@@ -195,41 +346,48 @@ public class MessageTests extends Common {
|
||||
return messageTransaction.hasValidReference();
|
||||
}
|
||||
|
||||
private void testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
|
||||
|
||||
private MessageTransaction testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
int txGroupId = 0;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
long assetId = Asset.QORT;
|
||||
byte[] data = new byte[1];
|
||||
boolean isText = false;
|
||||
boolean isEncrypted = false;
|
||||
|
||||
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
|
||||
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
|
||||
|
||||
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
|
||||
|
||||
if (withFee)
|
||||
transactionData.setFee(transaction.calcRecommendedFee());
|
||||
else
|
||||
transactionData.setFee(0L);
|
||||
|
||||
if (withNonce) {
|
||||
transaction.computeNonce();
|
||||
} else {
|
||||
transactionData.setNonce(-1);
|
||||
}
|
||||
|
||||
transaction.sign(alice);
|
||||
|
||||
assertEquals(isValid, transaction.isSignatureValid());
|
||||
return testFeeNonce(repository, withFee, withNonce, recipient, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
private void testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
private MessageTransaction testFeeNonce(Repository repository, boolean withFee, boolean withNonce, String recipient, boolean isValid) throws DataException {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
int txGroupId = 0;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
long assetId = Asset.QORT;
|
||||
byte[] data = new byte[1];
|
||||
boolean isText = false;
|
||||
boolean isEncrypted = false;
|
||||
|
||||
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
|
||||
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
|
||||
|
||||
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
|
||||
|
||||
if (withFee)
|
||||
transactionData.setFee(transaction.calcRecommendedFee());
|
||||
else
|
||||
transactionData.setFee(0L);
|
||||
|
||||
if (withNonce) {
|
||||
transaction.computeNonce();
|
||||
} else {
|
||||
transactionData.setNonce(-1);
|
||||
}
|
||||
|
||||
transaction.sign(alice);
|
||||
|
||||
assertEquals(isValid, transaction.isSignatureValid());
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private MessageTransaction testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
@@ -244,6 +402,8 @@ public class MessageTests extends Common {
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
return new MessageTransaction(repository, transactionData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import org.qortal.arbitrary.ArbitraryDataReader;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
@@ -24,6 +25,7 @@ import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -106,8 +108,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -157,8 +160,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -220,8 +224,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -272,8 +277,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -316,8 +322,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the metadata is correct
|
||||
|
@@ -10,6 +10,7 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.PaymentData;
|
||||
@@ -50,51 +51,6 @@ public class ArbitraryTransactionTests extends Common {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifficultyTooLow() throws IllegalAccessException, DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||
String name = "TEST"; // Can be anything for this test
|
||||
String identifier = null; // Not used for this test
|
||||
Service service = Service.ARBITRARY_DATA;
|
||||
int chunkSize = 100;
|
||||
int dataLength = 900; // Actual data length will be longer due to encryption
|
||||
|
||||
// Register the name to Alice
|
||||
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
|
||||
registerNameTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, registerNameTransactionData, alice);
|
||||
|
||||
// Set difficulty to 1
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
|
||||
|
||||
// Check that nonce validation succeeds
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
||||
ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData);
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
|
||||
// Increase difficulty to 15
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 15, true);
|
||||
|
||||
// Make sure the nonce validation fails
|
||||
// Note: there is a very tiny chance this could succeed due to being extremely lucky
|
||||
// and finding a high difficulty nonce in the first couple of cycles. It will be rare
|
||||
// enough that we shouldn't need to account for it.
|
||||
assertFalse(transaction.isSignatureValid());
|
||||
|
||||
// Reduce difficulty back to 1, to double check
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonceAndFee() throws IllegalAccessException, DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
@@ -497,8 +453,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
null, null, null, null);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
@@ -556,8 +513,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
@@ -614,8 +572,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
null, null, null, null);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
|
@@ -5,10 +5,12 @@ import org.qortal.arbitrary.ArbitraryDataFile;
|
||||
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@@ -20,16 +22,15 @@ import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ArbitraryUtils {
|
||||
|
||||
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
||||
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
|
||||
int chunkSize) throws DataException {
|
||||
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service,
|
||||
account, chunkSize, 0L, true, null, null, null, null);
|
||||
account, chunkSize, fee, false, null, null, null, null);
|
||||
}
|
||||
|
||||
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
||||
@@ -47,7 +48,9 @@ public class ArbitraryUtils {
|
||||
}
|
||||
ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData();
|
||||
Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account);
|
||||
assertEquals(Transaction.ValidationResult.OK, result);
|
||||
if (result != Transaction.ValidationResult.OK) {
|
||||
throw new DataException(String.format("Arbitrary transaction invalid: %s", result.toString()));
|
||||
}
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder
|
||||
|
@@ -7,13 +7,15 @@ import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public abstract class TestTransaction {
|
||||
|
||||
protected static final Random random = new Random();
|
||||
|
||||
public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException {
|
||||
return new BaseTransactionData(System.currentTimeMillis(), txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return new BaseTransactionData(timestamp, txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp), null);
|
||||
}
|
||||
|
||||
public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException {
|
||||
|
@@ -1,121 +1,59 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
|
||||
public class BitcoinTests extends Common {
|
||||
public class BitcoinTests extends BitcoinyTests {
|
||||
|
||||
private Bitcoin bitcoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Bitcoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "BTC";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Bitcoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
System.out.println(String.format("Starting BTC instance..."));
|
||||
System.out.println(String.format("BTC instance started"));
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
public void testGetMedianBlockTime() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
public void testBuildSpend() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(bitcoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(bitcoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
public void testGetWalletBalance() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = bitcoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testGetUnusedReceiveAddress() {}
|
||||
}
|
||||
|
130
src/test/java/org/qortal/test/crosschain/BitcoinyTests.java
Normal file
130
src/test/java/org/qortal/test/crosschain/BitcoinyTests.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public abstract class BitcoinyTests extends Common {
|
||||
|
||||
protected Bitcoiny bitcoiny;
|
||||
|
||||
protected abstract String getCoinName();
|
||||
|
||||
protected abstract String getCoinSymbol();
|
||||
|
||||
protected abstract Bitcoiny getCoin();
|
||||
|
||||
protected abstract void resetCoinForTesting();
|
||||
|
||||
protected abstract String getDeterministicKey58();
|
||||
|
||||
protected abstract String getRecipient();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoiny = getCoin();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
resetCoinForTesting();
|
||||
bitcoiny = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||
System.out.println(String.format("Starting " + getCoinSymbol() + " instance..."));
|
||||
System.out.println(String.format(getCoinSymbol() + " instance started"));
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
makeGetMedianBlockTimeAssertions(firstPeriod, secondPeriod);
|
||||
}
|
||||
|
||||
public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) {
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoiny, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
String recipient = getRecipient();
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
Long balance = bitcoiny.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(bitcoiny.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = bitcoiny.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(bitcoiny.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
String address = bitcoiny.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
@@ -1,115 +1,48 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Digibyte;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class DigibyteTests extends Common {
|
||||
public class DigibyteTests extends BitcoinyTests {
|
||||
|
||||
private Digibyte digibyte;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
digibyte = Digibyte.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Digibyte";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "DGB";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Digibyte.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Digibyte.resetForTesting();
|
||||
digibyte = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(digibyte, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
public void testBuildSpend() {}
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
|
||||
Long balance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(digibyte.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(digibyte.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
|
||||
String address = digibyte.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,115 +1,47 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Dogecoin;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import java.util.Arrays;
|
||||
public class DogecoinTests extends BitcoinyTests {
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DogecoinTests extends Common {
|
||||
|
||||
private Dogecoin dogecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
dogecoin = Dogecoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Dogecoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "DOGE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Dogecoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Dogecoin.resetForTesting();
|
||||
dogecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(dogecoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String recipient = "DP1iFao33xdEPa5vaArpj7sykfzKNeiJeX";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
Long balance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(dogecoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(dogecoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String address = dogecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testBuildSpend() {}
|
||||
}
|
||||
|
@@ -1,113 +1,43 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class LitecoinTests extends Common {
|
||||
public class LitecoinTests extends BitcoinyTests {
|
||||
|
||||
private Litecoin litecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
litecoin = Litecoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Litecoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "LTC";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Litecoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Litecoin.resetForTesting();
|
||||
litecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(litecoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(litecoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = litecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testFindHtlcSecret() {}
|
||||
}
|
||||
|
@@ -3,57 +3,53 @@ package org.qortal.test.crosschain;
|
||||
import cash.z.wallet.sdk.rpc.CompactFormats.*;
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.controller.tradebot.TradeBot;
|
||||
import org.qortal.crosschain.*;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.transform.TransformationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
|
||||
|
||||
public class PirateChainTests extends Common {
|
||||
public class PirateChainTests extends BitcoinyTests {
|
||||
|
||||
private PirateChain pirateChain;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
pirateChain = PirateChain.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "PirateChain";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "ARRR";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return PirateChain.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Litecoin.resetForTesting();
|
||||
pirateChain = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return null;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) {
|
||||
assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,7 +58,7 @@ public class PirateChainTests extends Common {
|
||||
int count = 20;
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
List<CompactBlock> compactBlocks = pirateChain.getCompactBlocks(startHeight, count);
|
||||
List<CompactBlock> compactBlocks = ((PirateChain) bitcoiny).getCompactBlocks(startHeight, count);
|
||||
long after = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Retrieval took: %d ms", after-before));
|
||||
@@ -82,7 +78,7 @@ public class PirateChainTests extends Common {
|
||||
Bytes.reverse(txBytes);
|
||||
String txHashBE = HashCode.fromBytes(txBytes).toString();
|
||||
|
||||
byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE);
|
||||
byte[] rawTransaction = bitcoiny.getBlockchainProvider().getRawTransaction(txHashBE);
|
||||
assertNotNull(rawTransaction);
|
||||
}
|
||||
|
||||
@@ -121,7 +117,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(FUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -130,7 +126,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REDEEMED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -139,14 +135,14 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REFUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTxidForUnspentAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);
|
||||
String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||
@@ -161,7 +157,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
String txid = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
String txid = PirateChainHTLC.getUnspentFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||
@@ -174,7 +170,7 @@ public class PirateChainTests extends Common {
|
||||
@Test
|
||||
public void testGetTxidForSpentAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
||||
String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);
|
||||
String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes();
|
||||
@@ -187,7 +183,7 @@ public class PirateChainTests extends Common {
|
||||
@Test
|
||||
public void testGetTransactionsForAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
||||
List<BitcoinyTransaction> transactions = pirateChain.getBlockchainProvider()
|
||||
List<BitcoinyTransaction> transactions = bitcoiny.getBlockchainProvider()
|
||||
.getAddressBitcoinyTransactions(p2shAddress, false);
|
||||
|
||||
assertEquals(2, transactions.size());
|
||||
@@ -232,66 +228,17 @@ public class PirateChainTests extends Common {
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(pirateChain, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = pirateChain.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = pirateChain.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
public void testBuildSpend() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = pirateChain.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(pirateChain.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = pirateChain.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(pirateChain.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
public void testGetWalletBalance() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = pirateChain.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
||||
public void testGetUnusedReceiveAddress() {}
|
||||
}
|
@@ -1,115 +1,47 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Ravencoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class RavencoinTests extends Common {
|
||||
public class RavencoinTests extends BitcoinyTests {
|
||||
|
||||
private Ravencoin ravencoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
ravencoin = Ravencoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Ravencoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "RVN";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Ravencoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Ravencoin.resetForTesting();
|
||||
ravencoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(ravencoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = ravencoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = ravencoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
|
||||
Long balance = ravencoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(ravencoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = ravencoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(ravencoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
|
||||
String address = ravencoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testBuildSpend() {}
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ public class BuySellTests extends Common {
|
||||
bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
name = "test name" + " " + random.nextInt(1_000_000);
|
||||
price = random.nextInt(1000) * Amounts.MULTIPLIER;
|
||||
price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER;
|
||||
}
|
||||
|
||||
@After
|
||||
|
@@ -20,6 +20,7 @@ import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.PaymentTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -329,15 +330,19 @@ public class MiscTests extends Common {
|
||||
public void testRegisterNameFeeIncrease() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set nameRegistrationUnitFeeTimestamp to a time far in the future
|
||||
// Add original fee to nameRegistrationUnitFees
|
||||
UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp();
|
||||
originalFee.timestamp = 0L;
|
||||
originalFee.fee = new AmountTypeAdapter().unmarshal("0.1");
|
||||
|
||||
// Add a time far in the future to nameRegistrationUnitFees
|
||||
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(futureFeeIncrease), true);
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, futureFeeIncrease), true);
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT
|
||||
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT
|
||||
|
||||
@@ -362,7 +367,7 @@ public class MiscTests extends Common {
|
||||
futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
||||
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease, futureFeeIncrease), true);
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true);
|
||||
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
@@ -387,4 +392,124 @@ public class MiscTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// test reading the name registration fee schedule from blockchain.json / test-chain-v2.json
|
||||
@Test
|
||||
public void testRegisterNameFeeScheduleInTestchainData() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json
|
||||
final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("5");
|
||||
|
||||
assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 5 QORT
|
||||
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// test general unit fee increase
|
||||
@Test
|
||||
public void testUnitFeeIncrease() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Add original fee to unitFees
|
||||
UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp();
|
||||
originalFee.timestamp = 0L;
|
||||
originalFee.fee = new AmountTypeAdapter().unmarshal("0.1");
|
||||
|
||||
// Add a time far in the future to unitFees
|
||||
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("1");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, futureFeeIncrease), true);
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||
assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 1 QORT
|
||||
|
||||
// Payment
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
PrivateKeyAccount chloe = Common.getTestAccount(repository, "chloe");
|
||||
|
||||
PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Set fee increase to a time in the past
|
||||
Long now = NTP.getTime();
|
||||
UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp();
|
||||
pastFeeIncrease.timestamp = now - 1000L; // 1 second ago
|
||||
pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3");
|
||||
|
||||
// Set another increase in the future
|
||||
futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
||||
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true);
|
||||
assertEquals(originalFee.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(originalFee.timestamp));
|
||||
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Send another payment transaction
|
||||
// Fee should be determined automatically
|
||||
transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000);
|
||||
assertEquals(300000000L, transactionData.getFee().longValue());
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertEquals("Transaction should be valid", ValidationResult.OK, result);
|
||||
|
||||
// Now try fetching and setting fee manually
|
||||
transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), chloe.getAddress(), 50000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(300000000L, transactionData.getFee().longValue());
|
||||
transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
result = transaction.importAsUnconfirmed();
|
||||
assertEquals("Transaction should be valid", ValidationResult.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
// test reading the fee schedule from blockchain.json / test-chain-v2.json
|
||||
@Test
|
||||
public void testFeeScheduleInTestchainData() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json
|
||||
final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("1");
|
||||
|
||||
assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT
|
||||
assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 1 QORT
|
||||
|
||||
// Payment
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -15,6 +18,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -19,6 +22,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -19,6 +22,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 0,
|
||||
"unitFee": "0.00000001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.00000001" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.00000001" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,9 +4,13 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 9999999999999, "fee": "1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 9999999999999, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
@@ -20,6 +24,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
@@ -4,7 +4,9 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "1.25" }
|
||||
],
|
||||
|
Reference in New Issue
Block a user