From f94655a0edd0262a4c729302d62fc2478f21e71f Mon Sep 17 00:00:00 2001 From: DcruBro Date: Fri, 15 May 2026 22:32:34 +0200 Subject: [PATCH] segfaults and orphans --- src/block/chain.c | 15 +++++++++++++++ src/nets/net_node.c | 43 ++++++++++++++++++++++++++++++++---------- src/nets/orphan_pool.c | 21 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/block/chain.c b/src/block/chain.c index 627a857..b2f9f40 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -151,6 +151,15 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) { pthread_rwlock_wrlock(&chainLock); pthread_mutex_lock(&balanceSheetLock); + // Ensure the incoming block's header.blockNumber matches the index it will be appended at. + size_t expectedIndex = DynArr_size(chain->blocks); + if (block->header.blockNumber != expectedIndex) { + // Mismatched block number; reject to avoid duplicate indices or inconsistent headers. + pthread_mutex_unlock(&balanceSheetLock); + pthread_rwlock_unlock(&chainLock); + return false; + } + do { // First pass: ensure all non-coinbase senders can cover the full spend // (amount1 + amount2 + fee) before mutating the chain or balance sheet. @@ -521,9 +530,12 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren uint64_t byteCount = (uint64_t)pos; // Get the size // Save blocks that are not yet saved + // Acquire write lock to protect block transaction pointers from concurrent freeing. + pthread_rwlock_wrlock(&chainLock); for (size_t i = savedSize; i < DynArr_size(chain->blocks); i++) { block_t* blk = (block_t*)DynArr_at(chain->blocks, i); if (!blk) { + pthread_rwlock_unlock(&chainLock); fclose(metaFile); fclose(chainFile); fclose(tableFile); @@ -542,6 +554,7 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren for (size_t j = 0; j < txSize; j++) { signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j); if (fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) { + pthread_rwlock_unlock(&chainLock); fclose(chainFile); fclose(metaFile); fclose(tableFile); @@ -562,6 +575,8 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk } + pthread_rwlock_unlock(&chainLock); + // Update metadata with new size and last block hash size_t newSize = DynArr_size(chain->blocks); fseek(metaFile, 0, SEEK_SET); diff --git a/src/nets/net_node.c b/src/nets/net_node.c index cbe0ab1..936beaa 100644 --- a/src/nets/net_node.c +++ b/src/nets/net_node.c @@ -123,17 +123,40 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char* } // If parent is missing, insert into orphan pool instead of rejecting immediately. - if (blk->header.blockNumber > 0) { - uint64_t parentIndex = blk->header.blockNumber - 1; - block_t* parentCopy = NULL; - if (parentIndex >= Chain_Size(currentChain) || !Chain_GetBlockCopy(currentChain, (size_t)parentIndex, &parentCopy) || !parentCopy) { - // Insert into orphan pool and take ownership of blk - OrphanPool_Insert(blk, blockHeight); - if (parentCopy) Block_Destroy(parentCopy); - printf("Queued orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight); - return NODE_BLOCK_ORPHAN_QUEUED; + uint64_t chainSize = Chain_Size(currentChain); + if (blk->header.blockNumber > chainSize) { + // Parent(s) missing; queue as orphan + OrphanPool_Insert(blk, blockHeight); + printf("Queued orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight); + return NODE_BLOCK_ORPHAN_QUEUED; + } else if (blk->header.blockNumber < chainSize) { + // Older block than current chain tip: reject + printf("Rejected BLOCK_DATA at height %" PRIu64 ": older than current chain\n", blockHeight); + DynArr_destroy(blk->transactions); + free(blk); + return NODE_BLOCK_REJECTED; + } else { + // blk->header.blockNumber == chainSize -> candidate to append. Ensure prevHash matches current tip. + if (chainSize > 0) { + block_t* last = NULL; + if (!Chain_GetBlockCopy(currentChain, (size_t)(chainSize - 1), &last) || !last) { + // Can't verify parent; queue as orphan conservatively + OrphanPool_Insert(blk, blockHeight); + printf("Queued orphan BLOCK_DATA at height %" PRIu64 " (unable to verify parent)\n", blockHeight); + if (last) Block_Destroy(last); + return NODE_BLOCK_ORPHAN_QUEUED; + } + uint8_t lastHash[32]; + Block_CalculateHash(last, lastHash); + if (memcmp(lastHash, blk->header.prevHash, 32) != 0) { + // Conflicting block at same height; queue as orphan until resolved by a subsequent extension. + OrphanPool_Insert(blk, blockHeight); + Block_Destroy(last); + printf("Queued conflicting BLOCK_DATA at same height %" PRIu64 " as orphan\n", blockHeight); + return NODE_BLOCK_ORPHAN_QUEUED; + } + Block_Destroy(last); } - Block_Destroy(parentCopy); } if (!Chain_AddBlock(currentChain, blk)) { diff --git a/src/nets/orphan_pool.c b/src/nets/orphan_pool.c index b87600c..a4c2bd2 100644 --- a/src/nets/orphan_pool.c +++ b/src/nets/orphan_pool.c @@ -67,6 +67,27 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) { } if (parentExists) { + // Verify that the parent's hash matches the orphan's prevHash before attaching. + bool parentMatches = false; + if (e->height == 0) { + parentMatches = (Chain_Size(chain) == 0); + } else { + block_t* parent = NULL; + if (Chain_GetBlockCopy(chain, (size_t)parentIndex, &parent) && parent) { + uint8_t parentHash[32]; + Block_CalculateHash(parent, parentHash); + parentMatches = (memcmp(parentHash, e->block->header.prevHash, 32) == 0); + Block_Destroy(parent); + } else { + parentMatches = false; + } + } + + if (!parentMatches) { + // Parent exists but does not match this orphan's prevHash; skip attaching now. + continue; + } + // Try to add to chain if (Chain_AddBlock(chain, e->block)) { attached++;