Fixed the target computation to trigger on syncs - old system would cause difficulty mismatches on syncs, reorgs, etc.
This commit is contained in:
+11
-1
@@ -41,6 +41,16 @@ bool Chain_LoadFromFile(blockchain_t* chain, const char* dirpath, uint256_t* out
|
|||||||
bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loadTransactions, block_t** outBlock, size_t* outTxCount);
|
bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loadTransactions, block_t** outBlock, size_t* outTxCount);
|
||||||
|
|
||||||
// Difficulty
|
// Difficulty
|
||||||
uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget);
|
// Retarget for the block at `height`, measured over the window [height - INTERVAL, height - 1].
|
||||||
|
// `chain` must hold blocks 0..height-1. Takes no locks; safe to call while holding `chainLock`.
|
||||||
|
uint32_t Chain_ComputeTargetAtHeight(blockchain_t* chain, uint64_t height, uint32_t currentTarget);
|
||||||
|
|
||||||
|
// The consensus-required difficultyTarget for the block at `height`, derived from the chain alone.
|
||||||
|
// Takes no locks; safe to call while holding `chainLock`.
|
||||||
|
uint32_t Chain_GetTargetForHeight(blockchain_t* chain, uint64_t height);
|
||||||
|
|
||||||
|
// Refresh runtime state derived from the chain tip (difficulty target, epoch DAG).
|
||||||
|
// Call after any change to the tip. Must NOT be called while holding `chainLock`.
|
||||||
|
void Chain_OnTipAdvanced(blockchain_t* chain);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+60
-5
@@ -192,6 +192,20 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the block was mined at the difficulty this chain requires at that height. Without this
|
||||||
|
// a peer whose difficulty went stale (or a malicious one) can hand us a block mined at an
|
||||||
|
// easier target, which Block_HasValidProofOfWork accepts because it checks the header's own value.
|
||||||
|
uint32_t expectedTarget = Chain_GetTargetForHeight(chain, (uint64_t)expectedIndex);
|
||||||
|
if (block->header.difficultyTarget != expectedTarget) {
|
||||||
|
printf("Chain_AddBlock: validation failed: blockIndex=%zu expectedDifficulty=%#x observedDifficulty=%#x\n",
|
||||||
|
expectedIndex,
|
||||||
|
(unsigned int)expectedTarget,
|
||||||
|
(unsigned int)block->header.difficultyTarget);
|
||||||
|
pthread_mutex_unlock(&balanceSheetLock);
|
||||||
|
pthread_rwlock_unlock(&chainLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
size_t txCount = DynArr_size(block->transactions);
|
size_t txCount = DynArr_size(block->transactions);
|
||||||
signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t));
|
signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t));
|
||||||
@@ -342,6 +356,11 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
|||||||
pthread_mutex_unlock(&balanceSheetLock);
|
pthread_mutex_unlock(&balanceSheetLock);
|
||||||
pthread_rwlock_unlock(&chainLock);
|
pthread_rwlock_unlock(&chainLock);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
// Every path that appends comes through here, so this is where difficulty/DAG catch up.
|
||||||
|
Chain_OnTipAdvanced(chain);
|
||||||
|
}
|
||||||
|
|
||||||
printf("Added new block to chain:\n");
|
printf("Added new block to chain:\n");
|
||||||
Block_ShortPrint(block);
|
Block_ShortPrint(block);
|
||||||
|
|
||||||
@@ -548,12 +567,16 @@ bool Chain_RollbackToHeight(blockchain_t* chain, size_t height) {
|
|||||||
pthread_mutex_unlock(&balanceSheetLock);
|
pthread_mutex_unlock(&balanceSheetLock);
|
||||||
pthread_rwlock_unlock(&chainLock);
|
pthread_rwlock_unlock(&chainLock);
|
||||||
|
|
||||||
|
// A reorg can move the tip back across an adjustment boundary, so the target must come down too.
|
||||||
|
Chain_OnTipAdvanced(chain);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chain_Wipe(blockchain_t* chain) {
|
void Chain_Wipe(blockchain_t* chain) {
|
||||||
Chain_ClearBlocks(chain);
|
Chain_ClearBlocks(chain);
|
||||||
currentBlockHeight = 0;
|
currentBlockHeight = 0;
|
||||||
|
difficultyTarget = INITIAL_DIFFICULTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply, uint64_t currentReward) {
|
bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t currentSupply, uint64_t currentReward) {
|
||||||
@@ -1093,20 +1116,23 @@ bool Chain_LoadBlockFromFile(const char* dirpath, uint64_t blockNumber, bool loa
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget) {
|
uint32_t Chain_ComputeTargetAtHeight(blockchain_t* chain, uint64_t height, uint32_t currentTarget) {
|
||||||
if (!chain || !chain->blocks) {
|
if (!chain || !chain->blocks) {
|
||||||
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t chainSize = DynArr_size(chain->blocks);
|
if (height < DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
||||||
if (chainSize < DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
|
||||||
// Baby-chain, return initial difficulty
|
// Baby-chain, return initial difficulty
|
||||||
return INITIAL_DIFFICULTY;
|
return INITIAL_DIFFICULTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (height > (uint64_t)DynArr_size(chain->blocks)) {
|
||||||
|
return 0x00; // Retarget window is not fully present in this chain
|
||||||
|
}
|
||||||
|
|
||||||
// Assuming block validation validates timestamps, we can assume they're valid and can just read them
|
// Assuming block validation validates timestamps, we can assume they're valid and can just read them
|
||||||
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, chainSize - 1);
|
block_t* lastBlock = (block_t*)DynArr_at(chain->blocks, (size_t)(height - 1));
|
||||||
block_t* adjustmentBlock = (block_t*)DynArr_at(chain->blocks, chainSize - DIFFICULTY_ADJUSTMENT_INTERVAL);
|
block_t* adjustmentBlock = (block_t*)DynArr_at(chain->blocks, (size_t)(height - DIFFICULTY_ADJUSTMENT_INTERVAL));
|
||||||
if (!lastBlock || !adjustmentBlock) {
|
if (!lastBlock || !adjustmentBlock) {
|
||||||
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
return 0x00; // Impossible difficulty, only valid hash is all zeros (practically impossible)
|
||||||
}
|
}
|
||||||
@@ -1168,3 +1194,32 @@ uint32_t Chain_ComputeNextTarget(blockchain_t* chain, uint32_t currentTarget) {
|
|||||||
|
|
||||||
return (exponent << 24) | (newCoeff & 0x007fffff);
|
return (exponent << 24) | (newCoeff & 0x007fffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Chain_GetTargetForHeight(blockchain_t* chain, uint64_t height) {
|
||||||
|
// The target is a pure function of the chain: replay every adjustment boundary at or below
|
||||||
|
// `height`, starting from the genesis difficulty. Never trust a cached or peer-supplied value.
|
||||||
|
uint32_t target = INITIAL_DIFFICULTY;
|
||||||
|
for (uint64_t h = DIFFICULTY_ADJUSTMENT_INTERVAL; h <= height; h += DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
||||||
|
target = Chain_ComputeTargetAtHeight(chain, h, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chain_OnTipAdvanced(blockchain_t* chain) {
|
||||||
|
if (!chain || !chain->blocks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t chainSize = Chain_Size(chain);
|
||||||
|
|
||||||
|
// Refresh the cached target for the block that comes next, so every path that moves the tip
|
||||||
|
// (mining, P2P accept, sync, orphan attach, reorg) stays on the same difficulty as its peers.
|
||||||
|
difficultyTarget = Chain_GetTargetForHeight(chain, (uint64_t)chainSize);
|
||||||
|
|
||||||
|
if (chainSize % EPOCH_LENGTH == 0 && chainSize > 0) {
|
||||||
|
uint8_t dagSeed[32];
|
||||||
|
GetNextDAGSeed(chain, dagSeed);
|
||||||
|
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(chain), dagSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-19
@@ -406,9 +406,8 @@ static bool Block_GetCoinbaseAndFeeTotals(const block_t* block, uint64_t* outCoi
|
|||||||
static bool MineAndAppendBlock(blockchain_t* chain,
|
static bool MineAndAppendBlock(blockchain_t* chain,
|
||||||
block_t* block,
|
block_t* block,
|
||||||
uint256_t* currentSupply,
|
uint256_t* currentSupply,
|
||||||
uint64_t* currentReward,
|
uint64_t* currentReward) {
|
||||||
uint32_t* difficultyTarget) {
|
if (!chain || !block || !currentSupply || !currentReward) {
|
||||||
if (!chain || !block || !currentSupply || !currentReward || !difficultyTarget) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,15 +464,8 @@ static bool MineAndAppendBlock(blockchain_t* chain,
|
|||||||
|
|
||||||
*currentReward = CalculateBlockReward(*currentSupply, chain);
|
*currentReward = CalculateBlockReward(*currentSupply, chain);
|
||||||
|
|
||||||
if (Chain_Size(chain) % DIFFICULTY_ADJUSTMENT_INTERVAL == 0) {
|
// The difficulty retarget and epoch DAG rebuild happen in Chain_AddBlock, so that blocks we
|
||||||
*difficultyTarget = Chain_ComputeNextTarget(chain, *difficultyTarget);
|
// receive from peers advance them exactly like blocks we mine ourselves.
|
||||||
}
|
|
||||||
|
|
||||||
if (Chain_Size(chain) % EPOCH_LENGTH == 0 && Chain_Size(chain) > 0) {
|
|
||||||
uint8_t dagSeed[32];
|
|
||||||
GetNextDAGSeed(chain, dagSeed);
|
|
||||||
(void)Block_RebuildAutolykos2Dag(CalculateTargetDAGSize(chain), dagSeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -551,12 +543,12 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
|||||||
Block_Destroy(prevBlk);
|
Block_Destroy(prevBlk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine expected difficulty for this block. TODO: Optimize to recompute at adjustment intervals only instead of every block.
|
// Determine expected difficulty for this block. The retarget window (i-INTERVAL..i-1) is
|
||||||
|
// already present in `chain`, so it reads from there rather than the replay copy.
|
||||||
if (i < DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
if (i < DIFFICULTY_ADJUSTMENT_INTERVAL) {
|
||||||
expectedDifficulty = INITIAL_DIFFICULTY;
|
expectedDifficulty = INITIAL_DIFFICULTY;
|
||||||
} else if ((i % DIFFICULTY_ADJUSTMENT_INTERVAL) == 0) {
|
} else if ((i % DIFFICULTY_ADJUSTMENT_INTERVAL) == 0) {
|
||||||
// Compute target using previous blocks only (0..i-1)
|
expectedDifficulty = Chain_ComputeTargetAtHeight(chain, (uint64_t)i, expectedDifficulty);
|
||||||
expectedDifficulty = Chain_ComputeNextTarget(prevChain, expectedDifficulty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the block's header difficulty matches the expected difficulty (can't cheat easier)
|
// Ensure the block's header difficulty matches the expected difficulty (can't cheat easier)
|
||||||
@@ -723,6 +715,10 @@ int main(int argc, char* argv[]) {
|
|||||||
if (!Chain_RecomputeRuntimeState(chain)) {
|
if (!Chain_RecomputeRuntimeState(chain)) {
|
||||||
fprintf(stderr, "Failed to recompute runtime state from loaded chain\n");
|
fprintf(stderr, "Failed to recompute runtime state from loaded chain\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chain.meta stores the tip's own target, which is not the next block's target when the tip
|
||||||
|
// sits on an adjustment boundary. Derive it from the chain instead.
|
||||||
|
Chain_OnTipAdvanced(chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BalanceSheet_LoadFromFile(chainDataDir)) {
|
if (!BalanceSheet_LoadFromFile(chainDataDir)) {
|
||||||
@@ -899,7 +895,7 @@ int main(int argc, char* argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
block_t* block = BuildNextBlock(chain, difficultyTarget);
|
block_t* block = BuildNextBlock(chain, Chain_GetTargetForHeight(chain, (uint64_t)Chain_Size(chain)));
|
||||||
if (!block) {
|
if (!block) {
|
||||||
fprintf(stderr, "failed to create block\n");
|
fprintf(stderr, "failed to create block\n");
|
||||||
free(acceptedTxs);
|
free(acceptedTxs);
|
||||||
@@ -923,7 +919,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
free(acceptedTxs);
|
free(acceptedTxs);
|
||||||
|
|
||||||
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward, &difficultyTarget)) {
|
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward)) {
|
||||||
Block_Destroy(block);
|
Block_Destroy(block);
|
||||||
minedAll = false;
|
minedAll = false;
|
||||||
break;
|
break;
|
||||||
@@ -1000,7 +996,7 @@ int main(int argc, char* argv[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
block_t* block = BuildNextBlock(chain, difficultyTarget);
|
block_t* block = BuildNextBlock(chain, Chain_GetTargetForHeight(chain, (uint64_t)Chain_Size(chain)));
|
||||||
if (!block) {
|
if (!block) {
|
||||||
fprintf(stderr, "failed to create block\n");
|
fprintf(stderr, "failed to create block\n");
|
||||||
continue;
|
continue;
|
||||||
@@ -1029,7 +1025,7 @@ int main(int argc, char* argv[]) {
|
|||||||
AddressToHexString(recipientAddress, recipientHex);
|
AddressToHexString(recipientAddress, recipientHex);
|
||||||
printf("%s\n\nMining block...\n", recipientHex);
|
printf("%s\n\nMining block...\n", recipientHex);
|
||||||
|
|
||||||
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward, &difficultyTarget)) {
|
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward)) {
|
||||||
Block_Destroy(block);
|
Block_Destroy(block);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user