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:
+155
-6
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user