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:
+71
-6
@@ -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];
|
||||
|
||||
+88
-20
@@ -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);
|
||||
|
||||
|
||||
+17
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user