Compare commits

..

9 Commits

Author SHA1 Message Date
da50b4e8c1 Start IPv6 - Lord help me 2026-06-03 11:20:19 +02:00
00bd711501 remove proof 2026-05-29 14:31:24 +02:00
17ef3b74fd remove from mempool on mine; TEMPORARY log math proof of fee inclusion in coinbase tx 2026-05-29 14:28:27 +02:00
c1914dc3e7 add optional fee argument to send command 2026-05-29 14:00:58 +02:00
39293029c5 recompute state bug fixed 2026-05-29 13:51:34 +02:00
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
41a154a9fd fix pushing to txmempool 2026-05-29 12:49:51 +02:00
91d7bfa4e7 start adding tx system - test broadcast 2026-05-29 12:45:22 +02:00
4cfe85f6f2 orphans and wallet files 2026-05-28 13:01:23 +02:00
20 changed files with 1318 additions and 213 deletions

View File

@@ -14,9 +14,17 @@ Check if Block FullVerify is actually verifying fully (not missing any condition
A loophole in the reorg penalty system could potentially exist where someone broadcasts blocks one-at-a-time. Determine a solution to this. A loophole in the reorg penalty system could potentially exist where someone broadcasts blocks one-at-a-time. Determine a solution to this.
IPv6 support for the P2P node. Come on guys, it's 2026. RFC 2460 was in 1998. It's about time.
Like if someone is behind NAT, fine, workable. CGNAT? Lmao good luck.
TO TEST: TO TEST:
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner. 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: 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: 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. 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 <stdio.h>
#include <khash/khash.h> #include <khash/khash.h>
#include <crypto/crypto.h> #include <crypto/crypto.h>
#include <block/transaction.h>
#include <string.h> #include <string.h>
#include <utils.h> #include <utils.h>
#include <uint256.h> #include <uint256.h>
@@ -29,4 +30,12 @@ bool BalanceSheet_LoadFromFile(const char* inPath);
void BalanceSheet_Print(); void BalanceSheet_Print();
void BalanceSheet_Destroy(); 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 #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); void Block_RemoveTransaction(block_t* block, uint8_t* txHash);
bool Block_HasValidProofOfWork(const block_t* block); bool Block_HasValidProofOfWork(const block_t* block);
bool Block_AllTransactionsValid(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); bool Block_IsFullyValid(const block_t* block);
void Block_ShutdownPowContext(void); void Block_ShutdownPowContext(void);
void Block_Destroy(block_t* block); void Block_Destroy(block_t* block);

View File

@@ -28,6 +28,10 @@ void Chain_Wipe(blockchain_t* chain);
// Returns true on success. // Returns true on success.
bool Chain_RollbackToHeight(blockchain_t* chain, size_t height); bool Chain_RollbackToHeight(blockchain_t* chain, size_t height);
// Recompute `currentSupply` and `currentReward` from the in-memory chain blocks.
// Returns true on success and updates runtime state globals.
bool Chain_RecomputeRuntimeState(blockchain_t* chain);
// Retrieve a deep copy of the block at `index`. Caller must free with `Block_Destroy`. // Retrieve a deep copy of the block at `index`. Caller must free with `Block_Destroy`.
bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy); bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy);

View File

@@ -57,6 +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_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_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, tcp_connection_t* excludeNode);
// Helpers for outbound peer selection and block broadcast // Helpers for outbound peer selection and block broadcast
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight); int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight);

View File

@@ -14,6 +14,9 @@ extern uint256_t currentSupply;
extern uint64_t currentReward; extern uint64_t currentReward;
extern uint32_t difficultyTarget; extern uint32_t difficultyTarget;
extern const char* chainDataDir; extern const char* chainDataDir;
extern unsigned short listenPort;
extern bool echoPeersEnabled;
extern bool forceOrphanReorgEnabled;
// Global synchronization primitives for runtime state // Global synchronization primitives for runtime state
extern pthread_rwlock_t chainLock; // protects chain structure and related mutations extern pthread_rwlock_t chainLock; // protects chain structure and related mutations

View File

@@ -19,8 +19,17 @@ typedef enum {
typedef struct tcp_connection_t tcp_connection_t; typedef struct tcp_connection_t tcp_connection_t;
struct tcp_connection_t { struct tcp_connection_t {
// TODO: We should make it so only ONE of this needs to be available.
// Because of my temporary "I just need something that works" horseshit that I'm about to write, you'll need IPv4 and IPv6 is optional.
// Note to self: Don't pull an IETF and some "NAT exists, we're fine" bullshit, because if we end up with our eqvivalent of Teredo or CGNAT, I'm gonna be fucking pissed.
// And no, the solution isn't "eh, just bind to 0.0.0.0 and ignore it", because if we do that, we'll inevitably end up with a host that only has IPv6 and then we'll be fucked.
// Honestly, I'm proud of whoever runs IPv6-only. Brave soul.
int sockFd; int sockFd;
struct sockaddr_in peerAddr; struct sockaddr_in peerAddr;
#ifdef USE_IPV6
int sockFd6; // For IPv6 support
struct sockaddr_in6 peerAddr6; // For IPv6 support
#endif
uint32_t connectionId; uint32_t connectionId;
tcp_connection_role_t role; tcp_connection_role_t role;
@@ -54,6 +63,7 @@ int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* dat
void TcpConnection_ResetFramingState(tcp_connection_t* conn); void TcpConnection_ResetFramingState(tcp_connection_t* conn);
int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen); int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen);
// This just takes a socket ID, so it's independent from the v4/v6 stuff. It works for both.
int TcpConnection_SendRaw(int sockFd, const void* data, size_t len); int TcpConnection_SendRaw(int sockFd, const void* data, size_t len);
int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen); int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen);

View File

@@ -11,7 +11,12 @@
typedef struct { typedef struct {
int sockFd; int sockFd;
struct sockaddr_in addr; struct sockaddr_in addr;
#ifdef USE_IPV6
int sockFd6; // IPv6 support
struct sockaddr_in6 addr6; // IPv6 support
#endif
int opt; int opt;
int opt6; // IPv6 support
int isRunning; int isRunning;
void* owner; void* owner;

View File

@@ -13,7 +13,10 @@ void TxMempool_Init();
// Assumed that the transation was confirmed to be valid // Assumed that the transation was confirmed to be valid
int TxMempool_Insert(signed_transaction_t tx); int TxMempool_Insert(signed_transaction_t tx);
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out); 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_Print();
// Remove a transaction from the mempool by its hash. Returns true if removed.
bool TxMempool_Remove(const uint8_t* txHash);
void TxMempool_Destroy(); void TxMempool_Destroy();
#endif #endif

View File

@@ -167,7 +167,7 @@ static inline bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t com
return false; return false;
} }
static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) { static inline bool GenerateRandomTestAddress(uint8_t outAddress[32], uint8_t outPrivateKey[32], uint8_t outCompressedPubkey[33]) {
if (!outAddress) { if (!outAddress) {
return false; return false;
} }
@@ -200,11 +200,18 @@ static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) {
} }
AddressFromCompressedPubkey(compressedPubkey, outAddress); AddressFromCompressedPubkey(compressedPubkey, outAddress);
if (outPrivateKey) {
memcpy(outPrivateKey, privateKey, 32);
}
if (outCompressedPubkey) {
memcpy(outCompressedPubkey, compressedPubkey, 33);
}
secp256k1_context_destroy(ctx); secp256k1_context_destroy(ctx);
return true; return true;
} }
secp256k1_context_destroy(ctx); secp256k1_context_destroy(ctx);
return false; return false;
} }

View File

@@ -4,6 +4,140 @@
khash_t(balance_sheet_map_m)* sheetMap = NULL; khash_t(balance_sheet_map_m)* sheetMap = NULL;
static pthread_mutex_t g_sheetLock; 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) { static int BalanceSheet_InsertLocked(balance_sheet_entry_t entry) {
if (!sheetMap) { if (!sheetMap) {
return -1; return -1;
@@ -143,3 +277,64 @@ void BalanceSheet_Destroy() {
sheetMap = NULL; sheetMap = NULL;
pthread_mutex_destroy(&g_sheetLock); 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,21 +214,86 @@ bool Block_AllTransactionsValid(const block_t* block) {
for (size_t i = 0; i < DynArr_size(block->transactions); i++) { for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(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 (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
if (hasCoinbase) { if (hasCoinbase) {
return false; // More than one coinbase transaction return false;
} }
hasCoinbase = true; hasCoinbase = true;
continue; // Coinbase transactions are valid since the miner has the right to create coins. Only rule is one per block. }
}
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)) { if (!Transaction_Verify(tx)) {
return false; return false;
} }
if (UINT64_MAX - totalFees < tx->transaction.fee) {
return false;
}
totalFees += tx->transaction.fee;
} }
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase) if (!hasCoinbase) {
return false;
}
if (outTotalFees) {
*outTotalFees = totalFees;
}
return true;
} }
bool Block_IsFullyValid(const block_t* block) { bool Block_IsFullyValid(const block_t* block) {

View File

@@ -1,6 +1,7 @@
#include <block/chain.h> #include <block/chain.h>
#include <constants.h> #include <constants.h>
#include <runtime_state.h> #include <runtime_state.h>
#include <txmempool.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -97,6 +98,37 @@ static bool DebitAddress(const uint8_t address[32], const uint256_t* amount) {
return BalanceSheet_Insert(entry) >= 0; return BalanceSheet_Insert(entry) >= 0;
} }
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) { static void Chain_ClearBlocks(blockchain_t* chain) {
if (!chain || !chain->blocks) { if (!chain || !chain->blocks) {
return; return;
@@ -161,34 +193,90 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
} }
do { 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); 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) { for (size_t i = 0; i < txCount; ++i) {
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
if (!tx) { if (!tx) {
ok = false; break; ok = false;
break;
} }
if (Address_IsCoinbase(tx->transaction.senderAddress)) { candidateTxs[i] = *tx;
continue; if (!Address_IsCoinbase(tx->transaction.senderAddress)) {
++nonCoinbaseCount;
}
} }
uint256_t spend; if (!ok) {
if (!BuildSpendAmount(tx, &spend)) { ok = false; break; } free(candidateTxs);
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) { signed_transaction_t* spendableTxs = NULL;
fprintf(stderr, "Error: Sender balance insufficient for block transaction. Bailing!\n"); size_t spendableCount = 0;
ok = false; break; 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;
// Debug: log expected coinbase and fees to aid diagnosis when nodes disagree
{
uint64_t cbAmount = 0;
if (block->transactions && DynArr_size(block->transactions) > 0) {
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
cbAmount = firstTx->transaction.amount1;
} }
} }
if (!ok) break; char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Chain_AddBlock: blockIndex=%zu expectedCoinbase=%llu totalFees=%llu observedBlockCoinbase=%llu currentReward=%llu currentSupply=%s\n",
expectedIndex,
(unsigned long long)expectedCoinbaseAmount,
(unsigned long long)totalFees,
(unsigned long long)cbAmount,
(unsigned long long)currentReward,
supplyStr);
}
uint64_t observedFees = 0;
if (!Block_ValidateCoinbaseAndFees(block, expectedCoinbaseAmount, &observedFees) || observedFees != totalFees) {
// Log mismatch details for debugging
printf("Chain_AddBlock: validation failed: expectedCoinbase=%llu totalFees=%llu observedFees=%llu\n",
(unsigned long long)expectedCoinbaseAmount,
(unsigned long long)totalFees,
(unsigned long long)observedFees);
free(spendableTxs);
ok = false;
break;
}
free(spendableTxs);
// Push the block only after validation succeeds. // Push the block only after validation succeeds.
block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block); block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block);
@@ -232,6 +320,21 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
} }
} }
} }
// Remove mined non-coinbase transactions from the mempool so they are not re-mined or re-broadcast.
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) continue;
if (Address_IsCoinbase(tx->transaction.senderAddress)) continue;
uint8_t txHash[32];
Transaction_CalculateHash(tx, txHash);
if (TxMempool_Remove(txHash)) {
// optional: log removal
// printf("TxMempool_Remove: removed tx from mempool: "); PrintHexBytes(txHash, 32); printf("\n");
}
}
}
// ok remains true if no failures // ok remains true if no failures
} while (0); } while (0);
@@ -242,6 +345,8 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
printf("Added new block to chain:\n"); printf("Added new block to chain:\n");
Block_ShortPrint(block); Block_ShortPrint(block);
/* Debug proof removed: coinbase == baseReward + totalFees was printed here during debugging. */
return ok; return ok;
} }
@@ -434,6 +539,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_mutex_unlock(&balanceSheetLock);
pthread_rwlock_unlock(&chainLock); pthread_rwlock_unlock(&chainLock);
@@ -468,117 +579,133 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
return false; return false;
} }
// Find metadata file (create if not exists) to get the saved chain size (+ other things) char metaTmpPath[512];
FILE* metaFile = fopen(metaPath, "rb+"); char chainTmpPath[512];
FILE* chainFile = fopen(chainPath, "rb+"); char tableTmpPath[512];
FILE* tableFile = fopen(tablePath, "rb+"); if (!BuildPath(metaTmpPath, sizeof(metaTmpPath), dirpath, "chain.meta.tmp") ||
if (!metaFile || !chainFile || !tableFile) { !BuildPath(chainTmpPath, sizeof(chainTmpPath), dirpath, "chain.data.tmp") ||
// Just overwrite everything !BuildPath(tableTmpPath, sizeof(tableTmpPath), dirpath, "chain.table.tmp")) {
metaFile = fopen(metaPath, "wb+");
if (!metaFile) { return false; }
// Initialize metadata with size 0
size_t initialSize = 0;
fwrite(&initialSize, sizeof(size_t), 1, metaFile);
// Write last block hash (32 bytes of zeros for now)
uint8_t zeroHash[32] = {0};
fwrite(zeroHash, sizeof(uint8_t), 32, metaFile);
uint256_t zeroSupply = {0};
fwrite(&zeroSupply, sizeof(uint256_t), 1, metaFile);
uint32_t initialTarget = INITIAL_DIFFICULTY;
fwrite(&initialTarget, sizeof(uint32_t), 1, metaFile);
uint64_t initialReward = 0;
fwrite(&initialReward, sizeof(uint64_t), 1, metaFile);
chainFile = fopen(chainPath, "wb+");
if (!chainFile) { return false; }
tableFile = fopen(tablePath, "wb+");
if (!tableFile) { return false; }
// TODO: Potentially some other things here, we'll see
}
// Read
size_t savedSize = 0;
fread(&savedSize, sizeof(size_t), 1, metaFile);
uint8_t lastSavedHash[32];
fread(lastSavedHash, sizeof(uint8_t), 32, metaFile);
// Assume chain saved is valid, and that the chain in memory is valid (as LoadFromFile will verify the saved one)
if (savedSize > DynArr_size(chain->blocks)) {
// Saved chain is longer than current chain, this should not happen if we are always saving the current chain, but just in case, fail to save to avoid overwriting a potentially valid longer chain with a shorter one.
fclose(metaFile);
fclose(chainFile);
fclose(tableFile);
return false; return false;
} }
// Filename format: dirpath/chain.data
// File format: ([block_header][num_transactions][transactions...])[*length] - since block_header is fixed size, LoadFromFile will only read those by default
fseek(chainFile, 0, SEEK_END); // Seek to the end of those files
fseek(tableFile, 0, SEEK_END);
long pos = ftell(chainFile);
if (pos < 0) {
fclose(metaFile);
fclose(chainFile);
fclose(tableFile);
return false;
}
uint64_t byteCount = (uint64_t)pos; // Get the size
// Save blocks that are not yet saved
// Acquire write lock to protect block transaction pointers from concurrent freeing.
pthread_rwlock_wrlock(&chainLock); pthread_rwlock_wrlock(&chainLock);
for (size_t i = savedSize; i < DynArr_size(chain->blocks); i++) {
FILE* metaFile = fopen(metaTmpPath, "wb+");
FILE* chainFile = fopen(chainTmpPath, "wb+");
FILE* tableFile = fopen(tableTmpPath, "wb+");
if (!metaFile || !chainFile || !tableFile) {
if (metaFile) fclose(metaFile);
if (chainFile) fclose(chainFile);
if (tableFile) fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
}
const size_t chainSize = DynArr_size(chain->blocks);
uint64_t byteCount = 0;
for (size_t i = 0; i < chainSize; ++i) {
block_t* blk = (block_t*)DynArr_at(chain->blocks, i); block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
if (!blk) { if (!blk) {
pthread_rwlock_unlock(&chainLock);
fclose(metaFile); fclose(metaFile);
fclose(chainFile); fclose(chainFile);
fclose(tableFile); fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false; return false;
} }
uint64_t preIncrementByteSize = byteCount; block_t* diskCopy = blk;
bool loadedTemp = false;
if (!diskCopy->transactions) {
if (!Chain_LoadBlockFromFile(dirpath, (uint64_t)i, true, &diskCopy, NULL) || !diskCopy || !diskCopy->transactions) {
if (loadedTemp && diskCopy) {
Block_Destroy(diskCopy);
}
fclose(metaFile);
fclose(chainFile);
fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
}
loadedTemp = true;
}
// Construct file path const uint64_t blockStart = byteCount;
// Write block header if (fwrite(&diskCopy->header, sizeof(block_header_t), 1, chainFile) != 1) {
fwrite(&blk->header, sizeof(block_header_t), 1, chainFile); if (loadedTemp) Block_Destroy(diskCopy);
size_t txSize = DynArr_size(blk->transactions); fclose(metaFile);
fwrite(&txSize, sizeof(size_t), 1, chainFile); // Write number of transactions fclose(chainFile);
fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
}
const size_t txSize = DynArr_size(diskCopy->transactions);
if (fwrite(&txSize, sizeof(size_t), 1, chainFile) != 1) {
if (loadedTemp) Block_Destroy(diskCopy);
fclose(metaFile);
fclose(chainFile);
fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
}
byteCount += sizeof(block_header_t) + sizeof(size_t); byteCount += sizeof(block_header_t) + sizeof(size_t);
// Write transactions
for (size_t j = 0; j < txSize; j++) { for (size_t j = 0; j < txSize; ++j) {
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j); signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(diskCopy->transactions, j);
if (fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) { if (!tx || fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
pthread_rwlock_unlock(&chainLock); if (loadedTemp) Block_Destroy(diskCopy);
fclose(chainFile);
fclose(metaFile); fclose(metaFile);
fclose(chainFile);
fclose(tableFile); fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false; return false;
} }
byteCount += sizeof(signed_transaction_t); byteCount += sizeof(signed_transaction_t);
} }
// Create an entry in the block table
block_table_entry_t entry; block_table_entry_t entry;
entry.blockNumber = i; entry.blockNumber = i;
entry.byteNumber = preIncrementByteSize; entry.byteNumber = blockStart;
entry.blockSize = byteCount - preIncrementByteSize; entry.blockSize = byteCount - blockStart;
fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile); if (fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile) != 1) {
if (loadedTemp) Block_Destroy(diskCopy);
DynArr_destroy(blk->transactions); fclose(metaFile);
blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk fclose(chainFile);
fclose(tableFile);
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
} }
pthread_rwlock_unlock(&chainLock); if (loadedTemp) {
Block_Destroy(diskCopy);
} else if (blk->transactions) {
DynArr_destroy(blk->transactions);
blk->transactions = NULL;
}
}
// Update metadata with new size and last block hash size_t newSize = chainSize;
size_t newSize = DynArr_size(chain->blocks);
fseek(metaFile, 0, SEEK_SET); fseek(metaFile, 0, SEEK_SET);
fwrite(&newSize, sizeof(size_t), 1, metaFile); fwrite(&newSize, sizeof(size_t), 1, metaFile);
uint32_t difficultyTarget = INITIAL_DIFFICULTY; uint32_t difficultyTarget = INITIAL_DIFFICULTY;
@@ -596,16 +723,23 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
fwrite(&difficultyTarget, sizeof(uint32_t), 1, metaFile); fwrite(&difficultyTarget, sizeof(uint32_t), 1, metaFile);
fwrite(&currentReward, sizeof(uint64_t), 1, metaFile); fwrite(&currentReward, sizeof(uint64_t), 1, metaFile);
// Safety
fflush(metaFile); fflush(metaFile);
fflush(chainFile); fflush(chainFile);
fflush(tableFile); fflush(tableFile);
// Close all pointers
fclose(metaFile); fclose(metaFile);
fclose(chainFile); fclose(chainFile);
fclose(tableFile); fclose(tableFile);
if (rename(metaTmpPath, metaPath) != 0 || rename(chainTmpPath, chainPath) != 0 || rename(tableTmpPath, tablePath) != 0) {
pthread_rwlock_unlock(&chainLock);
remove(metaTmpPath);
remove(chainTmpPath);
remove(tableTmpPath);
return false;
}
pthread_rwlock_unlock(&chainLock);
return true; return true;
} }

View File

@@ -42,7 +42,23 @@ bool Transaction_Verify(const signed_transaction_t* tx) {
} }
if (Address_IsCoinbase(tx->transaction.senderAddress)) { 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; return true;
} }

View File

@@ -12,7 +12,7 @@
#include <balance_sheet.h> #include <balance_sheet.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <txmempool.h>
#include <constants.h> #include <constants.h>
#include <runtime_state.h> #include <runtime_state.h>
@@ -28,6 +28,9 @@
blockchain_t* currentChain = NULL; blockchain_t* currentChain = NULL;
const char* chainDataDir = CHAIN_DATA_DIR; const char* chainDataDir = CHAIN_DATA_DIR;
unsigned short listenPort = LISTEN_PORT;
bool echoPeersEnabled = ECHO_PEERS != 0;
bool forceOrphanReorgEnabled = false;
uint256_t currentSupply = {{0, 0, 0, 0}}; uint256_t currentSupply = {{0, 0, 0, 0}};
uint64_t currentReward = 750000000000ULL; uint64_t currentReward = 750000000000ULL;
@@ -42,6 +45,32 @@ void handle_sigint(int sig) {
exit(0); exit(0);
} }
static void ApplyRuntimeConfigFromEnv(void) {
const char* dataDir = getenv("SKALACOIN_CHAIN_DATA_DIR");
if (dataDir && dataDir[0] != '\0') {
chainDataDir = dataDir;
}
const char* portStr = getenv("SKALACOIN_LISTEN_PORT");
if (portStr && portStr[0] != '\0') {
char* end = NULL;
long parsed = strtol(portStr, &end, 10);
if (end != portStr && *end == '\0' && parsed > 0 && parsed <= 65535) {
listenPort = (unsigned short)parsed;
}
}
const char* echoStr = getenv("SKALACOIN_ECHO_PEERS");
if (echoStr && echoStr[0] != '\0') {
echoPeersEnabled = (strcmp(echoStr, "0") != 0);
}
const char* forceOrphanStr = getenv("SKALACOIN_FORCE_ORPHAN_REORG");
if (forceOrphanStr && forceOrphanStr[0] != '\0') {
forceOrphanReorgEnabled = (strcmp(forceOrphanStr, "0") != 0);
}
}
uint32_t difficultyTarget = INITIAL_DIFFICULTY; uint32_t difficultyTarget = INITIAL_DIFFICULTY;
static bool MineBlock(block_t* block) { static bool MineBlock(block_t* block) {
@@ -118,6 +147,63 @@ static void AddCoinbaseTransaction(block_t* block, const uint8_t minerAddress[32
Block_AddTransaction(block, &coinbaseTx); 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]) { static void PrintBlockDetail(const block_t* block, size_t txCount, const uint8_t canonicalHash[32], const uint8_t powHash[32]) {
if (!block) { if (!block) {
return; return;
@@ -272,6 +358,46 @@ static bool ComputeHistoricalAutolykosHashFromDisk(const char* chainDataDir, uin
return ok; 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, static bool MineAndAppendBlock(blockchain_t* chain,
block_t* block, block_t* block,
uint256_t* currentSupply, uint256_t* currentSupply,
@@ -295,6 +421,16 @@ static bool MineAndAppendBlock(blockchain_t* chain,
return false; return false;
} }
uint64_t coinbaseAmount = 0;
if (block->transactions && DynArr_size(block->transactions) > 0) {
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
coinbaseAmount = firstTx->transaction.amount1;
}
}
/* Debug proof removed: miner printed proof that coinbase == baseReward + totalFees during debugging. */
// After successfully appending a block, attempt to attach any orphans. // After successfully appending a block, attempt to attach any orphans.
size_t attached = OrphanPool_AttemptAttach(chain); size_t attached = OrphanPool_AttemptAttach(chain);
if (attached > 0) { if (attached > 0) {
@@ -304,14 +440,6 @@ static bool MineAndAppendBlock(blockchain_t* chain,
BalanceSheet_SaveToFile(chainDataDir); BalanceSheet_SaveToFile(chainDataDir);
} }
uint64_t coinbaseAmount = 0;
if (block->transactions && DynArr_size(block->transactions) > 0) {
signed_transaction_t* firstTx = (signed_transaction_t*)DynArr_at(block->transactions, 0);
if (firstTx && Address_IsCoinbase(firstTx->transaction.senderAddress)) {
coinbaseAmount = firstTx->transaction.amount1;
}
}
(void)uint256_add_u64(currentSupply, coinbaseAmount); (void)uint256_add_u64(currentSupply, coinbaseAmount);
uint8_t canonicalHash[32]; uint8_t canonicalHash[32];
@@ -375,6 +503,7 @@ static bool VerifyChainFully(blockchain_t* chain) {
blockchain_t* prevChain = Chain_Create(); blockchain_t* prevChain = Chain_Create();
if (!prevChain) { return false; } if (!prevChain) { return false; }
uint256_t replaySupply = uint256_from_u64(0);
uint32_t expectedDifficulty = INITIAL_DIFFICULTY; uint32_t expectedDifficulty = INITIAL_DIFFICULTY;
for (size_t i = 0; i < chainSize; ++i) { for (size_t i = 0; i < chainSize; ++i) {
block_t* blk = NULL; block_t* blk = NULL;
@@ -451,12 +580,31 @@ static bool VerifyChainFully(blockchain_t* chain) {
return false; return false;
} }
uint64_t expectedReward = 0;
uint64_t savedReward = currentReward;
expectedReward = CalculateBlockReward(replaySupply, prevChain);
currentReward = savedReward;
if (!Block_AllTransactionsValid(blk)) { if (!Block_AllTransactionsValid(blk)) {
Block_Destroy(blk); Block_Destroy(blk);
Chain_Destroy(prevChain); Chain_Destroy(prevChain);
return false; 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]; uint8_t expectedMerkle[32];
Block_CalculateMerkleRoot(blk, expectedMerkle); Block_CalculateMerkleRoot(blk, expectedMerkle);
if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) { if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) {
@@ -479,6 +627,8 @@ static bool VerifyChainFully(blockchain_t* chain) {
headerOnly.transactions = NULL; headerOnly.transactions = NULL;
(void)DynArr_push_back(prevChain->blocks, &headerOnly); (void)DynArr_push_back(prevChain->blocks, &headerOnly);
(void)uint256_add_u64(&replaySupply, coinbaseAmount);
Block_Destroy(blk); Block_Destroy(blk);
} }
@@ -486,6 +636,16 @@ static bool VerifyChainFully(blockchain_t* chain) {
return true; return true;
} }
// Use when error
void KillEverythingAndExit(net_node_t* node, blockchain_t* chain) {
Node_Destroy(node);
currentChain = NULL;
Chain_Destroy(chain);
Block_ShutdownPowContext();
BalanceSheet_Destroy();
exit(1);
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
//(void)argc; //(void)argc;
//(void)argv; //(void)argv;
@@ -510,6 +670,8 @@ int main(int argc, char* argv[]) {
} }
} }
ApplyRuntimeConfigFromEnv();
signal(SIGINT, handle_sigint); signal(SIGINT, handle_sigint);
srand((unsigned int)time(NULL)); srand((unsigned int)time(NULL));
@@ -538,6 +700,11 @@ int main(int argc, char* argv[]) {
uint8_t lastSavedHash[32] = {0}; uint8_t lastSavedHash[32] = {0};
if (!Chain_LoadFromFile(chain, chainDataDir, &currentSupply, &difficultyTarget, &currentReward, lastSavedHash, false)) { if (!Chain_LoadFromFile(chain, chainDataDir, &currentSupply, &difficultyTarget, &currentReward, lastSavedHash, false)) {
printf("No existing chain loaded from %s\n", chainDataDir); printf("No existing chain loaded from %s\n", chainDataDir);
} else {
// Recompute runtime supply/reward from loaded blocks to avoid trusting stale meta values.
if (!Chain_RecomputeRuntimeState(chain)) {
fprintf(stderr, "Failed to recompute runtime state from loaded chain\n");
}
} }
if (!BalanceSheet_LoadFromFile(chainDataDir)) { if (!BalanceSheet_LoadFromFile(chainDataDir)) {
@@ -577,9 +744,79 @@ int main(int argc, char* argv[]) {
} }
} }
// TODO: Separate loading into its own header
// Load the wallet from disk or generate new random identity
uint8_t minerAddress[32]; uint8_t minerAddress[32];
uint8_t minerPrivateKey[32]; uint8_t minerPrivateKey[32];
uint8_t minerCompressedPubkey[33]; uint8_t minerCompressedPubkey[33];
bool loadedWallet = false;
// Attempt load
char* path = "chain_data/wallet.data"; // TODO: Don't hardcode path
FILE* walletFile = fopen(path, "rb");
if (walletFile) {
size_t read = fread(minerPrivateKey, 1, 32, walletFile);
if (read != 32) {
fprintf(stderr, "failed to read wallet file\n");
fclose(walletFile);
}
read = fread(minerCompressedPubkey, 1, 33, walletFile);
if (read != 33) {
fprintf(stderr, "failed to read wallet file\n");
fclose(walletFile);
}
read = fread(minerAddress, 1, 32, walletFile);
if (read != 32) {
fprintf(stderr, "failed to read wallet file\n");
fclose(walletFile);
}
fclose(walletFile);
loadedWallet = true;
} else if (errno != ENOENT || errno != EISDIR || errno != EACCES || errno != EROFS || !loadedWallet) {
fprintf(stderr, "failed to open wallet file: %s\n generating new wallet...\n", strerror(errno));
if (!GenerateRandomTestAddress(minerAddress, minerPrivateKey, minerCompressedPubkey)) {
fprintf(stderr, "failed to generate test miner keypair\n");
KillEverythingAndExit(node, chain);
}
// Save the generated wallet to disk for future runs
walletFile = fopen(path, "wb");
if (!walletFile) {
fprintf(stderr, "failed to create wallet file: %s\n", strerror(errno));
KillEverythingAndExit(node, chain);
}
size_t written = fwrite(minerPrivateKey, 1, 32, walletFile);
if (written != 32) {
fprintf(stderr, "failed to write wallet file\n");
fclose(walletFile);
KillEverythingAndExit(node, chain);
}
written = fwrite(minerCompressedPubkey, 1, 33, walletFile);
if (written != 33) {
fprintf(stderr, "failed to write wallet file\n");
fclose(walletFile);
KillEverythingAndExit(node, chain);
}
written = fwrite(minerAddress, 1, 32, walletFile);
if (written != 32) {
fprintf(stderr, "failed to write wallet file\n");
fclose(walletFile);
KillEverythingAndExit(node, chain);
}
fclose(walletFile);
}
/*uint8_t minerAddress[32];
uint8_t minerPrivateKey[32];
uint8_t minerCompressedPubkey[33];
if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) { if (!GenerateTestMinerIdentity(minerPrivateKey, minerCompressedPubkey, minerAddress)) {
fprintf(stderr, "failed to generate test miner keypair\n"); fprintf(stderr, "failed to generate test miner keypair\n");
Node_Destroy(node); Node_Destroy(node);
@@ -588,7 +825,7 @@ int main(int argc, char* argv[]) {
Block_ShutdownPowContext(); Block_ShutdownPowContext();
BalanceSheet_Destroy(); BalanceSheet_Destroy();
return 1; return 1;
} }*/
char minerAddressHex[65]; char minerAddressHex[65];
AddressToHexString(minerAddress, minerAddressHex); AddressToHexString(minerAddress, minerAddressHex);
@@ -597,7 +834,7 @@ int main(int argc, char* argv[]) {
char supplyStr[80]; char supplyStr[80];
Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr)); Uint256ToDecimal(&currentSupply, supplyStr, sizeof(supplyStr));
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr); printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
printf("Commands: mine <x>, send <address> <amount>, balance [address], connect <ipv4>, sync (requires nodes), flushchain, fullverify, blockdetail <block number>, wipechain, genaddr, exit\n"); printf("Commands: mine <x>, send <address> <amount> [fee], txpooldetail <txhash>, balance [address], connect <ipv4>, sync (requires nodes), flushchain, fullverify, blockdetail <block number>, wipechain, genaddr, exit\n");
char line[1024]; char line[1024];
while (true) { while (true) {
@@ -635,14 +872,38 @@ int main(int argc, char* argv[]) {
printf("Mining %llu block(s)...\n", requested); printf("Mining %llu block(s)...\n", requested);
bool minedAll = true; bool minedAll = true;
for (unsigned long long i = 0; i < requested; ++i) { for (unsigned long long i = 0; i < requested; ++i) {
block_t* block = BuildNextBlock(chain, difficultyTarget); signed_transaction_t* acceptedTxs = NULL;
if (!block) { size_t acceptedTxCount = 0;
fprintf(stderr, "failed to create block\n"); uint64_t totalFees = 0;
if (!BuildSpendableMempoolSelection(&acceptedTxs, &acceptedTxCount, &totalFees)) {
fprintf(stderr, "failed to select spendable transactions from mempool\n");
minedAll = false; minedAll = false;
break; 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)) { if (!MineAndAppendBlock(chain, block, &currentSupply, &currentReward, &difficultyTarget)) {
Block_Destroy(block); Block_Destroy(block);
@@ -675,6 +936,7 @@ int main(int argc, char* argv[]) {
if (strcmp(cmd, "send") == 0) { if (strcmp(cmd, "send") == 0) {
char* addressStr = strtok(NULL, " \t"); char* addressStr = strtok(NULL, " \t");
char* amountStr = strtok(NULL, " \t"); char* amountStr = strtok(NULL, " \t");
char* feeStr = strtok(NULL, " \t");
if (!addressStr || !amountStr) { if (!addressStr || !amountStr) {
printf("usage: send <address> <amount>\n"); printf("usage: send <address> <amount>\n");
continue; continue;
@@ -693,6 +955,21 @@ int main(int argc, char* argv[]) {
continue; continue;
} }
unsigned long long fee = 0;
if (feeStr) {
char* endptr2 = NULL;
fee = strtoull(feeStr, &endptr2, 10);
if (*feeStr == '\0' || feeStr[0] == '-' || (endptr2 && *endptr2 != '\0')) {
printf("invalid fee\n");
continue;
}
}
if (fee > UINT64_MAX - amount) {
printf("invalid fee: overflow\n");
continue;
}
balance_sheet_entry_t senderEntry; balance_sheet_entry_t senderEntry;
if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) { if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) {
printf("send failed: miner address has no balance\n"); printf("send failed: miner address has no balance\n");
@@ -711,12 +988,13 @@ int main(int argc, char* argv[]) {
continue; continue;
} }
AddCoinbaseTransaction(block, minerAddress, currentReward); uint64_t coinbaseAmount = currentReward;
AddCoinbaseTransaction(block, minerAddress, coinbaseAmount);
signed_transaction_t spendTx; signed_transaction_t spendTx;
Transaction_Init(&spendTx); Transaction_Init(&spendTx);
spendTx.transaction.version = 1; spendTx.transaction.version = 1;
spendTx.transaction.fee = 0; spendTx.transaction.fee = (uint64_t)fee;
spendTx.transaction.amount1 = (uint64_t)amount; spendTx.transaction.amount1 = (uint64_t)amount;
spendTx.transaction.amount2 = 0; spendTx.transaction.amount2 = 0;
memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress)); memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress));
@@ -725,6 +1003,7 @@ int main(int argc, char* argv[]) {
memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey)); memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey));
Transaction_Sign(&spendTx, minerPrivateKey); Transaction_Sign(&spendTx, minerPrivateKey);
/*
Block_AddTransaction(block, &spendTx); Block_AddTransaction(block, &spendTx);
printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount); printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount);
char recipientHex[65]; char recipientHex[65];
@@ -743,6 +1022,22 @@ int main(int argc, char* argv[]) {
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL); Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
} }
printf("send committed in mined block\n"); printf("send committed in mined block\n");
*/
// Insert into txmempool
if (TxMempool_Insert(spendTx) < 0) {
printf("failed to add transaction to mempool, transaction rejected\n");
continue;
}
printf("transaction added to mempool, broadcasting...\n");
if (Node_BroadcastTransaction(node, &spendTx, NULL) == 0) {
printf("transaction broadcast to peers\n");
} else {
printf("failed to broadcast transaction to peers\n");
}
continue; continue;
} }
@@ -903,6 +1198,11 @@ int main(int argc, char* argv[]) {
break; 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 // Apply additional penalty by shrinking end and restart window from current Chain_Size
if (peerHeight > reorgPenalty) { if (peerHeight > reorgPenalty) {
end = peerHeight - reorgPenalty; end = peerHeight - reorgPenalty;
@@ -1004,6 +1304,46 @@ int main(int argc, char* argv[]) {
continue; continue;
} }
if (strcmp(cmd, "txpooldetail") == 0) {
char* hashStr = strtok(NULL, " \t");
if (!hashStr) {
printf("usage: txpooldetail <txhash>\n");
continue;
}
uint8_t txHash[32];
if (!ParseHexAddress32(hashStr, txHash)) {
printf("invalid tx hash: expected 64 hex chars\n");
continue;
}
signed_transaction_t tx;
if (!TxMempool_Lookup(txHash, &tx)) {
printf("transaction not found in mempool\n");
continue;
}
char senderHex[65];
char recip1Hex[65];
char recip2Hex[65];
AddressToHexString(tx.transaction.senderAddress, senderHex);
AddressToHexString(tx.transaction.recipientAddress1, recip1Hex);
AddressToHexString(tx.transaction.recipientAddress2, recip2Hex);
uint8_t calcHash[32];
Transaction_CalculateHash(&tx, calcHash);
printf("Transaction details:\n");
printf(" TxHash: "); PrintHexBytes(calcHash, 32); printf("\n");
printf(" Sender: %s%s\n", senderHex, Address_IsCoinbase(tx.transaction.senderAddress) ? " (coinbase)" : "");
printf(" Recipient1: %s\n", recip1Hex);
printf(" Recipient2: %s\n", recip2Hex);
printf(" Amount1: %llu\n", (unsigned long long)tx.transaction.amount1);
printf(" Amount2: %llu\n", (unsigned long long)tx.transaction.amount2);
printf(" Fee: %llu\n", (unsigned long long)tx.transaction.fee);
continue;
}
} }
if (strcmp(cmd, "blockdetail") == 0) { if (strcmp(cmd, "blockdetail") == 0) {
@@ -1084,9 +1424,10 @@ int main(int argc, char* argv[]) {
if (strcmp(cmd, "connect") == 0) { if (strcmp(cmd, "connect") == 0) {
char* ipStr = strtok(NULL, " \t"); char* ipStr = strtok(NULL, " \t");
char* portStr = strtok(NULL, " \t");
char* extra = strtok(NULL, " \t"); char* extra = strtok(NULL, " \t");
if (!ipStr || extra) { if (!ipStr || extra) {
printf("usage: connect <ipv4>\n"); printf("usage: connect <ipv4> [port]\n");
continue; continue;
} }
@@ -1095,16 +1436,31 @@ int main(int argc, char* argv[]) {
continue; continue;
} }
if (Node_ConnectPeer(node, ipStr, LISTEN_PORT) != 0) { unsigned short peerPort = listenPort;
if (portStr) {
char* end = NULL;
long parsedPort = strtol(portStr, &end, 10);
if (*portStr == '\0' || portStr[0] == '-' || (end && *end != '\0') || parsedPort <= 0 || parsedPort > 65535) {
printf("invalid port\n");
continue;
}
peerPort = (unsigned short)parsedPort;
if (strtok(NULL, " \t")) {
printf("usage: connect <ipv4> [port]\n");
continue;
}
}
if (Node_ConnectPeer(node, ipStr, peerPort) != 0) {
if (errno == ETIMEDOUT) { if (errno == ETIMEDOUT) {
printf("failed to connect to %s:%u (timeout)\n", ipStr, (unsigned int)LISTEN_PORT); printf("failed to connect to %s:%u (timeout)\n", ipStr, (unsigned int)peerPort);
} else { } else {
printf("failed to connect to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT); printf("failed to connect to %s:%u\n", ipStr, (unsigned int)peerPort);
} }
continue; continue;
} }
printf("connect requested to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT); printf("connect requested to %s:%u\n", ipStr, (unsigned int)peerPort);
continue; continue;
} }
@@ -1166,7 +1522,7 @@ int main(int argc, char* argv[]) {
if (strcmp(cmd, "genaddr") == 0) { if (strcmp(cmd, "genaddr") == 0) {
uint8_t testAddress[32]; uint8_t testAddress[32];
if (!GenerateRandomTestAddress(testAddress)) { if (!GenerateRandomTestAddress(testAddress, NULL, NULL)) {
printf("failed to generate address\n"); printf("failed to generate address\n");
continue; continue;
} }

View File

@@ -11,6 +11,7 @@
#include <inttypes.h> #include <inttypes.h>
#include <pthread.h> #include <pthread.h>
#include <unistd.h> #include <unistd.h>
#include <txmempool.h>
static net_node_t* Node_FromConnection(tcp_connection_t* conn) { static net_node_t* Node_FromConnection(tcp_connection_t* conn) {
if (!conn) { if (!conn) {
@@ -122,6 +123,13 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
return NODE_BLOCK_REJECTED; return NODE_BLOCK_REJECTED;
} }
// Temporary debug mode: force network-received blocks through the orphan pool to exercise reorg handling.
if (forceOrphanReorgEnabled && blk->header.blockNumber > 0) {
OrphanPool_Insert(blk, blockHeight);
printf("Forced orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight);
return NODE_BLOCK_ORPHAN_QUEUED;
}
// If parent is missing, insert into orphan pool instead of rejecting immediately. // If parent is missing, insert into orphan pool instead of rejecting immediately.
uint64_t chainSize = Chain_Size(currentChain); uint64_t chainSize = Chain_Size(currentChain);
if (blk->header.blockNumber > chainSize) { if (blk->header.blockNumber > chainSize) {
@@ -169,6 +177,20 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
return NODE_BLOCK_REJECTED; 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 // Persist on accept if requested
if (persist) { if (persist) {
Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward); Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward);
@@ -231,8 +253,9 @@ net_node_t* Node_Create() {
pthread_mutex_init(&node->seenLock, NULL); pthread_mutex_init(&node->seenLock, NULL);
pthread_mutex_init(&node->outboundLock, NULL); pthread_mutex_init(&node->outboundLock, NULL);
node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes
TxMempool_Init();
TcpServer_Init(node->server, LISTEN_PORT, "0.0.0.0"); TcpServer_Init(node->server, listenPort, "0.0.0.0");
node->server->owner = node; node->server->owner = node;
node->server->on_connect = Node_Server_OnConnect; node->server->on_connect = Node_Server_OnConnect;
@@ -276,6 +299,7 @@ void Node_Destroy(net_node_t* node) {
} }
OrphanPool_Destroy(); OrphanPool_Destroy();
TxMempool_Destroy();
if (node->seenBlocks) { if (node->seenBlocks) {
DynSet_Destroy(node->seenBlocks); DynSet_Destroy(node->seenBlocks);
@@ -377,19 +401,45 @@ int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t pack
return rc; return rc;
} }
int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx, tcp_connection_t* excludeNode) {
if (!node || !tx) {
return -1;
}
// Serialize transaction into payload
size_t payloadLen = sizeof(signed_transaction_t);
unsigned char* payload = (unsigned char*)malloc(payloadLen);
if (!payload) {
return -1;
}
memcpy(payload, tx, sizeof(signed_transaction_t));
// Broadcast to all outbound peers
pthread_mutex_lock(&node->outboundLock);
for (size_t i = 0; i < MAX_CONS; ++i) {
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);
free(payload);
return 0;
}
void Node_Server_OnConnect(tcp_connection_t* client) { void Node_Server_OnConnect(tcp_connection_t* client) {
net_node_t* node = Node_FromConnection(client); net_node_t* node = Node_FromConnection(client);
Node_ForwardConnect(node, client); Node_ForwardConnect(node, client);
printf("Inbound node connected: %u\n", client ? client->connectionId : 0U); printf("Inbound node connected: %u\n", client ? client->connectionId : 0U);
#if ECHO_PEERS if (echoPeersEnabled && node && client) {
if (node && client) { // Attempt to create an outbound connection back to the peer's IP on our configured port.
// Attempt to create an outbound connection back to the peer's IP on our LISTEN_PORT.
// We avoid connecting if we already have an outbound to the same IP. // We avoid connecting if we already have an outbound to the same IP.
char ipbuf[INET_ADDRSTRLEN]; char ipbuf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &client->peerAddr.sin_addr, ipbuf, sizeof(ipbuf))) { if (inet_ntop(AF_INET, &client->peerAddr.sin_addr, ipbuf, sizeof(ipbuf))) {
// Use LISTEN_PORT as target port for peer's listening service, not the ephemeral source port. // Use the configured port as the target port for the peer's listening service.
unsigned short targetPort = LISTEN_PORT; unsigned short targetPort = listenPort;
int shouldConnect = 1; int shouldConnect = 1;
pthread_mutex_lock(&node->outboundLock); pthread_mutex_lock(&node->outboundLock);
@@ -410,7 +460,6 @@ void Node_Server_OnConnect(tcp_connection_t* client) {
} }
} }
} }
#endif
} }
void Node_Server_OnData(tcp_connection_t* client) { void Node_Server_OnData(tcp_connection_t* client) {
@@ -588,7 +637,48 @@ void Node_Server_OnData(tcp_connection_t* client) {
break; break;
} }
case PACKET_TYPE_ACK_BLOCK: case PACKET_TYPE_ACK_BLOCK:
case PACKET_TYPE_BROADCAST_TX: case PACKET_TYPE_BROADCAST_TX: {
// Decode the block or transaction data inside
if (payloadLen == sizeof(signed_transaction_t)) {
signed_transaction_t tx;
memcpy(&tx, payload, sizeof(tx));
uint8_t txHash[32];
char txHashHex[65];
Transaction_CalculateHash(&tx, txHash);
to_hex(txHash, txHashHex);
printf("Received packet type %u from node %u with transaction sending %llu pebble(s)\n",
(unsigned int)packetType, client ? client->connectionId : 0U, (unsigned long long)tx.transaction.amount1);
if (!Transaction_Verify(&tx)) {
printf("Received invalid transaction from node %u\n", client ? client->connectionId : 0U);
return;
}
// Push to mempool if it's not already present
if (!TxMempool_Lookup(txHash, &tx)) {
if (TxMempool_Insert(tx) >= 0) {
printf("Added transaction %s from node %u to mempool\n", txHashHex, client ? client->connectionId : 0U);
// Broadcast to other peers
net_node_t* node = Node_FromConnection(client);
if (node) {
Node_BroadcastTransaction(node, &tx, client);
}
} else {
printf("Failed to add transaction %s from node %u to mempool\n", txHashHex, client ? client->connectionId : 0U);
}
} else {
printf("Transaction %s from node %u already seen!\n", txHashHex, client ? client->connectionId : 0U);
}
} else {
printf("Received packet type %u from node %u with invalid payload length %zu\n",
(unsigned int)packetType, client ? client->connectionId : 0U, payloadLen);
// TODO: Ignoring for now, might error node later if we want to be strict about malformed messages
}
break;
}
case PACKET_TYPE_ACK_TX: case PACKET_TYPE_ACK_TX:
case PACKET_TYPE_ERROR: { case PACKET_TYPE_ERROR: {
// Decode the message inside as text // Decode the message inside as text
@@ -760,7 +850,12 @@ void Node_Client_OnData(tcp_connection_t* client) {
break; break;
} }
case PACKET_TYPE_ACK_BLOCK: case PACKET_TYPE_ACK_BLOCK:
case PACKET_TYPE_BROADCAST_TX: case PACKET_TYPE_BROADCAST_TX: {
// Client can't receive these!
printf("Received unexpected packet type %u from node %u\n", (unsigned int)packetType, client ? client->connectionId : 0U);
break;
}
case PACKET_TYPE_ACK_TX: case PACKET_TYPE_ACK_TX:
case PACKET_TYPE_ERROR: { case PACKET_TYPE_ERROR: {
// Decode the message inside as text // Decode the message inside as text

View File

@@ -38,6 +38,71 @@ void OrphanPool_Insert(block_t* block, uint64_t height) {
(void)DynArr_push_back(g_orphans, &e); (void)DynArr_push_back(g_orphans, &e);
} }
static size_t OrphanPool_TryAdoptBranch(blockchain_t* chain, uint64_t forkHeight) {
if (!g_orphans || !chain) return 0;
DynArr* seq = DYNARR_CREATE(block_t*, 8);
if (!seq) return 0;
size_t cursor = forkHeight;
while (1) {
bool found = false;
size_t count = DynArr_size(g_orphans);
for (size_t i = 0; i < count; ++i) {
orphan_entry_t* entry = (orphan_entry_t*)DynArr_at(g_orphans, i);
if (!entry || !entry->block) continue;
if (entry->height == cursor) {
(void)DynArr_push_back(seq, &entry->block);
found = true;
break;
}
}
if (!found) break;
cursor++;
}
size_t seqCount = DynArr_size(seq);
if (seqCount == 0) {
DynArr_destroy(seq);
return 0;
}
size_t currentTipHeight = Chain_Size(chain) == 0 ? 0 : Chain_Size(chain) - 1;
size_t seqTopHeight = forkHeight + seqCount - 1;
if (seqTopHeight <= currentTipHeight) {
DynArr_destroy(seq);
return 0;
}
size_t rollbackHeight = (forkHeight == 0) ? 0 : (forkHeight - 1);
if (!Chain_RollbackToHeight(chain, rollbackHeight)) {
DynArr_destroy(seq);
return 0;
}
size_t attached = 0;
for (size_t i = 0; i < seqCount; ++i) {
block_t* bptr = *(block_t**)DynArr_at(seq, i);
if (!bptr || !Chain_AddBlock(chain, bptr)) {
break;
}
size_t count = DynArr_size(g_orphans);
for (size_t j = 0; j < count; ++j) {
orphan_entry_t* entry = (orphan_entry_t*)DynArr_at(g_orphans, j);
if (entry && entry->block == bptr) {
DynArr_remove(g_orphans, j);
break;
}
}
attached++;
}
DynArr_destroy(seq);
return attached;
}
size_t OrphanPool_AttemptAttach(blockchain_t* chain) { size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
if (!g_orphans || !chain) return 0; if (!g_orphans || !chain) return 0;
size_t attached = 0; size_t attached = 0;
@@ -67,6 +132,30 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
} }
if (parentExists) { if (parentExists) {
if (e->height < Chain_Size(chain)) {
block_t* local = NULL;
if (Chain_GetBlockCopy(chain, (size_t)e->height, &local) && local) {
uint8_t localHash[32];
uint8_t orphanHash[32];
Block_CalculateHash(local, localHash);
Block_CalculateHash(e->block, orphanHash);
Block_Destroy(local);
if (memcmp(localHash, orphanHash, 32) != 0) {
size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height);
if (adopted > 0) {
attached += adopted;
madeProgress = true;
n = DynArr_size(g_orphans);
i = (size_t)-1;
break;
}
}
} else if (local) {
Block_Destroy(local);
}
}
// Verify that the parent's hash matches the orphan's prevHash before attaching. // Verify that the parent's hash matches the orphan's prevHash before attaching.
bool parentMatches = false; bool parentMatches = false;
if (e->height == 0) { if (e->height == 0) {
@@ -85,63 +174,15 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
if (!parentMatches) { if (!parentMatches) {
// Parent exists but does not match this orphan's prevHash. // Parent exists but does not match this orphan's prevHash.
// Attempt to detect a longer alternate chain in the orphan pool starting at this height. size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height);
// Build a consecutive sequence of orphans from this height upward. if (adopted > 0) {
DynArr* seq = DYNARR_CREATE(block_t*, 8); attached += adopted;
size_t h = e->height;
while (1) {
bool found = false;
size_t gn = DynArr_size(g_orphans);
for (size_t gi = 0; gi < gn; ++gi) {
orphan_entry_t* oe = (orphan_entry_t*)DynArr_at(g_orphans, gi);
if (!oe || !oe->block) continue;
if (oe->height == h) {
(void)DynArr_push_back(seq, &oe->block);
found = true;
break;
}
}
if (!found) break;
h++;
}
size_t seqCount = DynArr_size(seq);
if (seqCount > 0) {
size_t seqTopHeight = e->height + seqCount - 1;
if (seqTopHeight >= Chain_Size(chain)) {
// Found a candidate longer branch. Perform rollback to fork height and attach sequence.
if (Chain_RollbackToHeight(chain, (size_t)e->height)) {
// Attach in-order
for (size_t si = 0; si < seqCount; ++si) {
block_t* bptr = *(block_t**)DynArr_at(seq, si);
if (!Chain_AddBlock(chain, bptr)) {
// failed to add; stop attempting further
break;
}
// Remove the attached orphan from pool but keep the block object to preserve transactions in-memory (consistent with existing behavior)
// Find and remove corresponding orphan entry
size_t gn2 = DynArr_size(g_orphans);
for (size_t gi2 = 0; gi2 < gn2; ++gi2) {
orphan_entry_t* oe2 = (orphan_entry_t*)DynArr_at(g_orphans, gi2);
if (oe2 && oe2->block == bptr) {
DynArr_remove(g_orphans, gi2);
gn2 = DynArr_size(g_orphans);
gi2 = (size_t)-1; // restart search if needed
}
}
}
attached += seqCount;
madeProgress = true; madeProgress = true;
DynArr_destroy(seq);
// reset outer loop
n = DynArr_size(g_orphans); n = DynArr_size(g_orphans);
i = (size_t)-1; i = (size_t)-1;
break; break;
} }
}
}
DynArr_destroy(seq);
// If we didn't perform a reorg/attach, skip for now.
continue; continue;
} }
@@ -156,13 +197,8 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
i = (size_t)-1; // reset outer loop i = (size_t)-1; // reset outer loop
break; break;
} else { } else {
// Chain_AddBlock rejected it (maybe invalid). Drop it. // Keep the orphan around; rejection may be temporary while the local tip is being reorged.
Block_Destroy(e->block); continue;
DynArr_remove(g_orphans, i);
n = DynArr_size(g_orphans);
i = (size_t)-1;
madeProgress = true;
break;
} }
} }
} }

View File

@@ -214,10 +214,26 @@ int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t
pthread_mutex_lock(&conn->sendLock); pthread_mutex_lock(&conn->sendLock);
#ifdef USE_IPV6
int sock;
if (conn->sockFd6 >= 0) {
// IPv6 is available, attempt to send on it. If it fails, we'll fall back to IPv4 if available.
sock = conn->sockFd6;
} else {
// IPv4 fallback
sock = conn->sockFd;
}
int rc = TcpConnection_SendRaw(sock, &beLen, sizeof(beLen));
if (rc == 0 && payloadLen > 0) {
rc = TcpConnection_SendRaw(sock, payload, payloadLen);
}
#else
int rc = TcpConnection_SendRaw(conn->sockFd, &beLen, sizeof(beLen)); int rc = TcpConnection_SendRaw(conn->sockFd, &beLen, sizeof(beLen));
if (rc == 0 && payloadLen > 0) { if (rc == 0 && payloadLen > 0) {
rc = TcpConnection_SendRaw(conn->sockFd, payload, payloadLen); rc = TcpConnection_SendRaw(conn->sockFd, payload, payloadLen);
} }
#endif
pthread_mutex_unlock(&conn->sendLock); pthread_mutex_unlock(&conn->sendLock);
@@ -235,6 +251,11 @@ void TcpConnection_RequestClose(tcp_connection_t* conn) {
if (conn->sockFd >= 0) { if (conn->sockFd >= 0) {
shutdown(conn->sockFd, SHUT_RDWR); shutdown(conn->sockFd, SHUT_RDWR);
} }
#ifdef USE_IPV6
if (conn->sockFd6 >= 0) {
shutdown(conn->sockFd6, SHUT_RDWR);
}
#endif
} }
pthread_mutex_unlock(&conn->stateLock); pthread_mutex_unlock(&conn->stateLock);
} }

View File

@@ -172,6 +172,9 @@ tcp_server_t* TcpServer_Create() {
svr->isRunning = 0; svr->isRunning = 0;
svr->maxClients = 0; svr->maxClients = 0;
svr->clientsArrPtr = NULL; svr->clientsArrPtr = NULL;
#ifdef USE_IPV6
svr->sockFd6 = -1;
#endif
if (pthread_mutex_init(&svr->clientsMutex, NULL) != 0) { if (pthread_mutex_init(&svr->clientsMutex, NULL) != 0) {
free(svr); free(svr);
@@ -217,6 +220,28 @@ void TcpServer_Init(tcp_server_t* ptr, unsigned short port, const char* addr) {
close(ptr->sockFd); close(ptr->sockFd);
ptr->sockFd = -1; ptr->sockFd = -1;
} }
#ifdef USE_IPV6
// IPv6 support
ptr->sockFd6 = socket(AF_INET6, SOCK_STREAM, 0);
if (ptr->sockFd6 >= 0) {
ptr->opt6 = 1;
setsockopt(ptr->sockFd6, SOL_SOCKET, SO_REUSEADDR, &ptr->opt6, sizeof(int));
memset(&ptr->addr6, 0, sizeof(ptr->addr6));
ptr->addr6.sin6_family = AF_INET6;
ptr->addr6.sin6_port = htons(port);
inet_pton(AF_INET6, addr, &ptr->addr6.sin6_addr);
if (bind(ptr->sockFd6, (struct sockaddr*)&ptr->addr6, sizeof(ptr->addr6)) < 0) {
close(ptr->sockFd6);
ptr->sockFd6 = -1;
}
} else {
ptr->sockFd6 = -1; // IPv6 is optional, so if it isn't available, we just set it to -1
}
#else
// Safety for my future "I forgot the ifdef guard" self
ptr->sockFd6 = -1; // IPv6 not supported in this build
#endif
} }
void TcpServer_Start(tcp_server_t* ptr, int maxcons) { void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
@@ -228,6 +253,15 @@ void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
return; return;
} }
#ifdef USE_IPV6
if (ptr->sockFd6 >= 0) {
if (listen(ptr->sockFd6, maxcons) < 0) {
close(ptr->sockFd6);
ptr->sockFd6 = -1;
}
}
#endif
pthread_mutex_lock(&ptr->clientsMutex); pthread_mutex_lock(&ptr->clientsMutex);
ptr->maxClients = (size_t)maxcons; ptr->maxClients = (size_t)maxcons;
@@ -268,6 +302,14 @@ void TcpServer_Stop(tcp_server_t* ptr) {
ptr->sockFd = -1; ptr->sockFd = -1;
} }
#ifdef USE_IPV6
if (ptr->sockFd6 >= 0) {
shutdown(ptr->sockFd6, SHUT_RDWR);
close(ptr->sockFd6);
ptr->sockFd6 = -1;
}
#endif
if (ptr->svrThread != 0 && !pthread_equal(ptr->svrThread, pthread_self())) { if (ptr->svrThread != 0 && !pthread_equal(ptr->svrThread, pthread_self())) {
pthread_join(ptr->svrThread, NULL); pthread_join(ptr->svrThread, NULL);
} }
@@ -339,6 +381,12 @@ void TcpServer_KillClient(tcp_server_t* ptr, tcp_connection_t* cli) {
so_linger.l_linger = 0; so_linger.l_linger = 0;
setsockopt(cli->sockFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); setsockopt(cli->sockFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
#ifdef USE_IPV6
if (cli->sockFd6 >= 0) {
setsockopt(cli->sockFd6, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
}
#endif
TcpServer_Disconnect(ptr, cli); TcpServer_Disconnect(ptr, cli);
} }

View File

@@ -1,14 +1,21 @@
#include <txmempool.h> #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; khash_t(tx_mempool_map_m)* txMempool = NULL;
void TxMempool_Init() { void TxMempool_Init() {
txMempool = kh_init(tx_mempool_map_m); txMempool = kh_init(tx_mempool_map_m);
pthread_mutex_init(&g_txMempoolLock, NULL);
g_txMempoolLockInitialized = true;
} }
int TxMempool_Insert(signed_transaction_t tx) { int TxMempool_Insert(signed_transaction_t tx) {
if (!txMempool) { return -1; } if (!txMempool) { return -1; }
pthread_mutex_lock(&g_txMempoolLock);
uint8_t txHash[32]; uint8_t txHash[32];
Transaction_CalculateHash(&tx, txHash); Transaction_CalculateHash(&tx, txHash);
@@ -18,17 +25,21 @@ int TxMempool_Insert(signed_transaction_t tx) {
int ret; int ret;
khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret); khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret);
if (k == kh_end(txMempool)) { if (k == kh_end(txMempool)) {
pthread_mutex_unlock(&g_txMempoolLock);
return -1; return -1;
} }
kh_value(txMempool, k) = tx; kh_value(txMempool, k) = tx;
pthread_mutex_unlock(&g_txMempoolLock);
return ret; return ret;
} }
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) { bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
if (!txMempool || !txHash || !out) { return false; } if (!txMempool || !txHash || !out) { return false; }
pthread_mutex_lock(&g_txMempoolLock);
key32_t key; key32_t key;
memcpy(key.bytes, txHash, 32); memcpy(key.bytes, txHash, 32);
@@ -36,15 +47,65 @@ bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
if (k != kh_end(txMempool)) { if (k != kh_end(txMempool)) {
signed_transaction_t tx = kh_value(txMempool, k); signed_transaction_t tx = kh_value(txMempool, k);
memcpy(out, &tx, sizeof(signed_transaction_t)); memcpy(out, &tx, sizeof(signed_transaction_t));
pthread_mutex_unlock(&g_txMempoolLock);
return true; return true;
} }
pthread_mutex_unlock(&g_txMempoolLock);
return false; 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() { void TxMempool_Print() {
if (!txMempool) { return; } if (!txMempool) { return; }
pthread_mutex_lock(&g_txMempoolLock);
khiter_t k; khiter_t k;
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) { for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
if (kh_exist(txMempool, k)) { if (kh_exist(txMempool, k)) {
@@ -62,10 +123,37 @@ void TxMempool_Print() {
(unsigned long long)tx.transaction.fee); (unsigned long long)tx.transaction.fee);
} }
} }
pthread_mutex_unlock(&g_txMempoolLock);
} }
void TxMempool_Destroy() { void TxMempool_Destroy() {
if (txMempool) { if (txMempool) {
pthread_mutex_lock(&g_txMempoolLock);
kh_destroy(tx_mempool_map_m, txMempool); 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;
} }
} }
bool TxMempool_Remove(const uint8_t* txHash) {
if (!txMempool || !txHash) { return false; }
pthread_mutex_lock(&g_txMempoolLock);
key32_t key;
memcpy(key.bytes, txHash, 32);
khiter_t k = kh_get(tx_mempool_map_m, txMempool, key);
if (k == kh_end(txMempool)) {
pthread_mutex_unlock(&g_txMempoolLock);
return false;
}
kh_del(tx_mempool_map_m, txMempool, k);
pthread_mutex_unlock(&g_txMempoolLock);
return true;
}