@ -100,6 +100,13 @@ public class BlockChain {
/** Whether only one registered name is allowed per account. */
/** Whether only one registered name is allowed per account. */
private boolean oneNamePerAccount = false ;
private boolean oneNamePerAccount = false ;
/** Checkpoints */
public static class Checkpoint {
public int height ;
public String signature ;
}
private List < Checkpoint > checkpoints ;
/** Block rewards by block height */
/** Block rewards by block height */
public static class RewardByHeight {
public static class RewardByHeight {
public int height ;
public int height ;
@ -381,6 +388,10 @@ public class BlockChain {
return this . oneNamePerAccount ;
return this . oneNamePerAccount ;
}
}
public List < Checkpoint > getCheckpoints ( ) {
return this . checkpoints ;
}
public List < RewardByHeight > getBlockRewardsByHeight ( ) {
public List < RewardByHeight > getBlockRewardsByHeight ( ) {
return this . rewardsByHeight ;
return this . rewardsByHeight ;
}
}
@ -679,6 +690,7 @@ public class BlockChain {
boolean isTopOnly = Settings . getInstance ( ) . isTopOnly ( ) ;
boolean isTopOnly = Settings . getInstance ( ) . isTopOnly ( ) ;
boolean archiveEnabled = Settings . getInstance ( ) . isArchiveEnabled ( ) ;
boolean archiveEnabled = Settings . getInstance ( ) . isArchiveEnabled ( ) ;
boolean isLite = Settings . getInstance ( ) . isLite ( ) ;
boolean canBootstrap = Settings . getInstance ( ) . getBootstrap ( ) ;
boolean canBootstrap = Settings . getInstance ( ) . getBootstrap ( ) ;
boolean needsArchiveRebuild = false ;
boolean needsArchiveRebuild = false ;
BlockData chainTip ;
BlockData chainTip ;
@ -699,23 +711,45 @@ public class BlockChain {
}
}
}
}
}
}
}
boolean hasBlocks = ( chainTip ! = null & & chainTip . getHeight ( ) > 1 ) ;
// Validate checkpoints
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
if ( isTopOnly & & hasBlocks ) {
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
// Top-only mode is enabled and we have blocks, so it's possible that the genesis block has been pruned
if ( isTopOnly & & ! isLite ) {
// It's best not to validate it, and there's no real need to
List < Checkpoint > checkpoints = BlockChain . getInstance ( ) . getCheckpoints ( ) ;
} else {
for ( Checkpoint checkpoint : checkpoints ) {
// Check first block is Genesis Block
BlockData blockData = repository . getBlockRepository ( ) . fromHeight ( checkpoint . height ) ;
if ( ! isGenesisBlockValid ( ) | | needsArchiveRebuild ) {
if ( blockData = = null ) {
try {
// Try the archive
rebuildBlockchain ( ) ;
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 ;
}
} catch ( InterruptedException e ) {
byte [ ] signature = Base58 . decode ( checkpoint . signature ) ;
throw new DataException ( String . format ( "Interrupted when trying to rebuild blockchain: %s" , e . getMessage ( ) ) ) ;
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 ( ) ) ;
}
}
}
}
}
// Check first block is Genesis Block
if ( ! isGenesisBlockValid ( ) | | needsArchiveRebuild ) {
try {
rebuildBlockchain ( ) ;
} catch ( InterruptedException e ) {
throw new DataException ( String . format ( "Interrupted when trying to rebuild blockchain: %s" , e . getMessage ( ) ) ) ;
}
}
}
// We need to create a new connection, as the previous repository and its connections may be been
// We need to create a new connection, as the previous repository and its connections may be been