forked from Qortal/qortal
Compare commits
25 Commits
ignore-old
...
v1.5.2
Author | SHA1 | Date | |
---|---|---|---|
|
654dc5bff3 | ||
|
13dcf7f72a | ||
|
65c26f17df | ||
|
3bedba71d5 | ||
|
1ba64d9745 | ||
|
84bf570243 | ||
|
28d50bccf9 | ||
|
66711c2e9d | ||
|
92d8c37d7d | ||
|
5824f75669 | ||
|
deb8adafc9 | ||
|
d2649b237c | ||
|
6532c258f6 | ||
|
83e2b10904 | ||
|
23a9eea26b | ||
|
e4874f86f9 | ||
|
e300a957e4 | ||
|
1c38afcd25 | ||
|
a06faa7685 | ||
|
019ab2b21d | ||
|
f6ba5f5d51 | ||
|
45efe7cd56 | ||
|
78cac7f0e6 | ||
|
a1a1b8e94a | ||
|
641a658059 |
9
pom.xml
9
pom.xml
@@ -3,12 +3,12 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.5.2</version>
|
||||
<packaging>jar</packaging>
|
||||
<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>
|
||||
|
23
src/main/java/org/qortal/api/model/BlockMintingInfo.java
Normal file
23
src/main/java/org/qortal/api/model/BlockMintingInfo.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class BlockMintingInfo {
|
||||
|
||||
public byte[] minterPublicKey;
|
||||
public int minterLevel;
|
||||
public int onlineAccountsCount;
|
||||
public BigDecimal maxDistance;
|
||||
public BigInteger keyDistance;
|
||||
public double keyDistanceRatio;
|
||||
public long timestamp;
|
||||
public long timeDelta;
|
||||
|
||||
public BlockMintingInfo() {
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
return "true";
|
||||
} finally {
|
||||
blockchainLock.unlock();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// We couldn't lock blockchain to perform export
|
||||
return "false";
|
||||
repository.exportNodeLocalData();
|
||||
return "true";
|
||||
} 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();
|
||||
|
@@ -8,6 +8,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -20,10 +23,13 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiErrors;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.model.BlockMintingInfo;
|
||||
import org.qortal.api.model.BlockSignerSummary;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
@@ -328,6 +334,59 @@ public class BlocksResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/byheight/{height}/mintinginfo")
|
||||
@Operation(
|
||||
summary = "Fetch block minter info using block height",
|
||||
description = "Returns the minter info for the block with given height",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockMintingInfo getBlockMintingInfoByHeight(@PathParam("height") int height) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
if (blockData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
BlockData parentBlockData = repository.getBlockRepository().fromSignature(blockData.getReference());
|
||||
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockData.getMinterPublicKey());
|
||||
if (minterLevel == 0)
|
||||
// This may be unavailable when requesting a trimmed block
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
|
||||
BigInteger distance = block.calcKeyDistance(parentBlockData.getHeight(), parentBlockData.getSignature(), blockData.getMinterPublicKey(), minterLevel);
|
||||
double ratio = new BigDecimal(distance).divide(new BigDecimal(block.MAX_DISTANCE), 40, RoundingMode.DOWN).doubleValue();
|
||||
long timestamp = block.calcTimestamp(parentBlockData, blockData.getMinterPublicKey(), minterLevel);
|
||||
long timeDelta = timestamp - parentBlockData.getTimestamp();
|
||||
|
||||
BlockMintingInfo blockMintingInfo = new BlockMintingInfo();
|
||||
blockMintingInfo.minterPublicKey = blockData.getMinterPublicKey();
|
||||
blockMintingInfo.minterLevel = minterLevel;
|
||||
blockMintingInfo.onlineAccountsCount = blockData.getOnlineAccountsCount();
|
||||
blockMintingInfo.maxDistance = new BigDecimal(block.MAX_DISTANCE);
|
||||
blockMintingInfo.keyDistance = distance;
|
||||
blockMintingInfo.keyDistanceRatio = ratio;
|
||||
blockMintingInfo.timestamp = timestamp;
|
||||
blockMintingInfo.timeDelta = timeDelta;
|
||||
|
||||
return blockMintingInfo;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/timestamp/{timestamp}")
|
||||
@Operation(
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -232,7 +232,7 @@ public class Block {
|
||||
|
||||
// Other useful constants
|
||||
|
||||
private static final BigInteger MAX_DISTANCE;
|
||||
public static final BigInteger MAX_DISTANCE;
|
||||
static {
|
||||
byte[] maxValue = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
Arrays.fill(maxValue, (byte) 0xFF);
|
||||
@@ -831,7 +831,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;
|
||||
}
|
||||
@@ -2011,6 +2011,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)
|
||||
|
@@ -677,7 +677,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())
|
||||
|
@@ -111,6 +111,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?
|
||||
@@ -131,10 +132,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;
|
||||
}
|
||||
|
||||
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));
|
||||
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 {
|
||||
@@ -172,7 +178,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);
|
||||
@@ -282,7 +288,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);
|
||||
@@ -292,7 +300,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;
|
||||
}
|
||||
}
|
||||
@@ -302,15 +310,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
|
||||
@@ -383,8 +399,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);
|
||||
}
|
||||
@@ -406,6 +422,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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,14 +443,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +470,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.
|
||||
@@ -486,7 +511,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
|
||||
@@ -568,7 +593,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;
|
||||
@@ -597,8 +622,12 @@ public class Synchronizer {
|
||||
blockSummariesBatch = this.getBlockSummaries(peer, testSignature, step);
|
||||
|
||||
if (blockSummariesBatch == null) {
|
||||
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
|
||||
LOGGER.info(String.format("Error while trying to find common block with peer %s", peer));
|
||||
return SynchronizationResult.NO_REPLY;
|
||||
}
|
||||
|
||||
@@ -785,19 +814,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 ...
|
||||
@@ -813,7 +836,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;
|
||||
}
|
||||
|
||||
@@ -837,20 +860,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 ...
|
||||
@@ -866,7 +882,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);
|
||||
|
@@ -75,7 +75,7 @@ public enum Handshake {
|
||||
// Ensure the peer is running at least the minimum version allowed for connections
|
||||
final String minPeerVersion = Settings.getInstance().getMinPeerVersion();
|
||||
if (peer.isAtLeastVersion(minPeerVersion) == false) {
|
||||
LOGGER.info(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString));
|
||||
LOGGER.debug(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -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");
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
// We need to combine existing TradeBotStates data before overwriting
|
||||
String fileName = "qortal-backup/TradeBotStates.json";
|
||||
File tradeBotStatesBackupFile = new File(fileName);
|
||||
if (tradeBotStatesBackupFile.exists()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
FileWriter writer = new FileWriter(fileName);
|
||||
writer.write(allTradeBotDataJson.toString());
|
||||
writer.close();
|
||||
LOGGER.info("Exported sensitive/node-local data: trade bot states");
|
||||
|
||||
} 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());
|
||||
LOGGER.info(() -> String.format("Importing data into repository from %s", filename));
|
||||
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
|
||||
|
148
tools/block-timings.sh
Executable file
148
tools/block-timings.sh
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
start_height=$1
|
||||
count=$2
|
||||
target=$3
|
||||
deviation=$4
|
||||
power=$5
|
||||
|
||||
if [ -z "${start_height}" ]; then
|
||||
echo
|
||||
echo "Error: missing start height."
|
||||
echo
|
||||
echo "Usage:"
|
||||
echo "block-timings.sh <startheight> [count] [target] [deviation] [power]"
|
||||
echo
|
||||
echo "startheight: a block height, preferably within the untrimmed range, to avoid data gaps"
|
||||
echo "count: the number of blocks to request and analyse after the start height. Default: 100"
|
||||
echo "target: the target block time in milliseconds. Originates from blockchain.json. Default: 60000"
|
||||
echo "deviation: the allowed block time deviation in milliseconds. Originates from blockchain.json. Default: 30000"
|
||||
echo "power: used when transforming key distance to a time offset. Originates from blockchain.json. Default: 0.2"
|
||||
echo
|
||||
exit
|
||||
fi
|
||||
|
||||
count=${count:=100}
|
||||
target=${target:=60000}
|
||||
deviation=${deviation:=30000}
|
||||
power=${power:=0.2}
|
||||
|
||||
finish_height=$((start_height + count - 1))
|
||||
height=$start_height
|
||||
|
||||
echo "Settings:"
|
||||
echo "Target time offset: ${target}"
|
||||
echo "Deviation: ${deviation}"
|
||||
echo "Power transform: ${power}"
|
||||
echo
|
||||
|
||||
function calculate_time_offset {
|
||||
local key_distance_ratio=$1
|
||||
local transformed=$( echo "" | awk "END {print ${key_distance_ratio} ^ ${power}}")
|
||||
local time_offset=$(echo "${deviation}*2*${transformed}" | bc)
|
||||
time_offset=${time_offset%.*}
|
||||
echo $time_offset
|
||||
}
|
||||
|
||||
|
||||
function fetch_and_process_blocks {
|
||||
|
||||
echo "Fetching blocks from height ${start_height} to ${finish_height}..."
|
||||
echo
|
||||
|
||||
total_time_offset=0
|
||||
errors=0
|
||||
|
||||
while [ "${height}" -le "${finish_height}" ]; do
|
||||
block_minting_info=$(curl -s "http://localhost:12391/blocks/byheight/${height}/mintinginfo")
|
||||
error=$(echo "${block_minting_info}" | jq -r .error)
|
||||
if [ "${error}" != "null" ]; then
|
||||
echo "Error fetching minting info for block ${height}"
|
||||
echo
|
||||
errors=$((errors+1))
|
||||
height=$((height+1))
|
||||
continue;
|
||||
fi
|
||||
|
||||
# Parse minting info
|
||||
minter_level=$(echo "${block_minting_info}" | jq -r .minterLevel)
|
||||
online_accounts_count=$(echo "${block_minting_info}" | jq -r .onlineAccountsCount)
|
||||
key_distance_ratio=$(echo "${block_minting_info}" | jq -r .keyDistanceRatio)
|
||||
time_delta=$(echo "${block_minting_info}" | jq -r .timeDelta)
|
||||
|
||||
time_offset=$(calculate_time_offset "${key_distance_ratio}")
|
||||
block_time=$((target-deviation+time_offset))
|
||||
|
||||
echo "=== BLOCK ${height} ==="
|
||||
echo "Minter level: ${minter_level}"
|
||||
echo "Online accounts: ${online_accounts_count}"
|
||||
echo "Key distance ratio: ${key_distance_ratio}"
|
||||
echo "Time offset: ${time_offset}"
|
||||
echo "Block time (real): ${time_delta}"
|
||||
echo "Block time (calculated): ${block_time}"
|
||||
|
||||
if [ "${time_delta}" -ne "${block_time}" ]; then
|
||||
echo "WARNING: Block time mismatch. This is to be expected when using custom settings."
|
||||
fi
|
||||
echo
|
||||
|
||||
total_time_offset=$((total_time_offset+block_time))
|
||||
|
||||
height=$((height+1))
|
||||
done
|
||||
|
||||
adjusted_count=$((count-errors))
|
||||
if [ "${adjusted_count}" -eq 0 ]; then
|
||||
echo "No blocks were retrieved."
|
||||
echo
|
||||
exit;
|
||||
fi
|
||||
|
||||
mean_time_offset=$((total_time_offset/adjusted_count))
|
||||
time_offset_diff=$((mean_time_offset-target))
|
||||
|
||||
echo "==================="
|
||||
echo "===== SUMMARY ====="
|
||||
echo "==================="
|
||||
echo "Total blocks retrieved: ${adjusted_count}"
|
||||
echo "Total blocks failed: ${errors}"
|
||||
echo "Mean time offset: ${mean_time_offset}ms"
|
||||
echo "Target time offset: ${target}ms"
|
||||
echo "Difference from target: ${time_offset_diff}ms"
|
||||
echo
|
||||
|
||||
}
|
||||
|
||||
function estimate_key_distance_ratio_for_level {
|
||||
local level=$1
|
||||
local example_key_distance="0.5"
|
||||
echo "(${example_key_distance}/${level})"
|
||||
}
|
||||
|
||||
function estimate_block_timestamps {
|
||||
min_block_time=9999999
|
||||
max_block_time=0
|
||||
|
||||
echo "===== BLOCK TIME ESTIMATES ====="
|
||||
|
||||
for level in {1..10}; do
|
||||
example_key_distance_ratio=$(estimate_key_distance_ratio_for_level "${level}")
|
||||
time_offset=$(calculate_time_offset "${example_key_distance_ratio}")
|
||||
block_time=$((target-deviation+time_offset))
|
||||
|
||||
if [ "${block_time}" -gt "${max_block_time}" ]; then
|
||||
max_block_time=${block_time}
|
||||
fi
|
||||
if [ "${block_time}" -lt "${min_block_time}" ]; then
|
||||
min_block_time=${block_time}
|
||||
fi
|
||||
|
||||
echo "Level: ${level}, time offset: ${time_offset}, block time: ${block_time}"
|
||||
done
|
||||
block_time_range=$((max_block_time-min_block_time))
|
||||
echo "Range: ${block_time_range}"
|
||||
echo
|
||||
}
|
||||
|
||||
fetch_and_process_blocks
|
||||
estimate_block_timestamps
|
Reference in New Issue
Block a user