forked from Qortal/qortal
Merge branch 'master' into sync-multiple-blocks
# Conflicts: # src/main/java/org/qortal/controller/Synchronizer.java
This commit is contained in:
commit
27aeb4f05f
7
pom.xml
7
pom.xml
@ -8,7 +8,7 @@
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
<altcoinj.version>bf9fb80</altcoinj.version>
|
||||
<bitcoinj.version>0.15.6</bitcoinj.version>
|
||||
<bitcoinj.version>0.15.10</bitcoinj.version>
|
||||
<bouncycastle.version>1.64</bouncycastle.version>
|
||||
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
||||
<ciyam-at.version>1.3.8</ciyam-at.version>
|
||||
@ -439,6 +439,11 @@
|
||||
<artifactId>json-simple</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20210307</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
|
@ -542,19 +542,8 @@ public class AdminResource {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
|
||||
blockchainLock.lockInterruptibly();
|
||||
|
||||
try {
|
||||
repository.exportNodeLocalData(true);
|
||||
repository.exportNodeLocalData();
|
||||
return "true";
|
||||
} finally {
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// We couldn't lock blockchain to perform export
|
||||
return "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@ -564,7 +553,7 @@ public class AdminResource {
|
||||
@Path("/repository/data")
|
||||
@Operation(
|
||||
summary = "Import data into repository.",
|
||||
description = "Imports data from file on local machine. Filename is forced to 'import.script' if apiKey is not set.",
|
||||
description = "Imports data from file on local machine. Filename is forced to 'import.json' if apiKey is not set.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -588,7 +577,7 @@ public class AdminResource {
|
||||
|
||||
// Hard-coded because it's too dangerous to allow user-supplied filenames in weaker security contexts
|
||||
if (Settings.getInstance().getApiKey() == null)
|
||||
filename = "import.script";
|
||||
filename = "import.json";
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
|
@ -255,14 +255,19 @@ public class CrossChainResource {
|
||||
description = "foreign blockchain",
|
||||
example = "LITECOIN",
|
||||
schema = @Schema(implementation = SupportedBlockchain.class)
|
||||
) @PathParam("blockchain") SupportedBlockchain foreignBlockchain) {
|
||||
) @PathParam("blockchain") SupportedBlockchain foreignBlockchain,
|
||||
@Parameter(
|
||||
description = "Maximum number of trades to include in price calculation",
|
||||
example = "10",
|
||||
schema = @Schema(type = "integer", defaultValue = "10")
|
||||
) @QueryParam("maxtrades") Integer maxtrades) {
|
||||
// foreignBlockchain is required
|
||||
if (foreignBlockchain == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
// We want both a minimum of 5 trades and enough trades to span at least 4 hours
|
||||
int minimumCount = 5;
|
||||
int maximumCount = 10;
|
||||
int maximumCount = maxtrades != null ? maxtrades : 10;
|
||||
long minimumPeriod = 4 * 60 * 60 * 1000L; // ms
|
||||
Boolean isFinished = Boolean.TRUE;
|
||||
|
||||
|
@ -321,7 +321,7 @@ public class PeersResource {
|
||||
boolean force = true;
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
|
||||
SynchronizationResult findCommonBlockResult = Synchronizer.getInstance().fetchSummariesFromCommonBlock(repository, targetPeer, ourInitialHeight, force, peerBlockSummaries);
|
||||
SynchronizationResult findCommonBlockResult = Synchronizer.getInstance().fetchSummariesFromCommonBlock(repository, targetPeer, ourInitialHeight, force, peerBlockSummaries, true);
|
||||
if (findCommonBlockResult != SynchronizationResult.OK)
|
||||
return null;
|
||||
|
||||
|
@ -843,7 +843,7 @@ public class Block {
|
||||
if (NTP.getTime() >= BlockChain.getInstance().getCalcChainWeightTimestamp() && parentHeight >= maxHeight)
|
||||
break;
|
||||
}
|
||||
LOGGER.debug(String.format("Chain weight calculation was based on %d blocks", blockCount));
|
||||
LOGGER.trace(String.format("Chain weight calculation was based on %d blocks", blockCount));
|
||||
|
||||
return cumulativeWeight;
|
||||
}
|
||||
@ -2023,6 +2023,7 @@ public class Block {
|
||||
LOGGER.debug(String.format("Timestamp: %d", this.getBlockData().getTimestamp()));
|
||||
LOGGER.debug(String.format("Minter level: %d", minterLevel));
|
||||
LOGGER.debug(String.format("Online accounts: %d", this.getBlockData().getOnlineAccountsCount()));
|
||||
LOGGER.debug(String.format("AT count: %d", this.getBlockData().getATCount()));
|
||||
|
||||
BlockSummaryData blockSummaryData = new BlockSummaryData(this.getBlockData());
|
||||
if (this.getParent() == null || this.getParent().getSignature() == null || blockSummaryData == null || minterLevel == 0)
|
||||
|
@ -691,7 +691,7 @@ public class Controller extends Thread {
|
||||
peers.removeIf(hasInferiorChainTip);
|
||||
|
||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||
if (peersRemoved > 0)
|
||||
if (peersRemoved > 0 && peers.size() > 0)
|
||||
LOGGER.info(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size()));
|
||||
|
||||
if (peers.isEmpty())
|
||||
|
@ -119,6 +119,7 @@ public class Synchronizer {
|
||||
LOGGER.debug(String.format("Searching for common blocks with %d peers...", peers.size()));
|
||||
final long startTime = System.currentTimeMillis();
|
||||
int commonBlocksFound = 0;
|
||||
boolean wereNewRequestsMade = false;
|
||||
|
||||
for (Peer peer : peers) {
|
||||
// Are we shutting down?
|
||||
@ -139,10 +140,15 @@ public class Synchronizer {
|
||||
Synchronizer.getInstance().findCommonBlockWithPeer(peer, repository);
|
||||
if (peer.getCommonBlockData() != null)
|
||||
commonBlocksFound++;
|
||||
|
||||
// This round wasn't served entirely from the cache, so we may want to log the results
|
||||
wereNewRequestsMade = true;
|
||||
}
|
||||
|
||||
if (wereNewRequestsMade) {
|
||||
final long totalTimeTaken = System.currentTimeMillis() - startTime;
|
||||
LOGGER.info(String.format("Finished searching for common blocks with %d peer%s. Found: %d. Total time taken: %d ms", peers.size(), (peers.size() != 1 ? "s" : ""), commonBlocksFound, totalTimeTaken));
|
||||
}
|
||||
|
||||
return SynchronizationResult.OK;
|
||||
} finally {
|
||||
@ -180,7 +186,7 @@ public class Synchronizer {
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
|
||||
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, false, peerBlockSummaries);
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, false, peerBlockSummaries, false);
|
||||
if (findCommonBlockResult != SynchronizationResult.OK) {
|
||||
// Logging performed by fetchSummariesFromCommonBlock() above
|
||||
peer.setCommonBlockData(null);
|
||||
@ -290,7 +296,9 @@ public class Synchronizer {
|
||||
return peers;
|
||||
|
||||
// Count the number of blocks this peer has beyond our common block
|
||||
final int peerHeight = peer.getChainTipData().getLastHeight();
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
final int peerHeight = peerChainTipData.getLastHeight();
|
||||
final byte[] peerLastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
// Limit the number of blocks we are comparing. FUTURE: we could request more in batches, but there may not be a case when this is needed
|
||||
int summariesRequired = Math.min(peerAdditionalBlocksAfterCommonBlock, MAXIMUM_REQUEST_SIZE);
|
||||
@ -300,7 +308,7 @@ public class Synchronizer {
|
||||
if (peer.canUseCachedCommonBlockData()) {
|
||||
if (peer.getCommonBlockData().getBlockSummariesAfterCommonBlock() != null) {
|
||||
if (peer.getCommonBlockData().getBlockSummariesAfterCommonBlock().size() == summariesRequired) {
|
||||
LOGGER.debug(String.format("Using cached block summaries for peer %s", peer));
|
||||
LOGGER.trace(String.format("Using cached block summaries for peer %s", peer));
|
||||
useCachedSummaries = true;
|
||||
}
|
||||
}
|
||||
@ -310,15 +318,23 @@ public class Synchronizer {
|
||||
if (summariesRequired > 0) {
|
||||
LOGGER.trace(String.format("Requesting %d block summar%s from peer %s after common block %.8s. Peer height: %d", summariesRequired, (summariesRequired != 1 ? "ies" : "y"), peer, Base58.encode(commonBlockSummary.getSignature()), peerHeight));
|
||||
|
||||
List<BlockSummaryData> blockSummaries = this.getBlockSummaries(peer, commonBlockSummary.getSignature(), summariesRequired);
|
||||
peer.getCommonBlockData().setBlockSummariesAfterCommonBlock(blockSummaries);
|
||||
// Forget any cached summaries
|
||||
peer.getCommonBlockData().setBlockSummariesAfterCommonBlock(null);
|
||||
|
||||
// Request new block summaries
|
||||
List<BlockSummaryData> blockSummaries = this.getBlockSummaries(peer, commonBlockSummary.getSignature(), summariesRequired);
|
||||
if (blockSummaries != null) {
|
||||
LOGGER.trace(String.format("Peer %s returned %d block summar%s", peer, blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y")));
|
||||
|
||||
if (blockSummaries.size() < summariesRequired)
|
||||
// This could mean that the peer has re-orged. But we still have the same common block, so it's safe to proceed with this set of signatures instead.
|
||||
LOGGER.debug(String.format("Peer %s returned %d block summar%s instead of expected %d", peer, blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y"), summariesRequired));
|
||||
// This could mean that the peer has re-orged. Exclude this peer until they return the summaries we expect.
|
||||
LOGGER.debug(String.format("Peer %s returned %d block summar%s instead of expected %d - excluding them from this round", peer, blockSummaries.size(), (blockSummaries.size() != 1 ? "ies" : "y"), summariesRequired));
|
||||
else if (blockSummaryWithSignature(peerLastBlockSignature, blockSummaries) == null)
|
||||
// We don't have a block summary for the peer's reported chain tip, so should exclude it
|
||||
LOGGER.debug(String.format("Peer %s didn't return a block summary with signature %.8s - excluding them from this round", peer, Base58.encode(peerLastBlockSignature)));
|
||||
else
|
||||
// All looks good, so store the retrieved block summaries in the peer's cache
|
||||
peer.getCommonBlockData().setBlockSummariesAfterCommonBlock(blockSummaries);
|
||||
}
|
||||
} else {
|
||||
// There are no block summaries after this common block
|
||||
@ -391,8 +407,8 @@ public class Synchronizer {
|
||||
peers.remove(peer);
|
||||
}
|
||||
else {
|
||||
// Our chain is inferior
|
||||
LOGGER.debug(String.format("Peer %s is on a better chain to us. We will compare the other peers sharing this common block against each other, and drop all peers sharing higher common blocks.", peer));
|
||||
// Our chain is inferior or equal
|
||||
LOGGER.debug(String.format("Peer %s is on an equal or better chain to us. We will compare the other peers sharing this common block against each other, and drop all peers sharing higher common blocks.", peer));
|
||||
dropPeersAfterCommonBlockHeight = commonBlockSummary.getHeight();
|
||||
superiorPeersForComparison.add(peer);
|
||||
}
|
||||
@ -414,6 +430,9 @@ public class Synchronizer {
|
||||
peers.remove(peer);
|
||||
}
|
||||
}
|
||||
// FUTURE: we may want to prefer peers with additional blocks, and compare the additional blocks against each other.
|
||||
// This would fast track us to the best candidate for the latest block.
|
||||
// Right now, peers with the exact same chain as us are treated equally to those with an additional block.
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,14 +451,14 @@ public class Synchronizer {
|
||||
|
||||
for (Peer peer : peers) {
|
||||
if (peer.getCommonBlockData() != null && peer.getCommonBlockData().getCommonBlockSummary() != null) {
|
||||
LOGGER.debug(String.format("Peer %s has common block %.8s", peer, Base58.encode(peer.getCommonBlockData().getCommonBlockSummary().getSignature())));
|
||||
LOGGER.trace(String.format("Peer %s has common block %.8s", peer, Base58.encode(peer.getCommonBlockData().getCommonBlockSummary().getSignature())));
|
||||
|
||||
BlockSummaryData commonBlockSummary = peer.getCommonBlockData().getCommonBlockSummary();
|
||||
if (!commonBlocks.contains(commonBlockSummary))
|
||||
commonBlocks.add(commonBlockSummary);
|
||||
}
|
||||
else {
|
||||
LOGGER.debug(String.format("Peer %s has no common block data. Skipping...", peer));
|
||||
LOGGER.trace(String.format("Peer %s has no common block data. Skipping...", peer));
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,6 +478,12 @@ public class Synchronizer {
|
||||
return minChainLength;
|
||||
}
|
||||
|
||||
private BlockSummaryData blockSummaryWithSignature(byte[] signature, List<BlockSummaryData> blockSummaries) {
|
||||
if (blockSummaries != null)
|
||||
return blockSummaries.stream().filter(blockSummary -> Arrays.equals(blockSummary.getSignature(), signature)).findAny().orElse(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to synchronize blockchain with peer.
|
||||
@ -494,7 +519,7 @@ public class Synchronizer {
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
|
||||
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries);
|
||||
SynchronizationResult findCommonBlockResult = fetchSummariesFromCommonBlock(repository, peer, ourInitialHeight, force, peerBlockSummaries, true);
|
||||
if (findCommonBlockResult != SynchronizationResult.OK) {
|
||||
// Logging performed by fetchSummariesFromCommonBlock() above
|
||||
// Clear our common block cache for this peer
|
||||
@ -576,7 +601,7 @@ public class Synchronizer {
|
||||
* @throws DataException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public SynchronizationResult fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight, boolean force, List<BlockSummaryData> blockSummariesFromCommon) throws DataException, InterruptedException {
|
||||
public SynchronizationResult fetchSummariesFromCommonBlock(Repository repository, Peer peer, int ourHeight, boolean force, List<BlockSummaryData> blockSummariesFromCommon, boolean infoLogWhenNotFound) throws DataException, InterruptedException {
|
||||
// Start by asking for a few recent block hashes as this will cover a majority of reorgs
|
||||
// Failing that, back off exponentially
|
||||
int step = INITIAL_BLOCK_STEP;
|
||||
@ -605,8 +630,12 @@ public class Synchronizer {
|
||||
blockSummariesBatch = this.getBlockSummaries(peer, testSignature, step);
|
||||
|
||||
if (blockSummariesBatch == null) {
|
||||
// No response - give up this time
|
||||
if (infoLogWhenNotFound)
|
||||
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
|
||||
else
|
||||
LOGGER.debug(String.format("Error while trying to find common block with peer %s", peer));
|
||||
|
||||
// No response - give up this time
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
@ -793,19 +822,13 @@ public class Synchronizer {
|
||||
if (cachedCommonBlockData != null)
|
||||
cachedCommonBlockData.setBlockSummariesAfterCommonBlock(null);
|
||||
|
||||
// If we have already received recent or newer blocks from this peer, go ahead and apply them
|
||||
// If we have already received newer blocks from this peer that what we have already, go ahead and apply them
|
||||
if (peerBlocks.size() > 0) {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (ourLatestBlockData != null && peerLatestBlock != null && minLatestBlockTimestamp != null) {
|
||||
|
||||
// If we have received at least one recent block, we can apply them
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
}
|
||||
|
||||
// If our latest block is very old....
|
||||
if (ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
// ... and we have received a block that is more recent than our latest block ...
|
||||
@ -821,7 +844,7 @@ public class Synchronizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated state
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated or incomplete state
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
@ -845,20 +868,13 @@ public class Synchronizer {
|
||||
nextHeight, Base58.encode(nextPeerSignature)));
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
|
||||
// If we have already received recent or newer blocks from this peer, go ahead and apply them
|
||||
// If we have already received newer blocks from this peer that what we have already, go ahead and apply them
|
||||
if (peerBlocks.size() > 0) {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (ourLatestBlockData != null && peerLatestBlock != null && minLatestBlockTimestamp != null) {
|
||||
|
||||
// If we have received at least one recent block, we can apply them
|
||||
if (peerLatestBlock.getBlockData().getTimestamp() > minLatestBlockTimestamp) {
|
||||
LOGGER.debug("Newly received blocks are recent, so we will apply them");
|
||||
break;
|
||||
}
|
||||
|
||||
// If our latest block is very old....
|
||||
if (ourLatestBlockData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
// ... and we have received a block that is more recent than our latest block ...
|
||||
@ -874,7 +890,7 @@ public class Synchronizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated state
|
||||
// Otherwise, give up and move on to the next peer, to avoid putting our chain into an outdated or incomplete state
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
|
||||
} else {
|
||||
|
@ -272,15 +272,9 @@ public class TradeBot implements Listener {
|
||||
// Attempt to backup the trade bot data. This an optional step and doesn't impact trading, so don't throw an exception on failure
|
||||
try {
|
||||
LOGGER.info("About to backup trade bot data...");
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
blockchainLock.lockInterruptibly();
|
||||
try {
|
||||
repository.exportNodeLocalData(true);
|
||||
} finally {
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
} catch (InterruptedException | DataException e) {
|
||||
LOGGER.info(String.format("Failed to obtain blockchain lock when exporting trade bot data: %s", e.getMessage()));
|
||||
repository.exportNodeLocalData();
|
||||
} catch (DataException e) {
|
||||
LOGGER.info(String.format("Repository issue when exporting trade bot data: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,9 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@ -205,6 +208,58 @@ public class TradeBotData {
|
||||
return this.receivingAccountInfo;
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("tradePrivateKey", Base58.encode(this.getTradePrivateKey()));
|
||||
jsonObject.put("acctName", this.getAcctName());
|
||||
jsonObject.put("tradeState", this.getState());
|
||||
jsonObject.put("tradeStateValue", this.getStateValue());
|
||||
jsonObject.put("creatorAddress", this.getCreatorAddress());
|
||||
jsonObject.put("atAddress", this.getAtAddress());
|
||||
jsonObject.put("timestamp", this.getTimestamp());
|
||||
jsonObject.put("qortAmount", this.getQortAmount());
|
||||
if (this.getTradeNativePublicKey() != null) jsonObject.put("tradeNativePublicKey", Base58.encode(this.getTradeNativePublicKey()));
|
||||
if (this.getTradeNativePublicKeyHash() != null) jsonObject.put("tradeNativePublicKeyHash", Base58.encode(this.getTradeNativePublicKeyHash()));
|
||||
jsonObject.put("tradeNativeAddress", this.getTradeNativeAddress());
|
||||
if (this.getSecret() != null) jsonObject.put("secret", Base58.encode(this.getSecret()));
|
||||
if (this.getHashOfSecret() != null) jsonObject.put("hashOfSecret", Base58.encode(this.getHashOfSecret()));
|
||||
jsonObject.put("foreignBlockchain", this.getForeignBlockchain());
|
||||
if (this.getTradeForeignPublicKey() != null) jsonObject.put("tradeForeignPublicKey", Base58.encode(this.getTradeForeignPublicKey()));
|
||||
if (this.getTradeForeignPublicKeyHash() != null) jsonObject.put("tradeForeignPublicKeyHash", Base58.encode(this.getTradeForeignPublicKeyHash()));
|
||||
jsonObject.put("foreignKey", this.getForeignKey());
|
||||
jsonObject.put("foreignAmount", this.getForeignAmount());
|
||||
if (this.getLastTransactionSignature() != null) jsonObject.put("lastTransactionSignature", Base58.encode(this.getLastTransactionSignature()));
|
||||
jsonObject.put("lockTimeA", this.getLockTimeA());
|
||||
if (this.getReceivingAccountInfo() != null) jsonObject.put("receivingAccountInfo", Base58.encode(this.getReceivingAccountInfo()));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static TradeBotData fromJson(JSONObject json) {
|
||||
return new TradeBotData(
|
||||
json.isNull("tradePrivateKey") ? null : Base58.decode(json.getString("tradePrivateKey")),
|
||||
json.isNull("acctName") ? null : json.getString("acctName"),
|
||||
json.isNull("tradeState") ? null : json.getString("tradeState"),
|
||||
json.isNull("tradeStateValue") ? null : json.getInt("tradeStateValue"),
|
||||
json.isNull("creatorAddress") ? null : json.getString("creatorAddress"),
|
||||
json.isNull("atAddress") ? null : json.getString("atAddress"),
|
||||
json.isNull("timestamp") ? null : json.getLong("timestamp"),
|
||||
json.isNull("qortAmount") ? null : json.getLong("qortAmount"),
|
||||
json.isNull("tradeNativePublicKey") ? null : Base58.decode(json.getString("tradeNativePublicKey")),
|
||||
json.isNull("tradeNativePublicKeyHash") ? null : Base58.decode(json.getString("tradeNativePublicKeyHash")),
|
||||
json.isNull("tradeNativeAddress") ? null : json.getString("tradeNativeAddress"),
|
||||
json.isNull("secret") ? null : Base58.decode(json.getString("secret")),
|
||||
json.isNull("hashOfSecret") ? null : Base58.decode(json.getString("hashOfSecret")),
|
||||
json.isNull("foreignBlockchain") ? null : json.getString("foreignBlockchain"),
|
||||
json.isNull("tradeForeignPublicKey") ? null : Base58.decode(json.getString("tradeForeignPublicKey")),
|
||||
json.isNull("tradeForeignPublicKeyHash") ? null : Base58.decode(json.getString("tradeForeignPublicKeyHash")),
|
||||
json.isNull("foreignAmount") ? null : json.getLong("foreignAmount"),
|
||||
json.isNull("foreignKey") ? null : json.getString("foreignKey"),
|
||||
json.isNull("lastTransactionSignature") ? null : Base58.decode(json.getString("lastTransactionSignature")),
|
||||
json.isNull("lockTimeA") ? null : json.getInt("lockTimeA"),
|
||||
json.isNull("receivingAccountInfo") ? null : Base58.decode(json.getString("receivingAccountInfo"))
|
||||
);
|
||||
}
|
||||
|
||||
// Mostly for debugging
|
||||
public String toString() {
|
||||
return String.format("%s: %s (%d)", this.atAddress, this.tradeState, this.tradeStateValue);
|
||||
|
@ -49,7 +49,7 @@ public interface Repository extends AutoCloseable {
|
||||
|
||||
public void performPeriodicMaintenance() throws DataException;
|
||||
|
||||
public void exportNodeLocalData(boolean keepArchivedCopy) throws DataException;
|
||||
public void exportNodeLocalData() throws DataException;
|
||||
|
||||
public void importDataFromFile(String filename) throws DataException;
|
||||
|
||||
|
@ -2,6 +2,7 @@ package org.qortal.repository.hsqldb;
|
||||
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
@ -15,23 +16,19 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.crosschain.TradeBotData;
|
||||
import org.qortal.globalization.Translator;
|
||||
import org.qortal.gui.SysTray;
|
||||
import org.qortal.repository.ATRepository;
|
||||
@ -52,7 +49,7 @@ import org.qortal.repository.TransactionRepository;
|
||||
import org.qortal.repository.VotingRepository;
|
||||
import org.qortal.repository.hsqldb.transaction.HSQLDBTransactionRepository;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.utils.NTP;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
public class HSQLDBRepository implements Repository {
|
||||
|
||||
@ -460,8 +457,7 @@ public class HSQLDBRepository implements Repository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportNodeLocalData(boolean keepArchivedCopy) throws DataException {
|
||||
|
||||
public void exportNodeLocalData() throws DataException {
|
||||
// Create the qortal-backup folder if it doesn't exist
|
||||
Path backupPath = Paths.get("qortal-backup");
|
||||
try {
|
||||
@ -471,52 +467,59 @@ public class HSQLDBRepository implements Repository {
|
||||
throw new DataException("Unable to create backup folder");
|
||||
}
|
||||
|
||||
// We need to rename or delete an existing TradeBotStates backup before creating a new one
|
||||
File tradeBotStatesBackupFile = new File("qortal-backup/TradeBotStates.script");
|
||||
try {
|
||||
// Load trade bot data
|
||||
List<TradeBotData> allTradeBotData = this.getCrossChainRepository().getAllTradeBotData();
|
||||
JSONArray allTradeBotDataJson = new JSONArray();
|
||||
for (TradeBotData tradeBotData : allTradeBotData) {
|
||||
JSONObject tradeBotDataJson = tradeBotData.toJson();
|
||||
allTradeBotDataJson.put(tradeBotDataJson);
|
||||
}
|
||||
|
||||
// We need to combine existing TradeBotStates data before overwriting
|
||||
String fileName = "qortal-backup/TradeBotStates.json";
|
||||
File tradeBotStatesBackupFile = new File(fileName);
|
||||
if (tradeBotStatesBackupFile.exists()) {
|
||||
if (keepArchivedCopy) {
|
||||
// Rename existing TradeBotStates backup, to make sure that we're not overwriting any keys
|
||||
File archivedBackupFile = new File(String.format("qortal-backup/TradeBotStates-archive-%d.script", NTP.getTime()));
|
||||
if (tradeBotStatesBackupFile.renameTo(archivedBackupFile))
|
||||
LOGGER.info(String.format("Moved existing TradeBotStates backup file to %s", archivedBackupFile.getPath()));
|
||||
else
|
||||
throw new DataException("Unable to rename existing TradeBotStates backup");
|
||||
} else {
|
||||
// Delete existing copy
|
||||
LOGGER.info("Deleting existing TradeBotStates backup because it is being replaced with a new one");
|
||||
tradeBotStatesBackupFile.delete();
|
||||
String jsonString = new String(Files.readAllBytes(Paths.get(fileName)));
|
||||
JSONArray allExistingTradeBotData = new JSONArray(jsonString);
|
||||
Iterator<Object> iterator = allExistingTradeBotData.iterator();
|
||||
while(iterator.hasNext()) {
|
||||
JSONObject existingTradeBotData = (JSONObject)iterator.next();
|
||||
String existingTradePrivateKey = (String) existingTradeBotData.get("tradePrivateKey");
|
||||
// Check if we already have an entry for this trade
|
||||
boolean found = allTradeBotData.stream().anyMatch(tradeBotData -> Base58.encode(tradeBotData.getTradePrivateKey()).equals(existingTradePrivateKey));
|
||||
if (found == false)
|
||||
// We need to add this to our list
|
||||
allTradeBotDataJson.put(existingTradeBotData);
|
||||
}
|
||||
}
|
||||
|
||||
// There's currently no need to take an archived copy of the MintingAccounts data - just delete the old one if it exists
|
||||
File mintingAccountsBackupFile = new File("qortal-backup/MintingAccounts.script");
|
||||
if (mintingAccountsBackupFile.exists()) {
|
||||
LOGGER.info("Deleting existing MintingAccounts backup because it is being replaced with a new one");
|
||||
mintingAccountsBackupFile.delete();
|
||||
}
|
||||
FileWriter writer = new FileWriter(fileName);
|
||||
writer.write(allTradeBotDataJson.toString());
|
||||
writer.close();
|
||||
LOGGER.info("Exported sensitive/node-local data: trade bot states");
|
||||
|
||||
try (Statement stmt = this.connection.createStatement()) {
|
||||
stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'qortal-backup/MintingAccounts.script'");
|
||||
stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'qortal-backup/TradeBotStates.script'");
|
||||
LOGGER.info("Exported sensitive/node-local data: minting keys and trade bot states");
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to export sensitive/node-local data from repository");
|
||||
} catch (DataException | IOException e) {
|
||||
throw new DataException("Unable to export trade bot states from repository");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importDataFromFile(String filename) throws DataException {
|
||||
try (Statement stmt = this.connection.createStatement()) {
|
||||
LOGGER.info(() -> String.format("Importing data into repository from %s", filename));
|
||||
|
||||
String escapedFilename = stmt.enquoteLiteral(filename);
|
||||
stmt.execute("PERFORM IMPORT SCRIPT DATA FROM " + escapedFilename + " CONTINUE ON ERROR");
|
||||
|
||||
LOGGER.info(() -> String.format("Imported data into repository from %s", filename));
|
||||
} catch (SQLException e) {
|
||||
LOGGER.info(() -> String.format("Failed to import data into repository from %s: %s", filename, e.getMessage()));
|
||||
throw new DataException("Unable to import sensitive/node-local data to repository: " + e.getMessage());
|
||||
try {
|
||||
String jsonString = new String(Files.readAllBytes(Paths.get(filename)));
|
||||
JSONArray tradeBotDataToImport = new JSONArray(jsonString);
|
||||
Iterator<Object> iterator = tradeBotDataToImport.iterator();
|
||||
while(iterator.hasNext()) {
|
||||
JSONObject tradeBotDataJson = (JSONObject)iterator.next();
|
||||
TradeBotData tradeBotData = TradeBotData.fromJson(tradeBotDataJson);
|
||||
this.getCrossChainRepository().save(tradeBotData);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DataException("Unable to import sensitive/node-local trade bot states to repository: " + e.getMessage());
|
||||
}
|
||||
LOGGER.info(() -> String.format("Imported trade bot states into repository from %s", filename));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user