orphans and wallet files

This commit is contained in:
2026-05-28 13:01:23 +02:00
parent 4f10f013f6
commit 4cfe85f6f2
6 changed files with 375 additions and 169 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -468,117 +468,133 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
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;
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;
}
// 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
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);
// 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);
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);
DynArr_destroy(blk->transactions);
blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk
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;
}
pthread_rwlock_unlock(&chainLock);
if (loadedTemp) {
Block_Destroy(diskCopy);
} else if (blk->transactions) {
DynArr_destroy(blk->transactions);
blk->transactions = NULL;
}
}
// 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(&currentReward, 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;
}

View File

@@ -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 <ipv4>\n");
printf("usage: connect <ipv4> [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 <ipv4> [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;
}

View File

@@ -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) {

View File

@@ -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) {
@@ -85,63 +174,15 @@ 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++;
}
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;
size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height);
if (adopted > 0) {
attached += adopted;
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;
}