Browse Source

Added checkpoint lookup on startup.

Currently enabled for topOnly nodes only. This will detect if the node is on a divergent chain, and will force a bootstrap or resync (depending on settings) in order to rejoin the main chain.
at-states-fix
CalDescent 2 years ago
parent
commit
e91e612b55
  1. 46
      src/main/java/org/qortal/block/BlockChain.java
  2. 3
      src/main/resources/blockchain.json

46
src/main/java/org/qortal/block/BlockChain.java

@ -100,6 +100,13 @@ public class BlockChain {
/** Whether only one registered name is allowed per account. */
private boolean oneNamePerAccount = false;
/** Checkpoints */
public static class Checkpoint {
public int height;
public String signature;
}
private List<Checkpoint> checkpoints;
/** Block rewards by block height */
public static class RewardByHeight {
public int height;
@ -381,6 +388,10 @@ public class BlockChain {
return this.oneNamePerAccount;
}
public List<Checkpoint> getCheckpoints() {
return this.checkpoints;
}
public List<RewardByHeight> getBlockRewardsByHeight() {
return this.rewardsByHeight;
}
@ -679,6 +690,7 @@ public class BlockChain {
boolean isTopOnly = Settings.getInstance().isTopOnly();
boolean archiveEnabled = Settings.getInstance().isArchiveEnabled();
boolean isLite = Settings.getInstance().isLite();
boolean canBootstrap = Settings.getInstance().getBootstrap();
boolean needsArchiveRebuild = false;
BlockData chainTip;
@ -699,14 +711,37 @@ public class BlockChain {
}
}
}
// Validate checkpoints
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
if (isTopOnly && !isLite) {
List<Checkpoint> checkpoints = BlockChain.getInstance().getCheckpoints();
for (Checkpoint checkpoint : checkpoints) {
BlockData blockData = repository.getBlockRepository().fromHeight(checkpoint.height);
if (blockData == null) {
// Try the archive
blockData = repository.getBlockArchiveRepository().fromHeight(checkpoint.height);
}
if (blockData == null) {
LOGGER.trace("Couldn't find block for height {}", checkpoint.height);
// This is likely due to the block being pruned, so is safe to ignore.
// Continue, as there might be other blocks we can check more definitively.
continue;
}
byte[] signature = Base58.decode(checkpoint.signature);
if (!Arrays.equals(signature, blockData.getSignature())) {
LOGGER.info("Error: block at height {} with signature {} doesn't match checkpoint sig: {}. Bootstrapping...", checkpoint.height, Base58.encode(blockData.getSignature()), checkpoint.signature);
needsArchiveRebuild = true;
break;
}
LOGGER.info("Block at height {} matches checkpoint signature", blockData.getHeight());
}
}
boolean hasBlocks = (chainTip != null && chainTip.getHeight() > 1);
}
if (isTopOnly && hasBlocks) {
// Top-only mode is enabled and we have blocks, so it's possible that the genesis block has been pruned
// It's best not to validate it, and there's no real need to
} else {
// Check first block is Genesis Block
if (!isGenesisBlockValid() || needsArchiveRebuild) {
try {
@ -716,7 +751,6 @@ public class BlockChain {
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
}
}
}
// We need to create a new connection, as the previous repository and its connections may be been
// closed by rebuildBlockchain() if a bootstrap was applied

3
src/main/resources/blockchain.json

@ -87,6 +87,9 @@
"feeValidationFixTimestamp": 1671918000000,
"chatReferenceTimestamp": 1674316800000
},
"checkpoints": [
{ "height": 1131800, "signature": "EpRam4PLdKzULMp7xNU7XG964AKfioG3g1k7cxwxWXnXspPwnjfF6UncEz4feuSA9mr1vW5d3YQPGruXYjj4vciSh4SPj5iWRxkHRWFeRpQnmVUyaVumuBTwM8nnLKJTdtkZnd6d8Mc5mVFdHs6EwLBTY4HECoRcbo4e4FwkfqVon4M" }
],
"genesisInfo": {
"version": 4,
"timestamp": "1593450000000",

Loading…
Cancel
Save