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.
This commit is contained in:
2026-05-29 13:44:15 +02:00
parent 41a154a9fd
commit 763aeb648f
13 changed files with 634 additions and 45 deletions

View File

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

View File

@@ -8,6 +8,7 @@
#include <stdio.h>
#include <khash/khash.h>
#include <crypto/crypto.h>
#include <block/transaction.h>
#include <string.h>
#include <utils.h>
#include <uint256.h>
@@ -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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, &currentSupply, &currentReward, &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;

View File

@@ -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(&currentSupply, 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);

View File

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

View File

@@ -1,14 +1,21 @@
#include <txmempool.h>
#include <pthread.h>
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;
}
}