Files
skalacoin/src/balance_sheet.c
DcruBro 763aeb648f 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.
2026-05-29 13:44:15 +02:00

341 lines
8.5 KiB
C

#include <balance_sheet.h>
#include <pthread.h>
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;
}
key32_t key;
memcpy(key.bytes, entry.address, 32);
int ret = 0;
khiter_t k = kh_put(balance_sheet_map_m, sheetMap, key, &ret);
if (k == kh_end(sheetMap)) {
return -1;
}
kh_value(sheetMap, k) = entry;
return ret;
}
void BalanceSheet_Init() {
sheetMap = kh_init(balance_sheet_map_m);
pthread_mutex_init(&g_sheetLock, NULL);
}
int BalanceSheet_Insert(balance_sheet_entry_t entry) {
if (!sheetMap) { return -1; }
pthread_mutex_lock(&g_sheetLock);
int ret = BalanceSheet_InsertLocked(entry);
pthread_mutex_unlock(&g_sheetLock);
return ret;
}
bool BalanceSheet_Lookup(uint8_t* address, balance_sheet_entry_t* out) {
if (!address || !out) { return false; }
pthread_mutex_lock(&g_sheetLock);
key32_t key;
memcpy(key.bytes, address, 32);
khiter_t k = kh_get(balance_sheet_map_m, sheetMap, key);
if (k != kh_end(sheetMap)) {
balance_sheet_entry_t entry = kh_value(sheetMap, k);
memcpy(out, &entry, sizeof(balance_sheet_entry_t));
pthread_mutex_unlock(&g_sheetLock);
return true;
}
pthread_mutex_unlock(&g_sheetLock);
return false;
}
bool BalanceSheet_SaveToFile(const char* outPath) {
if (!sheetMap) { return false; }
pthread_mutex_lock(&g_sheetLock);
char outFile[512];
strcpy(outFile, outPath);
strcat(outFile, "/balance_sheet.data");
FILE* file = fopen(outFile, "wb");
if (!file) {
pthread_mutex_unlock(&g_sheetLock);
return false;
}
khiter_t k;
for (k = kh_begin(sheetMap); k != kh_end(sheetMap); ++k) {
if (kh_exist(sheetMap, k)) {
balance_sheet_entry_t entry = kh_val(sheetMap, k);
if (fwrite(&entry, sizeof(balance_sheet_entry_t), 1, file) != 1) {
fclose(file);
pthread_mutex_unlock(&g_sheetLock);
return false;
}
}
}
fclose(file);
pthread_mutex_unlock(&g_sheetLock);
return true;
}
bool BalanceSheet_LoadFromFile(const char* inPath) {
if (!sheetMap) { return false; }
pthread_mutex_lock(&g_sheetLock);
char inFile[512];
strcpy(inFile, inPath);
strcat(inFile, "/balance_sheet.data");
FILE* file = fopen(inFile, "rb");
if (!file) {
pthread_mutex_unlock(&g_sheetLock);
return false;
}
balance_sheet_entry_t entry;
while (fread(&entry, sizeof(balance_sheet_entry_t), 1, file) == 1) {
if (BalanceSheet_InsertLocked(entry) < 0) {
fclose(file);
pthread_mutex_unlock(&g_sheetLock);
return false;
}
}
fclose(file);
pthread_mutex_unlock(&g_sheetLock);
return true;
}
void BalanceSheet_Print() {
if (!sheetMap) { return; }
pthread_mutex_lock(&g_sheetLock);
// Iterate through every entry
khiter_t k;
for (k = kh_begin(sheetMap); k != kh_end(sheetMap); ++k) {
if (kh_exist(sheetMap, k)) {
key32_t key = kh_key(sheetMap, k);
balance_sheet_entry_t val = kh_val(sheetMap, k);
char balanceStr[80];
uint256_serialize(&val.balance, balanceStr);
char addrHex[65];
AddressToHexString(key.bytes, addrHex);
// Print full address
printf("Address %s: balance=%s pebble(s)\n",
addrHex,
balanceStr);
}
}
pthread_mutex_unlock(&g_sheetLock);
}
void BalanceSheet_Destroy() {
kh_destroy(balance_sheet_map_m, sheetMap);
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;
}