Compare commits
9 Commits
4f10f013f6
...
txtest
| Author | SHA1 | Date | |
|---|---|---|---|
| da50b4e8c1 | |||
| 00bd711501 | |||
| 17ef3b74fd | |||
| c1914dc3e7 | |||
| 39293029c5 | |||
| 763aeb648f | |||
| 41a154a9fd | |||
| 91d7bfa4e7 | |||
| 4cfe85f6f2 |
8
TODO.txt
8
TODO.txt
@@ -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.
|
||||
|
||||
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:
|
||||
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner.
|
||||
|
||||
NOTE:
|
||||
Because tx sizes are currently fixed, mining can use raw fee ordering for now. If tx sizes ever become dynamic, revisit selection to consider fee/byte instead.
|
||||
|
||||
Mempool snapshotting for mining should hold the lock only long enough to copy pending txs, but if the mempool grows very large that copy may still be non-trivial.
|
||||
|
||||
DONE:
|
||||
I want to move away from the Monero emission. I want to do something a bit radical for cryptocurrency, but I feel like it's necessary to make it more like money:
|
||||
a constant inflation rate of 1.5% per year. It's lower than fiat (USD is ~2.8% per year), and it additionally doesn't fluctuate during crisis. It's constant.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stdio.h>
|
||||
#include <khash/khash.h>
|
||||
#include <crypto/crypto.h>
|
||||
#include <block/transaction.h>
|
||||
#include <string.h>
|
||||
#include <utils.h>
|
||||
#include <uint256.h>
|
||||
@@ -29,4 +30,12 @@ bool BalanceSheet_LoadFromFile(const char* inPath);
|
||||
void BalanceSheet_Print();
|
||||
void BalanceSheet_Destroy();
|
||||
|
||||
bool BalanceSheet_SelectSpendableTransactions(
|
||||
const signed_transaction_t* candidates,
|
||||
size_t candidateCount,
|
||||
signed_transaction_t** outAccepted,
|
||||
size_t* outAcceptedCount,
|
||||
uint64_t* outTotalFees
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -36,6 +36,7 @@ void Block_AddTransaction(block_t* block, signed_transaction_t* tx);
|
||||
void Block_RemoveTransaction(block_t* block, uint8_t* txHash);
|
||||
bool Block_HasValidProofOfWork(const block_t* block);
|
||||
bool Block_AllTransactionsValid(const block_t* block);
|
||||
bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees);
|
||||
bool Block_IsFullyValid(const block_t* block);
|
||||
void Block_ShutdownPowContext(void);
|
||||
void Block_Destroy(block_t* block);
|
||||
|
||||
@@ -28,6 +28,10 @@ void Chain_Wipe(blockchain_t* chain);
|
||||
// Returns true on success.
|
||||
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`.
|
||||
bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy);
|
||||
|
||||
|
||||
@@ -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_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
|
||||
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight);
|
||||
|
||||
@@ -14,6 +14,9 @@ extern uint256_t currentSupply;
|
||||
extern uint64_t currentReward;
|
||||
extern uint32_t difficultyTarget;
|
||||
extern const char* chainDataDir;
|
||||
extern unsigned short listenPort;
|
||||
extern bool echoPeersEnabled;
|
||||
extern bool forceOrphanReorgEnabled;
|
||||
|
||||
// Global synchronization primitives for runtime state
|
||||
extern pthread_rwlock_t chainLock; // protects chain structure and related mutations
|
||||
|
||||
@@ -19,8 +19,17 @@ typedef enum {
|
||||
typedef struct tcp_connection_t 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;
|
||||
struct sockaddr_in peerAddr;
|
||||
#ifdef USE_IPV6
|
||||
int sockFd6; // For IPv6 support
|
||||
struct sockaddr_in6 peerAddr6; // For IPv6 support
|
||||
#endif
|
||||
uint32_t connectionId;
|
||||
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);
|
||||
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_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen);
|
||||
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
typedef struct {
|
||||
int sockFd;
|
||||
struct sockaddr_in addr;
|
||||
#ifdef USE_IPV6
|
||||
int sockFd6; // IPv6 support
|
||||
struct sockaddr_in6 addr6; // IPv6 support
|
||||
#endif
|
||||
int opt;
|
||||
int opt6; // IPv6 support
|
||||
int isRunning;
|
||||
void* owner;
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ void TxMempool_Init();
|
||||
// Assumed that the transation was confirmed to be valid
|
||||
int TxMempool_Insert(signed_transaction_t tx);
|
||||
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out);
|
||||
bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount);
|
||||
void TxMempool_Print();
|
||||
// Remove a transaction from the mempool by its hash. Returns true if removed.
|
||||
bool TxMempool_Remove(const uint8_t* txHash);
|
||||
void TxMempool_Destroy();
|
||||
|
||||
#endif
|
||||
|
||||
@@ -167,7 +167,7 @@ static inline bool GenerateTestMinerIdentity(uint8_t privateKey[32], uint8_t com
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@@ -200,11 +200,18 @@ static inline bool GenerateRandomTestAddress(uint8_t outAddress[32]) {
|
||||
}
|
||||
|
||||
AddressFromCompressedPubkey(compressedPubkey, outAddress);
|
||||
if (outPrivateKey) {
|
||||
memcpy(outPrivateKey, privateKey, 32);
|
||||
}
|
||||
if (outCompressedPubkey) {
|
||||
memcpy(outCompressedPubkey, compressedPubkey, 33);
|
||||
}
|
||||
secp256k1_context_destroy(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
secp256k1_context_destroy(ctx);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,140 @@
|
||||
khash_t(balance_sheet_map_m)* sheetMap = NULL;
|
||||
static pthread_mutex_t g_sheetLock;
|
||||
|
||||
static bool BalanceSheet_GetSimEntry(
|
||||
khash_t(balance_sheet_map_m)* simMap,
|
||||
const uint8_t address[32],
|
||||
balance_sheet_entry_t* out
|
||||
) {
|
||||
if (!simMap || !address || !out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
key32_t key;
|
||||
memcpy(key.bytes, address, 32);
|
||||
|
||||
khiter_t k = kh_get(balance_sheet_map_m, simMap, key);
|
||||
if (k != kh_end(simMap)) {
|
||||
*out = kh_value(simMap, k);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (BalanceSheet_Lookup((uint8_t*)address, out)) {
|
||||
int ret = 0;
|
||||
k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||
if (k == kh_end(simMap)) {
|
||||
return false;
|
||||
}
|
||||
kh_value(simMap, k) = *out;
|
||||
return true;
|
||||
}
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
memcpy(out->address, address, 32);
|
||||
out->balance = uint256_from_u64(0);
|
||||
|
||||
int ret = 0;
|
||||
k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||
if (k == kh_end(simMap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
kh_value(simMap, k) = *out;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BalanceSheet_StoreSimEntry(
|
||||
khash_t(balance_sheet_map_m)* simMap,
|
||||
const balance_sheet_entry_t* entry
|
||||
) {
|
||||
if (!simMap || !entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
key32_t key;
|
||||
memcpy(key.bytes, entry->address, 32);
|
||||
|
||||
int ret = 0;
|
||||
khiter_t k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||
if (k == kh_end(simMap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
kh_value(simMap, k) = *entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BalanceSheet_ApplyCandidateTransaction(
|
||||
khash_t(balance_sheet_map_m)* simMap,
|
||||
const signed_transaction_t* tx,
|
||||
uint64_t* outFee
|
||||
) {
|
||||
if (!simMap || !tx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
balance_sheet_entry_t senderEntry;
|
||||
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.senderAddress, &senderEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256_t spend = uint256_from_u64(0);
|
||||
if (uint256_add_u64(&spend, tx->transaction.amount1) ||
|
||||
uint256_add_u64(&spend, tx->transaction.amount2) ||
|
||||
uint256_add_u64(&spend, tx->transaction.fee)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uint256_cmp(&senderEntry.balance, &spend) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!uint256_subtract(&senderEntry.balance, &spend)) {
|
||||
return false;
|
||||
}
|
||||
if (!BalanceSheet_StoreSimEntry(simMap, &senderEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
balance_sheet_entry_t recipient1Entry;
|
||||
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress1, &recipient1Entry)) {
|
||||
return false;
|
||||
}
|
||||
if (uint256_add_u64(&recipient1Entry.balance, tx->transaction.amount1)) {
|
||||
return false;
|
||||
}
|
||||
if (!BalanceSheet_StoreSimEntry(simMap, &recipient1Entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx->transaction.amount2 > 0) {
|
||||
balance_sheet_entry_t recipient2Entry;
|
||||
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress2, &recipient2Entry)) {
|
||||
return false;
|
||||
}
|
||||
if (uint256_add_u64(&recipient2Entry.balance, tx->transaction.amount2)) {
|
||||
return false;
|
||||
}
|
||||
if (!BalanceSheet_StoreSimEntry(simMap, &recipient2Entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (outFee) {
|
||||
*outFee = tx->transaction.fee;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int BalanceSheet_InsertLocked(balance_sheet_entry_t entry) {
|
||||
if (!sheetMap) {
|
||||
return -1;
|
||||
@@ -143,3 +277,64 @@ void BalanceSheet_Destroy() {
|
||||
sheetMap = NULL;
|
||||
pthread_mutex_destroy(&g_sheetLock);
|
||||
}
|
||||
|
||||
bool BalanceSheet_SelectSpendableTransactions(
|
||||
const signed_transaction_t* candidates,
|
||||
size_t candidateCount,
|
||||
signed_transaction_t** outAccepted,
|
||||
size_t* outAcceptedCount,
|
||||
uint64_t* outTotalFees
|
||||
) {
|
||||
if (!outAccepted || !outAcceptedCount || !outTotalFees) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*outAccepted = NULL;
|
||||
*outAcceptedCount = 0;
|
||||
*outTotalFees = 0;
|
||||
|
||||
if (!candidates || candidateCount == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
signed_transaction_t* accepted = (signed_transaction_t*)calloc(candidateCount, sizeof(signed_transaction_t));
|
||||
if (!accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
khash_t(balance_sheet_map_m)* simMap = kh_init(balance_sheet_map_m);
|
||||
if (!simMap) {
|
||||
free(accepted);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t acceptedCount = 0;
|
||||
uint64_t totalFees = 0;
|
||||
|
||||
for (size_t i = 0; i < candidateCount; ++i) {
|
||||
const signed_transaction_t* tx = &candidates[i];
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t fee = 0;
|
||||
if (!BalanceSheet_ApplyCandidateTransaction(simMap, tx, &fee)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
accepted[acceptedCount++] = *tx;
|
||||
totalFees += fee;
|
||||
}
|
||||
|
||||
kh_destroy(balance_sheet_map_m, simMap);
|
||||
|
||||
if (acceptedCount == 0) {
|
||||
free(accepted);
|
||||
accepted = NULL;
|
||||
}
|
||||
|
||||
*outAccepted = accepted;
|
||||
*outAcceptedCount = acceptedCount;
|
||||
*outTotalFees = totalFees;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -214,21 +214,86 @@ bool Block_AllTransactionsValid(const block_t* block) {
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
if (hasCoinbase) {
|
||||
return false; // More than one coinbase transaction
|
||||
return false;
|
||||
}
|
||||
|
||||
hasCoinbase = true;
|
||||
continue; // Coinbase transactions are valid since the miner has the right to create coins. Only rule is one per block.
|
||||
}
|
||||
}
|
||||
|
||||
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
||||
}
|
||||
|
||||
bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees) {
|
||||
if (!block || !block->transactions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasCoinbase = false;
|
||||
uint64_t totalFees = 0;
|
||||
uint8_t zeroAddress[32] = {0};
|
||||
|
||||
for (size_t i = 0; i < DynArr_size(block->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
if (!tx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
if (hasCoinbase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasCoinbase = true;
|
||||
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx->transaction.fee != 0 || tx->transaction.amount2 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx->transaction.amount1 != expectedCoinbaseAmount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.recipientAddress1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(tx->transaction.recipientAddress2, zeroAddress, sizeof(zeroAddress)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UINT64_MAX - totalFees < tx->transaction.fee) {
|
||||
return false;
|
||||
}
|
||||
totalFees += tx->transaction.fee;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <block/chain.h>
|
||||
#include <constants.h>
|
||||
#include <runtime_state.h>
|
||||
#include <txmempool.h>
|
||||
#include <errno.h>
|
||||
#include <limits.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;
|
||||
}
|
||||
|
||||
bool Chain_RecomputeRuntimeState(blockchain_t* chain) {
|
||||
if (!chain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256_t rebuiltSupply = uint256_from_u64(0);
|
||||
for (size_t i = 0; i < chain->size; ++i) {
|
||||
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||
if (!blk || !blk->transactions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < DynArr_size(blk->transactions); ++j) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j);
|
||||
if (!tx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
if (uint256_add_u64(&rebuiltSupply, tx->transaction.amount1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentSupply = rebuiltSupply;
|
||||
currentReward = CalculateBlockReward(currentSupply, chain);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void Chain_ClearBlocks(blockchain_t* chain) {
|
||||
if (!chain || !chain->blocks) {
|
||||
return;
|
||||
@@ -161,34 +193,90 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||
}
|
||||
|
||||
do {
|
||||
// First pass: ensure all non-coinbase senders can cover the full spend
|
||||
// (amount1 + amount2 + fee) before mutating the chain or balance sheet.
|
||||
size_t txCount = DynArr_size(block->transactions);
|
||||
signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t));
|
||||
if (!candidateTxs) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t nonCoinbaseCount = 0;
|
||||
for (size_t i = 0; i < txCount; ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||
if (!tx) {
|
||||
ok = false; break;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
continue;
|
||||
candidateTxs[i] = *tx;
|
||||
if (!Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
++nonCoinbaseCount;
|
||||
}
|
||||
}
|
||||
|
||||
uint256_t spend;
|
||||
if (!BuildSpendAmount(tx, &spend)) { ok = false; break; }
|
||||
|
||||
balance_sheet_entry_t senderEntry;
|
||||
if (!BalanceSheet_Lookup(tx->transaction.senderAddress, &senderEntry)) {
|
||||
fprintf(stderr, "Error: Sender address not found in balance sheet during block addition. Bailing!\n");
|
||||
ok = false; break;
|
||||
if (!ok) {
|
||||
free(candidateTxs);
|
||||
break;
|
||||
}
|
||||
|
||||
if (uint256_cmp(&senderEntry.balance, &spend) < 0) {
|
||||
fprintf(stderr, "Error: Sender balance insufficient for block transaction. Bailing!\n");
|
||||
ok = false; break;
|
||||
signed_transaction_t* spendableTxs = NULL;
|
||||
size_t spendableCount = 0;
|
||||
uint64_t totalFees = 0;
|
||||
if (!BalanceSheet_SelectSpendableTransactions(candidateTxs, txCount, &spendableTxs, &spendableCount, &totalFees)) {
|
||||
free(candidateTxs);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
free(candidateTxs);
|
||||
|
||||
if (spendableCount != nonCoinbaseCount) {
|
||||
free(spendableTxs);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t expectedCoinbaseAmount = currentReward;
|
||||
if (UINT64_MAX - expectedCoinbaseAmount < totalFees) {
|
||||
free(spendableTxs);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
expectedCoinbaseAmount += totalFees;
|
||||
|
||||
// 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(¤tSupply, 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.
|
||||
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
|
||||
} while (0);
|
||||
|
||||
@@ -242,6 +345,8 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||
printf("Added new block to chain:\n");
|
||||
Block_ShortPrint(block);
|
||||
|
||||
/* Debug proof removed: coinbase == baseReward + totalFees was printed here during debugging. */
|
||||
|
||||
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_rwlock_unlock(&chainLock);
|
||||
|
||||
@@ -468,117 +579,133 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find metadata file (create if not exists) to get the saved chain size (+ other things)
|
||||
FILE* metaFile = fopen(metaPath, "rb+");
|
||||
FILE* chainFile = fopen(chainPath, "rb+");
|
||||
FILE* tableFile = fopen(tablePath, "rb+");
|
||||
if (!metaFile || !chainFile || !tableFile) {
|
||||
// Just overwrite everything
|
||||
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);
|
||||
char metaTmpPath[512];
|
||||
char chainTmpPath[512];
|
||||
char tableTmpPath[512];
|
||||
if (!BuildPath(metaTmpPath, sizeof(metaTmpPath), dirpath, "chain.meta.tmp") ||
|
||||
!BuildPath(chainTmpPath, sizeof(chainTmpPath), dirpath, "chain.data.tmp") ||
|
||||
!BuildPath(tableTmpPath, sizeof(tableTmpPath), dirpath, "chain.table.tmp")) {
|
||||
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);
|
||||
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);
|
||||
if (!blk) {
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
fclose(metaFile);
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
remove(metaTmpPath);
|
||||
remove(chainTmpPath);
|
||||
remove(tableTmpPath);
|
||||
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
|
||||
// Write block header
|
||||
fwrite(&blk->header, sizeof(block_header_t), 1, chainFile);
|
||||
size_t txSize = DynArr_size(blk->transactions);
|
||||
fwrite(&txSize, sizeof(size_t), 1, chainFile); // Write number of transactions
|
||||
const uint64_t blockStart = byteCount;
|
||||
if (fwrite(&diskCopy->header, sizeof(block_header_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;
|
||||
}
|
||||
|
||||
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);
|
||||
// Write transactions
|
||||
for (size_t j = 0; j < txSize; j++) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j);
|
||||
if (fwrite(tx, sizeof(signed_transaction_t), 1, chainFile) != 1) {
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
fclose(chainFile);
|
||||
|
||||
for (size_t j = 0; j < txSize; ++j) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(diskCopy->transactions, j);
|
||||
if (!tx || fwrite(tx, sizeof(signed_transaction_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(signed_transaction_t);
|
||||
}
|
||||
|
||||
// Create an entry in the block table
|
||||
block_table_entry_t entry;
|
||||
entry.blockNumber = i;
|
||||
entry.byteNumber = preIncrementByteSize;
|
||||
entry.blockSize = byteCount - preIncrementByteSize;
|
||||
fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile);
|
||||
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk
|
||||
entry.byteNumber = blockStart;
|
||||
entry.blockSize = byteCount - blockStart;
|
||||
if (fwrite(&entry, sizeof(block_table_entry_t), 1, tableFile) != 1) {
|
||||
if (loadedTemp) Block_Destroy(diskCopy);
|
||||
fclose(metaFile);
|
||||
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 = DynArr_size(chain->blocks);
|
||||
size_t newSize = chainSize;
|
||||
fseek(metaFile, 0, SEEK_SET);
|
||||
fwrite(&newSize, sizeof(size_t), 1, metaFile);
|
||||
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(¤tReward, sizeof(uint64_t), 1, metaFile);
|
||||
|
||||
// Safety
|
||||
fflush(metaFile);
|
||||
fflush(chainFile);
|
||||
fflush(tableFile);
|
||||
|
||||
// Close all pointers
|
||||
fclose(metaFile);
|
||||
fclose(chainFile);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,23 @@ bool Transaction_Verify(const signed_transaction_t* tx) {
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
// Coinbase transactions are valid if the signature is correct for the block (handled in Block_Verify)
|
||||
if (tx->transaction.amount1 == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tx->transaction.amount2 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Address_IsCoinbase(tx->transaction.recipientAddress1) || Address_IsCoinbase(tx->transaction.recipientAddress2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t zeroAddress[32] = {0};
|
||||
if (memcmp(tx->transaction.recipientAddress2, zeroAddress, 32) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
402
src/main.c
402
src/main.c
@@ -12,7 +12,7 @@
|
||||
#include <balance_sheet.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <txmempool.h>
|
||||
|
||||
#include <constants.h>
|
||||
#include <runtime_state.h>
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
blockchain_t* currentChain = NULL;
|
||||
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}};
|
||||
uint64_t currentReward = 750000000000ULL;
|
||||
|
||||
@@ -42,6 +45,32 @@ void handle_sigint(int sig) {
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -272,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,
|
||||
@@ -295,6 +421,16 @@ static bool MineAndAppendBlock(blockchain_t* chain,
|
||||
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.
|
||||
size_t attached = OrphanPool_AttemptAttach(chain);
|
||||
if (attached > 0) {
|
||||
@@ -304,14 +440,6 @@ static bool MineAndAppendBlock(blockchain_t* chain,
|
||||
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);
|
||||
|
||||
uint8_t canonicalHash[32];
|
||||
@@ -375,6 +503,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;
|
||||
@@ -451,12 +580,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) {
|
||||
@@ -479,6 +627,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);
|
||||
}
|
||||
|
||||
@@ -486,6 +636,16 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
||||
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[]) {
|
||||
//(void)argc;
|
||||
//(void)argv;
|
||||
@@ -510,6 +670,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
ApplyRuntimeConfigFromEnv();
|
||||
|
||||
signal(SIGINT, handle_sigint);
|
||||
srand((unsigned int)time(NULL));
|
||||
|
||||
@@ -538,6 +700,11 @@ int main(int argc, char* argv[]) {
|
||||
uint8_t lastSavedHash[32] = {0};
|
||||
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget, ¤tReward, lastSavedHash, false)) {
|
||||
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)) {
|
||||
@@ -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 minerPrivateKey[32];
|
||||
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)) {
|
||||
fprintf(stderr, "failed to generate test miner keypair\n");
|
||||
Node_Destroy(node);
|
||||
@@ -588,7 +825,7 @@ int main(int argc, char* argv[]) {
|
||||
Block_ShutdownPowContext();
|
||||
BalanceSheet_Destroy();
|
||||
return 1;
|
||||
}
|
||||
}*/
|
||||
|
||||
char minerAddressHex[65];
|
||||
AddressToHexString(minerAddress, minerAddressHex);
|
||||
@@ -597,7 +834,7 @@ int main(int argc, char* argv[]) {
|
||||
char supplyStr[80];
|
||||
Uint256ToDecimal(¤tSupply, supplyStr, sizeof(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];
|
||||
while (true) {
|
||||
@@ -635,14 +872,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);
|
||||
@@ -675,6 +936,7 @@ int main(int argc, char* argv[]) {
|
||||
if (strcmp(cmd, "send") == 0) {
|
||||
char* addressStr = strtok(NULL, " \t");
|
||||
char* amountStr = strtok(NULL, " \t");
|
||||
char* feeStr = strtok(NULL, " \t");
|
||||
if (!addressStr || !amountStr) {
|
||||
printf("usage: send <address> <amount>\n");
|
||||
continue;
|
||||
@@ -693,6 +955,21 @@ int main(int argc, char* argv[]) {
|
||||
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;
|
||||
if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) {
|
||||
printf("send failed: miner address has no balance\n");
|
||||
@@ -711,12 +988,13 @@ 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);
|
||||
spendTx.transaction.version = 1;
|
||||
spendTx.transaction.fee = 0;
|
||||
spendTx.transaction.fee = (uint64_t)fee;
|
||||
spendTx.transaction.amount1 = (uint64_t)amount;
|
||||
spendTx.transaction.amount2 = 0;
|
||||
memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress));
|
||||
@@ -725,6 +1003,7 @@ int main(int argc, char* argv[]) {
|
||||
memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey));
|
||||
Transaction_Sign(&spendTx, minerPrivateKey);
|
||||
|
||||
/*
|
||||
Block_AddTransaction(block, &spendTx);
|
||||
printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount);
|
||||
char recipientHex[65];
|
||||
@@ -743,6 +1022,22 @@ int main(int argc, char* argv[]) {
|
||||
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -903,6 +1198,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;
|
||||
@@ -1004,6 +1304,46 @@ int main(int argc, char* argv[]) {
|
||||
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) {
|
||||
@@ -1084,9 +1424,10 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (strcmp(cmd, "connect") == 0) {
|
||||
char* ipStr = strtok(NULL, " \t");
|
||||
char* portStr = strtok(NULL, " \t");
|
||||
char* extra = strtok(NULL, " \t");
|
||||
if (!ipStr || extra) {
|
||||
printf("usage: connect <ipv4>\n");
|
||||
printf("usage: connect <ipv4> [port]\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1095,16 +1436,31 @@ int main(int argc, char* argv[]) {
|
||||
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) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
printf("connect requested to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT);
|
||||
printf("connect requested to %s:%u\n", ipStr, (unsigned int)peerPort);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1166,7 +1522,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
if (strcmp(cmd, "genaddr") == 0) {
|
||||
uint8_t testAddress[32];
|
||||
if (!GenerateRandomTestAddress(testAddress)) {
|
||||
if (!GenerateRandomTestAddress(testAddress, NULL, NULL)) {
|
||||
printf("failed to generate address\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <txmempool.h>
|
||||
|
||||
static net_node_t* Node_FromConnection(tcp_connection_t* conn) {
|
||||
if (!conn) {
|
||||
@@ -122,6 +123,13 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
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.
|
||||
uint64_t chainSize = Chain_Size(currentChain);
|
||||
if (blk->header.blockNumber > chainSize) {
|
||||
@@ -169,6 +177,20 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
|
||||
uint64_t coinbaseAmount = 0;
|
||||
if (blk->transactions) {
|
||||
for (size_t i = 0; i < DynArr_size(blk->transactions); ++i) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i);
|
||||
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
coinbaseAmount = tx->transaction.amount1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(void)uint256_add_u64(¤tSupply, coinbaseAmount);
|
||||
currentReward = CalculateBlockReward(currentSupply, currentChain);
|
||||
|
||||
// Persist on accept if requested
|
||||
if (persist) {
|
||||
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->outboundLock, NULL);
|
||||
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->on_connect = Node_Server_OnConnect;
|
||||
@@ -276,6 +299,7 @@ void Node_Destroy(net_node_t* node) {
|
||||
}
|
||||
|
||||
OrphanPool_Destroy();
|
||||
TxMempool_Destroy();
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
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) {
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
Node_ForwardConnect(node, client);
|
||||
printf("Inbound node connected: %u\n", client ? client->connectionId : 0U);
|
||||
|
||||
#if ECHO_PEERS
|
||||
if (node && client) {
|
||||
// Attempt to create an outbound connection back to the peer's IP on our LISTEN_PORT.
|
||||
if (echoPeersEnabled && node && client) {
|
||||
// Attempt to create an outbound connection back to the peer's IP on our configured port.
|
||||
// We avoid connecting if we already have an outbound to the same IP.
|
||||
char ipbuf[INET_ADDRSTRLEN];
|
||||
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.
|
||||
unsigned short targetPort = LISTEN_PORT;
|
||||
// Use the configured port as the target port for the peer's listening service.
|
||||
unsigned short targetPort = listenPort;
|
||||
|
||||
int shouldConnect = 1;
|
||||
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) {
|
||||
@@ -588,7 +637,48 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
break;
|
||||
}
|
||||
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_ERROR: {
|
||||
// Decode the message inside as text
|
||||
@@ -760,7 +850,12 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
break;
|
||||
}
|
||||
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_ERROR: {
|
||||
// Decode the message inside as text
|
||||
|
||||
@@ -38,6 +38,71 @@ void OrphanPool_Insert(block_t* block, uint64_t height) {
|
||||
(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) {
|
||||
if (!g_orphans || !chain) return 0;
|
||||
size_t attached = 0;
|
||||
@@ -67,6 +132,30 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
|
||||
}
|
||||
|
||||
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.
|
||||
bool parentMatches = false;
|
||||
if (e->height == 0) {
|
||||
@@ -85,63 +174,15 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
|
||||
|
||||
if (!parentMatches) {
|
||||
// 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.
|
||||
// Build a consecutive sequence of orphans from this height upward.
|
||||
DynArr* seq = DYNARR_CREATE(block_t*, 8);
|
||||
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;
|
||||
size_t adopted = OrphanPool_TryAdoptBranch(chain, e->height);
|
||||
if (adopted > 0) {
|
||||
attached += adopted;
|
||||
madeProgress = true;
|
||||
DynArr_destroy(seq);
|
||||
// reset outer loop
|
||||
n = DynArr_size(g_orphans);
|
||||
i = (size_t)-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
DynArr_destroy(seq);
|
||||
// If we didn't perform a reorg/attach, skip for now.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -156,13 +197,8 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
|
||||
i = (size_t)-1; // reset outer loop
|
||||
break;
|
||||
} else {
|
||||
// Chain_AddBlock rejected it (maybe invalid). Drop it.
|
||||
Block_Destroy(e->block);
|
||||
DynArr_remove(g_orphans, i);
|
||||
n = DynArr_size(g_orphans);
|
||||
i = (size_t)-1;
|
||||
madeProgress = true;
|
||||
break;
|
||||
// Keep the orphan around; rejection may be temporary while the local tip is being reorged.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +214,26 @@ int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t
|
||||
|
||||
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));
|
||||
if (rc == 0 && payloadLen > 0) {
|
||||
rc = TcpConnection_SendRaw(conn->sockFd, payload, payloadLen);
|
||||
}
|
||||
#endif
|
||||
|
||||
pthread_mutex_unlock(&conn->sendLock);
|
||||
|
||||
@@ -235,6 +251,11 @@ void TcpConnection_RequestClose(tcp_connection_t* conn) {
|
||||
if (conn->sockFd >= 0) {
|
||||
shutdown(conn->sockFd, SHUT_RDWR);
|
||||
}
|
||||
#ifdef USE_IPV6
|
||||
if (conn->sockFd6 >= 0) {
|
||||
shutdown(conn->sockFd6, SHUT_RDWR);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
pthread_mutex_unlock(&conn->stateLock);
|
||||
}
|
||||
|
||||
@@ -172,6 +172,9 @@ tcp_server_t* TcpServer_Create() {
|
||||
svr->isRunning = 0;
|
||||
svr->maxClients = 0;
|
||||
svr->clientsArrPtr = NULL;
|
||||
#ifdef USE_IPV6
|
||||
svr->sockFd6 = -1;
|
||||
#endif
|
||||
|
||||
if (pthread_mutex_init(&svr->clientsMutex, NULL) != 0) {
|
||||
free(svr);
|
||||
@@ -217,6 +220,28 @@ void TcpServer_Init(tcp_server_t* ptr, unsigned short port, const char* addr) {
|
||||
close(ptr->sockFd);
|
||||
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) {
|
||||
@@ -228,6 +253,15 @@ void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
|
||||
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);
|
||||
|
||||
ptr->maxClients = (size_t)maxcons;
|
||||
@@ -268,6 +302,14 @@ void TcpServer_Stop(tcp_server_t* ptr) {
|
||||
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())) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
#include <txmempool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static pthread_mutex_t g_txMempoolLock;
|
||||
static bool g_txMempoolLockInitialized = false;
|
||||
|
||||
khash_t(tx_mempool_map_m)* txMempool = NULL;
|
||||
|
||||
void TxMempool_Init() {
|
||||
txMempool = kh_init(tx_mempool_map_m);
|
||||
pthread_mutex_init(&g_txMempoolLock, NULL);
|
||||
g_txMempoolLockInitialized = true;
|
||||
}
|
||||
|
||||
int TxMempool_Insert(signed_transaction_t tx) {
|
||||
if (!txMempool) { return -1; }
|
||||
|
||||
pthread_mutex_lock(&g_txMempoolLock);
|
||||
uint8_t txHash[32];
|
||||
Transaction_CalculateHash(&tx, txHash);
|
||||
|
||||
@@ -18,17 +25,21 @@ int TxMempool_Insert(signed_transaction_t tx) {
|
||||
int ret;
|
||||
khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret);
|
||||
if (k == kh_end(txMempool)) {
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
kh_value(txMempool, k) = tx;
|
||||
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
|
||||
if (!txMempool || !txHash || !out) { return false; }
|
||||
|
||||
pthread_mutex_lock(&g_txMempoolLock);
|
||||
key32_t key;
|
||||
memcpy(key.bytes, txHash, 32);
|
||||
|
||||
@@ -36,15 +47,65 @@ bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
|
||||
if (k != kh_end(txMempool)) {
|
||||
signed_transaction_t tx = kh_value(txMempool, k);
|
||||
memcpy(out, &tx, sizeof(signed_transaction_t));
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount) {
|
||||
if (!outTxs || !outCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*outTxs = NULL;
|
||||
*outCount = 0;
|
||||
|
||||
if (!txMempool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_txMempoolLock);
|
||||
|
||||
size_t count = 0;
|
||||
khiter_t k;
|
||||
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||
if (kh_exist(txMempool, k)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
signed_transaction_t* snapshot = (signed_transaction_t*)malloc(count * sizeof(signed_transaction_t));
|
||||
if (!snapshot) {
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||
if (kh_exist(txMempool, k)) {
|
||||
snapshot[index++] = kh_value(txMempool, k);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
|
||||
*outTxs = snapshot;
|
||||
*outCount = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TxMempool_Print() {
|
||||
if (!txMempool) { return; }
|
||||
|
||||
pthread_mutex_lock(&g_txMempoolLock);
|
||||
khiter_t k;
|
||||
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||
if (kh_exist(txMempool, k)) {
|
||||
@@ -62,10 +123,37 @@ void TxMempool_Print() {
|
||||
(unsigned long long)tx.transaction.fee);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
}
|
||||
|
||||
void TxMempool_Destroy() {
|
||||
if (txMempool) {
|
||||
pthread_mutex_lock(&g_txMempoolLock);
|
||||
kh_destroy(tx_mempool_map_m, txMempool);
|
||||
txMempool = NULL;
|
||||
pthread_mutex_unlock(&g_txMempoolLock);
|
||||
}
|
||||
|
||||
if (g_txMempoolLockInitialized) {
|
||||
pthread_mutex_destroy(&g_txMempoolLock);
|
||||
g_txMempoolLockInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user