diff --git a/include/runtime_state.h b/include/runtime_state.h index 198c8ff..1f904df 100644 --- a/include/runtime_state.h +++ b/include/runtime_state.h @@ -14,6 +14,9 @@ extern uint256_t currentSupply; extern uint64_t currentReward; extern uint32_t difficultyTarget; extern const char* chainDataDir; +extern unsigned short listenPort; +extern bool echoPeersEnabled; +extern bool forceOrphanReorgEnabled; // Global synchronization primitives for runtime state extern pthread_rwlock_t chainLock; // protects chain structure and related mutations diff --git a/include/utils.h b/include/utils.h index 086c200..68d7d18 100644 --- a/include/utils.h +++ b/include/utils.h @@ -167,7 +167,7 @@ static inline bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t com return false; } -static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) { +static inline bool GenerateRandomTestAddress(uint8_t outAddress[32], uint8_t outPrivateKey[32], uint8_t outCompressedPubkey[33]) { if (!outAddress) { return false; } @@ -200,11 +200,18 @@ static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) { } AddressFromCompressedPubkey(compressedPubkey, outAddress); + if (outPrivateKey) { + memcpy(outPrivateKey, privateKey, 32); + } + if (outCompressedPubkey) { + memcpy(outCompressedPubkey, compressedPubkey, 33); + } secp256k1_context_destroy(ctx); return true; } secp256k1_context_destroy(ctx); + return false; } diff --git a/src/block/chain.c b/src/block/chain.c index b2f9f40..37c1725 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -467,118 +467,134 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren if (!BuildPath(tablePath, sizeof(tablePath), dirpath, "chain.table")) { return false; } - - // Find metadata file (create if not exists) to get the saved chain size (+ other things) - FILE* metaFile = fopen(metaPath, "rb+"); - FILE* chainFile = fopen(chainPath, "rb+"); - FILE* tableFile = fopen(tablePath, "rb+"); - if (!metaFile || !chainFile || !tableFile) { - // Just overwrite everything - metaFile = fopen(metaPath, "wb+"); - if (!metaFile) { return false; } - // Initialize metadata with size 0 - size_t initialSize = 0; - fwrite(&initialSize, sizeof(size_t), 1, metaFile); - // Write last block hash (32 bytes of zeros for now) - uint8_t zeroHash[32] = {0}; - fwrite(zeroHash, sizeof(uint8_t), 32, metaFile); - uint256_t zeroSupply = {0}; - fwrite(&zeroSupply, sizeof(uint256_t), 1, metaFile); - uint32_t initialTarget = INITIAL_DIFFICULTY; - fwrite(&initialTarget, sizeof(uint32_t), 1, metaFile); - uint64_t initialReward = 0; - fwrite(&initialReward, sizeof(uint64_t), 1, metaFile); - - chainFile = fopen(chainPath, "wb+"); - if (!chainFile) { return false; } - - tableFile = fopen(tablePath, "wb+"); - if (!tableFile) { return false; } - - // TODO: Potentially some other things here, we'll see - } - - // Read - size_t savedSize = 0; - fread(&savedSize, sizeof(size_t), 1, metaFile); - uint8_t lastSavedHash[32]; - fread(lastSavedHash, sizeof(uint8_t), 32, metaFile); - - // Assume chain saved is valid, and that the chain in memory is valid (as LoadFromFile will verify the saved one) - if (savedSize > DynArr_size(chain->blocks)) { - // Saved chain is longer than current chain, this should not happen if we are always saving the current chain, but just in case, fail to save to avoid overwriting a potentially valid longer chain with a shorter one. - fclose(metaFile); - fclose(chainFile); - fclose(tableFile); + char metaTmpPath[512]; + char chainTmpPath[512]; + char tableTmpPath[512]; + if (!BuildPath(metaTmpPath, sizeof(metaTmpPath), dirpath, "chain.meta.tmp") || + !BuildPath(chainTmpPath, sizeof(chainTmpPath), dirpath, "chain.data.tmp") || + !BuildPath(tableTmpPath, sizeof(tableTmpPath), dirpath, "chain.table.tmp")) { return false; } - // Filename format: dirpath/chain.data - // File format: ([block_header][num_transactions][transactions...])[*length] - since block_header is fixed size, LoadFromFile will only read those by default - - fseek(chainFile, 0, SEEK_END); // Seek to the end of those files - fseek(tableFile, 0, SEEK_END); - long pos = ftell(chainFile); - if (pos < 0) { - fclose(metaFile); - fclose(chainFile); - fclose(tableFile); - return false; - } - - 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++) { + + FILE* metaFile = fopen(metaTmpPath, "wb+"); + FILE* chainFile = fopen(chainTmpPath, "wb+"); + FILE* tableFile = fopen(tableTmpPath, "wb+"); + if (!metaFile || !chainFile || !tableFile) { + if (metaFile) fclose(metaFile); + if (chainFile) fclose(chainFile); + if (tableFile) fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } + + const size_t chainSize = DynArr_size(chain->blocks); + uint64_t byteCount = 0; + for (size_t i = 0; i < chainSize; ++i) { block_t* blk = (block_t*)DynArr_at(chain->blocks, i); if (!blk) { - pthread_rwlock_unlock(&chainLock); fclose(metaFile); fclose(chainFile); fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); return false; } - uint64_t preIncrementByteSize = byteCount; - - // Construct file path - // Write block header - fwrite(&blk->header, sizeof(block_header_t), 1, chainFile); - size_t txSize = DynArr_size(blk->transactions); - fwrite(&txSize, sizeof(size_t), 1, chainFile); // Write number of transactions - byteCount += sizeof(block_header_t) + sizeof(size_t); - // Write transactions - 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); + block_t* diskCopy = blk; + bool loadedTemp = false; + if (!diskCopy->transactions) { + if (!Chain_LoadBlockFromFile(dirpath, (uint64_t)i, true, &diskCopy, NULL) || !diskCopy || !diskCopy->transactions) { + if (loadedTemp && diskCopy) { + Block_Destroy(diskCopy); + } fclose(metaFile); + fclose(chainFile); fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); return false; } + loadedTemp = true; + } + const uint64_t blockStart = byteCount; + if (fwrite(&diskCopy->header, sizeof(block_header_t), 1, chainFile) != 1) { + if (loadedTemp) Block_Destroy(diskCopy); + fclose(metaFile); + fclose(chainFile); + fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } + + const size_t txSize = DynArr_size(diskCopy->transactions); + if (fwrite(&txSize, sizeof(size_t), 1, chainFile) != 1) { + if (loadedTemp) Block_Destroy(diskCopy); + fclose(metaFile); + fclose(chainFile); + fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } + byteCount += sizeof(block_header_t) + sizeof(size_t); + + for (size_t j = 0; j < txSize; ++j) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(diskCopy->transactions, j); + if (!tx || fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) { + if (loadedTemp) Block_Destroy(diskCopy); + fclose(metaFile); + fclose(chainFile); + fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } byteCount += sizeof(signed_transaction_t); } - // Create an entry in the block table block_table_entry_t entry; entry.blockNumber = i; - entry.byteNumber = preIncrementByteSize; - entry.blockSize = byteCount - preIncrementByteSize; - fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile); + entry.byteNumber = blockStart; + entry.blockSize = byteCount - blockStart; + if (fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile) != 1) { + if (loadedTemp) Block_Destroy(diskCopy); + fclose(metaFile); + fclose(chainFile); + fclose(tableFile); + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } - DynArr_destroy(blk->transactions); - blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk + if (loadedTemp) { + Block_Destroy(diskCopy); + } else if (blk->transactions) { + DynArr_destroy(blk->transactions); + blk->transactions = NULL; + } } - pthread_rwlock_unlock(&chainLock); - - // Update metadata with new size and last block hash - size_t newSize = DynArr_size(chain->blocks); + size_t newSize = chainSize; fseek(metaFile, 0, SEEK_SET); fwrite(&newSize, sizeof(size_t), 1, metaFile); uint32_t difficultyTarget = INITIAL_DIFFICULTY; @@ -596,16 +612,23 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren fwrite(&difficultyTarget, sizeof(uint32_t), 1, metaFile); fwrite(¤tReward, sizeof(uint64_t), 1, metaFile); - // Safety fflush(metaFile); fflush(chainFile); fflush(tableFile); - // Close all pointers fclose(metaFile); fclose(chainFile); fclose(tableFile); + if (rename(metaTmpPath, metaPath) != 0 || rename(chainTmpPath, chainPath) != 0 || rename(tableTmpPath, tablePath) != 0) { + pthread_rwlock_unlock(&chainLock); + remove(metaTmpPath); + remove(chainTmpPath); + remove(tableTmpPath); + return false; + } + + pthread_rwlock_unlock(&chainLock); return true; } diff --git a/src/main.c b/src/main.c index dfaa8f5..dac21bd 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,9 @@ blockchain_t* currentChain = NULL; const char* chainDataDir = CHAIN_DATA_DIR; +unsigned short listenPort = LISTEN_PORT; +bool echoPeersEnabled = ECHO_PEERS != 0; +bool forceOrphanReorgEnabled = false; uint256_t currentSupply = {{0, 0, 0, 0}}; uint64_t currentReward = 750000000000ULL; @@ -42,6 +45,32 @@ void handle_sigint(int sig) { exit(0); } +static void ApplyRuntimeConfigFromEnv(void) { + const char* dataDir = getenv("SKALACOIN_CHAIN_DATA_DIR"); + if (dataDir && dataDir[0] != '\0') { + chainDataDir = dataDir; + } + + const char* portStr = getenv("SKALACOIN_LISTEN_PORT"); + if (portStr && portStr[0] != '\0') { + char* end = NULL; + long parsed = strtol(portStr, &end, 10); + if (end != portStr && *end == '\0' && parsed > 0 && parsed <= 65535) { + listenPort = (unsigned short)parsed; + } + } + + const char* echoStr = getenv("SKALACOIN_ECHO_PEERS"); + if (echoStr && echoStr[0] != '\0') { + echoPeersEnabled = (strcmp(echoStr, "0") != 0); + } + + const char* forceOrphanStr = getenv("SKALACOIN_FORCE_ORPHAN_REORG"); + if (forceOrphanStr && forceOrphanStr[0] != '\0') { + forceOrphanReorgEnabled = (strcmp(forceOrphanStr, "0") != 0); + } +} + uint32_t difficultyTarget = INITIAL_DIFFICULTY; static bool MineBlock(block_t* block) { @@ -295,6 +324,14 @@ static bool MineAndAppendBlock(blockchain_t* chain, return false; } + uint64_t coinbaseAmount = 0; + if (block->transactions && DynArr_size(block->transactions) > 0) { + signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0); + if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) { + coinbaseAmount = firstTx->transaction.amount1; + } + } + // After successfully appending a block, attempt to attach any orphans. size_t attached = OrphanPool_AttemptAttach(chain); if (attached > 0) { @@ -304,14 +341,6 @@ static bool MineAndAppendBlock(blockchain_t* chain, BalanceSheet_SaveToFile(chainDataDir); } - uint64_t coinbaseAmount = 0; - if (block->transactions && DynArr_size(block->transactions) > 0) { - signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0); - if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) { - coinbaseAmount = firstTx->transaction.amount1; - } - } - (void)uint256_add_u64(currentSupply, coinbaseAmount); uint8_t canonicalHash[32]; @@ -486,6 +515,16 @@ static bool VerifyChainFully(blockchain_t* chain) { return true; } +// Use when error +void KillEverythingAndExit(net_node_t* node, blockchain_t* chain) { + Node_Destroy(node); + currentChain = NULL; + Chain_Destroy(chain); + Block_ShutdownPowContext(); + BalanceSheet_Destroy(); + exit(1); +} + int main(int argc, char* argv[]) { //(void)argc; //(void)argv; @@ -510,6 +549,8 @@ int main(int argc, char* argv[]) { } } + ApplyRuntimeConfigFromEnv(); + signal(SIGINT, handle_sigint); srand((unsigned int)time(NULL)); @@ -577,9 +618,79 @@ int main(int argc, char* argv[]) { } } + // TODO: Separate loading into its own header + // Load the wallet from disk or generate new random identity + uint8_t minerAddress[32]; uint8_t minerPrivateKey[32]; uint8_t minerCompressedPubkey[33]; + bool loadedWallet = false; + + // Attempt load + char* path = "chain_data/wallet.data"; // TODO: Don't hardcode path + FILE* walletFile = fopen(path, "rb"); + if (walletFile) { + size_t read = fread(minerPrivateKey, 1, 32, walletFile); + if (read != 32) { + fprintf(stderr, "failed to read wallet file\n"); + fclose(walletFile); + } + + read = fread(minerCompressedPubkey, 1, 33, walletFile); + if (read != 33) { + fprintf(stderr, "failed to read wallet file\n"); + fclose(walletFile); + } + + read = fread(minerAddress, 1, 32, walletFile); + if (read != 32) { + fprintf(stderr, "failed to read wallet file\n"); + fclose(walletFile); + } + + fclose(walletFile); + loadedWallet = true; + } else if (errno != ENOENT || errno != EISDIR || errno != EACCES || errno != EROFS || !loadedWallet) { + fprintf(stderr, "failed to open wallet file: %s\n generating new wallet...\n", strerror(errno)); + if (!GenerateRandomTestAddress(minerAddress, minerPrivateKey, minerCompressedPubkey)) { + fprintf(stderr, "failed to generate test miner keypair\n"); + KillEverythingAndExit(node, chain); + } + + // Save the generated wallet to disk for future runs + walletFile = fopen(path, "wb"); + if (!walletFile) { + fprintf(stderr, "failed to create wallet file: %s\n", strerror(errno)); + KillEverythingAndExit(node, chain); + } + + size_t written = fwrite(minerPrivateKey, 1, 32, walletFile); + if (written != 32) { + fprintf(stderr, "failed to write wallet file\n"); + fclose(walletFile); + KillEverythingAndExit(node, chain); + } + + written = fwrite(minerCompressedPubkey, 1, 33, walletFile); + if (written != 33) { + fprintf(stderr, "failed to write wallet file\n"); + fclose(walletFile); + KillEverythingAndExit(node, chain); + } + + written = fwrite(minerAddress, 1, 32, walletFile); + if (written != 32) { + fprintf(stderr, "failed to write wallet file\n"); + fclose(walletFile); + KillEverythingAndExit(node, chain); + } + + fclose(walletFile); + } + + /*uint8_t minerAddress[32]; + uint8_t minerPrivateKey[32]; + uint8_t minerCompressedPubkey[33]; if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) { fprintf(stderr, "failed to generate test miner keypair\n"); Node_Destroy(node); @@ -588,7 +699,7 @@ int main(int argc, char* argv[]) { Block_ShutdownPowContext(); BalanceSheet_Destroy(); return 1; - } + }*/ char minerAddressHex[65]; AddressToHexString(minerAddress, minerAddressHex); @@ -1084,9 +1195,10 @@ int main(int argc, char* argv[]) { if (strcmp(cmd, "connect") == 0) { char* ipStr = strtok(NULL, " \t"); + char* portStr = strtok(NULL, " \t"); char* extra = strtok(NULL, " \t"); if (!ipStr || extra) { - printf("usage: connect \n"); + printf("usage: connect [port]\n"); continue; } @@ -1095,16 +1207,31 @@ int main(int argc, char* argv[]) { continue; } - if (Node_ConnectPeer(node, ipStr, LISTEN_PORT) != 0) { + unsigned short peerPort = listenPort; + if (portStr) { + char* end = NULL; + long parsedPort = strtol(portStr, &end, 10); + if (*portStr == '\0' || portStr[0] == '-' || (end && *end != '\0') || parsedPort <= 0 || parsedPort > 65535) { + printf("invalid port\n"); + continue; + } + peerPort = (unsigned short)parsedPort; + if (strtok(NULL, " \t")) { + printf("usage: connect [port]\n"); + continue; + } + } + + if (Node_ConnectPeer(node, ipStr, peerPort) != 0) { if (errno == ETIMEDOUT) { - printf("failed to connect to %s:%u (timeout)\n", ipStr, (unsigned int)LISTEN_PORT); + printf("failed to connect to %s:%u (timeout)\n", ipStr, (unsigned int)peerPort); } else { - printf("failed to connect to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT); + printf("failed to connect to %s:%u\n", ipStr, (unsigned int)peerPort); } continue; } - printf("connect requested to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT); + printf("connect requested to %s:%u\n", ipStr, (unsigned int)peerPort); continue; } @@ -1166,7 +1293,7 @@ int main(int argc, char* argv[]) { if (strcmp(cmd, "genaddr") == 0) { uint8_t testAddress[32]; - if (!GenerateRandomTestAddress(testAddress)) { + if (!GenerateRandomTestAddress(testAddress, NULL, NULL)) { printf("failed to generate address\n"); continue; } diff --git a/src/nets/net_node.c b/src/nets/net_node.c index 936beaa..60997a8 100644 --- a/src/nets/net_node.c +++ b/src/nets/net_node.c @@ -122,6 +122,13 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char* return NODE_BLOCK_REJECTED; } + // Temporary debug mode: force network-received blocks through the orphan pool to exercise reorg handling. + if (forceOrphanReorgEnabled && blk->header.blockNumber > 0) { + OrphanPool_Insert(blk, blockHeight); + printf("Forced orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight); + return NODE_BLOCK_ORPHAN_QUEUED; + } + // If parent is missing, insert into orphan pool instead of rejecting immediately. uint64_t chainSize = Chain_Size(currentChain); if (blk->header.blockNumber > chainSize) { @@ -232,7 +239,7 @@ net_node_t* Node_Create() { pthread_mutex_init(&node->outboundLock, NULL); node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes - TcpServer_Init(node->server, LISTEN_PORT, "0.0.0.0"); + TcpServer_Init(node->server, listenPort, "0.0.0.0"); node->server->owner = node; node->server->on_connect = Node_Server_OnConnect; @@ -382,14 +389,13 @@ void Node_Server_OnConnect(tcp_connection_t* client) { Node_ForwardConnect(node, client); printf("Inbound node connected: %u\n", client ? client->connectionId : 0U); -#if ECHO_PEERS - if (node && client) { - // Attempt to create an outbound connection back to the peer's IP on our LISTEN_PORT. + if (echoPeersEnabled && node && client) { + // Attempt to create an outbound connection back to the peer's IP on our configured port. // We avoid connecting if we already have an outbound to the same IP. char ipbuf[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &client->peerAddr.sin_addr, ipbuf, sizeof(ipbuf))) { - // Use LISTEN_PORT as target port for peer's listening service, not the ephemeral source port. - unsigned short targetPort = LISTEN_PORT; + // Use the configured port as the target port for the peer's listening service. + unsigned short targetPort = listenPort; int shouldConnect = 1; pthread_mutex_lock(&node->outboundLock); @@ -410,7 +416,6 @@ void Node_Server_OnConnect(tcp_connection_t* client) { } } } -#endif } void Node_Server_OnData(tcp_connection_t* client) { diff --git a/src/nets/orphan_pool.c b/src/nets/orphan_pool.c index 7108302..0b7cce1 100644 --- a/src/nets/orphan_pool.c +++ b/src/nets/orphan_pool.c @@ -38,6 +38,71 @@ void OrphanPool_Insert(block_t* block, uint64_t height) { (void)DynArr_push_back(g_orphans, &e); } +static size_t OrphanPool_TryAdoptBranch(blockchain_t* chain, uint64_t forkHeight) { + if (!g_orphans || !chain) return 0; + + DynArr* seq = DYNARR_CREATE(block_t*, 8); + if (!seq) return 0; + + size_t cursor = forkHeight; + while (1) { + bool found = false; + size_t count = DynArr_size(g_orphans); + for (size_t i = 0; i < count; ++i) { + orphan_entry_t* entry = (orphan_entry_t*)DynArr_at(g_orphans, i); + if (!entry || !entry->block) continue; + if (entry->height == cursor) { + (void)DynArr_push_back(seq, &entry->block); + found = true; + break; + } + } + if (!found) break; + cursor++; + } + + size_t seqCount = DynArr_size(seq); + if (seqCount == 0) { + DynArr_destroy(seq); + return 0; + } + + size_t currentTipHeight = Chain_Size(chain) == 0 ? 0 : Chain_Size(chain) - 1; + size_t seqTopHeight = forkHeight + seqCount - 1; + if (seqTopHeight <= currentTipHeight) { + DynArr_destroy(seq); + return 0; + } + + size_t rollbackHeight = (forkHeight == 0) ? 0 : (forkHeight - 1); + if (!Chain_RollbackToHeight(chain, rollbackHeight)) { + DynArr_destroy(seq); + return 0; + } + + size_t attached = 0; + for (size_t i = 0; i < seqCount; ++i) { + block_t* bptr = *(block_t**)DynArr_at(seq, i); + if (!bptr || !Chain_AddBlock(chain, bptr)) { + break; + } + + size_t count = DynArr_size(g_orphans); + for (size_t j = 0; j < count; ++j) { + orphan_entry_t* entry = (orphan_entry_t*)DynArr_at(g_orphans, j); + if (entry && entry->block == bptr) { + DynArr_remove(g_orphans, j); + break; + } + } + + attached++; + } + + DynArr_destroy(seq); + return attached; +} + size_t OrphanPool_AttemptAttach(blockchain_t* chain) { if (!g_orphans || !chain) return 0; size_t attached = 0; @@ -67,6 +132,30 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) { } if (parentExists) { + if (e->height < Chain_Size(chain)) { + block_t* local = NULL; + if (Chain_GetBlockCopy(chain, (size_t)e->height, &local) && local) { + uint8_t localHash[32]; + uint8_t orphanHash[32]; + Block_CalculateHash(local, localHash); + Block_CalculateHash(e->block, orphanHash); + Block_Destroy(local); + + if (memcmp(localHash, orphanHash, 32) != 0) { + size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height); + if (adopted > 0) { + attached += adopted; + madeProgress = true; + n = DynArr_size(g_orphans); + i = (size_t)-1; + break; + } + } + } else if (local) { + Block_Destroy(local); + } + } + // Verify that the parent's hash matches the orphan's prevHash before attaching. bool parentMatches = false; if (e->height == 0) { @@ -84,65 +173,17 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) { } if (!parentMatches) { - // Parent exists but does not match this orphan's prevHash. - // Attempt to detect a longer alternate chain in the orphan pool starting at this height. - // Build a consecutive sequence of orphans from this height upward. - DynArr* seq = DYNARR_CREATE(block_t*, 8); - size_t h = e->height; - while (1) { - bool found = false; - size_t gn = DynArr_size(g_orphans); - for (size_t gi = 0; gi < gn; ++gi) { - orphan_entry_t* oe = (orphan_entry_t*)DynArr_at(g_orphans, gi); - if (!oe || !oe->block) continue; - if (oe->height == h) { - (void)DynArr_push_back(seq, &oe->block); - found = true; - break; - } - } - if (!found) break; - h++; - } + // Parent exists but does not match this orphan's prevHash. + size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height); + if (adopted > 0) { + attached += adopted; + madeProgress = true; + n = DynArr_size(g_orphans); + i = (size_t)-1; + break; + } - size_t seqCount = DynArr_size(seq); - if (seqCount > 0) { - size_t seqTopHeight = e->height + seqCount - 1; - if (seqTopHeight >= Chain_Size(chain)) { - // Found a candidate longer branch. Perform rollback to fork height and attach sequence. - if (Chain_RollbackToHeight(chain, (size_t)e->height)) { - // Attach in-order - for (size_t si = 0; si < seqCount; ++si) { - block_t* bptr = *(block_t**)DynArr_at(seq, si); - if (!Chain_AddBlock(chain, bptr)) { - // failed to add; stop attempting further - break; - } - // Remove the attached orphan from pool but keep the block object to preserve transactions in-memory (consistent with existing behavior) - // Find and remove corresponding orphan entry - size_t gn2 = DynArr_size(g_orphans); - for (size_t gi2 = 0; gi2 < gn2; ++gi2) { - orphan_entry_t* oe2 = (orphan_entry_t*)DynArr_at(g_orphans, gi2); - if (oe2 && oe2->block == bptr) { - DynArr_remove(g_orphans, gi2); - gn2 = DynArr_size(g_orphans); - gi2 = (size_t)-1; // restart search if needed - } - } - } - attached += seqCount; - madeProgress = true; - DynArr_destroy(seq); - // reset outer loop - n = DynArr_size(g_orphans); - i = (size_t)-1; - break; - } - } - } - DynArr_destroy(seq); - // If we didn't perform a reorg/attach, skip for now. - continue; + continue; } // Try to add to chain