From 763aeb648f45177e1cd3ee0b75ca27b040799f1d Mon Sep 17 00:00:00 2001 From: DcruBro Date: Fri, 29 May 2026 13:44:15 +0200 Subject: [PATCH] add fee-aware mining, coinbase validation, and reorg-safe orphan handling Mining: blocks now include mempool txs, select spendable txs by fee, and pay coinbase as base reward + fees in main.c. - Consensus: block validation now enforces coinbase accounting and rejects invalid coinbase placement, including coinbase on amount2, in block.c and transaction.c. - Chain state: rollback now rebuilds currentSupply/currentReward, and block addition preflights spendability before mutating balances in chain.c. - Orphans/reorgs: orphan retry is safer, rollback-triggered sync reattaches orphans immediately, and transient orphan failures no longer drop blocks in orphan_pool.c and main.c. - Networking/mempool: node lifecycle now initializes the mempool, broadcasts can exclude one peer, and mempool snapshotting supports mining selection in net_node.c and txmempool.c. - Ledger simulation: added non-mutating spendable-transaction selection for block assembly in balance_sheet.c. --- TODO.txt | 5 ++ include/balance_sheet.h | 9 ++ include/block/block.h | 1 + include/nets/net_node.h | 2 +- include/txmempool.h | 1 + src/balance_sheet.c | 195 ++++++++++++++++++++++++++++++++++++++++ src/block/block.c | 77 ++++++++++++++-- src/block/chain.c | 108 +++++++++++++++++----- src/block/transaction.c | 18 +++- src/main.c | 161 +++++++++++++++++++++++++++++++-- src/nets/net_node.c | 23 ++++- src/nets/orphan_pool.c | 9 +- src/txmempool.c | 70 +++++++++++++++ 13 files changed, 634 insertions(+), 45 deletions(-) diff --git a/TODO.txt b/TODO.txt index 6c00673..19cac69 100644 --- a/TODO.txt +++ b/TODO.txt @@ -17,6 +17,11 @@ A loophole in the reorg penalty system could potentially exist where someone bro TO TEST: Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner. +NOTE: +Because tx sizes are currently fixed, mining can use raw fee ordering for now. If tx sizes ever become dynamic, revisit selection to consider fee/byte instead. + +Mempool snapshotting for mining should hold the lock only long enough to copy pending txs, but if the mempool grows very large that copy may still be non-trivial. + DONE: I want to move away from the Monero emission. I want to do something a bit radical for cryptocurrency, but I feel like it's necessary to make it more like money: a constant inflation rate of 1.5% per year. It's lower than fiat (USD is ~2.8% per year), and it additionally doesn't fluctuate during crisis. It's constant. diff --git a/include/balance_sheet.h b/include/balance_sheet.h index c83784b..0b5b5be 100644 --- a/include/balance_sheet.h +++ b/include/balance_sheet.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,4 +30,12 @@ bool BalanceSheet_LoadFromFile(const char* inPath); void BalanceSheet_Print(); void BalanceSheet_Destroy(); +bool BalanceSheet_SelectSpendableTransactions( + const signed_transaction_t* candidates, + size_t candidateCount, + signed_transaction_t** outAccepted, + size_t* outAcceptedCount, + uint64_t* outTotalFees +); + #endif diff --git a/include/block/block.h b/include/block/block.h index 47de693..e37a029 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -36,6 +36,7 @@ void Block_AddTransaction(block_t* block, signed_transaction_t* tx); void Block_RemoveTransaction(block_t* block, uint8_t* txHash); bool Block_HasValidProofOfWork(const block_t* block); bool Block_AllTransactionsValid(const block_t* block); +bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees); bool Block_IsFullyValid(const block_t* block); void Block_ShutdownPowContext(void); void Block_Destroy(block_t* block); diff --git a/include/nets/net_node.h b/include/nets/net_node.h index 5ccbca0..cf35826 100644 --- a/include/nets/net_node.h +++ b/include/nets/net_node.h @@ -57,7 +57,7 @@ int Node_ConnectPeer(net_node_t* node, const char* ip, unsigned short port); int Node_ConnectStartupPeers(net_node_t* node, const char** ips, const unsigned short* ports, size_t peersCount); int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t packetType, const void* payload, size_t payloadLen); -int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx); +int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx, tcp_connection_t* excludeNode); // Helpers for outbound peer selection and block broadcast int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight); diff --git a/include/txmempool.h b/include/txmempool.h index 01e137a..05de667 100644 --- a/include/txmempool.h +++ b/include/txmempool.h @@ -13,6 +13,7 @@ void TxMempool_Init(); // Assumed that the transation was confirmed to be valid int TxMempool_Insert(signed_transaction_t tx); bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out); +bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount); void TxMempool_Print(); void TxMempool_Destroy(); diff --git a/src/balance_sheet.c b/src/balance_sheet.c index 870a284..f12c726 100644 --- a/src/balance_sheet.c +++ b/src/balance_sheet.c @@ -4,6 +4,140 @@ khash_t(balance_sheet_map_m)* sheetMap = NULL; static pthread_mutex_t g_sheetLock; +static bool BalanceSheet_GetSimEntry( + khash_t(balance_sheet_map_m)* simMap, + const uint8_t address[32], + balance_sheet_entry_t* out +) { + if (!simMap || !address || !out) { + return false; + } + + key32_t key; + memcpy(key.bytes, address, 32); + + khiter_t k = kh_get(balance_sheet_map_m, simMap, key); + if (k != kh_end(simMap)) { + *out = kh_value(simMap, k); + return true; + } + + if (BalanceSheet_Lookup((uint8_t*)address, out)) { + int ret = 0; + k = kh_put(balance_sheet_map_m, simMap, key, &ret); + if (k == kh_end(simMap)) { + return false; + } + kh_value(simMap, k) = *out; + return true; + } + + memset(out, 0, sizeof(*out)); + memcpy(out->address, address, 32); + out->balance = uint256_from_u64(0); + + int ret = 0; + k = kh_put(balance_sheet_map_m, simMap, key, &ret); + if (k == kh_end(simMap)) { + return false; + } + + kh_value(simMap, k) = *out; + return true; +} + +static bool BalanceSheet_StoreSimEntry( + khash_t(balance_sheet_map_m)* simMap, + const balance_sheet_entry_t* entry +) { + if (!simMap || !entry) { + return false; + } + + key32_t key; + memcpy(key.bytes, entry->address, 32); + + int ret = 0; + khiter_t k = kh_put(balance_sheet_map_m, simMap, key, &ret); + if (k == kh_end(simMap)) { + return false; + } + + kh_value(simMap, k) = *entry; + return true; +} + +static bool BalanceSheet_ApplyCandidateTransaction( + khash_t(balance_sheet_map_m)* simMap, + const signed_transaction_t* tx, + uint64_t* outFee +) { + if (!simMap || !tx) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + return true; + } + + if (!Transaction_Verify(tx)) { + return false; + } + + balance_sheet_entry_t senderEntry; + if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.senderAddress, &senderEntry)) { + return false; + } + + uint256_t spend = uint256_from_u64(0); + if (uint256_add_u64(&spend, tx->transaction.amount1) || + uint256_add_u64(&spend, tx->transaction.amount2) || + uint256_add_u64(&spend, tx->transaction.fee)) { + return false; + } + + if (uint256_cmp(&senderEntry.balance, &spend) < 0) { + return false; + } + + if (!uint256_subtract(&senderEntry.balance, &spend)) { + return false; + } + if (!BalanceSheet_StoreSimEntry(simMap, &senderEntry)) { + return false; + } + + balance_sheet_entry_t recipient1Entry; + if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress1, &recipient1Entry)) { + return false; + } + if (uint256_add_u64(&recipient1Entry.balance, tx->transaction.amount1)) { + return false; + } + if (!BalanceSheet_StoreSimEntry(simMap, &recipient1Entry)) { + return false; + } + + if (tx->transaction.amount2 > 0) { + balance_sheet_entry_t recipient2Entry; + if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress2, &recipient2Entry)) { + return false; + } + if (uint256_add_u64(&recipient2Entry.balance, tx->transaction.amount2)) { + return false; + } + if (!BalanceSheet_StoreSimEntry(simMap, &recipient2Entry)) { + return false; + } + } + + if (outFee) { + *outFee = tx->transaction.fee; + } + + return true; +} + static int BalanceSheet_InsertLocked(balance_sheet_entry_t entry) { if (!sheetMap) { return -1; @@ -143,3 +277,64 @@ void BalanceSheet_Destroy() { sheetMap = NULL; pthread_mutex_destroy(&g_sheetLock); } + +bool BalanceSheet_SelectSpendableTransactions( + const signed_transaction_t* candidates, + size_t candidateCount, + signed_transaction_t** outAccepted, + size_t* outAcceptedCount, + uint64_t* outTotalFees +) { + if (!outAccepted || !outAcceptedCount || !outTotalFees) { + return false; + } + + *outAccepted = NULL; + *outAcceptedCount = 0; + *outTotalFees = 0; + + if (!candidates || candidateCount == 0) { + return true; + } + + signed_transaction_t* accepted = (signed_transaction_t*)calloc(candidateCount, sizeof(signed_transaction_t)); + if (!accepted) { + return false; + } + + khash_t(balance_sheet_map_m)* simMap = kh_init(balance_sheet_map_m); + if (!simMap) { + free(accepted); + return false; + } + + size_t acceptedCount = 0; + uint64_t totalFees = 0; + + for (size_t i = 0; i < candidateCount; ++i) { + const signed_transaction_t* tx = &candidates[i]; + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + continue; + } + + uint64_t fee = 0; + if (!BalanceSheet_ApplyCandidateTransaction(simMap, tx, &fee)) { + continue; + } + + accepted[acceptedCount++] = *tx; + totalFees += fee; + } + + kh_destroy(balance_sheet_map_m, simMap); + + if (acceptedCount == 0) { + free(accepted); + accepted = NULL; + } + + *outAccepted = accepted; + *outAcceptedCount = acceptedCount; + *outTotalFees = totalFees; + return true; +} diff --git a/src/block/block.c b/src/block/block.c index 2ea4151..8f13fff 100644 --- a/src/block/block.c +++ b/src/block/block.c @@ -214,23 +214,88 @@ bool Block_AllTransactionsValid(const block_t* block) { for (size_t i = 0; i < DynArr_size(block->transactions); i++) { signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); + if (!Transaction_Verify(tx)) { + return false; + } + if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) { if (hasCoinbase) { - return false; // More than one coinbase transaction + return false; } hasCoinbase = true; - continue; // Coinbase transactions are valid since the miner has the right to create coins. Only rule is one per block. - } - - if (!Transaction_Verify(tx)) { - return false; } } return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase) } +bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees) { + if (!block || !block->transactions) { + return false; + } + + bool hasCoinbase = false; + uint64_t totalFees = 0; + uint8_t zeroAddress[32] = {0}; + + for (size_t i = 0; i < DynArr_size(block->transactions); ++i) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); + if (!tx) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + if (hasCoinbase) { + return false; + } + + hasCoinbase = true; + + if (!Transaction_Verify(tx)) { + return false; + } + + if (tx->transaction.fee != 0 || tx->transaction.amount2 != 0) { + return false; + } + + if (tx->transaction.amount1 != expectedCoinbaseAmount) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.recipientAddress1)) { + return false; + } + + if (memcmp(tx->transaction.recipientAddress2, zeroAddress, sizeof(zeroAddress)) != 0) { + return false; + } + + continue; + } + + if (!Transaction_Verify(tx)) { + return false; + } + + if (UINT64_MAX - totalFees < tx->transaction.fee) { + return false; + } + totalFees += tx->transaction.fee; + } + + if (!hasCoinbase) { + return false; + } + + if (outTotalFees) { + *outTotalFees = totalFees; + } + + return true; +} + bool Block_IsFullyValid(const block_t* block) { bool merkleValid = false; uint8_t calculatedMerkleRoot[32]; diff --git a/src/block/chain.c b/src/block/chain.c index 37c1725..539f926 100644 --- a/src/block/chain.c +++ b/src/block/chain.c @@ -97,6 +97,37 @@ static bool DebitAddress(const uint8_t address[32], const uint256_t* amount) { return BalanceSheet_Insert(entry) >= 0; } +static bool Chain_RecomputeRuntimeState(blockchain_t* chain) { + if (!chain) { + return false; + } + + uint256_t rebuiltSupply = uint256_from_u64(0); + for (size_t i = 0; i < chain->size; ++i) { + block_t* blk = (block_t*)DynArr_at(chain->blocks, i); + if (!blk || !blk->transactions) { + return false; + } + + for (size_t j = 0; j < DynArr_size(blk->transactions); ++j) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j); + if (!tx) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + if (uint256_add_u64(&rebuiltSupply, tx->transaction.amount1)) { + return false; + } + } + } + } + + currentSupply = rebuiltSupply; + currentReward = CalculateBlockReward(currentSupply, chain); + return true; +} + static void Chain_ClearBlocks(blockchain_t* chain) { if (!chain || !chain->blocks) { return; @@ -161,34 +192,65 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) { } do { - // First pass: ensure all non-coinbase senders can cover the full spend - // (amount1 + amount2 + fee) before mutating the chain or balance sheet. size_t txCount = DynArr_size(block->transactions); + signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t)); + if (!candidateTxs) { + ok = false; + break; + } + + size_t nonCoinbaseCount = 0; for (size_t i = 0; i < txCount; ++i) { signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); if (!tx) { - ok = false; break; + ok = false; + break; } - if (Address_IsCoinbase(tx->transaction.senderAddress)) { - continue; - } - - uint256_t spend; - if (!BuildSpendAmount(tx, &spend)) { ok = false; break; } - - balance_sheet_entry_t senderEntry; - if (!BalanceSheet_Lookup(tx->transaction.senderAddress, &senderEntry)) { - fprintf(stderr, "Error: Sender address not found in balance sheet during block addition. Bailing!\n"); - ok = false; break; - } - - if (uint256_cmp(&senderEntry.balance, &spend) < 0) { - fprintf(stderr, "Error: Sender balance insufficient for block transaction. Bailing!\n"); - ok = false; break; + candidateTxs[i] = *tx; + if (!Address_IsCoinbase(tx->transaction.senderAddress)) { + ++nonCoinbaseCount; } } - if (!ok) break; + + if (!ok) { + free(candidateTxs); + break; + } + + signed_transaction_t* spendableTxs = NULL; + size_t spendableCount = 0; + uint64_t totalFees = 0; + if (!BalanceSheet_SelectSpendableTransactions(candidateTxs, txCount, &spendableTxs, &spendableCount, &totalFees)) { + free(candidateTxs); + ok = false; + break; + } + + free(candidateTxs); + + if (spendableCount != nonCoinbaseCount) { + free(spendableTxs); + ok = false; + break; + } + + uint64_t expectedCoinbaseAmount = currentReward; + if (UINT64_MAX - expectedCoinbaseAmount < totalFees) { + free(spendableTxs); + ok = false; + break; + } + expectedCoinbaseAmount += totalFees; + + uint64_t observedFees = 0; + if (!Block_ValidateCoinbaseAndFees(block, expectedCoinbaseAmount, &observedFees) || observedFees != totalFees) { + free(spendableTxs); + ok = false; + break; + } + + free(spendableTxs); // Push the block only after validation succeeds. block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block); @@ -434,6 +496,12 @@ bool Chain_RollbackToHeight(blockchain_t* chain, size_t height) { } } + if (!Chain_RecomputeRuntimeState(chain)) { + pthread_mutex_unlock(&balanceSheetLock); + pthread_rwlock_unlock(&chainLock); + return false; + } + pthread_mutex_unlock(&balanceSheetLock); pthread_rwlock_unlock(&chainLock); diff --git a/src/block/transaction.c b/src/block/transaction.c index 4acf3ac..054c1e0 100644 --- a/src/block/transaction.c +++ b/src/block/transaction.c @@ -42,7 +42,23 @@ bool Transaction_Verify(const signed_transaction_t* tx) { } if (Address_IsCoinbase(tx->transaction.senderAddress)) { - // Coinbase transactions are valid if the signature is correct for the block (handled in Block_Verify) + if (tx->transaction.amount1 == 0) { + return false; + } + + if (tx->transaction.amount2 != 0) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.recipientAddress1) || Address_IsCoinbase(tx->transaction.recipientAddress2)) { + return false; + } + + uint8_t zeroAddress[32] = {0}; + if (memcmp(tx->transaction.recipientAddress2, zeroAddress, 32) != 0) { + return false; + } + return true; } diff --git a/src/main.c b/src/main.c index 7101a49..6cc3b5b 100644 --- a/src/main.c +++ b/src/main.c @@ -147,6 +147,63 @@ static void AddCoinbaseTransaction(block_t* block, const uint8_t minerAddress[32 Block_AddTransaction(block, &coinbaseTx); } +static int CompareTransactionPriority(const void* lhs, const void* rhs) { + const signed_transaction_t* left = (const signed_transaction_t*)lhs; + const signed_transaction_t* right = (const signed_transaction_t*)rhs; + + if (left->transaction.fee > right->transaction.fee) { + return -1; + } + if (left->transaction.fee < right->transaction.fee) { + return 1; + } + + uint8_t leftHash[32]; + uint8_t rightHash[32]; + Transaction_CalculateHash(left, leftHash); + Transaction_CalculateHash(right, rightHash); + return memcmp(leftHash, rightHash, sizeof(leftHash)); +} + +static bool BuildSpendableMempoolSelection( + signed_transaction_t** outAcceptedTxs, + size_t* outAcceptedCount, + uint64_t* outTotalFees +) { + if (!outAcceptedTxs || !outAcceptedCount || !outTotalFees) { + return false; + } + + *outAcceptedTxs = NULL; + *outAcceptedCount = 0; + *outTotalFees = 0; + + signed_transaction_t* snapshot = NULL; + size_t snapshotCount = 0; + if (!TxMempool_Snapshot(&snapshot, &snapshotCount)) { + return false; + } + + if (snapshot && snapshotCount > 1) { + qsort(snapshot, snapshotCount, sizeof(signed_transaction_t), CompareTransactionPriority); + } + + signed_transaction_t* acceptedTxs = NULL; + size_t acceptedCount = 0; + uint64_t totalFees = 0; + bool ok = BalanceSheet_SelectSpendableTransactions(snapshot, snapshotCount, &acceptedTxs, &acceptedCount, &totalFees); + free(snapshot); + if (!ok) { + free(acceptedTxs); + return false; + } + + *outAcceptedTxs = acceptedTxs; + *outAcceptedCount = acceptedCount; + *outTotalFees = totalFees; + return true; +} + static void PrintBlockDetail(const block_t* block, size_t txCount, const uint8_t canonicalHash[32], const uint8_t powHash[32]) { if (!block) { return; @@ -301,6 +358,46 @@ static bool ComputeHistoricalAutolykosHashFromDisk(const char* chainDataDir, uin return ok; } +static bool Block_GetCoinbaseAndFeeTotals(const block_t* block, uint64_t* outCoinbaseAmount, uint64_t* outTotalFees) { + if (!block || !block->transactions || !outCoinbaseAmount || !outTotalFees) { + return false; + } + + bool hasCoinbase = false; + uint64_t coinbaseAmount = 0; + uint64_t totalFees = 0; + + for (size_t i = 0; i < DynArr_size(block->transactions); ++i) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); + if (!tx) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + if (hasCoinbase) { + return false; + } + + hasCoinbase = true; + coinbaseAmount = tx->transaction.amount1; + continue; + } + + if (UINT64_MAX - totalFees < tx->transaction.fee) { + return false; + } + totalFees += tx->transaction.fee; + } + + if (!hasCoinbase) { + return false; + } + + *outCoinbaseAmount = coinbaseAmount; + *outTotalFees = totalFees; + return true; +} + static bool MineAndAppendBlock(blockchain_t* chain, block_t* block, uint256_t* currentSupply, @@ -404,6 +501,7 @@ static bool VerifyChainFully(blockchain_t* chain) { blockchain_t* prevChain = Chain_Create(); if (!prevChain) { return false; } + uint256_t replaySupply = uint256_from_u64(0); uint32_t expectedDifficulty = INITIAL_DIFFICULTY; for (size_t i = 0; i < chainSize; ++i) { block_t* blk = NULL; @@ -480,12 +578,31 @@ static bool VerifyChainFully(blockchain_t* chain) { return false; } + uint64_t expectedReward = 0; + uint64_t savedReward = currentReward; + expectedReward = CalculateBlockReward(replaySupply, prevChain); + currentReward = savedReward; + if (!Block_AllTransactionsValid(blk)) { Block_Destroy(blk); Chain_Destroy(prevChain); return false; } + uint64_t coinbaseAmount = 0; + uint64_t totalFees = 0; + if (!Block_GetCoinbaseAndFeeTotals(blk, &coinbaseAmount, &totalFees)) { + Block_Destroy(blk); + Chain_Destroy(prevChain); + return false; + } + + if (UINT64_MAX - expectedReward < totalFees || coinbaseAmount != (expectedReward + totalFees)) { + Block_Destroy(blk); + Chain_Destroy(prevChain); + return false; + } + uint8_t expectedMerkle[32]; Block_CalculateMerkleRoot(blk, expectedMerkle); if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) { @@ -508,6 +625,8 @@ static bool VerifyChainFully(blockchain_t* chain) { headerOnly.transactions = NULL; (void)DynArr_push_back(prevChain->blocks, &headerOnly); + (void)uint256_add_u64(&replaySupply, coinbaseAmount); + Block_Destroy(blk); } @@ -746,14 +865,38 @@ int main(int argc, char* argv[]) { printf("Mining %llu block(s)...\n", requested); bool minedAll = true; for (unsigned long long i = 0; i < requested; ++i) { - block_t* block = BuildNextBlock(chain, difficultyTarget); - if (!block) { - fprintf(stderr, "failed to create block\n"); + signed_transaction_t* acceptedTxs = NULL; + size_t acceptedTxCount = 0; + uint64_t totalFees = 0; + if (!BuildSpendableMempoolSelection(&acceptedTxs, &acceptedTxCount, &totalFees)) { + fprintf(stderr, "failed to select spendable transactions from mempool\n"); minedAll = false; break; } - AddCoinbaseTransaction(block, minerAddress, currentReward); + block_t* block = BuildNextBlock(chain, difficultyTarget); + if (!block) { + fprintf(stderr, "failed to create block\n"); + free(acceptedTxs); + minedAll = false; + break; + } + + uint64_t coinbaseAmount = currentReward; + if (UINT64_MAX - coinbaseAmount < totalFees) { + free(acceptedTxs); + Block_Destroy(block); + minedAll = false; + break; + } + coinbaseAmount += totalFees; + + AddCoinbaseTransaction(block, minerAddress, coinbaseAmount); + + for (size_t txIndex = 0; txIndex < acceptedTxCount; ++txIndex) { + Block_AddTransaction(block, &acceptedTxs[txIndex]); + } + free(acceptedTxs); if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward, &difficultyTarget)) { Block_Destroy(block); @@ -822,7 +965,8 @@ int main(int argc, char* argv[]) { continue; } - AddCoinbaseTransaction(block, minerAddress, currentReward); + uint64_t coinbaseAmount = currentReward; + AddCoinbaseTransaction(block, minerAddress, coinbaseAmount); signed_transaction_t spendTx; Transaction_Init(&spendTx); @@ -865,7 +1009,7 @@ int main(int argc, char* argv[]) { printf("transaction added to mempool, broadcasting...\n"); - if (Node_BroadcastTransaction(node, &spendTx) == 0) { + if (Node_BroadcastTransaction(node, &spendTx, NULL) == 0) { printf("transaction broadcast to peers\n"); } else { printf("failed to broadcast transaction to peers\n"); @@ -1031,6 +1175,11 @@ int main(int argc, char* argv[]) { break; } + size_t reattached = OrphanPool_AttemptAttach(chain); + if (reattached > 0) { + printf("Reorg rollback attached %zu orphan(s)\n", reattached); + } + // Apply additional penalty by shrinking end and restart window from current Chain_Size if (peerHeight > reorgPenalty) { end = peerHeight - reorgPenalty; diff --git a/src/nets/net_node.c b/src/nets/net_node.c index 9fe8888..6c8c586 100644 --- a/src/nets/net_node.c +++ b/src/nets/net_node.c @@ -177,6 +177,20 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char* return NODE_BLOCK_REJECTED; } + uint64_t coinbaseAmount = 0; + if (blk->transactions) { + for (size_t i = 0; i < DynArr_size(blk->transactions); ++i) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i); + if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) { + coinbaseAmount = tx->transaction.amount1; + break; + } + } + } + + (void)uint256_add_u64(¤tSupply, coinbaseAmount); + currentReward = CalculateBlockReward(currentSupply, currentChain); + // Persist on accept if requested if (persist) { Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward); @@ -387,7 +401,7 @@ int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t pack return rc; } -int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx) { +int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx, tcp_connection_t* excludeNode) { if (!node || !tx) { return -1; } @@ -403,8 +417,9 @@ int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx) { // Broadcast to all outbound peers pthread_mutex_lock(&node->outboundLock); for (size_t i = 0; i < MAX_CONS; ++i) { - if (node->outboundClients[i].connection) { - (void)Node_SendPacket(node, node->outboundClients[i].connection, PACKET_TYPE_BROADCAST_TX, payload, payloadLen); + tcp_connection_t* connection = node->outboundClients[i].connection; + if (connection && connection != excludeNode) { + (void)Node_SendPacket(node, connection, PACKET_TYPE_BROADCAST_TX, payload, payloadLen); } } pthread_mutex_unlock(&node->outboundLock); @@ -646,7 +661,7 @@ void Node_Server_OnData(tcp_connection_t* client) { // Broadcast to other peers net_node_t* node = Node_FromConnection(client); if (node) { - Node_BroadcastTransaction(node, &tx); + Node_BroadcastTransaction(node, &tx, client); } } else { printf("Failed to add transaction %s from node %u to mempool\n", txHashHex, client ? client->connectionId : 0U); diff --git a/src/nets/orphan_pool.c b/src/nets/orphan_pool.c index 0b7cce1..79e49ff 100644 --- a/src/nets/orphan_pool.c +++ b/src/nets/orphan_pool.c @@ -197,13 +197,8 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) { i = (size_t)-1; // reset outer loop break; } else { - // Chain_AddBlock rejected it (maybe invalid). Drop it. - Block_Destroy(e->block); - DynArr_remove(g_orphans, i); - n = DynArr_size(g_orphans); - i = (size_t)-1; - madeProgress = true; - break; + // Keep the orphan around; rejection may be temporary while the local tip is being reorged. + continue; } } } diff --git a/src/txmempool.c b/src/txmempool.c index 84e9651..6238b4a 100644 --- a/src/txmempool.c +++ b/src/txmempool.c @@ -1,14 +1,21 @@ #include +#include + +static pthread_mutex_t g_txMempoolLock; +static bool g_txMempoolLockInitialized = false; khash_t(tx_mempool_map_m)* txMempool = NULL; void TxMempool_Init() { txMempool = kh_init(tx_mempool_map_m); + pthread_mutex_init(&g_txMempoolLock, NULL); + g_txMempoolLockInitialized = true; } int TxMempool_Insert(signed_transaction_t tx) { if (!txMempool) { return -1; } + pthread_mutex_lock(&g_txMempoolLock); uint8_t txHash[32]; Transaction_CalculateHash(&tx, txHash); @@ -18,17 +25,21 @@ int TxMempool_Insert(signed_transaction_t tx) { int ret; khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret); if (k == kh_end(txMempool)) { + pthread_mutex_unlock(&g_txMempoolLock); return -1; } kh_value(txMempool, k) = tx; + pthread_mutex_unlock(&g_txMempoolLock); + return ret; } bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) { if (!txMempool || !txHash || !out) { return false; } + pthread_mutex_lock(&g_txMempoolLock); key32_t key; memcpy(key.bytes, txHash, 32); @@ -36,15 +47,65 @@ bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) { if (k != kh_end(txMempool)) { signed_transaction_t tx = kh_value(txMempool, k); memcpy(out, &tx, sizeof(signed_transaction_t)); + pthread_mutex_unlock(&g_txMempoolLock); return true; } + pthread_mutex_unlock(&g_txMempoolLock); return false; } +bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount) { + if (!outTxs || !outCount) { + return false; + } + + *outTxs = NULL; + *outCount = 0; + + if (!txMempool) { + return true; + } + + pthread_mutex_lock(&g_txMempoolLock); + + size_t count = 0; + khiter_t k; + for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) { + if (kh_exist(txMempool, k)) { + ++count; + } + } + + if (count == 0) { + pthread_mutex_unlock(&g_txMempoolLock); + return true; + } + + signed_transaction_t* snapshot = (signed_transaction_t*)malloc(count * sizeof(signed_transaction_t)); + if (!snapshot) { + pthread_mutex_unlock(&g_txMempoolLock); + return false; + } + + size_t index = 0; + for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) { + if (kh_exist(txMempool, k)) { + snapshot[index++] = kh_value(txMempool, k); + } + } + + pthread_mutex_unlock(&g_txMempoolLock); + + *outTxs = snapshot; + *outCount = count; + return true; +} + void TxMempool_Print() { if (!txMempool) { return; } + pthread_mutex_lock(&g_txMempoolLock); khiter_t k; for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) { if (kh_exist(txMempool, k)) { @@ -62,10 +123,19 @@ void TxMempool_Print() { (unsigned long long)tx.transaction.fee); } } + pthread_mutex_unlock(&g_txMempoolLock); } void TxMempool_Destroy() { if (txMempool) { + pthread_mutex_lock(&g_txMempoolLock); kh_destroy(tx_mempool_map_m, txMempool); + txMempool = NULL; + pthread_mutex_unlock(&g_txMempoolLock); + } + + if (g_txMempoolLockInitialized) { + pthread_mutex_destroy(&g_txMempoolLock); + g_txMempoolLockInitialized = false; } }