Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9e3f8cbbb
|
||
|
|
4417095ab5
|
||
|
|
21fe73fb01
|
||
|
|
1345ff7fa6 | ||
|
|
da50b4e8c1 | ||
|
|
00bd711501 | ||
|
|
17ef3b74fd | ||
|
|
c1914dc3e7 | ||
|
|
39293029c5 | ||
|
|
763aeb648f | ||
|
|
41a154a9fd | ||
|
|
91d7bfa4e7 | ||
|
|
4cfe85f6f2 | ||
|
|
4f10f013f6 | ||
|
|
f94655a0ed | ||
|
|
58ff36b218 | ||
|
|
8f3559b3f6 | ||
|
|
971a4d9e49 | ||
|
|
f9c94876d9 | ||
|
|
9405801f6b | ||
|
|
0fb2615d4c | ||
|
|
55ca03f4ff | ||
|
|
ce27dafaba |
@@ -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,23 @@ 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.
|
||||
|
||||
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,10 +36,12 @@ 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);
|
||||
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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
// 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)
|
||||
|
||||
// Node discovery
|
||||
#define DISCOVERY_FANOUT 2 // "A couple" - how many peers to query per round, and how many new peers to accept per PEERS response (keeps the crawl spread out)
|
||||
#define DISCOVERY_MAX_HOPS 3 // How many hops away from us we keep crawling
|
||||
#define DISCOVERY_TARGET_CONNECTIONS 8 // Desired outbound connection count discovery tries to reach (bounded by MAX_CONS)
|
||||
#define DISCOVERY_MAX_KNOWN_PEERS 256 // Cap on the known-peer table size
|
||||
#define DISCOVERY_PEERS_RESPONSE_CAP 8 // Max endpoints we put in a single PEERS response
|
||||
#define DISCOVERY_MAX_PINGS_PER_TICK 8 // Cap on UDP pings sent per discovery tick
|
||||
#define DISCOVERY_PING_TIMEOUT_MS 5000ULL // Backstop: a PINGED peer with no pong for this long is marked unreachable
|
||||
#define DISCOVERY_PING_REFRESH_MS 60000ULL // Re-ping a reachable peer after this long to refresh its latency
|
||||
#define DISCOVERY_QUERY_INTERVAL_MS 15000ULL // Minimum interval between GET_PEERS to the same peer
|
||||
#define DISCOVERY_CONNECT_RETRY_MS 30000ULL // Minimum interval between connect attempts to the same discovered peer
|
||||
#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).
|
||||
|
||||
|
||||
@@ -10,10 +10,15 @@
|
||||
|
||||
#include <constants.h>
|
||||
#include <packettype.h>
|
||||
#include <udpd/udpnode.h>
|
||||
|
||||
// Forward declaration - the discovery state is defined in nodediscovery.c (opaque here).
|
||||
typedef struct node_discovery node_discovery_t;
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <dynarr.h>
|
||||
#include <dynset.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -25,6 +30,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);
|
||||
@@ -33,6 +44,9 @@ typedef struct {
|
||||
pthread_t maintenanceThread;
|
||||
volatile int maintenanceRunning;
|
||||
int maintenanceIntervalMs;
|
||||
// UDP ping/pong daemon (latency oracle) and peer discovery state
|
||||
udp_node_t* udpNode;
|
||||
node_discovery_t* discovery;
|
||||
} net_node_t;
|
||||
|
||||
net_node_t* Node_Create();
|
||||
@@ -50,6 +64,11 @@ 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);
|
||||
void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp_connection_t* sourceConn);
|
||||
|
||||
// Callback logic
|
||||
void Node_Server_OnConnect(tcp_connection_t* client);
|
||||
@@ -59,4 +78,15 @@ void Node_Client_OnConnect(tcp_connection_t* client);
|
||||
void Node_Client_OnData(tcp_connection_t* client);
|
||||
void Node_Client_OnDisconnect(tcp_connection_t* client);
|
||||
|
||||
void Node_GetClientList(net_node_t* node, tcp_connection_t** outClients, size_t* outCount);
|
||||
|
||||
// Computes a connection's peer listen endpoint (IP + advertised/dialed listen port) into *out.
|
||||
// Outbound: the dialed peerAddr port already is the listen port. Inbound: uses peerListenPort.
|
||||
// Returns non-zero on success (usable endpoint with a known, non-zero port), zero otherwise.
|
||||
int Node_ConnListenEndpoint(const tcp_connection_t* conn, struct sockaddr_storage* out);
|
||||
|
||||
// Fills outEndpoints with the listen endpoints of all current connections (inbound + outbound),
|
||||
// deduped by IP+port. Returns the number of endpoints written (<= maxOut).
|
||||
size_t Node_GetPeerEndpoints(net_node_t* node, struct sockaddr_storage* outEndpoints, size_t maxOut);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef NODEDISCOVERY_H
|
||||
#define NODEDISCOVERY_H
|
||||
|
||||
#include <nets/net_node.h>
|
||||
#include <udpd/udpnode.h>
|
||||
|
||||
// Create/destroy the peer-discovery state. Owns the known-peer table and its lock.
|
||||
node_discovery_t* NodeDiscovery_Create(net_node_t* node, udp_node_t* udpNode);
|
||||
void NodeDiscovery_Destroy(node_discovery_t* disc);
|
||||
|
||||
// Periodic tick (driven by the node maintenance thread): seed currently-connected peers,
|
||||
// UDP-ping newly-learned ones, query a couple of connected peers for more, and connect to
|
||||
// the reachable peers with the lowest ping until we reach the target connection count.
|
||||
void NodeDiscovery_Iterate(node_discovery_t* disc);
|
||||
|
||||
// UDP latency callbacks (forwarded from the udp node via net_node thunks).
|
||||
void NodeDiscovery_OnPong(node_discovery_t* disc, const struct sockaddr_storage* from, uint64_t nonce, uint64_t rttMs);
|
||||
void NodeDiscovery_OnPingTimeout(node_discovery_t* disc, const struct sockaddr_storage* dest, uint64_t nonce);
|
||||
|
||||
// TCP peer-exchange handlers (called from the net_node packet dispatch).
|
||||
// Build a PEERS response (a sample of our peers, excluding the requester) and send it over fromConn.
|
||||
void NodeDiscovery_OnGetPeers(node_discovery_t* disc, tcp_connection_t* fromConn);
|
||||
// Decode a received PEERS payload and fold a couple of its endpoints into the known-peer table.
|
||||
void NodeDiscovery_OnPeersReceived(node_discovery_t* disc, tcp_connection_t* fromConn, const unsigned char* payload, size_t payloadLen);
|
||||
|
||||
// Dump the known-peer table to stdout (for the CLI `peers` command).
|
||||
void NodeDiscovery_PrintPeers(node_discovery_t* disc);
|
||||
|
||||
#endif
|
||||
@@ -14,7 +14,9 @@ typedef enum {
|
||||
PACKET_TYPE_BROADCAST_TX = 7, // Here's a new transaction I want to share with the network
|
||||
PACKET_TYPE_ACK_TX = 8, // I have received your transaction, here's what I did with it (response to broadcast)
|
||||
PACKET_TYPE_ERROR = 9, // Something went wrong with the packet you sent me, here's an error message (can be response to any packet)
|
||||
PACKET_TYPE_MAX = 10
|
||||
PACKET_TYPE_GET_PEERS = 10, // Who are your peers? Send me a few of them so I can discover more of the network
|
||||
PACKET_TYPE_PEERS = 11, // Here are some of my peers' listen endpoints (response to GET_PEERS)
|
||||
PACKET_TYPE_MAX = 12
|
||||
} packet_type_t;
|
||||
|
||||
static inline int PacketType_IsValid(uint8_t packetType) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define TCP_IO_BUFFER_SIZE 1500
|
||||
#define TCP_FRAME_HEADER_SIZE 4U
|
||||
@@ -20,10 +21,15 @@ typedef struct tcp_connection_t tcp_connection_t;
|
||||
|
||||
struct tcp_connection_t {
|
||||
int sockFd;
|
||||
struct sockaddr_in peerAddr;
|
||||
sa_family_t addrFamily;
|
||||
struct sockaddr_storage peerAddr;
|
||||
uint32_t connectionId;
|
||||
tcp_connection_role_t role;
|
||||
|
||||
// Peer's advertised TCP/UDP listen port (learned from HELLO/ACK_HELLO). 0 until known.
|
||||
// For OUTBOUND connections the peerAddr port already is the listen port; this matters for INBOUND peers.
|
||||
uint16_t peerListenPort;
|
||||
|
||||
pthread_t ioThread;
|
||||
pthread_mutex_t sendLock;
|
||||
pthread_mutex_t stateLock;
|
||||
@@ -46,7 +52,7 @@ struct tcp_connection_t {
|
||||
void* owner;
|
||||
};
|
||||
|
||||
int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_in* peerAddr, tcp_connection_role_t role);
|
||||
int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_storage* peerAddr, tcp_connection_role_t role);
|
||||
void TcpConnection_Destroy(tcp_connection_t* conn);
|
||||
|
||||
int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* data, size_t len);
|
||||
@@ -54,6 +60,14 @@ 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);
|
||||
|
||||
// Returns the peer's canonical IP string (strips ::ffff: IPv4-mapped prefix).
|
||||
// Writes at most bufLen bytes to buf. Returns buf on success, NULL on failure.
|
||||
const char* TcpConnection_GetPeerAddrStr(const tcp_connection_t* conn, char* buf, size_t bufLen);
|
||||
|
||||
// Returns non-zero if both connections have the same peer IP address.
|
||||
// Handles AF_INET vs AF_INET6 mismatches via IPv4-mapped normalisation.
|
||||
int TcpConnection_PeerAddrEqual(const tcp_connection_t* a, const tcp_connection_t* b);
|
||||
|
||||
int TcpConnection_SendRaw(int sockFd, const void* data, size_t len);
|
||||
int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen);
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <tcpd/tcpconnection.h>
|
||||
|
||||
typedef struct {
|
||||
int sockFd;
|
||||
struct sockaddr_in addr;
|
||||
int sockFd; // IPv6 listening socket (-1 if IPv6 unavailable)
|
||||
int sockFdV4; // IPv4 listening socket (-1 on bind failure)
|
||||
int opt;
|
||||
int isRunning;
|
||||
void* owner;
|
||||
@@ -27,7 +27,8 @@ typedef struct {
|
||||
tcp_connection_t** clientsArrPtr;
|
||||
pthread_mutex_t clientsMutex;
|
||||
|
||||
pthread_t svrThread;
|
||||
pthread_t svrThread; // IPv6 accept thread
|
||||
pthread_t svrThreadV4; // IPv4 accept thread
|
||||
} tcp_server_t;
|
||||
|
||||
struct tcpclient_thread_args {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef UDP_NODE_H
|
||||
#define UDP_NODE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <udpd/udppackettype.h>
|
||||
|
||||
#define UDP_LISTEN_PORT 9393
|
||||
#define UDP_PING_RETRY_INTERVAL_MS 1000
|
||||
#define UDP_PING_MAX_RETRIES 3
|
||||
#define UDP_MAX_PENDING_PINGS 64
|
||||
|
||||
typedef struct {
|
||||
uint64_t nonce;
|
||||
struct sockaddr_storage dest;
|
||||
uint64_t lastSentMs;
|
||||
int retries;
|
||||
bool active;
|
||||
} pending_ping_t;
|
||||
|
||||
typedef struct udp_node {
|
||||
int sockFd; // AF_INET6, IPV6_V6ONLY=1
|
||||
int sockFdV4; // AF_INET
|
||||
|
||||
volatile int isRunning;
|
||||
|
||||
pthread_t recvThreadV6;
|
||||
pthread_t recvThreadV4;
|
||||
pthread_t retryThread;
|
||||
|
||||
pending_ping_t pendingPings[UDP_MAX_PENDING_PINGS];
|
||||
pthread_mutex_t pingsMutex;
|
||||
|
||||
void (*on_pong)(struct udp_node* node,
|
||||
const struct sockaddr_storage* from,
|
||||
uint64_t nonce, int protoVersion, uint64_t rttMs, void* user);
|
||||
void (*on_ping_timeout)(struct udp_node* node,
|
||||
const struct sockaddr_storage* dest,
|
||||
uint64_t nonce, void* user);
|
||||
void* callbackUser;
|
||||
} udp_node_t;
|
||||
|
||||
int UdpNode_Init(udp_node_t* node, uint16_t port);
|
||||
void UdpNode_SetCallbacks(udp_node_t* node,
|
||||
void (*on_pong)(udp_node_t*, const struct sockaddr_storage*, uint64_t, int, uint64_t, void*),
|
||||
void (*on_ping_timeout)(udp_node_t*, const struct sockaddr_storage*, uint64_t, void*),
|
||||
void* user);
|
||||
int UdpNode_Start(udp_node_t* node);
|
||||
void UdpNode_Stop(udp_node_t* node);
|
||||
void UdpNode_Destroy(udp_node_t* node);
|
||||
int UdpNode_SendPing(udp_node_t* node, const struct sockaddr_storage* dest);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef UDP_PACKET_TYPE_H
|
||||
#define UDP_PACKET_TYPE_H
|
||||
|
||||
typedef enum {
|
||||
UDP_PACKET_TYPE_NONE = 0,
|
||||
UDP_PACKET_TYPE_PING = 1,
|
||||
UDP_PACKET_TYPE_PONG = 2,
|
||||
} udp_packet_type_t;
|
||||
|
||||
// Wire sizes in bytes
|
||||
#define UDP_PING_WIRE_SIZE 9 // 1 (type) + 8 (nonce)
|
||||
#define UDP_PONG_WIRE_SIZE 13 // 1 (type) + 8 (nonce) + 4 (proto_version)
|
||||
|
||||
#endif
|
||||
+10
-1
@@ -14,6 +14,8 @@ typedef struct {
|
||||
uint8_t bytes[32];
|
||||
} key32_t;
|
||||
|
||||
#define PROTO_VERSION 1
|
||||
|
||||
static inline uint32_t hash_key32(key32_t k) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
@@ -167,7 +169,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 +202,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;
|
||||
}
|
||||
|
||||
+85
-6
@@ -214,23 +214,88 @@ 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.
|
||||
}
|
||||
|
||||
if (!Transaction_Verify(tx)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!hasCoinbase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outTotalFees) {
|
||||
*outTotalFees = totalFees;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Block_IsFullyValid(const block_t* block) {
|
||||
bool merkleValid = false;
|
||||
uint8_t calculatedMerkleRoot[32];
|
||||
@@ -286,6 +351,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));
|
||||
|
||||
+255
-103
@@ -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;
|
||||
@@ -151,35 +183,100 @@ 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.
|
||||
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;
|
||||
}
|
||||
|
||||
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 (uint256_cmp(&senderEntry.balance, &spend) < 0) {
|
||||
fprintf(stderr, "Error: Sender balance insufficient for block transaction. Bailing!\n");
|
||||
ok = false; break;
|
||||
candidateTxs[i] = *tx;
|
||||
if (!Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||
++nonCoinbaseCount;
|
||||
}
|
||||
}
|
||||
if (!ok) break;
|
||||
|
||||
if (!ok) {
|
||||
free(candidateTxs);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -223,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);
|
||||
|
||||
@@ -230,6 +342,11 @@ 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);
|
||||
|
||||
/* Debug proof removed: coinbase == baseReward + totalFees was printed here during debugging. */
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -422,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);
|
||||
|
||||
@@ -455,112 +578,134 @@ bool Chain_SaveToFile(blockchain_t* chain, const char* dirpath, uint256_t curren
|
||||
if (!BuildPath(tablePath, sizeof(tablePath), dirpath, "chain.table")) {
|
||||
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 +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;
|
||||
}
|
||||
|
||||
|
||||
+17
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+456
-48
@@ -11,13 +11,15 @@
|
||||
#include <signal.h>
|
||||
#include <balance_sheet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <txmempool.h>
|
||||
|
||||
#include <constants.h>
|
||||
#include <runtime_state.h>
|
||||
#include <autolykos2/autolykos2.h>
|
||||
|
||||
#include <nets/net_node.h>
|
||||
#include <nets/nodediscovery.h>
|
||||
#include <nets/fetch_scheduler.h>
|
||||
#include <nets/orphan_pool.h>
|
||||
|
||||
@@ -27,6 +29,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 +46,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) {
|
||||
@@ -117,6 +148,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;
|
||||
@@ -271,6 +359,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,
|
||||
@@ -294,6 +422,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) {
|
||||
@@ -303,14 +441,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];
|
||||
@@ -374,6 +504,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;
|
||||
@@ -450,12 +581,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) {
|
||||
@@ -478,6 +628,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);
|
||||
}
|
||||
|
||||
@@ -485,6 +637,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,7 +671,13 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
ApplyRuntimeConfigFromEnv();
|
||||
|
||||
signal(SIGINT, handle_sigint);
|
||||
// Ignore SIGPIPE so a write to a socket whose peer has already disconnected returns EPIPE
|
||||
// (handled by the send paths) instead of terminating the whole process. Peers connecting and
|
||||
// disconnecting is normal p2p behaviour and must never take the node down.
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
srand((unsigned int)time(NULL));
|
||||
|
||||
// Initialize runtime locks before any thread or helper can touch chain state.
|
||||
@@ -537,6 +705,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)) {
|
||||
@@ -576,9 +749,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 +830,7 @@ int main(int argc, char* argv[]) {
|
||||
Block_ShutdownPowContext();
|
||||
BalanceSheet_Destroy();
|
||||
return 1;
|
||||
}
|
||||
}*/
|
||||
|
||||
char minerAddressHex[65];
|
||||
AddressToHexString(minerAddress, minerAddressHex);
|
||||
@@ -596,7 +839,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) {
|
||||
@@ -634,14 +877,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);
|
||||
@@ -651,6 +918,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);
|
||||
@@ -669,6 +941,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;
|
||||
@@ -687,6 +960,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");
|
||||
@@ -705,12 +993,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));
|
||||
@@ -719,6 +1008,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];
|
||||
@@ -733,7 +1023,26 @@ 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");
|
||||
*/
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -744,46 +1053,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;
|
||||
@@ -895,6 +1203,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;
|
||||
@@ -968,10 +1281,76 @@ 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, "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) {
|
||||
char* blockNumberStr = strtok(NULL, " \t");
|
||||
char* extra = strtok(NULL, " \t");
|
||||
@@ -1050,9 +1429,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 +1441,40 @@ 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;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "peers") == 0) {
|
||||
if (strtok(NULL, " \t")) {
|
||||
printf("usage: peers\n");
|
||||
continue;
|
||||
}
|
||||
NodeDiscovery_PrintPeers(node->discovery);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1128,7 +1536,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;
|
||||
}
|
||||
|
||||
+703
-47
@@ -1,4 +1,5 @@
|
||||
#include <nets/net_node.h>
|
||||
#include <nets/nodediscovery.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -11,6 +12,9 @@
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <txmempool.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
static net_node_t* Node_FromConnection(tcp_connection_t* conn) {
|
||||
if (!conn) {
|
||||
@@ -28,6 +32,200 @@ static uint64_t Node_GetCurrentBlockHeight(void) {
|
||||
return currentBlockHeight;
|
||||
}
|
||||
|
||||
// Compares two listen endpoints (family + IP + port).
|
||||
static int NetNode_EndpointEqual(const struct sockaddr_storage* a, const struct sockaddr_storage* b) {
|
||||
if (a->ss_family != b->ss_family) return 0;
|
||||
if (a->ss_family == AF_INET) {
|
||||
const struct sockaddr_in* x = (const struct sockaddr_in*)a;
|
||||
const struct sockaddr_in* y = (const struct sockaddr_in*)b;
|
||||
return x->sin_port == y->sin_port &&
|
||||
memcmp(&x->sin_addr, &y->sin_addr, sizeof(struct in_addr)) == 0;
|
||||
}
|
||||
if (a->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6* x = (const struct sockaddr_in6*)a;
|
||||
const struct sockaddr_in6* y = (const struct sockaddr_in6*)b;
|
||||
return x->sin6_port == y->sin6_port &&
|
||||
memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(struct in6_addr)) == 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Builds a listen endpoint from an IP string + port, normalising IPv4-mapped IPv6 to plain IPv4
|
||||
// so it compares equal to Node_ConnListenEndpoint output. Returns non-zero on success.
|
||||
static int NetNode_MakeEndpoint(const char* ip, unsigned short port, struct sockaddr_storage* out) {
|
||||
if (!ip || !out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
struct in_addr a4;
|
||||
if (inet_pton(AF_INET, ip, &a4) == 1) {
|
||||
struct sockaddr_in* o = (struct sockaddr_in*)out;
|
||||
o->sin_family = AF_INET;
|
||||
o->sin_addr = a4;
|
||||
o->sin_port = htons(port);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct in6_addr a6;
|
||||
if (inet_pton(AF_INET6, ip, &a6) == 1) {
|
||||
if (IN6_IS_ADDR_V4MAPPED(&a6)) {
|
||||
struct sockaddr_in* o = (struct sockaddr_in*)out;
|
||||
o->sin_family = AF_INET;
|
||||
memcpy(&o->sin_addr, ((const uint8_t*)&a6) + 12, sizeof(struct in_addr));
|
||||
o->sin_port = htons(port);
|
||||
} else {
|
||||
struct sockaddr_in6* o = (struct sockaddr_in6*)out;
|
||||
o->sin6_family = AF_INET6;
|
||||
o->sin6_addr = a6;
|
||||
o->sin6_port = htons(port);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns non-zero if we already hold an outbound connection to the given listen endpoint.
|
||||
static int Node_HasOutboundTo(net_node_t* node, const struct sockaddr_storage* endpoint) {
|
||||
int found = 0;
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
tcp_connection_t* c = node->outboundClients[i].connection;
|
||||
if (!c) continue;
|
||||
struct sockaddr_storage ep;
|
||||
if (Node_ConnListenEndpoint(c, &ep) && NetNode_EndpointEqual(&ep, endpoint)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
return found;
|
||||
}
|
||||
|
||||
// Returns non-zero if some inbound connection OTHER than `self` already has the given listen
|
||||
// endpoint (used to reject a duplicate inbound once we learn the peer's advertised listen port).
|
||||
static int Node_HasOtherInboundFrom(net_node_t* node, const tcp_connection_t* self, const struct sockaddr_storage* endpoint) {
|
||||
if (!node->server) return 0;
|
||||
int found = 0;
|
||||
pthread_mutex_lock(&node->server->clientsMutex);
|
||||
for (size_t i = 0; i < node->server->maxClients; ++i) {
|
||||
tcp_connection_t* other = node->server->clientsArrPtr ? node->server->clientsArrPtr[i] : NULL;
|
||||
if (!other || other == self) continue;
|
||||
struct sockaddr_storage ep;
|
||||
if (Node_ConnListenEndpoint(other, &ep) && NetNode_EndpointEqual(&ep, endpoint)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->server->clientsMutex);
|
||||
return found;
|
||||
}
|
||||
|
||||
int Node_ConnListenEndpoint(const tcp_connection_t* conn, struct sockaddr_storage* out) {
|
||||
if (!conn || !out) return 0;
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
// Determine the peer's listen port. For an outbound connection the port we dialed already
|
||||
// is the peer's listen port; for an inbound one it is the port advertised in HELLO.
|
||||
unsigned short listenP;
|
||||
if (conn->role == TCP_CONNECTION_ROLE_OUTBOUND) {
|
||||
listenP = (conn->addrFamily == AF_INET6)
|
||||
? ntohs(((const struct sockaddr_in6*)&conn->peerAddr)->sin6_port)
|
||||
: ntohs(((const struct sockaddr_in*)&conn->peerAddr)->sin_port);
|
||||
} else {
|
||||
listenP = conn->peerListenPort;
|
||||
}
|
||||
if (listenP == 0) return 0; // unknown listen port -> not a usable endpoint
|
||||
|
||||
if (conn->addrFamily == AF_INET) {
|
||||
const struct sockaddr_in* a = (const struct sockaddr_in*)&conn->peerAddr;
|
||||
struct sockaddr_in* o = (struct sockaddr_in*)out;
|
||||
o->sin_family = AF_INET;
|
||||
o->sin_addr = a->sin_addr;
|
||||
o->sin_port = htons(listenP);
|
||||
return 1;
|
||||
}
|
||||
if (conn->addrFamily == AF_INET6) {
|
||||
const struct sockaddr_in6* a = (const struct sockaddr_in6*)&conn->peerAddr;
|
||||
if (IN6_IS_ADDR_V4MAPPED(&a->sin6_addr)) {
|
||||
// Normalise IPv4-mapped IPv6 to plain IPv4.
|
||||
struct sockaddr_in* o = (struct sockaddr_in*)out;
|
||||
o->sin_family = AF_INET;
|
||||
memcpy(&o->sin_addr, ((const uint8_t*)&a->sin6_addr) + 12, sizeof(struct in_addr));
|
||||
o->sin_port = htons(listenP);
|
||||
} else {
|
||||
struct sockaddr_in6* o = (struct sockaddr_in6*)out;
|
||||
o->sin6_family = AF_INET6;
|
||||
o->sin6_addr = a->sin6_addr;
|
||||
o->sin6_port = htons(listenP);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Node_GetPeerEndpoints(net_node_t* node, struct sockaddr_storage* outEndpoints, size_t maxOut) {
|
||||
if (!node || !outEndpoints || maxOut == 0) return 0;
|
||||
size_t count = 0;
|
||||
|
||||
// Outbound connections
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
for (size_t i = 0; i < MAX_CONS && count < maxOut; ++i) {
|
||||
tcp_connection_t* c = node->outboundClients[i].connection;
|
||||
if (!c) continue;
|
||||
struct sockaddr_storage ep;
|
||||
if (!Node_ConnListenEndpoint(c, &ep)) continue;
|
||||
int dup = 0;
|
||||
for (size_t k = 0; k < count; ++k) {
|
||||
if (NetNode_EndpointEqual(&outEndpoints[k], &ep)) { dup = 1; break; }
|
||||
}
|
||||
if (!dup) outEndpoints[count++] = ep;
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
// Inbound connections
|
||||
if (node->server) {
|
||||
pthread_mutex_lock(&node->server->clientsMutex);
|
||||
for (size_t i = 0; i < node->server->maxClients && count < maxOut; ++i) {
|
||||
tcp_connection_t* c = node->server->clientsArrPtr ? node->server->clientsArrPtr[i] : NULL;
|
||||
if (!c) continue;
|
||||
struct sockaddr_storage ep;
|
||||
if (!Node_ConnListenEndpoint(c, &ep)) continue;
|
||||
int dup = 0;
|
||||
for (size_t k = 0; k < count; ++k) {
|
||||
if (NetNode_EndpointEqual(&outEndpoints[k], &ep)) { dup = 1; break; }
|
||||
}
|
||||
if (!dup) outEndpoints[count++] = ep;
|
||||
}
|
||||
pthread_mutex_unlock(&node->server->clientsMutex);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Thunks routing UDP ping/pong events into the discovery state.
|
||||
static void Node_OnPongThunk(udp_node_t* udp, const struct sockaddr_storage* from,
|
||||
uint64_t nonce, int protoVersion, uint64_t rttMs, void* user) {
|
||||
(void)udp; (void)protoVersion;
|
||||
net_node_t* node = (net_node_t*)user;
|
||||
if (node && node->discovery) {
|
||||
NodeDiscovery_OnPong(node->discovery, from, nonce, rttMs);
|
||||
}
|
||||
}
|
||||
|
||||
static void Node_OnPingTimeoutThunk(udp_node_t* udp, const struct sockaddr_storage* dest,
|
||||
uint64_t nonce, void* user) {
|
||||
(void)udp;
|
||||
net_node_t* node = (net_node_t*)user;
|
||||
if (node && node->discovery) {
|
||||
NodeDiscovery_OnPingTimeout(node->discovery, dest, nonce);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -40,6 +238,10 @@ static void* Node_MaintenanceThread(void* arg) {
|
||||
BalanceSheet_SaveToFile(chainDataDir);
|
||||
}
|
||||
}
|
||||
// Peer discovery tick: ping/query connected peers and connect to the best-ping discoveries.
|
||||
if (n->discovery) {
|
||||
NodeDiscovery_Iterate(n->discovery);
|
||||
}
|
||||
sleep_for_milliseconds((uint64_t)n->maintenanceIntervalMs);
|
||||
}
|
||||
return NULL;
|
||||
@@ -61,18 +263,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 +285,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 +299,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 +308,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,9 +369,23 @@ static bool Node_ParseAndAcceptBlock(const unsigned char* payload, size_t payloa
|
||||
DynArr_destroy(blk->transactions);
|
||||
}
|
||||
free(blk);
|
||||
return false;
|
||||
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);
|
||||
@@ -156,7 +402,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 +444,13 @@ 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
|
||||
TxMempool_Init();
|
||||
|
||||
TcpServer_Init(node->server, listenPort, "::");
|
||||
|
||||
node->server->owner = node;
|
||||
node->server->on_connect = Node_Server_OnConnect;
|
||||
@@ -209,6 +461,25 @@ net_node_t* Node_Create() {
|
||||
|
||||
OrphanPool_Init();
|
||||
|
||||
// Start the UDP ping/pong daemon (latency oracle) and peer discovery. Non-fatal on failure;
|
||||
// the node still works without discovery, it just won't crawl for new peers.
|
||||
node->udpNode = (udp_node_t*)malloc(sizeof(udp_node_t));
|
||||
if (node->udpNode) {
|
||||
if (UdpNode_Init(node->udpNode, (uint16_t)listenPort) == 0) {
|
||||
UdpNode_SetCallbacks(node->udpNode, Node_OnPongThunk, Node_OnPingTimeoutThunk, node);
|
||||
if (UdpNode_Start(node->udpNode) == 0) {
|
||||
node->discovery = NodeDiscovery_Create(node, node->udpNode);
|
||||
} else {
|
||||
UdpNode_Destroy(node->udpNode);
|
||||
free(node->udpNode);
|
||||
node->udpNode = NULL;
|
||||
}
|
||||
} else {
|
||||
free(node->udpNode);
|
||||
node->udpNode = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Start maintenance thread
|
||||
node->maintenanceRunning = 1;
|
||||
node->maintenanceIntervalMs = 1000; // 1s
|
||||
@@ -235,13 +506,35 @@ void Node_Destroy(net_node_t* node) {
|
||||
TcpServer_Destroy(node->server);
|
||||
}
|
||||
|
||||
// Stop maintenance thread
|
||||
// Stop maintenance thread (no more discovery ticks after this)
|
||||
if (node->maintenanceRunning) {
|
||||
node->maintenanceRunning = 0;
|
||||
pthread_join(node->maintenanceThread, NULL);
|
||||
}
|
||||
|
||||
// Tear down UDP + discovery. Stop UDP first so no pong/timeout callback races the destroy.
|
||||
if (node->udpNode) {
|
||||
UdpNode_Stop(node->udpNode);
|
||||
}
|
||||
if (node->discovery) {
|
||||
NodeDiscovery_Destroy(node->discovery);
|
||||
node->discovery = NULL;
|
||||
}
|
||||
if (node->udpNode) {
|
||||
UdpNode_Destroy(node->udpNode);
|
||||
free(node->udpNode);
|
||||
node->udpNode = NULL;
|
||||
}
|
||||
|
||||
OrphanPool_Destroy();
|
||||
TxMempool_Destroy();
|
||||
|
||||
if (node->seenBlocks) {
|
||||
DynSet_Destroy(node->seenBlocks);
|
||||
node->seenBlocks = NULL;
|
||||
}
|
||||
pthread_mutex_destroy(&node->seenLock);
|
||||
pthread_mutex_destroy(&node->outboundLock);
|
||||
|
||||
free(node);
|
||||
}
|
||||
@@ -268,6 +561,14 @@ int Node_ConnectPeer(net_node_t* node, const char* ip, unsigned short port) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Enforce a single outbound connection per endpoint: if we already have an outbound to this
|
||||
// (ip, port), do not open a second one. (Inbound from the same endpoint is still allowed - that
|
||||
// is the peer's own outbound to us.)
|
||||
struct sockaddr_storage target;
|
||||
if (NetNode_MakeEndpoint(ip, port, &target) && Node_HasOutboundTo(node, &target)) {
|
||||
return 0; // already connected outbound to this endpoint
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection == NULL) {
|
||||
if (TcpClient_Connect(
|
||||
@@ -336,10 +637,64 @@ 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 (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[INET6_ADDRSTRLEN];
|
||||
if (TcpConnection_GetPeerAddrStr(client, 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) {
|
||||
if (TcpConnection_PeerAddrEqual(node->outboundClients[i].connection, client)) {
|
||||
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) {
|
||||
@@ -363,11 +718,36 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
||||
memcpy(&blockHeight, payload + sizeof(protoVersion), sizeof(blockHeight));
|
||||
|
||||
// TODO: Save these somewhere and maybe respond
|
||||
printf("Received HELLO from node %u: protoVersion=%u, blockHeight=%" PRIu64 "\n",
|
||||
client ? client->connectionId : 0U, protoVersion, blockHeight);
|
||||
// Optional trailing listen port. This inbound peer's source port is ephemeral,
|
||||
// so we record the port it actually listens on to make it discoverable/reachable.
|
||||
// Length-guarded so older peers that omit it still work.
|
||||
if (client && payloadLen >= sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint16_t)) {
|
||||
uint16_t peerListenPort;
|
||||
memcpy(&peerListenPort, payload + sizeof(protoVersion) + sizeof(blockHeight), sizeof(peerListenPort));
|
||||
client->peerListenPort = peerListenPort;
|
||||
}
|
||||
|
||||
// Craft and send ACK_HELLO
|
||||
printf("Received HELLO from node %u: protoVersion=%u, blockHeight=%" PRIu64 ", listenPort=%u\n",
|
||||
client ? client->connectionId : 0U, protoVersion, blockHeight,
|
||||
client ? client->peerListenPort : 0U);
|
||||
|
||||
// Enforce a single inbound connection per endpoint. Now that we know this peer's listen
|
||||
// port, drop this connection if another inbound from the same endpoint already exists
|
||||
// (keep the established one). An outbound to the same endpoint is unaffected - that is
|
||||
// this node's own connection to the peer.
|
||||
if (client && client->peerListenPort != 0) {
|
||||
net_node_t* dupNode = Node_FromConnection(client);
|
||||
struct sockaddr_storage myEp;
|
||||
if (dupNode && Node_ConnListenEndpoint(client, &myEp) &&
|
||||
Node_HasOtherInboundFrom(dupNode, client, &myEp)) {
|
||||
printf("Rejecting duplicate inbound connection %u (already have an inbound from this endpoint)\n",
|
||||
client->connectionId);
|
||||
TcpConnection_RequestClose(client);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Craft and send ACK_HELLO (echo protoVersion, our height, and our own listen port)
|
||||
uint8_t ackBuf[100];
|
||||
uint8_t* ackData = ackBuf;
|
||||
size_t ackOffset = 0;
|
||||
@@ -376,6 +756,9 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
uint64_t currentHeight = Node_GetCurrentBlockHeight();
|
||||
memcpy(ackData + ackOffset, ¤tHeight, sizeof(currentHeight));
|
||||
ackOffset += sizeof(currentHeight);
|
||||
uint16_t myListenPort = (uint16_t)listenPort;
|
||||
memcpy(ackData + ackOffset, &myListenPort, sizeof(myListenPort));
|
||||
ackOffset += sizeof(myListenPort);
|
||||
|
||||
Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ACK_HELLO, ackData, ackOffset);
|
||||
|
||||
@@ -498,15 +881,67 @@ 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;
|
||||
}
|
||||
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
|
||||
@@ -519,12 +954,26 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
||||
printf("Received packet type %u from node %u with message: %s\n",
|
||||
(unsigned int)packetType, client ? client->connectionId : 0U, text);
|
||||
free(text);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_GET_PEERS: {
|
||||
net_node_t* dnode = Node_FromConnection(client);
|
||||
if (dnode && dnode->discovery) {
|
||||
NodeDiscovery_OnGetPeers(dnode->discovery, client);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_PEERS: {
|
||||
net_node_t* dnode = Node_FromConnection(client);
|
||||
if (dnode && dnode->discovery) {
|
||||
NodeDiscovery_OnPeersReceived(dnode->discovery, client, payload, payloadLen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
net_node_t* node = Node_FromConnection(client);
|
||||
Node_ForwardData(node, client, payload, payloadLen);
|
||||
@@ -547,12 +996,16 @@ void Node_Client_OnConnect(tcp_connection_t* client) {
|
||||
uint8_t* data = buf;
|
||||
|
||||
size_t offset = 0;
|
||||
uint32_t protoVersion = 1; // little-endian
|
||||
uint32_t protoVersion = PROTO_VERSION; // little-endian
|
||||
uint64_t blockHeight = Node_GetCurrentBlockHeight();
|
||||
memcpy((unsigned char*)data + offset, &protoVersion, sizeof(protoVersion)); // This is technically "unsafe", but I honestly just don't give a shit at this point
|
||||
offset += sizeof(protoVersion);
|
||||
memcpy((unsigned char*)data + offset, &blockHeight, sizeof(blockHeight));
|
||||
offset += sizeof(blockHeight);
|
||||
// Advertise the port we listen on so the peer can share us with others (and reach us back)
|
||||
uint16_t myListenPort = (uint16_t)listenPort;
|
||||
memcpy((unsigned char*)data + offset, &myListenPort, sizeof(myListenPort));
|
||||
offset += sizeof(myListenPort);
|
||||
|
||||
Node_SendPacket(node, client, PACKET_TYPE_HELLO, data, offset);
|
||||
}
|
||||
@@ -589,17 +1042,27 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
||||
memcpy(&blockHeight, payload + sizeof(protoVersion), sizeof(blockHeight));
|
||||
|
||||
// Optional trailing listen port (for outbound peers the dialed port is already the
|
||||
// listen port, but record the advertised one for consistency). Length-guarded.
|
||||
if (client && payloadLen >= sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint16_t)) {
|
||||
uint16_t peerListenPort;
|
||||
memcpy(&peerListenPort, payload + sizeof(protoVersion) + sizeof(blockHeight), sizeof(peerListenPort));
|
||||
client->peerListenPort = peerListenPort;
|
||||
}
|
||||
|
||||
printf("Received ACK_HELLO from node %u with protoVersion %u and blockHeight %" PRIu64 "\n", client ? client->connectionId : 0U, protoVersion, blockHeight);
|
||||
|
||||
// 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,23 +1077,74 @@ 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;
|
||||
}
|
||||
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
|
||||
@@ -643,7 +1157,21 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
||||
printf("Received packet type %u from node %u with message: %s\n",
|
||||
(unsigned int)packetType, client ? client->connectionId : 0U, text);
|
||||
free(text);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_GET_PEERS: {
|
||||
net_node_t* dnode = Node_FromConnection(client);
|
||||
if (dnode && dnode->discovery) {
|
||||
NodeDiscovery_OnGetPeers(dnode->discovery, client);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_PEERS: {
|
||||
net_node_t* dnode = Node_FromConnection(client);
|
||||
if (dnode && dnode->discovery) {
|
||||
NodeDiscovery_OnPeersReceived(dnode->discovery, client, payload, payloadLen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -656,10 +1184,138 @@ 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;
|
||||
|
||||
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 (sourceConn && TcpConnection_PeerAddrEqual(conn, sourceConn)) continue;
|
||||
Node_SendPacket(node, conn, PACKET_TYPE_BROADCAST_BLOCK, payload, off);
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
free(payload);
|
||||
Block_Destroy(blk);
|
||||
}
|
||||
}
|
||||
|
||||
void Node_GetClientList(net_node_t* node, tcp_connection_t** outClients, size_t* outCount) {
|
||||
if (!node || !outClients || !outCount) return;
|
||||
|
||||
pthread_mutex_lock(&node->outboundLock);
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||
if (node->outboundClients[i].connection) {
|
||||
outClients[count++] = node->outboundClients[i].connection;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->outboundLock);
|
||||
|
||||
*outCount = count;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
#include <nets/nodediscovery.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <constants.h>
|
||||
#include <dynarr.h>
|
||||
#include <numgen.h>
|
||||
#include <utils.h>
|
||||
|
||||
// Wire layout of a single peer endpoint inside a PEERS payload:
|
||||
// [uint8 family (4 or 6)][uint8 ip[16]][uint16 port (host order)] -> 19 bytes
|
||||
// (Host-endian raw layout, consistent with the rest of the protocol.)
|
||||
#define DISCOVERY_WIRE_ENTRY_SIZE (1 + 16 + 2)
|
||||
|
||||
typedef enum {
|
||||
DISCOVERY_STATE_NEW = 0, // learned, not yet pinged
|
||||
DISCOVERY_STATE_PINGED, // first ping in flight, reachability unknown
|
||||
DISCOVERY_STATE_REACHABLE, // pong received, latency known
|
||||
DISCOVERY_STATE_CONNECTED, // currently a live outbound connection
|
||||
DISCOVERY_STATE_UNREACHABLE // ping timed out
|
||||
} discovery_state_t;
|
||||
|
||||
typedef struct {
|
||||
struct sockaddr_storage addr; // listen endpoint (port already set to the peer's listen port)
|
||||
uint64_t pingMs; // measured UDP RTT, UINT64_MAX if unknown
|
||||
uint32_t hop; // distance from us (0 = directly connected)
|
||||
discovery_state_t state;
|
||||
int pingPending; // 1 while a ping is outstanding (matched by address on pong/timeout).
|
||||
// The UDP layer generates its own nonce, so we can't match by nonce here.
|
||||
uint64_t lastPingMs; // when we last sent a ping
|
||||
uint64_t lastQueryMs; // when we last sent GET_PEERS to it
|
||||
uint64_t lastConnectMs; // when we last attempted a connect to it
|
||||
} discovered_peer_t;
|
||||
|
||||
struct node_discovery {
|
||||
net_node_t* node;
|
||||
udp_node_t* udpNode;
|
||||
DynArr* peers; // of discovered_peer_t
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
// ---- small helpers (most assume the caller holds disc->lock) ------------------------------
|
||||
|
||||
static int Discovery_AddrEqual(const struct sockaddr_storage* a, const struct sockaddr_storage* b) {
|
||||
if (a->ss_family != b->ss_family) return 0;
|
||||
if (a->ss_family == AF_INET) {
|
||||
const struct sockaddr_in* x = (const struct sockaddr_in*)a;
|
||||
const struct sockaddr_in* y = (const struct sockaddr_in*)b;
|
||||
return x->sin_port == y->sin_port &&
|
||||
memcmp(&x->sin_addr, &y->sin_addr, sizeof(struct in_addr)) == 0;
|
||||
}
|
||||
if (a->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6* x = (const struct sockaddr_in6*)a;
|
||||
const struct sockaddr_in6* y = (const struct sockaddr_in6*)b;
|
||||
return x->sin6_port == y->sin6_port &&
|
||||
memcmp(&x->sin6_addr, &y->sin6_addr, sizeof(struct in6_addr)) == 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static discovered_peer_t* Discovery_FindPtr(node_discovery_t* disc, const struct sockaddr_storage* addr) {
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (Discovery_AddrEqual(&p->addr, addr)) return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Insert addr if not already present. Returns a pointer to the (existing or new) entry, or NULL
|
||||
// if the table is full. Note: the returned pointer is invalidated by any later push_back.
|
||||
static discovered_peer_t* Discovery_Upsert(node_discovery_t* disc, const struct sockaddr_storage* addr, uint32_t hop) {
|
||||
discovered_peer_t* existing = Discovery_FindPtr(disc, addr);
|
||||
if (existing) {
|
||||
if (hop < existing->hop) existing->hop = hop; // keep the shortest known distance
|
||||
return existing;
|
||||
}
|
||||
if (DynArr_size(disc->peers) >= DISCOVERY_MAX_KNOWN_PEERS) return NULL;
|
||||
|
||||
discovered_peer_t np;
|
||||
memset(&np, 0, sizeof(np));
|
||||
np.addr = *addr;
|
||||
np.pingMs = UINT64_MAX;
|
||||
np.hop = hop;
|
||||
np.state = DISCOVERY_STATE_NEW;
|
||||
DynArr_push_back(disc->peers, &np);
|
||||
return (discovered_peer_t*)DynArr_at(disc->peers, DynArr_size(disc->peers) - 1);
|
||||
}
|
||||
|
||||
static int Discovery_AddrToWire(const struct sockaddr_storage* addr, unsigned char out[DISCOVERY_WIRE_ENTRY_SIZE]) {
|
||||
memset(out, 0, DISCOVERY_WIRE_ENTRY_SIZE);
|
||||
if (addr->ss_family == AF_INET) {
|
||||
const struct sockaddr_in* a = (const struct sockaddr_in*)addr;
|
||||
out[0] = 4;
|
||||
memcpy(out + 1, &a->sin_addr, sizeof(struct in_addr));
|
||||
uint16_t port = ntohs(a->sin_port);
|
||||
memcpy(out + 1 + 16, &port, sizeof(port));
|
||||
return 1;
|
||||
}
|
||||
if (addr->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6* a = (const struct sockaddr_in6*)addr;
|
||||
out[0] = 6;
|
||||
memcpy(out + 1, &a->sin6_addr, sizeof(struct in6_addr));
|
||||
uint16_t port = ntohs(a->sin6_port);
|
||||
memcpy(out + 1 + 16, &port, sizeof(port));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Discovery_WireToAddr(const unsigned char in[DISCOVERY_WIRE_ENTRY_SIZE], struct sockaddr_storage* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
uint8_t fam = in[0];
|
||||
uint16_t port;
|
||||
memcpy(&port, in + 1 + 16, sizeof(port));
|
||||
if (fam == 4) {
|
||||
struct sockaddr_in* a = (struct sockaddr_in*)out;
|
||||
a->sin_family = AF_INET;
|
||||
memcpy(&a->sin_addr, in + 1, sizeof(struct in_addr));
|
||||
a->sin_port = htons(port);
|
||||
return port != 0;
|
||||
}
|
||||
if (fam == 6) {
|
||||
struct sockaddr_in6* a = (struct sockaddr_in6*)out;
|
||||
a->sin6_family = AF_INET6;
|
||||
memcpy(&a->sin6_addr, in + 1, sizeof(struct in6_addr));
|
||||
a->sin6_port = htons(port);
|
||||
return port != 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Discovery_AddrToIpPort(const struct sockaddr_storage* addr, char* ipOut, size_t ipLen, unsigned short* portOut) {
|
||||
if (addr->ss_family == AF_INET) {
|
||||
const struct sockaddr_in* a = (const struct sockaddr_in*)addr;
|
||||
if (!inet_ntop(AF_INET, &a->sin_addr, ipOut, (socklen_t)ipLen)) return 0;
|
||||
*portOut = ntohs(a->sin_port);
|
||||
return 1;
|
||||
}
|
||||
if (addr->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6* a = (const struct sockaddr_in6*)addr;
|
||||
if (!inet_ntop(AF_INET6, &a->sin6_addr, ipOut, (socklen_t)ipLen)) return 0;
|
||||
*portOut = ntohs(a->sin6_port);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- lifecycle ---------------------------------------------------------------------------
|
||||
|
||||
node_discovery_t* NodeDiscovery_Create(net_node_t* node, udp_node_t* udpNode) {
|
||||
if (!node || !udpNode) return NULL;
|
||||
node_discovery_t* disc = (node_discovery_t*)malloc(sizeof(node_discovery_t));
|
||||
if (!disc) return NULL;
|
||||
memset(disc, 0, sizeof(*disc));
|
||||
disc->node = node;
|
||||
disc->udpNode = udpNode;
|
||||
disc->peers = DYNARR_CREATE(discovered_peer_t, 16);
|
||||
if (!disc->peers) {
|
||||
free(disc);
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_init(&disc->lock, NULL);
|
||||
return disc;
|
||||
}
|
||||
|
||||
void NodeDiscovery_Destroy(node_discovery_t* disc) {
|
||||
if (!disc) return;
|
||||
if (disc->peers) DynArr_destroy(disc->peers);
|
||||
pthread_mutex_destroy(&disc->lock);
|
||||
free(disc);
|
||||
}
|
||||
|
||||
// ---- UDP latency callbacks ---------------------------------------------------------------
|
||||
|
||||
void NodeDiscovery_OnPong(node_discovery_t* disc, const struct sockaddr_storage* from, uint64_t nonce, uint64_t rttMs) {
|
||||
if (!disc || !from) return;
|
||||
(void)nonce; // UDP layer owns the nonce; we match the peer by its reply address instead.
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
discovered_peer_t* p = Discovery_FindPtr(disc, from);
|
||||
if (p && p->pingPending) {
|
||||
p->pingMs = rttMs;
|
||||
p->pingPending = 0;
|
||||
if (p->state == DISCOVERY_STATE_PINGED) p->state = DISCOVERY_STATE_REACHABLE;
|
||||
}
|
||||
pthread_mutex_unlock(&disc->lock);
|
||||
}
|
||||
|
||||
void NodeDiscovery_OnPingTimeout(node_discovery_t* disc, const struct sockaddr_storage* dest, uint64_t nonce) {
|
||||
if (!disc || !dest) return;
|
||||
(void)nonce; // matched by the destination address we pinged
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
discovered_peer_t* p = Discovery_FindPtr(disc, dest);
|
||||
if (p && p->pingPending) {
|
||||
p->pingPending = 0;
|
||||
// Only demote a peer whose reachability was still unknown; a refresh ping that times
|
||||
// out on an already-connected/reachable peer must not drop it.
|
||||
if (p->state == DISCOVERY_STATE_PINGED) p->state = DISCOVERY_STATE_UNREACHABLE;
|
||||
}
|
||||
pthread_mutex_unlock(&disc->lock);
|
||||
}
|
||||
|
||||
// ---- TCP peer exchange -------------------------------------------------------------------
|
||||
|
||||
void NodeDiscovery_OnGetPeers(node_discovery_t* disc, tcp_connection_t* fromConn) {
|
||||
if (!disc || !fromConn) return;
|
||||
|
||||
// Snapshot our current peers' listen endpoints (inbound + outbound).
|
||||
struct sockaddr_storage all[MAX_CONS * 2];
|
||||
size_t total = Node_GetPeerEndpoints(disc->node, all, sizeof(all) / sizeof(all[0]));
|
||||
|
||||
struct sockaddr_storage reqEndpoint;
|
||||
int haveReq = Node_ConnListenEndpoint(fromConn, &reqEndpoint);
|
||||
|
||||
// Build the response payload: [uint16 count][entries...], capped and sampled for spread.
|
||||
unsigned char payload[sizeof(uint16_t) + DISCOVERY_PEERS_RESPONSE_CAP * DISCOVERY_WIRE_ENTRY_SIZE];
|
||||
size_t offset = sizeof(uint16_t);
|
||||
uint16_t count = 0;
|
||||
|
||||
size_t startIdx = total ? (size_t)(random_four_byte() % total) : 0;
|
||||
for (size_t k = 0; k < total && count < DISCOVERY_PEERS_RESPONSE_CAP; ++k) {
|
||||
size_t idx = (startIdx + k) % total;
|
||||
if (haveReq && Discovery_AddrEqual(&all[idx], &reqEndpoint)) continue; // don't tell them about themselves
|
||||
unsigned char entry[DISCOVERY_WIRE_ENTRY_SIZE];
|
||||
if (!Discovery_AddrToWire(&all[idx], entry)) continue;
|
||||
memcpy(payload + offset, entry, DISCOVERY_WIRE_ENTRY_SIZE);
|
||||
offset += DISCOVERY_WIRE_ENTRY_SIZE;
|
||||
count++;
|
||||
}
|
||||
memcpy(payload, &count, sizeof(count));
|
||||
|
||||
Node_SendPacket(disc->node, fromConn, PACKET_TYPE_PEERS, payload, offset);
|
||||
}
|
||||
|
||||
void NodeDiscovery_OnPeersReceived(node_discovery_t* disc, tcp_connection_t* fromConn, const unsigned char* payload, size_t payloadLen) {
|
||||
if (!disc || !payload || payloadLen < sizeof(uint16_t)) return;
|
||||
|
||||
uint16_t count;
|
||||
memcpy(&count, payload, sizeof(count));
|
||||
size_t need = sizeof(uint16_t) + (size_t)count * DISCOVERY_WIRE_ENTRY_SIZE;
|
||||
if (payloadLen < need) return; // malformed / truncated
|
||||
|
||||
// Determine the hop distance of the peer that answered, so its peers land one hop further out.
|
||||
struct sockaddr_storage srcEndpoint;
|
||||
int haveSrc = fromConn ? Node_ConnListenEndpoint(fromConn, &srcEndpoint) : 0;
|
||||
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
|
||||
uint32_t srcHop = 0;
|
||||
if (haveSrc) {
|
||||
discovered_peer_t* srcp = Discovery_FindPtr(disc, &srcEndpoint);
|
||||
if (srcp) srcHop = srcp->hop;
|
||||
}
|
||||
uint32_t newHop = srcHop + 1;
|
||||
|
||||
// Fold in at most DISCOVERY_FANOUT *new* endpoints (a couple per node -> keeps the crawl spread).
|
||||
if (newHop <= DISCOVERY_MAX_HOPS) {
|
||||
int added = 0;
|
||||
for (uint16_t i = 0; i < count && added < DISCOVERY_FANOUT; ++i) {
|
||||
const unsigned char* entry = payload + sizeof(uint16_t) + (size_t)i * DISCOVERY_WIRE_ENTRY_SIZE;
|
||||
struct sockaddr_storage ep;
|
||||
if (!Discovery_WireToAddr(entry, &ep)) continue;
|
||||
if (Discovery_FindPtr(disc, &ep) != NULL) continue; // already known -> doesn't count toward fanout
|
||||
if (Discovery_Upsert(disc, &ep, newHop) != NULL) added++;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&disc->lock);
|
||||
}
|
||||
|
||||
// ---- periodic tick -----------------------------------------------------------------------
|
||||
|
||||
void NodeDiscovery_Iterate(node_discovery_t* disc) {
|
||||
if (!disc || !disc->node || !disc->udpNode) return;
|
||||
uint64_t now = get_current_time_ms();
|
||||
|
||||
// Snapshot current outbound connections and their listen endpoints (used for querying and
|
||||
// for the "already connected?" checks below).
|
||||
tcp_connection_t* outConns[MAX_CONS];
|
||||
size_t outCount = 0;
|
||||
Node_GetClientList(disc->node, outConns, &outCount);
|
||||
|
||||
struct sockaddr_storage outEndpoints[MAX_CONS];
|
||||
size_t outEpCount = 0;
|
||||
for (size_t i = 0; i < outCount; ++i) {
|
||||
struct sockaddr_storage ep;
|
||||
if (Node_ConnListenEndpoint(outConns[i], &ep)) {
|
||||
outEndpoints[outEpCount++] = ep;
|
||||
}
|
||||
}
|
||||
|
||||
// Deferred network actions, collected under the lock and executed after releasing it
|
||||
// (Node_ConnectPeer / Node_SendPacket must not run while holding disc->lock).
|
||||
tcp_connection_t* toQuery[MAX_CONS];
|
||||
size_t toQueryCount = 0;
|
||||
struct { char ip[INET6_ADDRSTRLEN]; unsigned short port; } toConnect[MAX_CONS];
|
||||
size_t toConnectCount = 0;
|
||||
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
|
||||
// 1. Seed: upsert connected (outbound) peers as CONNECTED at hop 0.
|
||||
for (size_t i = 0; i < outEpCount; ++i) {
|
||||
discovered_peer_t* p = Discovery_Upsert(disc, &outEndpoints[i], 0);
|
||||
if (p) {
|
||||
p->hop = 0;
|
||||
p->state = DISCOVERY_STATE_CONNECTED;
|
||||
}
|
||||
}
|
||||
// Demote entries still marked CONNECTED that are no longer in the outbound set.
|
||||
{
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (p->state != DISCOVERY_STATE_CONNECTED) continue;
|
||||
int stillConnected = 0;
|
||||
for (size_t j = 0; j < outEpCount; ++j) {
|
||||
if (Discovery_AddrEqual(&p->addr, &outEndpoints[j])) { stillConnected = 1; break; }
|
||||
}
|
||||
if (!stillConnected) {
|
||||
p->state = (p->pingMs != UINT64_MAX) ? DISCOVERY_STATE_REACHABLE : DISCOVERY_STATE_NEW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Ping NEW peers (and refresh stale REACHABLE ones), capped per tick.
|
||||
{
|
||||
int pings = 0;
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
for (size_t i = 0; i < n && pings < DISCOVERY_MAX_PINGS_PER_TICK; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
int shouldPing = 0;
|
||||
if (!p->pingPending) {
|
||||
if (p->state == DISCOVERY_STATE_NEW) {
|
||||
shouldPing = 1;
|
||||
} else if (p->state == DISCOVERY_STATE_REACHABLE &&
|
||||
(now - p->lastPingMs) > DISCOVERY_PING_REFRESH_MS) {
|
||||
shouldPing = 1;
|
||||
}
|
||||
}
|
||||
if (!shouldPing) continue;
|
||||
|
||||
p->pingPending = 1;
|
||||
p->lastPingMs = now;
|
||||
if (p->state == DISCOVERY_STATE_NEW) p->state = DISCOVERY_STATE_PINGED;
|
||||
UdpNode_SendPing(disc->udpNode, &p->addr);
|
||||
pings++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Timeout backstop (in case the UDP layer's own timeout callback is missed).
|
||||
{
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (p->state == DISCOVERY_STATE_PINGED && p->pingPending &&
|
||||
(now - p->lastPingMs) > DISCOVERY_PING_TIMEOUT_MS) {
|
||||
p->pingPending = 0;
|
||||
p->state = DISCOVERY_STATE_UNREACHABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Query up to DISCOVERY_FANOUT connected peers with GET_PEERS, preferring the lowest ping
|
||||
// and skipping ones we queried recently or that are already at the hop horizon.
|
||||
{
|
||||
int queries = 0;
|
||||
while (queries < DISCOVERY_FANOUT) {
|
||||
size_t bestIdx = outCount; // sentinel = none
|
||||
uint64_t bestPing = UINT64_MAX;
|
||||
for (size_t i = 0; i < outCount; ++i) {
|
||||
struct sockaddr_storage ep;
|
||||
if (!Node_ConnListenEndpoint(outConns[i], &ep)) continue;
|
||||
discovered_peer_t* p = Discovery_FindPtr(disc, &ep);
|
||||
if (!p) continue;
|
||||
if (p->hop >= DISCOVERY_MAX_HOPS) continue;
|
||||
if (p->lastQueryMs != 0 && (now - p->lastQueryMs) < DISCOVERY_QUERY_INTERVAL_MS) continue;
|
||||
if (bestIdx == outCount || p->pingMs < bestPing) {
|
||||
bestIdx = i;
|
||||
bestPing = p->pingMs;
|
||||
}
|
||||
}
|
||||
if (bestIdx == outCount) break; // nothing eligible
|
||||
|
||||
// Mark queried so it isn't picked again this tick, and queue the send.
|
||||
struct sockaddr_storage ep;
|
||||
if (Node_ConnListenEndpoint(outConns[bestIdx], &ep)) {
|
||||
discovered_peer_t* p = Discovery_FindPtr(disc, &ep);
|
||||
if (p) p->lastQueryMs = now;
|
||||
}
|
||||
toQuery[toQueryCount++] = outConns[bestIdx];
|
||||
queries++;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Connect: pick REACHABLE, not-currently-connected, cooled-down peers with the lowest ping
|
||||
// until we reach the target connection count.
|
||||
if (outCount < (size_t)DISCOVERY_TARGET_CONNECTIONS) {
|
||||
size_t slots = (size_t)DISCOVERY_TARGET_CONNECTIONS - outCount;
|
||||
for (size_t s = 0; s < slots && toConnectCount < MAX_CONS; ++s) {
|
||||
discovered_peer_t* best = NULL;
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
if (p->state != DISCOVERY_STATE_REACHABLE) continue;
|
||||
if (p->lastConnectMs != 0 && (now - p->lastConnectMs) < DISCOVERY_CONNECT_RETRY_MS) continue;
|
||||
int already = 0;
|
||||
for (size_t j = 0; j < outEpCount; ++j) {
|
||||
if (Discovery_AddrEqual(&p->addr, &outEndpoints[j])) { already = 1; break; }
|
||||
}
|
||||
if (already) continue;
|
||||
if (!best || p->pingMs < best->pingMs) best = p;
|
||||
}
|
||||
if (!best) break;
|
||||
|
||||
best->lastConnectMs = now; // reserve so it isn't picked again this tick
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
unsigned short port = 0;
|
||||
if (Discovery_AddrToIpPort(&best->addr, ip, sizeof(ip), &port) && port != 0) {
|
||||
strncpy(toConnect[toConnectCount].ip, ip, INET6_ADDRSTRLEN - 1);
|
||||
toConnect[toConnectCount].ip[INET6_ADDRSTRLEN - 1] = '\0';
|
||||
toConnect[toConnectCount].port = port;
|
||||
toConnectCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&disc->lock);
|
||||
|
||||
// Execute the deferred network actions outside the lock.
|
||||
for (size_t i = 0; i < toQueryCount; ++i) {
|
||||
Node_SendPacket(disc->node, toQuery[i], PACKET_TYPE_GET_PEERS, NULL, 0);
|
||||
}
|
||||
for (size_t i = 0; i < toConnectCount; ++i) {
|
||||
printf("NodeDiscovery: connecting to discovered peer %s:%u\n", toConnect[i].ip, toConnect[i].port);
|
||||
(void)Node_ConnectPeer(disc->node, toConnect[i].ip, toConnect[i].port);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- diagnostics -------------------------------------------------------------------------
|
||||
|
||||
void NodeDiscovery_PrintPeers(node_discovery_t* disc) {
|
||||
if (!disc) {
|
||||
printf("NodeDiscovery: not active\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const char* stateNames[] = { "NEW", "PINGED", "REACHABLE", "CONNECTED", "UNREACHABLE" };
|
||||
|
||||
pthread_mutex_lock(&disc->lock);
|
||||
size_t n = DynArr_size(disc->peers);
|
||||
printf("Known peers (%zu):\n", n);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
discovered_peer_t* p = (discovered_peer_t*)DynArr_at(disc->peers, i);
|
||||
char ip[INET6_ADDRSTRLEN] = {0};
|
||||
unsigned short port = 0;
|
||||
Discovery_AddrToIpPort(&p->addr, ip, sizeof(ip), &port);
|
||||
const char* stateStr = (p->state <= DISCOVERY_STATE_UNREACHABLE) ? stateNames[p->state] : "?";
|
||||
if (p->pingMs == UINT64_MAX) {
|
||||
printf(" %-46s hop=%u state=%-11s ping=--\n", ip, p->hop, stateStr);
|
||||
} else {
|
||||
printf(" %-46s hop=%u state=%-11s ping=%" PRIu64 "ms\n", ip, p->hop, stateStr, p->pingMs);
|
||||
}
|
||||
(void)port; // port is part of ip endpoint identity; shown via connect logs
|
||||
}
|
||||
pthread_mutex_unlock(&disc->lock);
|
||||
}
|
||||
+121
-7
@@ -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++;
|
||||
@@ -78,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+78
-13
@@ -3,12 +3,15 @@
|
||||
#include <tcpd/tcpclient.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <numgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
@@ -80,25 +83,87 @@ int TcpClient_Connect(
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
// Detect address family from the IP string
|
||||
struct sockaddr_in6 addr6;
|
||||
struct sockaddr_in addr4;
|
||||
struct sockaddr* pSockAddr;
|
||||
socklen_t sockAddrLen;
|
||||
int af;
|
||||
|
||||
memset(&addr6, 0, sizeof(addr6));
|
||||
memset(&addr4, 0, sizeof(addr4));
|
||||
|
||||
if (inet_pton(AF_INET6, peerIp, &addr6.sin6_addr) == 1) {
|
||||
af = AF_INET6;
|
||||
addr6.sin6_family = AF_INET6;
|
||||
addr6.sin6_port = htons(peerPort);
|
||||
pSockAddr = (struct sockaddr*)&addr6;
|
||||
sockAddrLen = sizeof(addr6);
|
||||
} else if (inet_pton(AF_INET, peerIp, &addr4.sin_addr) == 1) {
|
||||
af = AF_INET;
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_port = htons(peerPort);
|
||||
pSockAddr = (struct sockaddr*)&addr4;
|
||||
sockAddrLen = sizeof(addr4);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sockFd = socket(af, SOCK_STREAM, 0);
|
||||
if (sockFd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in peerAddr;
|
||||
memset(&peerAddr, 0, sizeof(peerAddr));
|
||||
peerAddr.sin_family = AF_INET;
|
||||
peerAddr.sin_port = htons(peerPort);
|
||||
// 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);
|
||||
|
||||
if (inet_pton(AF_INET, peerIp, &peerAddr.sin_addr) <= 0) {
|
||||
close(sockFd);
|
||||
return -1;
|
||||
int rc = connect(sockFd, pSockAddr, sockAddrLen);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (connect(sockFd, (struct sockaddr*)&peerAddr, sizeof(peerAddr)) < 0) {
|
||||
close(sockFd);
|
||||
return -1;
|
||||
}
|
||||
// Restore blocking mode
|
||||
fcntl(sockFd, F_SETFL, flags & ~O_NONBLOCK);
|
||||
|
||||
// Pack the address into sockaddr_storage for TcpConnection_Init
|
||||
struct sockaddr_storage peerStorage;
|
||||
memset(&peerStorage, 0, sizeof(peerStorage));
|
||||
memcpy(&peerStorage, pSockAddr, sockAddrLen);
|
||||
|
||||
tcp_connection_t* conn = (tcp_connection_t*)malloc(sizeof(*conn));
|
||||
if (!conn) {
|
||||
@@ -106,7 +171,7 @@ int TcpClient_Connect(
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (TcpConnection_Init(conn, sockFd, &peerAddr, TCP_CONNECTION_ROLE_OUTBOUND) != 0) {
|
||||
if (TcpConnection_Init(conn, sockFd, &peerStorage, TCP_CONNECTION_ROLE_OUTBOUND) != 0) {
|
||||
free(conn);
|
||||
close(sockFd);
|
||||
return -1;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_in* peerAddr, tcp_connection_role_t role) {
|
||||
int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr_storage* peerAddr, tcp_connection_role_t role) {
|
||||
if (!conn || sockFd < 0 || !peerAddr) {
|
||||
return -1;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr
|
||||
memset(conn, 0, sizeof(*conn));
|
||||
conn->sockFd = sockFd;
|
||||
conn->peerAddr = *peerAddr;
|
||||
conn->addrFamily = peerAddr->ss_family;
|
||||
conn->role = role;
|
||||
|
||||
if (pthread_mutex_init(&conn->sendLock, NULL) != 0) {
|
||||
@@ -261,4 +262,58 @@ bool TcpConnection_IsDisconnectNotified(tcp_connection_t* conn) {
|
||||
return notified;
|
||||
}
|
||||
|
||||
static int extract_v4(const tcp_connection_t* conn, struct in_addr* v4out) {
|
||||
if (conn->addrFamily == AF_INET6) {
|
||||
const struct sockaddr_in6* a6 = (const struct sockaddr_in6*)&conn->peerAddr;
|
||||
if (IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
|
||||
memcpy(v4out, &a6->sin6_addr.s6_addr[12], sizeof(*v4out));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
*v4out = ((const struct sockaddr_in*)&conn->peerAddr)->sin_addr;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* TcpConnection_GetPeerAddrStr(const tcp_connection_t* conn, char* buf, size_t bufLen) {
|
||||
if (!conn || !buf || bufLen == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (conn->addrFamily == AF_INET6) {
|
||||
const struct sockaddr_in6* a6 = (const struct sockaddr_in6*)&conn->peerAddr;
|
||||
if (IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
|
||||
struct in_addr v4;
|
||||
memcpy(&v4, &a6->sin6_addr.s6_addr[12], sizeof(v4));
|
||||
return inet_ntop(AF_INET, &v4, buf, (socklen_t)bufLen);
|
||||
}
|
||||
return inet_ntop(AF_INET6, &a6->sin6_addr, buf, (socklen_t)bufLen);
|
||||
}
|
||||
|
||||
const struct sockaddr_in* a4 = (const struct sockaddr_in*)&conn->peerAddr;
|
||||
return inet_ntop(AF_INET, &a4->sin_addr, buf, (socklen_t)bufLen);
|
||||
}
|
||||
|
||||
int TcpConnection_PeerAddrEqual(const tcp_connection_t* a, const tcp_connection_t* b) {
|
||||
if (!a || !b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct in_addr va, vb;
|
||||
int a_is_v4 = extract_v4(a, &va);
|
||||
int b_is_v4 = extract_v4(b, &vb);
|
||||
|
||||
if (a_is_v4 && b_is_v4) {
|
||||
return va.s_addr == vb.s_addr;
|
||||
}
|
||||
|
||||
if (!a_is_v4 && !b_is_v4) {
|
||||
const struct in6_addr* aa6 = &((const struct sockaddr_in6*)&a->peerAddr)->sin6_addr;
|
||||
const struct in6_addr* ab6 = &((const struct sockaddr_in6*)&b->peerAddr)->sin6_addr;
|
||||
return memcmp(aa6, ab6, sizeof(*aa6)) == 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+105
-19
@@ -3,6 +3,7 @@
|
||||
#include <tcpd/tcpserver.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <numgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -10,6 +11,11 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
tcp_server_t* serverPtr;
|
||||
int listenFd;
|
||||
} tcpaccept_thread_args_t;
|
||||
|
||||
static void TcpServer_RemoveClientByPtrUnlocked(tcp_server_t* svr, tcp_connection_t* cli) {
|
||||
if (!svr || !svr->clientsArrPtr || !cli) {
|
||||
return;
|
||||
@@ -70,15 +76,20 @@ static void* TcpServer_clientthreadprocess(void* ptr) {
|
||||
}
|
||||
|
||||
static void* TcpServer_threadprocess(void* ptr) {
|
||||
tcp_server_t* svr = (tcp_server_t*)ptr;
|
||||
if (!svr) {
|
||||
tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)ptr;
|
||||
if (!args || !args->serverPtr) {
|
||||
free(args);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tcp_server_t* svr = args->serverPtr;
|
||||
int listenFd = args->listenFd;
|
||||
free(args);
|
||||
|
||||
while (svr->isRunning) {
|
||||
struct sockaddr_in clientAddr;
|
||||
struct sockaddr_storage clientAddr;
|
||||
socklen_t clientSize = sizeof(clientAddr);
|
||||
int clientFd = accept(svr->sockFd, (struct sockaddr*)&clientAddr, &clientSize);
|
||||
int clientFd = accept(listenFd, (struct sockaddr*)&clientAddr, &clientSize);
|
||||
|
||||
if (clientFd < 0) {
|
||||
if (!svr->isRunning) {
|
||||
@@ -168,7 +179,9 @@ tcp_server_t* TcpServer_Create() {
|
||||
|
||||
memset(svr, 0, sizeof(*svr));
|
||||
svr->sockFd = -1;
|
||||
svr->sockFdV4 = -1;
|
||||
svr->svrThread = 0;
|
||||
svr->svrThreadV4 = 0;
|
||||
svr->isRunning = 0;
|
||||
svr->maxClients = 0;
|
||||
svr->clientsArrPtr = NULL;
|
||||
@@ -200,31 +213,65 @@ void TcpServer_Init(tcp_server_t* ptr, unsigned short port, const char* addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ptr->sockFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (ptr->sockFd < 0) {
|
||||
return;
|
||||
ptr->opt = 1;
|
||||
|
||||
// IPv6 (pure, not dual-stack — a dedicated IPv4 socket handles IPv4 clients)
|
||||
int fd6 = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (fd6 >= 0) {
|
||||
setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(ptr->opt));
|
||||
int v6only = 1;
|
||||
setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
|
||||
|
||||
struct sockaddr_in6 a6;
|
||||
memset(&a6, 0, sizeof(a6));
|
||||
a6.sin6_family = AF_INET6;
|
||||
a6.sin6_port = htons(port);
|
||||
a6.sin6_addr = in6addr_any;
|
||||
|
||||
if (bind(fd6, (struct sockaddr*)&a6, sizeof(a6)) == 0) {
|
||||
ptr->sockFd = fd6;
|
||||
} else {
|
||||
close(fd6);
|
||||
}
|
||||
}
|
||||
|
||||
ptr->opt = 1;
|
||||
setsockopt(ptr->sockFd, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(int));
|
||||
// IPv4 (always attempted regardless of IPv6 result)
|
||||
int fd4 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd4 >= 0) {
|
||||
setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(ptr->opt));
|
||||
|
||||
memset(&ptr->addr, 0, sizeof(ptr->addr));
|
||||
ptr->addr.sin_family = AF_INET;
|
||||
ptr->addr.sin_port = htons(port);
|
||||
inet_pton(AF_INET, addr, &ptr->addr.sin_addr);
|
||||
struct sockaddr_in a4;
|
||||
memset(&a4, 0, sizeof(a4));
|
||||
a4.sin_family = AF_INET;
|
||||
a4.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr, &a4.sin_addr) <= 0) {
|
||||
a4.sin_addr.s_addr = INADDR_ANY;
|
||||
}
|
||||
|
||||
if (bind(ptr->sockFd, (struct sockaddr*)&ptr->addr, sizeof(ptr->addr)) < 0) {
|
||||
close(ptr->sockFd);
|
||||
ptr->sockFd = -1;
|
||||
if (bind(fd4, (struct sockaddr*)&a4, sizeof(a4)) == 0) {
|
||||
ptr->sockFdV4 = fd4;
|
||||
} else {
|
||||
close(fd4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
|
||||
if (!ptr || ptr->sockFd < 0 || maxcons <= 0 || ptr->isRunning) {
|
||||
if (!ptr || (ptr->sockFd < 0 && ptr->sockFdV4 < 0) || maxcons <= 0 || ptr->isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listen(ptr->sockFd, maxcons) < 0) {
|
||||
if (ptr->sockFd >= 0 && listen(ptr->sockFd, maxcons) < 0) {
|
||||
close(ptr->sockFd);
|
||||
ptr->sockFd = -1;
|
||||
}
|
||||
|
||||
if (ptr->sockFdV4 >= 0 && listen(ptr->sockFdV4, maxcons) < 0) {
|
||||
close(ptr->sockFdV4);
|
||||
ptr->sockFdV4 = -1;
|
||||
}
|
||||
|
||||
if (ptr->sockFd < 0 && ptr->sockFdV4 < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -245,7 +292,35 @@ void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
|
||||
ptr->isRunning = 1;
|
||||
pthread_mutex_unlock(&ptr->clientsMutex);
|
||||
|
||||
if (pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, ptr) != 0) {
|
||||
int anyThreadStarted = 0;
|
||||
|
||||
if (ptr->sockFd >= 0) {
|
||||
tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)malloc(sizeof(*args));
|
||||
if (args) {
|
||||
args->serverPtr = ptr;
|
||||
args->listenFd = ptr->sockFd;
|
||||
if (pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, args) == 0) {
|
||||
anyThreadStarted = 1;
|
||||
} else {
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr->sockFdV4 >= 0) {
|
||||
tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)malloc(sizeof(*args));
|
||||
if (args) {
|
||||
args->serverPtr = ptr;
|
||||
args->listenFd = ptr->sockFdV4;
|
||||
if (pthread_create(&ptr->svrThreadV4, NULL, TcpServer_threadprocess, args) == 0) {
|
||||
anyThreadStarted = 1;
|
||||
} else {
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyThreadStarted) {
|
||||
pthread_mutex_lock(&ptr->clientsMutex);
|
||||
ptr->isRunning = 0;
|
||||
free(ptr->clientsArrPtr);
|
||||
@@ -268,11 +343,22 @@ void TcpServer_Stop(tcp_server_t* ptr) {
|
||||
ptr->sockFd = -1;
|
||||
}
|
||||
|
||||
if (ptr->sockFdV4 >= 0) {
|
||||
shutdown(ptr->sockFdV4, SHUT_RDWR);
|
||||
close(ptr->sockFdV4);
|
||||
ptr->sockFdV4 = -1;
|
||||
}
|
||||
|
||||
if (ptr->svrThread != 0 && !pthread_equal(ptr->svrThread, pthread_self())) {
|
||||
pthread_join(ptr->svrThread, NULL);
|
||||
}
|
||||
ptr->svrThread = 0;
|
||||
|
||||
if (ptr->svrThreadV4 != 0 && !pthread_equal(ptr->svrThreadV4, pthread_self())) {
|
||||
pthread_join(ptr->svrThreadV4, NULL);
|
||||
}
|
||||
ptr->svrThreadV4 = 0;
|
||||
|
||||
pthread_mutex_lock(&ptr->clientsMutex);
|
||||
size_t maxClients = ptr->maxClients;
|
||||
tcp_connection_t** local = ptr->clientsArrPtr;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
#include <udpd/udpnode.h>
|
||||
#include <udpd/udppackettype.h>
|
||||
#include <utils.h>
|
||||
#include <numgen.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <errno.h>
|
||||
|
||||
typedef struct {
|
||||
udp_node_t* node;
|
||||
int sockFd;
|
||||
} udprecv_thread_args_t;
|
||||
|
||||
// Send a raw PING packet (nonce already chosen) to dest.
|
||||
static void UdpNode_SendRawPing(udp_node_t* node, uint64_t nonce, const struct sockaddr_storage* dest) {
|
||||
unsigned char buf[UDP_PING_WIRE_SIZE];
|
||||
buf[0] = (unsigned char)UDP_PACKET_TYPE_PING;
|
||||
memcpy(buf + 1, &nonce, sizeof(nonce));
|
||||
|
||||
int sock = -1;
|
||||
socklen_t addrLen = 0;
|
||||
|
||||
if (dest->ss_family == AF_INET6 && node->sockFd >= 0) {
|
||||
sock = node->sockFd;
|
||||
addrLen = sizeof(struct sockaddr_in6);
|
||||
} else if (dest->ss_family == AF_INET && node->sockFdV4 >= 0) {
|
||||
sock = node->sockFdV4;
|
||||
addrLen = sizeof(struct sockaddr_in);
|
||||
}
|
||||
|
||||
if (sock < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendto(sock, buf, sizeof(buf), 0, (const struct sockaddr*)dest, addrLen);
|
||||
}
|
||||
|
||||
static void UdpNode_HandlePacket(udp_node_t* node, int fromSock,
|
||||
const unsigned char* buf, ssize_t n,
|
||||
const struct sockaddr_storage* from) {
|
||||
if (n < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
udp_packet_type_t type = (udp_packet_type_t)buf[0];
|
||||
|
||||
switch (type) {
|
||||
case UDP_PACKET_TYPE_PING: {
|
||||
if (n < UDP_PING_WIRE_SIZE) {
|
||||
return;
|
||||
}
|
||||
uint64_t nonce;
|
||||
memcpy(&nonce, buf + 1, sizeof(nonce));
|
||||
|
||||
// Build and send PONG
|
||||
unsigned char reply[UDP_PONG_WIRE_SIZE];
|
||||
reply[0] = (unsigned char)UDP_PACKET_TYPE_PONG;
|
||||
memcpy(reply + 1, &nonce, sizeof(nonce));
|
||||
int32_t protoVer = (int32_t)PROTO_VERSION;
|
||||
memcpy(reply + 1 + sizeof(nonce), &protoVer, sizeof(protoVer));
|
||||
|
||||
socklen_t addrLen = (from->ss_family == AF_INET6)
|
||||
? sizeof(struct sockaddr_in6)
|
||||
: sizeof(struct sockaddr_in);
|
||||
sendto(fromSock, reply, sizeof(reply), 0, (const struct sockaddr*)from, addrLen);
|
||||
break;
|
||||
}
|
||||
|
||||
case UDP_PACKET_TYPE_PONG: {
|
||||
if (n < UDP_PONG_WIRE_SIZE) {
|
||||
return;
|
||||
}
|
||||
uint64_t nonce;
|
||||
int32_t protoVer;
|
||||
memcpy(&nonce, buf + 1, sizeof(nonce));
|
||||
memcpy(&protoVer, buf + 1 + sizeof(nonce), sizeof(protoVer));
|
||||
|
||||
bool found = false;
|
||||
uint64_t rttMs = 0;
|
||||
pthread_mutex_lock(&node->pingsMutex);
|
||||
for (int i = 0; i < UDP_MAX_PENDING_PINGS; i++) {
|
||||
if (node->pendingPings[i].active && node->pendingPings[i].nonce == nonce) {
|
||||
uint64_t nowMs = get_current_time_ms();
|
||||
rttMs = (nowMs >= node->pendingPings[i].lastSentMs)
|
||||
? (nowMs - node->pendingPings[i].lastSentMs)
|
||||
: 0;
|
||||
node->pendingPings[i].active = false;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->pingsMutex);
|
||||
|
||||
if (found && node->on_pong) {
|
||||
node->on_pong(node, from, nonce, (int)protoVer, rttMs, node->callbackUser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void* UdpNode_RecvThreadProc(void* arg) {
|
||||
udprecv_thread_args_t* args = (udprecv_thread_args_t*)arg;
|
||||
udp_node_t* node = args->node;
|
||||
int sock = args->sockFd;
|
||||
free(args);
|
||||
|
||||
unsigned char buf[1500];
|
||||
|
||||
while (node->isRunning) {
|
||||
struct sockaddr_storage from;
|
||||
socklen_t fromLen = sizeof(from);
|
||||
ssize_t n = recvfrom(sock, buf, sizeof(buf), 0,
|
||||
(struct sockaddr*)&from, &fromLen);
|
||||
if (n < 1) {
|
||||
if (!node->isRunning) {
|
||||
break;
|
||||
}
|
||||
// Transient error — keep going
|
||||
continue;
|
||||
}
|
||||
UdpNode_HandlePacket(node, sock, buf, n, &from);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void* UdpNode_RetryThreadProc(void* arg) {
|
||||
udp_node_t* node = (udp_node_t*)arg;
|
||||
|
||||
struct {
|
||||
uint64_t nonce;
|
||||
struct sockaddr_storage dest;
|
||||
} timedOut[UDP_MAX_PENDING_PINGS];
|
||||
|
||||
while (node->isRunning) {
|
||||
sleep_for_milliseconds(100);
|
||||
|
||||
int timedOutCount = 0;
|
||||
|
||||
pthread_mutex_lock(&node->pingsMutex);
|
||||
uint64_t now = get_current_time_ms();
|
||||
for (int i = 0; i < UDP_MAX_PENDING_PINGS; i++) {
|
||||
pending_ping_t* p = &node->pendingPings[i];
|
||||
if (!p->active) {
|
||||
continue;
|
||||
}
|
||||
if (now - p->lastSentMs < UDP_PING_RETRY_INTERVAL_MS) {
|
||||
continue;
|
||||
}
|
||||
if (p->retries >= UDP_PING_MAX_RETRIES) {
|
||||
timedOut[timedOutCount].nonce = p->nonce;
|
||||
timedOut[timedOutCount].dest = p->dest;
|
||||
timedOutCount++;
|
||||
p->active = false;
|
||||
} else {
|
||||
UdpNode_SendRawPing(node, p->nonce, &p->dest);
|
||||
p->retries++;
|
||||
p->lastSentMs = now;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&node->pingsMutex);
|
||||
|
||||
for (int i = 0; i < timedOutCount; i++) {
|
||||
if (node->on_ping_timeout) {
|
||||
node->on_ping_timeout(node, &timedOut[i].dest,
|
||||
timedOut[i].nonce, node->callbackUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int UdpNode_Init(udp_node_t* node, uint16_t port) {
|
||||
if (!node) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(node, 0, sizeof(*node));
|
||||
node->sockFd = -1;
|
||||
node->sockFdV4 = -1;
|
||||
|
||||
int opt = 1;
|
||||
|
||||
// IPv6 (pure, not dual-stack)
|
||||
int fd6 = socket(AF_INET6, SOCK_DGRAM, 0);
|
||||
if (fd6 >= 0) {
|
||||
setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
int v6only = 1;
|
||||
setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
|
||||
|
||||
struct sockaddr_in6 a6;
|
||||
memset(&a6, 0, sizeof(a6));
|
||||
a6.sin6_family = AF_INET6;
|
||||
a6.sin6_port = htons(port);
|
||||
a6.sin6_addr = in6addr_any;
|
||||
|
||||
if (bind(fd6, (struct sockaddr*)&a6, sizeof(a6)) == 0) {
|
||||
node->sockFd = fd6;
|
||||
} else {
|
||||
close(fd6);
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4
|
||||
int fd4 = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd4 >= 0) {
|
||||
setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
struct sockaddr_in a4;
|
||||
memset(&a4, 0, sizeof(a4));
|
||||
a4.sin_family = AF_INET;
|
||||
a4.sin_port = htons(port);
|
||||
a4.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if (bind(fd4, (struct sockaddr*)&a4, sizeof(a4)) == 0) {
|
||||
node->sockFdV4 = fd4;
|
||||
} else {
|
||||
close(fd4);
|
||||
}
|
||||
}
|
||||
|
||||
if (node->sockFd < 0 && node->sockFdV4 < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&node->pingsMutex, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UdpNode_SetCallbacks(udp_node_t* node,
|
||||
void (*on_pong)(udp_node_t*, const struct sockaddr_storage*, uint64_t, int, uint64_t, void*),
|
||||
void (*on_ping_timeout)(udp_node_t*, const struct sockaddr_storage*, uint64_t, void*),
|
||||
void* user) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
node->on_pong = on_pong;
|
||||
node->on_ping_timeout = on_ping_timeout;
|
||||
node->callbackUser = user;
|
||||
}
|
||||
|
||||
int UdpNode_Start(udp_node_t* node) {
|
||||
if (!node || node->isRunning) {
|
||||
return -1;
|
||||
}
|
||||
if (node->sockFd < 0 && node->sockFdV4 < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
node->isRunning = 1;
|
||||
int anyStarted = 0;
|
||||
|
||||
if (node->sockFd >= 0) {
|
||||
udprecv_thread_args_t* args = (udprecv_thread_args_t*)malloc(sizeof(*args));
|
||||
if (args) {
|
||||
args->node = node;
|
||||
args->sockFd = node->sockFd;
|
||||
if (pthread_create(&node->recvThreadV6, NULL, UdpNode_RecvThreadProc, args) == 0) {
|
||||
anyStarted = 1;
|
||||
} else {
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node->sockFdV4 >= 0) {
|
||||
udprecv_thread_args_t* args = (udprecv_thread_args_t*)malloc(sizeof(*args));
|
||||
if (args) {
|
||||
args->node = node;
|
||||
args->sockFd = node->sockFdV4;
|
||||
if (pthread_create(&node->recvThreadV4, NULL, UdpNode_RecvThreadProc, args) == 0) {
|
||||
anyStarted = 1;
|
||||
} else {
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pthread_create(&node->retryThread, NULL, UdpNode_RetryThreadProc, node) == 0) {
|
||||
anyStarted = 1;
|
||||
}
|
||||
|
||||
if (!anyStarted) {
|
||||
node->isRunning = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UdpNode_Stop(udp_node_t* node) {
|
||||
if (!node || !node->isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
node->isRunning = 0;
|
||||
|
||||
// Close sockets to unblock recvfrom in receive threads
|
||||
if (node->sockFd >= 0) {
|
||||
int fd = node->sockFd;
|
||||
node->sockFd = -1;
|
||||
close(fd);
|
||||
}
|
||||
if (node->sockFdV4 >= 0) {
|
||||
int fd = node->sockFdV4;
|
||||
node->sockFdV4 = -1;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
pthread_join(node->recvThreadV6, NULL);
|
||||
pthread_join(node->recvThreadV4, NULL);
|
||||
pthread_join(node->retryThread, NULL);
|
||||
}
|
||||
|
||||
void UdpNode_Destroy(udp_node_t* node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->sockFd >= 0) {
|
||||
close(node->sockFd);
|
||||
node->sockFd = -1;
|
||||
}
|
||||
if (node->sockFdV4 >= 0) {
|
||||
close(node->sockFdV4);
|
||||
node->sockFdV4 = -1;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&node->pingsMutex);
|
||||
}
|
||||
|
||||
int UdpNode_SendPing(udp_node_t* node, const struct sockaddr_storage* dest) {
|
||||
if (!node || !dest) {
|
||||
return -1;
|
||||
}
|
||||
if (node->sockFd < 0 && node->sockFdV4 < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t nonce = random_eight_byte();
|
||||
uint64_t now = get_current_time_ms();
|
||||
|
||||
pthread_mutex_lock(&node->pingsMutex);
|
||||
|
||||
int slot = -1;
|
||||
for (int i = 0; i < UDP_MAX_PENDING_PINGS; i++) {
|
||||
if (!node->pendingPings[i].active) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0) {
|
||||
pthread_mutex_unlock(&node->pingsMutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
node->pendingPings[slot].nonce = nonce;
|
||||
node->pendingPings[slot].dest = *dest;
|
||||
node->pendingPings[slot].lastSentMs = now;
|
||||
node->pendingPings[slot].retries = 0;
|
||||
node->pendingPings[slot].active = true;
|
||||
|
||||
pthread_mutex_unlock(&node->pingsMutex);
|
||||
|
||||
UdpNode_SendRawPing(node, nonce, dest);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user