Compare commits
11 Commits
4201b5bcc6
...
p2ptest
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cfe85f6f2 | |||
| 4f10f013f6 | |||
| f94655a0ed | |||
| 58ff36b218 | |||
| 8f3559b3f6 | |||
| 971a4d9e49 | |||
| f9c94876d9 | |||
| 9405801f6b | |||
| 0fb2615d4c | |||
| 55ca03f4ff | |||
| ce27dafaba |
11
TODO.txt
11
TODO.txt
@@ -1,6 +1,4 @@
|
||||
TODO:
|
||||
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner.
|
||||
|
||||
Make transactions private. A bit more work, but it's a challenge worth taking on.
|
||||
I want to make an "optional privacy" system, where the TX can be public or private. Of course private TXs need more bytes, so the fees (although low) will be higher for them.
|
||||
I need to figure out a way to make the privacy work without a UTXO system, and instead, with a "Balance Sheet" approach.
|
||||
@@ -10,6 +8,15 @@ Maybe move the node system to an async event loop instead of spawning threads.
|
||||
|
||||
A potential race could occur if the P2P node receives a new block, or flushes a new block to disk while the user is running a full verify.
|
||||
|
||||
Maybe think about how block broadcasting works. Instead of unsolicited broadcasting, maybe only advertise a new height and have peers request the block if they want it. This would reduce bandwidth usage, but it also means that blocks won't propagate as fast, which could lead to more orphaned blocks. It's a tradeoff.
|
||||
|
||||
Check if Block FullVerify is actually verifying fully (not missing any conditions).
|
||||
|
||||
A loophole in the reorg penalty system could potentially exist where someone broadcasts blocks one-at-a-time. Determine a solution to this.
|
||||
|
||||
TO TEST:
|
||||
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner.
|
||||
|
||||
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.
|
||||
|
||||
@@ -40,6 +40,7 @@ bool Block_IsFullyValid(const block_t* block);
|
||||
void Block_ShutdownPowContext(void);
|
||||
void Block_Destroy(block_t* block);
|
||||
void Block_Print(const block_t* block);
|
||||
void Block_ShortPrint(const block_t* block);
|
||||
// Deep-copy a block (allocates a new `block_t*`). Caller must call `Block_Destroy`.
|
||||
block_t* Block_Copy(const block_t* src);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// Nets
|
||||
#define MAX_CONS 32 // Some baseline for now
|
||||
#define LISTEN_PORT 9393
|
||||
#define ECHO_PEERS 1 // If non-zero, automatically attempt to connect back to any inbound peers (helps form bidirectional peering)
|
||||
#define TCP_THREAD_STACK_SIZE (512 * 1024) // 512 KB. We could get away with like 128 KB since it's mostly just recv bufs, but it's good having some breathing room.
|
||||
// This is also for client threads. The server has the default (~8 MB on POSIX).
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <dynarr.h>
|
||||
#include <dynset.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -25,6 +26,12 @@ typedef struct {
|
||||
tcp_server_t* server;
|
||||
tcp_client_t outboundClients[MAX_CONS];
|
||||
size_t outboundCount;
|
||||
// Dedup cache for recently seen block hashes (canonical 32-byte hash)
|
||||
DynSet* seenBlocks;
|
||||
// Protects seenBlocks
|
||||
pthread_mutex_t seenLock;
|
||||
// Protects outboundClients snapshots and peerBlockHeight writes
|
||||
pthread_mutex_t outboundLock;
|
||||
void (*on_connect)(tcp_connection_t* conn, void* user);
|
||||
void (*on_data)(tcp_connection_t* conn, const unsigned char* data, size_t len, void* user);
|
||||
void (*on_disconnect)(tcp_connection_t* conn, void* user);
|
||||
@@ -51,6 +58,10 @@ int Node_ConnectStartupPeers(net_node_t* node, const char** ips, const unsigned
|
||||
|
||||
int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t packetType, const void* payload, size_t payloadLen);
|
||||
|
||||
// Helpers for outbound peer selection and block broadcast
|
||||
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight);
|
||||
void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp_connection_t* sourceConn);
|
||||
|
||||
// Callback logic
|
||||
void Node_Server_OnConnect(tcp_connection_t* client);
|
||||
void Node_Server_OnData(tcp_connection_t* client);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
0
myeasylog.log
Normal file
0
myeasylog.log
Normal file
@@ -286,6 +286,20 @@ void Block_Print(const block_t* block) {
|
||||
}
|
||||
}
|
||||
|
||||
void Block_ShortPrint(const block_t* block) {
|
||||
if (!block) return;
|
||||
|
||||
printf("Block #%llu: Timestamp %llu, Nonce %llu, DiffTarget 0x%08x, Version %u, PrevHash %02x%02x...%02x%02x, MerkleRoot %02x%02x...%02x%02x, TxCount %zu\n",
|
||||
(unsigned long long)block->header.blockNumber,
|
||||
(unsigned long long)block->header.timestamp,
|
||||
(unsigned long long)block->header.nonce,
|
||||
block->header.difficultyTarget,
|
||||
block->header.version,
|
||||
block->header.prevHash[0], block->header.prevHash[1], block->header.prevHash[30], block->header.prevHash[31],
|
||||
block->header.merkleRoot[0], block->header.merkleRoot[1], block->header.merkleRoot[30], block->header.merkleRoot[31],
|
||||
block->transactions ? DynArr_size(block->transactions) : 0);
|
||||
}
|
||||
|
||||
block_t* Block_Copy(const block_t* src) {
|
||||
if (!src) return NULL;
|
||||
block_t* dst = (block_t*)malloc(sizeof(block_t));
|
||||
|
||||
@@ -151,6 +151,15 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||
pthread_rwlock_wrlock(&chainLock);
|
||||
pthread_mutex_lock(&balanceSheetLock);
|
||||
|
||||
// Ensure the incoming block's header.blockNumber matches the index it will be appended at.
|
||||
size_t expectedIndex = DynArr_size(chain->blocks);
|
||||
if (block->header.blockNumber != expectedIndex) {
|
||||
// Mismatched block number; reject to avoid duplicate indices or inconsistent headers.
|
||||
pthread_mutex_unlock(&balanceSheetLock);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
// First pass: ensure all non-coinbase senders can cover the full spend
|
||||
// (amount1 + amount2 + fee) before mutating the chain or balance sheet.
|
||||
@@ -230,6 +239,9 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
||||
pthread_mutex_unlock(&balanceSheetLock);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
|
||||
printf("Added new block to chain:\n");
|
||||
Block_ShortPrint(block);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -456,111 +468,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+");
|
||||
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;
|
||||
}
|
||||
|
||||
pthread_rwlock_wrlock(&chainLock);
|
||||
|
||||
FILE* metaFile = fopen(metaTmpPath, "wb+");
|
||||
FILE* chainFile = fopen(chainTmpPath, "wb+");
|
||||
FILE* tableFile = fopen(tableTmpPath, "wb+");
|
||||
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);
|
||||
if (metaFile) fclose(metaFile);
|
||||
if (chainFile) fclose(chainFile);
|
||||
if (tableFile) fclose(tableFile);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
remove(metaTmpPath);
|
||||
remove(chainTmpPath);
|
||||
remove(tableTmpPath);
|
||||
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
|
||||
for (size_t i = savedSize; i < DynArr_size(chain->blocks); i++) {
|
||||
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) {
|
||||
fclose(metaFile);
|
||||
fclose(chainFile);
|
||||
fclose(tableFile);
|
||||
pthread_rwlock_unlock(&chainLock);
|
||||
remove(metaTmpPath);
|
||||
remove(chainTmpPath);
|
||||
remove(tableTmpPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t preIncrementByteSize = byteCount;
|
||||
|
||||
// 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
|
||||
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) {
|
||||
fclose(chainFile);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
DynArr_destroy(blk->transactions);
|
||||
blk->transactions = NULL; // Clear transactions to save memory since they're now saved on disk
|
||||
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;
|
||||
@@ -578,16 +612,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;
|
||||
}
|
||||
|
||||
|
||||
245
src/main.c
245
src/main.c
@@ -11,6 +11,7 @@
|
||||
#include <signal.h>
|
||||
#include <balance_sheet.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
#include <constants.h>
|
||||
@@ -27,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;
|
||||
|
||||
@@ -41,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) {
|
||||
@@ -294,6 +324,14 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
// After successfully appending a block, attempt to attach any orphans.
|
||||
size_t attached = OrphanPool_AttemptAttach(chain);
|
||||
if (attached > 0) {
|
||||
@@ -303,14 +341,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];
|
||||
@@ -485,6 +515,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;
|
||||
@@ -509,6 +549,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
ApplyRuntimeConfigFromEnv();
|
||||
|
||||
signal(SIGINT, handle_sigint);
|
||||
srand((unsigned int)time(NULL));
|
||||
|
||||
@@ -576,9 +618,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);
|
||||
@@ -587,7 +699,7 @@ int main(int argc, char* argv[]) {
|
||||
Block_ShutdownPowContext();
|
||||
BalanceSheet_Destroy();
|
||||
return 1;
|
||||
}
|
||||
}*/
|
||||
|
||||
char minerAddressHex[65];
|
||||
AddressToHexString(minerAddress, minerAddressHex);
|
||||
@@ -651,6 +763,11 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
free(block); // Chain stores block by value and owns copied transaction array.
|
||||
|
||||
// Broadcast newly mined block to outbound peers
|
||||
if (node) {
|
||||
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
|
||||
}
|
||||
|
||||
if (i % 50 == 0) {
|
||||
// Mid-mine flush
|
||||
(void)FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
|
||||
@@ -733,6 +850,9 @@ int main(int argc, char* argv[]) {
|
||||
FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward);
|
||||
|
||||
free(block);
|
||||
if (node) {
|
||||
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
|
||||
}
|
||||
printf("send committed in mined block\n");
|
||||
continue;
|
||||
}
|
||||
@@ -744,46 +864,45 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// Choose the best outbound peer by advertised height
|
||||
int bestIdx = -1;
|
||||
uint64_t bestHeight = 0;
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection) {
|
||||
if (node->outboundClients[i].peerBlockHeight > bestHeight) {
|
||||
bestHeight = node->outboundClients[i].peerBlockHeight;
|
||||
bestIdx = (int)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIdx < 0) {
|
||||
tcp_connection_t* peerConn = NULL;
|
||||
uint64_t peerHeight = 0;
|
||||
if (Node_GetBestOutboundPeer(node, &peerConn, &peerHeight) != 0 || !peerConn) {
|
||||
printf("no outbound peers to sync from\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t localHeight = (uint64_t)Chain_Size(chain);
|
||||
uint64_t peerHeight = node->outboundClients[bestIdx].peerBlockHeight;
|
||||
// Continue syncing in a loop until we've caught up to the peer or no progress is made.
|
||||
bool madeProgressOverall = false;
|
||||
while (true) {
|
||||
uint64_t localHeight = (uint64_t)Chain_Size(chain);
|
||||
|
||||
// Determine if this is an initial sync. If so, do not apply penalty.
|
||||
bool isInitialSync = (localHeight == 0);
|
||||
// Only penalize small near-tip gaps. Large gaps are treated as normal catch-up,
|
||||
// because a much taller peer on the same chain is not evidence of a reorg. TODO: Maybe look at this again some other day.
|
||||
bool isInitialSync = (localHeight == 0) || ((peerHeight > localHeight) && ((peerHeight - localHeight) > INITIAL_SYNC_HEIGHT_DIFF));
|
||||
|
||||
// Compute penalty and adjusted peer height (skip penalty for initial sync)
|
||||
// Compute penalty and adjusted peer height.
|
||||
uint64_t delay = (peerHeight > localHeight) ? (peerHeight - localHeight) : 0ULL;
|
||||
uint64_t penalty = isInitialSync ? 0ULL : FetchScheduler_ComputeReorgPenaltyBlocks(delay);
|
||||
uint64_t adjustedPeerHeight = (peerHeight > penalty) ? (peerHeight - penalty) : 0ULL;
|
||||
|
||||
// Ensure we always make forward progress: if the penalty would reduce the
|
||||
// target below our current height, fetch at least the next block. This
|
||||
// lets us apply penalties for near-tip reorg risk while still allowing
|
||||
// normal syncing when the peer is ahead by a small amount.
|
||||
if (adjustedPeerHeight <= localHeight) {
|
||||
printf("already synced (local=%" PRIu64 ", peer=%" PRIu64 ", penalty=%" PRIu64 ")\n", localHeight, peerHeight, penalty);
|
||||
continue;
|
||||
adjustedPeerHeight = localHeight + 1;
|
||||
}
|
||||
|
||||
printf("syncing from peer %d: peerHeight=%" PRIu64 " adjusted=%" PRIu64 " local=%" PRIu64 " penalty=%" PRIu64 "\n",
|
||||
bestIdx, peerHeight, adjustedPeerHeight, localHeight, penalty);
|
||||
if (adjustedPeerHeight > peerHeight) {
|
||||
adjustedPeerHeight = peerHeight;
|
||||
}
|
||||
|
||||
tcp_connection_t* peerConn = node->outboundClients[bestIdx].connection;
|
||||
printf("syncing: peerHeight=%" PRIu64 " adjusted=%" PRIu64 " local=%" PRIu64 " penalty=%" PRIu64 "\n",
|
||||
peerHeight, adjustedPeerHeight, localHeight, penalty);
|
||||
|
||||
// Windowed parallel fetch
|
||||
uint64_t start = localHeight;
|
||||
uint64_t end = adjustedPeerHeight; // exclusive target height
|
||||
// Windowed parallel fetch
|
||||
uint64_t start = localHeight;
|
||||
uint64_t end = adjustedPeerHeight; // exclusive target height
|
||||
uint64_t nextReq = start;
|
||||
|
||||
const int maxInFlight = MAX_PARALLEL_FETCHES;
|
||||
@@ -968,10 +1087,36 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
printf("sync complete: localHeight=%zu\n", Chain_Size(chain));
|
||||
// After the window completes, check progress and possibly refresh peer height
|
||||
uint64_t newLocal = (uint64_t)Chain_Size(chain);
|
||||
if (newLocal > localHeight) madeProgressOverall = true;
|
||||
printf("sync complete: localHeight=%" PRIu64 "\n", newLocal);
|
||||
|
||||
// If we've caught up to the peer, stop. Otherwise refresh peerHeight and loop again.
|
||||
if (newLocal >= peerHeight) break;
|
||||
|
||||
// Refresh advertised peer height for this connection (it may have been updated during fetch)
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == peerConn) {
|
||||
peerHeight = node->outboundClients[i].peerBlockHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
// If no progress was made in this iteration, stop to avoid tight loop
|
||||
if (!madeProgressOverall) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Re-evaluate loop condition: continue while local < peerHeight
|
||||
if ((uint64_t)Chain_Size(chain) >= peerHeight) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "blockdetail") == 0) {
|
||||
char* blockNumberStr = strtok(NULL, " \t");
|
||||
char* extra = strtok(NULL, " \t");
|
||||
@@ -1050,9 +1195,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;
|
||||
}
|
||||
|
||||
@@ -1061,12 +1207,31 @@ int main(int argc, char* argv[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Node_ConnectPeer(node, ipStr, LISTEN_PORT) != 0) {
|
||||
printf("failed to connect to %s:%u\n", ipStr, (unsigned int)LISTEN_PORT);
|
||||
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)peerPort);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1128,7 +1293,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;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ static uint64_t Node_GetCurrentBlockHeight(void) {
|
||||
return currentBlockHeight;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
NODE_BLOCK_REJECTED = 0,
|
||||
NODE_BLOCK_ORPHAN_QUEUED = 1,
|
||||
NODE_BLOCK_ACCEPTED = 2
|
||||
} node_block_accept_result_t;
|
||||
|
||||
static void* Node_MaintenanceThread(void* arg) {
|
||||
net_node_t* n = (net_node_t*)arg;
|
||||
if (!n) return NULL;
|
||||
@@ -61,18 +67,18 @@ static int Node_DecodePacket(const tcp_connection_t* conn, packet_type_t* outTyp
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloadLen, bool persist) {
|
||||
if (!payload) { return false; }
|
||||
static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloadLen, bool persist) {
|
||||
if (!payload) { return NODE_BLOCK_REJECTED; }
|
||||
|
||||
size_t offset = 0;
|
||||
if (payloadLen < sizeof(uint64_t) + sizeof(block_header_t) + sizeof(uint64_t)) { return false; }
|
||||
if (payloadLen < sizeof(uint64_t) + sizeof(block_header_t) + sizeof(uint64_t)) { return NODE_BLOCK_REJECTED; }
|
||||
|
||||
uint64_t blockHeight = 0;
|
||||
memcpy(&blockHeight, payload + offset, sizeof(blockHeight));
|
||||
offset += sizeof(blockHeight);
|
||||
|
||||
block_t* blk = (block_t*)calloc(1, sizeof(block_t));
|
||||
if (!blk) { return false; }
|
||||
if (!blk) { return NODE_BLOCK_REJECTED; }
|
||||
|
||||
memcpy(&blk->header, payload + offset, sizeof(blk->header));
|
||||
blk->header.blockNumber = blockHeight;
|
||||
@@ -83,13 +89,13 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
offset += sizeof(txCount);
|
||||
|
||||
blk->transactions = DYNARR_CREATE(signed_transaction_t, txCount == 0 ? 1 : (size_t)txCount);
|
||||
if (!blk->transactions) { free(blk); return false; }
|
||||
if (!blk->transactions) { free(blk); return NODE_BLOCK_REJECTED; }
|
||||
|
||||
for (uint64_t i = 0; i < txCount; ++i) {
|
||||
if (offset + sizeof(signed_transaction_t) > payloadLen) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
signed_transaction_t tx;
|
||||
memcpy(&tx, payload + offset, sizeof(tx));
|
||||
@@ -97,7 +103,7 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
if (!DynArr_push_back(blk->transactions, &tx)) {
|
||||
DynArr_destroy(blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,28 +112,58 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
printf("Rejected BLOCK_DATA at height %" PRIu64 " during validation\n", blockHeight);
|
||||
DynArr_destroy(blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
|
||||
if (!currentChain) {
|
||||
printf("Rejected BLOCK_DATA at height %" PRIu64 ": no active chain\n", blockHeight);
|
||||
DynArr_destroy(blk->transactions);
|
||||
free(blk);
|
||||
return false;
|
||||
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 (blk->header.blockNumber > 0) {
|
||||
uint64_t parentIndex = blk->header.blockNumber - 1;
|
||||
block_t* parentCopy = NULL;
|
||||
if (parentIndex >= Chain_Size(currentChain) || !Chain_GetBlockCopy(currentChain, (size_t)parentIndex, &parentCopy) || !parentCopy) {
|
||||
// Insert into orphan pool and take ownership of blk
|
||||
OrphanPool_Insert(blk, blockHeight);
|
||||
if (parentCopy) Block_Destroy(parentCopy);
|
||||
printf("Queued orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight);
|
||||
return true;
|
||||
uint64_t chainSize = Chain_Size(currentChain);
|
||||
if (blk->header.blockNumber > chainSize) {
|
||||
// Parent(s) missing; queue as orphan
|
||||
OrphanPool_Insert(blk, blockHeight);
|
||||
printf("Queued orphan BLOCK_DATA at height %" PRIu64 "\n", blockHeight);
|
||||
return NODE_BLOCK_ORPHAN_QUEUED;
|
||||
} else if (blk->header.blockNumber < chainSize) {
|
||||
// Older block than current chain tip: reject
|
||||
printf("Rejected BLOCK_DATA at height %" PRIu64 ": older than current chain\n", blockHeight);
|
||||
DynArr_destroy(blk->transactions);
|
||||
free(blk);
|
||||
return NODE_BLOCK_REJECTED;
|
||||
} else {
|
||||
// blk->header.blockNumber == chainSize -> candidate to append. Ensure prevHash matches current tip.
|
||||
if (chainSize > 0) {
|
||||
block_t* last = NULL;
|
||||
if (!Chain_GetBlockCopy(currentChain, (size_t)(chainSize - 1), &last) || !last) {
|
||||
// Can't verify parent; queue as orphan conservatively
|
||||
OrphanPool_Insert(blk, blockHeight);
|
||||
printf("Queued orphan BLOCK_DATA at height %" PRIu64 " (unable to verify parent)\n", blockHeight);
|
||||
if (last) Block_Destroy(last);
|
||||
return NODE_BLOCK_ORPHAN_QUEUED;
|
||||
}
|
||||
uint8_t lastHash[32];
|
||||
Block_CalculateHash(last, lastHash);
|
||||
if (memcmp(lastHash, blk->header.prevHash, 32) != 0) {
|
||||
// Conflicting block at same height; queue as orphan until resolved by a subsequent extension.
|
||||
OrphanPool_Insert(blk, blockHeight);
|
||||
Block_Destroy(last);
|
||||
printf("Queued conflicting BLOCK_DATA at same height %" PRIu64 " as orphan\n", blockHeight);
|
||||
return NODE_BLOCK_ORPHAN_QUEUED;
|
||||
}
|
||||
Block_Destroy(last);
|
||||
}
|
||||
Block_Destroy(parentCopy);
|
||||
}
|
||||
|
||||
if (!Chain_AddBlock(currentChain, blk)) {
|
||||
@@ -137,7 +173,7 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
DynArr_destroy(blk->transactions);
|
||||
}
|
||||
free(blk);
|
||||
return false;
|
||||
return NODE_BLOCK_REJECTED;
|
||||
}
|
||||
|
||||
// Persist on accept if requested
|
||||
@@ -156,7 +192,7 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward);
|
||||
BalanceSheet_SaveToFile(chainDataDir);
|
||||
}
|
||||
return true;
|
||||
return NODE_BLOCK_ACCEPTED;
|
||||
}
|
||||
|
||||
static void Node_ForwardConnect(net_node_t* node, tcp_connection_t* conn) {
|
||||
@@ -198,7 +234,12 @@ net_node_t* Node_Create() {
|
||||
}
|
||||
}
|
||||
|
||||
TcpServer_Init(node->server, LISTEN_PORT, "0.0.0.0");
|
||||
// Initialize outbound lock and seen-block cache
|
||||
pthread_mutex_init(&node->seenLock, NULL);
|
||||
pthread_mutex_init(&node->outboundLock, NULL);
|
||||
node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes
|
||||
|
||||
TcpServer_Init(node->server, listenPort, "0.0.0.0");
|
||||
|
||||
node->server->owner = node;
|
||||
node->server->on_connect = Node_Server_OnConnect;
|
||||
@@ -243,6 +284,13 @@ void Node_Destroy(net_node_t* node) {
|
||||
|
||||
OrphanPool_Destroy();
|
||||
|
||||
if (node->seenBlocks) {
|
||||
DynSet_Destroy(node->seenBlocks);
|
||||
node->seenBlocks = NULL;
|
||||
}
|
||||
pthread_mutex_destroy(&node->seenLock);
|
||||
pthread_mutex_destroy(&node->outboundLock);
|
||||
|
||||
free(node);
|
||||
}
|
||||
|
||||
@@ -340,6 +388,34 @@ 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 (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 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);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection) {
|
||||
struct in_addr otherAddr = node->outboundClients[i].connection->peerAddr.sin_addr;
|
||||
if (otherAddr.s_addr == client->peerAddr.sin_addr.s_addr) {
|
||||
shouldConnect = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
if (shouldConnect) {
|
||||
// Try to connect; ignore failure silently
|
||||
(void)Node_ConnectPeer(node, ipbuf, targetPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node_Server_OnData(tcp_connection_t* client) {
|
||||
@@ -498,10 +574,21 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
}
|
||||
case PACKET_TYPE_BROADCAST_BLOCK: {
|
||||
// Accept broadcast blocks from peers and try to append
|
||||
if (Node_ParseAndAcceptBlock(payload, payloadLen, true)) {
|
||||
printf("Accepted BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
if (payloadLen >= sizeof(uint64_t)) {
|
||||
uint64_t blockHeight = 0;
|
||||
memcpy(&blockHeight, payload, sizeof(blockHeight));
|
||||
node_block_accept_result_t result = Node_ParseAndAcceptBlock(payload, payloadLen, true);
|
||||
if (result == NODE_BLOCK_ACCEPTED) {
|
||||
printf("Accepted BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
if (node) {
|
||||
Node_BroadcastChainRange(node, (size_t)blockHeight, client);
|
||||
}
|
||||
} else if (result == NODE_BLOCK_ORPHAN_QUEUED) {
|
||||
printf("Queued orphan BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -594,12 +681,14 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
// Store peer-advertised height on matching outbound client
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
if (node) {
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == client) {
|
||||
node->outboundClients[i].peerBlockHeight = blockHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -614,18 +703,64 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
return;
|
||||
}
|
||||
case PACKET_TYPE_BLOCK_DATA: {
|
||||
if (Node_ParseAndAcceptBlock(payload, payloadLen, true)) {
|
||||
printf("Accepted BLOCK_DATA from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BLOCK_DATA from node %u\n", client ? client->connectionId : 0U);
|
||||
if (payloadLen >= sizeof(uint64_t)) {
|
||||
uint64_t blockHeight = 0;
|
||||
memcpy(&blockHeight, payload, sizeof(blockHeight));
|
||||
node_block_accept_result_t result = Node_ParseAndAcceptBlock(payload, payloadLen, true);
|
||||
if (result == NODE_BLOCK_ACCEPTED) {
|
||||
printf("Accepted BLOCK_DATA from node %u\n", client ? client->connectionId : 0U);
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
if (node) {
|
||||
// Update peer advertised height
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == client) {
|
||||
if (node->outboundClients[i].peerBlockHeight < blockHeight) {
|
||||
node->outboundClients[i].peerBlockHeight = blockHeight;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
Node_BroadcastChainRange(node, (size_t)blockHeight, client);
|
||||
}
|
||||
} else if (result == NODE_BLOCK_ORPHAN_QUEUED) {
|
||||
printf("Queued orphan BLOCK_DATA from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BLOCK_DATA from node %u\n", client ? client->connectionId : 0U);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_BROADCAST_BLOCK: {
|
||||
if (Node_ParseAndAcceptBlock(payload, payloadLen, true)) {
|
||||
printf("Accepted BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
if (payloadLen >= sizeof(uint64_t)) {
|
||||
uint64_t blockHeight = 0;
|
||||
memcpy(&blockHeight, payload, sizeof(blockHeight));
|
||||
node_block_accept_result_t result = Node_ParseAndAcceptBlock(payload, payloadLen, true);
|
||||
if (result == NODE_BLOCK_ACCEPTED) {
|
||||
printf("Accepted BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
if (node) {
|
||||
// Update peer advertised height
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == client) {
|
||||
if (node->outboundClients[i].peerBlockHeight < blockHeight) {
|
||||
node->outboundClients[i].peerBlockHeight = blockHeight;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
Node_BroadcastChainRange(node, (size_t)blockHeight, client);
|
||||
}
|
||||
} else if (result == NODE_BLOCK_ORPHAN_QUEUED) {
|
||||
printf("Queued orphan BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
} else {
|
||||
printf("Rejected BROADCAST_BLOCK from node %u\n", client ? client->connectionId : 0U);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -656,10 +791,128 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
|
||||
void Node_Client_OnDisconnect(tcp_connection_t* client) {
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
if (node && node->outboundCount > 0) {
|
||||
node->outboundCount--;
|
||||
if (node) {
|
||||
// Clear peer advertised height for this outbound slot
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == client) {
|
||||
node->outboundClients[i].peerBlockHeight = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
if (node->outboundCount > 0) {
|
||||
node->outboundCount--;
|
||||
}
|
||||
}
|
||||
|
||||
Node_ForwardDisconnect(node, client);
|
||||
printf("Outbound node disconnected: %u\n", client ? client->connectionId : 0U);
|
||||
}
|
||||
|
||||
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight) {
|
||||
if (!node || !outConn || !outHeight) return -1;
|
||||
|
||||
tcp_connection_t* best = NULL;
|
||||
uint64_t bestH = 0;
|
||||
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection) {
|
||||
if (node->outboundClients[i].peerBlockHeight > bestH || best == NULL) {
|
||||
best = node->outboundClients[i].connection;
|
||||
bestH = node->outboundClients[i].peerBlockHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
if (!best) return -1;
|
||||
*outConn = best;
|
||||
*outHeight = bestH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp_connection_t* sourceConn) {
|
||||
if (!node || !currentChain) return;
|
||||
|
||||
size_t chainSize = Chain_Size(currentChain);
|
||||
if (startHeightInclusive >= chainSize) return;
|
||||
|
||||
uint32_t sourceIp = 0;
|
||||
if (sourceConn) {
|
||||
sourceIp = sourceConn->peerAddr.sin_addr.s_addr;
|
||||
}
|
||||
|
||||
for (size_t h = startHeightInclusive; h < chainSize; ++h) {
|
||||
block_t* blk = NULL;
|
||||
if (!Chain_GetBlockCopy(currentChain, h, &blk) || !blk) {
|
||||
if (!Chain_LoadBlockFromFile(chainDataDir, h, true, &blk, NULL) || !blk) {
|
||||
continue;
|
||||
}
|
||||
} else if (!blk->transactions) {
|
||||
block_t* full = NULL;
|
||||
if (Chain_LoadBlockFromFile(chainDataDir, h, true, &full, NULL) && full) {
|
||||
Block_Destroy(blk);
|
||||
blk = full;
|
||||
}
|
||||
}
|
||||
|
||||
if (!blk || !blk->transactions) {
|
||||
if (blk) Block_Destroy(blk);
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned char hash[32];
|
||||
Block_CalculateHash(blk, hash);
|
||||
|
||||
// Dedupe using seenBlocks
|
||||
int seen = 0;
|
||||
pthread_mutex_lock(&node->seenLock);
|
||||
if (DynSet_Contains(node->seenBlocks, hash)) {
|
||||
seen = 1;
|
||||
} else {
|
||||
DynSet_Insert(node->seenBlocks, hash);
|
||||
}
|
||||
pthread_mutex_unlock(&node->seenLock);
|
||||
|
||||
if (seen) {
|
||||
Block_Destroy(blk);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Serialize payload: [uint64_t height][block_header_t][uint64_t txCount][transactions...]
|
||||
size_t txCount = DynArr_size(blk->transactions);
|
||||
size_t payloadLen = sizeof(uint64_t) + sizeof(block_header_t) + sizeof(uint64_t) + (txCount * sizeof(signed_transaction_t));
|
||||
unsigned char* payload = (unsigned char*)malloc(payloadLen);
|
||||
if (!payload) {
|
||||
Block_Destroy(blk);
|
||||
continue;
|
||||
}
|
||||
size_t off = 0;
|
||||
uint64_t h64 = (uint64_t)h;
|
||||
memcpy(payload + off, &h64, sizeof(h64)); off += sizeof(h64);
|
||||
memcpy(payload + off, &blk->header, sizeof(block_header_t)); off += sizeof(block_header_t);
|
||||
uint64_t txCount64 = (uint64_t)txCount;
|
||||
memcpy(payload + off, &txCount64, sizeof(txCount64)); off += sizeof(txCount64);
|
||||
for (size_t ti = 0; ti < txCount; ++ti) {
|
||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, ti);
|
||||
memcpy(payload + off, tx, sizeof(signed_transaction_t)); off += sizeof(signed_transaction_t);
|
||||
}
|
||||
|
||||
// Snapshot outbound clients and send
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
tcp_connection_t* conn = node->outboundClients[i].connection;
|
||||
if (!conn) continue;
|
||||
if (conn == sourceConn) continue;
|
||||
if (sourceIp != 0 && conn->peerAddr.sin_addr.s_addr == sourceIp) continue;
|
||||
Node_SendPacket(node, conn, PACKET_TYPE_BROADCAST_BLOCK, payload, off);
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
free(payload);
|
||||
Block_Destroy(blk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,60 @@ 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) {
|
||||
parentMatches = (Chain_Size(chain) == 0);
|
||||
} else {
|
||||
block_t* parent = NULL;
|
||||
if (Chain_GetBlockCopy(chain, (size_t)parentIndex, &parent) && parent) {
|
||||
uint8_t parentHash[32];
|
||||
Block_CalculateHash(parent, parentHash);
|
||||
parentMatches = (memcmp(parentHash, e->block->header.prevHash, 32) == 0);
|
||||
Block_Destroy(parent);
|
||||
} else {
|
||||
parentMatches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parentMatches) {
|
||||
// Parent exists but does not match this orphan's prevHash.
|
||||
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;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to add to chain
|
||||
if (Chain_AddBlock(chain, e->block)) {
|
||||
attached++;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
static void* TcpClient_ThreadProc(void* arg) {
|
||||
tcp_client_t* client = (tcp_client_t*)arg;
|
||||
@@ -95,11 +97,52 @@ int TcpClient_Connect(
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(sockFd, (struct sockaddr*)&peerAddr, sizeof(peerAddr)) < 0) {
|
||||
close(sockFd);
|
||||
return -1;
|
||||
// Use non-blocking connect with a timeout to avoid long blocking in the CLI.
|
||||
int flags = fcntl(sockFd, F_GETFL, 0);
|
||||
if (flags == -1) flags = 0;
|
||||
fcntl(sockFd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
int rc = connect(sockFd, (struct sockaddr*)&peerAddr, sizeof(peerAddr));
|
||||
if (rc < 0) {
|
||||
if (errno != EINPROGRESS) {
|
||||
close(sockFd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Wait up to 5 seconds for the socket to become writable (connected)
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(sockFd, &wfds);
|
||||
int sel = select(sockFd + 1, NULL, &wfds, NULL, &tv);
|
||||
if (sel <= 0) {
|
||||
// timeout or error
|
||||
if (sel == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
}
|
||||
close(sockFd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check for socket error
|
||||
int so_error = 0;
|
||||
socklen_t len = sizeof(so_error);
|
||||
if (getsockopt(sockFd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
|
||||
close(sockFd);
|
||||
return -1;
|
||||
}
|
||||
if (so_error != 0) {
|
||||
errno = so_error;
|
||||
close(sockFd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore blocking mode
|
||||
fcntl(sockFd, F_SETFL, flags & ~O_NONBLOCK);
|
||||
|
||||
tcp_connection_t* conn = (tcp_connection_t*)malloc(sizeof(*conn));
|
||||
if (!conn) {
|
||||
close(sockFd);
|
||||
|
||||
Reference in New Issue
Block a user