Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9e3f8cbbb
|
||
|
|
4417095ab5
|
||
|
|
21fe73fb01
|
||
|
|
1345ff7fa6 | ||
|
|
da50b4e8c1 | ||
|
|
00bd711501 | ||
|
|
17ef3b74fd | ||
|
|
c1914dc3e7 | ||
|
|
39293029c5 | ||
|
|
763aeb648f | ||
|
|
41a154a9fd | ||
|
|
91d7bfa4e7 |
@@ -14,9 +14,17 @@ Check if Block FullVerify is actually verifying fully (not missing any condition
|
|||||||
|
|
||||||
A loophole in the reorg penalty system could potentially exist where someone broadcasts blocks one-at-a-time. Determine a solution to this.
|
A loophole in the reorg penalty system could potentially exist where someone broadcasts blocks one-at-a-time. Determine a solution to this.
|
||||||
|
|
||||||
|
IPv6 support for the P2P node. Come on guys, it's 2026. RFC 2460 was in 1998. It's about time.
|
||||||
|
Like if someone is behind NAT, fine, workable. CGNAT? Lmao good luck.
|
||||||
|
|
||||||
TO TEST:
|
TO TEST:
|
||||||
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner.
|
Implement Horizen's "Reorg Penalty" system to make it harder for the young chain to be attacked by a powerful miner.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
Because tx sizes are currently fixed, mining can use raw fee ordering for now. If tx sizes ever become dynamic, revisit selection to consider fee/byte instead.
|
||||||
|
|
||||||
|
Mempool snapshotting for mining should hold the lock only long enough to copy pending txs, but if the mempool grows very large that copy may still be non-trivial.
|
||||||
|
|
||||||
DONE:
|
DONE:
|
||||||
I want to move away from the Monero emission. I want to do something a bit radical for cryptocurrency, but I feel like it's necessary to make it more like money:
|
I want to move away from the Monero emission. I want to do something a bit radical for cryptocurrency, but I feel like it's necessary to make it more like money:
|
||||||
a constant inflation rate of 1.5% per year. It's lower than fiat (USD is ~2.8% per year), and it additionally doesn't fluctuate during crisis. It's constant.
|
a constant inflation rate of 1.5% per year. It's lower than fiat (USD is ~2.8% per year), and it additionally doesn't fluctuate during crisis. It's constant.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <khash/khash.h>
|
#include <khash/khash.h>
|
||||||
#include <crypto/crypto.h>
|
#include <crypto/crypto.h>
|
||||||
|
#include <block/transaction.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <utils.h>
|
#include <utils.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
@@ -29,4 +30,12 @@ bool BalanceSheet_LoadFromFile(const char* inPath);
|
|||||||
void BalanceSheet_Print();
|
void BalanceSheet_Print();
|
||||||
void BalanceSheet_Destroy();
|
void BalanceSheet_Destroy();
|
||||||
|
|
||||||
|
bool BalanceSheet_SelectSpendableTransactions(
|
||||||
|
const signed_transaction_t* candidates,
|
||||||
|
size_t candidateCount,
|
||||||
|
signed_transaction_t** outAccepted,
|
||||||
|
size_t* outAcceptedCount,
|
||||||
|
uint64_t* outTotalFees
|
||||||
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ void Block_AddTransaction(block_t* block, signed_transaction_t* tx);
|
|||||||
void Block_RemoveTransaction(block_t* block, uint8_t* txHash);
|
void Block_RemoveTransaction(block_t* block, uint8_t* txHash);
|
||||||
bool Block_HasValidProofOfWork(const block_t* block);
|
bool Block_HasValidProofOfWork(const block_t* block);
|
||||||
bool Block_AllTransactionsValid(const block_t* block);
|
bool Block_AllTransactionsValid(const block_t* block);
|
||||||
|
bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees);
|
||||||
bool Block_IsFullyValid(const block_t* block);
|
bool Block_IsFullyValid(const block_t* block);
|
||||||
void Block_ShutdownPowContext(void);
|
void Block_ShutdownPowContext(void);
|
||||||
void Block_Destroy(block_t* block);
|
void Block_Destroy(block_t* block);
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ void Chain_Wipe(blockchain_t* chain);
|
|||||||
// Returns true on success.
|
// Returns true on success.
|
||||||
bool Chain_RollbackToHeight(blockchain_t* chain, size_t height);
|
bool Chain_RollbackToHeight(blockchain_t* chain, size_t height);
|
||||||
|
|
||||||
|
// Recompute `currentSupply` and `currentReward` from the in-memory chain blocks.
|
||||||
|
// Returns true on success and updates runtime state globals.
|
||||||
|
bool Chain_RecomputeRuntimeState(blockchain_t* chain);
|
||||||
|
|
||||||
// Retrieve a deep copy of the block at `index`. Caller must free with `Block_Destroy`.
|
// Retrieve a deep copy of the block at `index`. Caller must free with `Block_Destroy`.
|
||||||
bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy);
|
bool Chain_GetBlockCopy(blockchain_t* chain, size_t index, block_t** outCopy);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,18 @@
|
|||||||
#define MAX_CONS 32 // Some baseline for now
|
#define MAX_CONS 32 // Some baseline for now
|
||||||
#define LISTEN_PORT 9393
|
#define LISTEN_PORT 9393
|
||||||
#define ECHO_PEERS 1 // If non-zero, automatically attempt to connect back to any inbound peers (helps form bidirectional peering)
|
#define 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.
|
#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).
|
// This is also for client threads. The server has the default (~8 MB on POSIX).
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
|
|
||||||
#include <constants.h>
|
#include <constants.h>
|
||||||
#include <packettype.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 <stddef.h>
|
||||||
|
|
||||||
@@ -40,6 +44,9 @@ typedef struct {
|
|||||||
pthread_t maintenanceThread;
|
pthread_t maintenanceThread;
|
||||||
volatile int maintenanceRunning;
|
volatile int maintenanceRunning;
|
||||||
int maintenanceIntervalMs;
|
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;
|
||||||
|
|
||||||
net_node_t* Node_Create();
|
net_node_t* Node_Create();
|
||||||
@@ -57,6 +64,7 @@ int Node_ConnectPeer(net_node_t* node, const char* ip, unsigned short port);
|
|||||||
int Node_ConnectStartupPeers(net_node_t* node, const char** ips, const unsigned short* ports, size_t peersCount);
|
int Node_ConnectStartupPeers(net_node_t* node, const char** ips, const unsigned short* ports, size_t peersCount);
|
||||||
|
|
||||||
int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t packetType, const void* payload, size_t payloadLen);
|
int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t packetType, const void* payload, size_t payloadLen);
|
||||||
|
int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx, tcp_connection_t* excludeNode);
|
||||||
|
|
||||||
// Helpers for outbound peer selection and block broadcast
|
// Helpers for outbound peer selection and block broadcast
|
||||||
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight);
|
int Node_GetBestOutboundPeer(net_node_t* node, tcp_connection_t** outConn, uint64_t* outHeight);
|
||||||
@@ -70,4 +78,15 @@ void Node_Client_OnConnect(tcp_connection_t* client);
|
|||||||
void Node_Client_OnData(tcp_connection_t* client);
|
void Node_Client_OnData(tcp_connection_t* client);
|
||||||
void Node_Client_OnDisconnect(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
|
#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_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_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_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;
|
} packet_type_t;
|
||||||
|
|
||||||
static inline int PacketType_IsValid(uint8_t packetType) {
|
static inline int PacketType_IsValid(uint8_t packetType) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
#define TCP_IO_BUFFER_SIZE 1500
|
#define TCP_IO_BUFFER_SIZE 1500
|
||||||
#define TCP_FRAME_HEADER_SIZE 4U
|
#define TCP_FRAME_HEADER_SIZE 4U
|
||||||
@@ -20,10 +21,15 @@ typedef struct tcp_connection_t tcp_connection_t;
|
|||||||
|
|
||||||
struct tcp_connection_t {
|
struct tcp_connection_t {
|
||||||
int sockFd;
|
int sockFd;
|
||||||
struct sockaddr_in peerAddr;
|
sa_family_t addrFamily;
|
||||||
|
struct sockaddr_storage peerAddr;
|
||||||
uint32_t connectionId;
|
uint32_t connectionId;
|
||||||
tcp_connection_role_t role;
|
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_t ioThread;
|
||||||
pthread_mutex_t sendLock;
|
pthread_mutex_t sendLock;
|
||||||
pthread_mutex_t stateLock;
|
pthread_mutex_t stateLock;
|
||||||
@@ -46,7 +52,7 @@ struct tcp_connection_t {
|
|||||||
void* owner;
|
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);
|
void TcpConnection_Destroy(tcp_connection_t* conn);
|
||||||
|
|
||||||
int TcpConnection_SetDataBuffer(tcp_connection_t* conn, const unsigned char* data, size_t len);
|
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);
|
void TcpConnection_ResetFramingState(tcp_connection_t* conn);
|
||||||
int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen);
|
int TcpConnection_FeedFramedData(tcp_connection_t* conn, const unsigned char* input, size_t inputLen);
|
||||||
|
|
||||||
|
// 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_SendRaw(int sockFd, const void* data, size_t len);
|
||||||
int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen);
|
int TcpConnection_SendFramed(tcp_connection_t* conn, const void* payload, size_t payloadLen);
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
#include <tcpd/tcpconnection.h>
|
#include <tcpd/tcpconnection.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int sockFd;
|
int sockFd; // IPv6 listening socket (-1 if IPv6 unavailable)
|
||||||
struct sockaddr_in addr;
|
int sockFdV4; // IPv4 listening socket (-1 on bind failure)
|
||||||
int opt;
|
int opt;
|
||||||
int isRunning;
|
int isRunning;
|
||||||
void* owner;
|
void* owner;
|
||||||
@@ -27,7 +27,8 @@ typedef struct {
|
|||||||
tcp_connection_t** clientsArrPtr;
|
tcp_connection_t** clientsArrPtr;
|
||||||
pthread_mutex_t clientsMutex;
|
pthread_mutex_t clientsMutex;
|
||||||
|
|
||||||
pthread_t svrThread;
|
pthread_t svrThread; // IPv6 accept thread
|
||||||
|
pthread_t svrThreadV4; // IPv4 accept thread
|
||||||
} tcp_server_t;
|
} tcp_server_t;
|
||||||
|
|
||||||
struct tcpclient_thread_args {
|
struct tcpclient_thread_args {
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ void TxMempool_Init();
|
|||||||
// Assumed that the transation was confirmed to be valid
|
// Assumed that the transation was confirmed to be valid
|
||||||
int TxMempool_Insert(signed_transaction_t tx);
|
int TxMempool_Insert(signed_transaction_t tx);
|
||||||
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out);
|
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out);
|
||||||
|
bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount);
|
||||||
void TxMempool_Print();
|
void TxMempool_Print();
|
||||||
|
// Remove a transaction from the mempool by its hash. Returns true if removed.
|
||||||
|
bool TxMempool_Remove(const uint8_t* txHash);
|
||||||
void TxMempool_Destroy();
|
void TxMempool_Destroy();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -14,6 +14,8 @@ typedef struct {
|
|||||||
uint8_t bytes[32];
|
uint8_t bytes[32];
|
||||||
} key32_t;
|
} key32_t;
|
||||||
|
|
||||||
|
#define PROTO_VERSION 1
|
||||||
|
|
||||||
static inline uint32_t hash_key32(key32_t k) {
|
static inline uint32_t hash_key32(key32_t k) {
|
||||||
uint32_t hash = 2166136261u;
|
uint32_t hash = 2166136261u;
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
|
|||||||
@@ -4,6 +4,140 @@
|
|||||||
khash_t(balance_sheet_map_m)* sheetMap = NULL;
|
khash_t(balance_sheet_map_m)* sheetMap = NULL;
|
||||||
static pthread_mutex_t g_sheetLock;
|
static pthread_mutex_t g_sheetLock;
|
||||||
|
|
||||||
|
static bool BalanceSheet_GetSimEntry(
|
||||||
|
khash_t(balance_sheet_map_m)* simMap,
|
||||||
|
const uint8_t address[32],
|
||||||
|
balance_sheet_entry_t* out
|
||||||
|
) {
|
||||||
|
if (!simMap || !address || !out) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
key32_t key;
|
||||||
|
memcpy(key.bytes, address, 32);
|
||||||
|
|
||||||
|
khiter_t k = kh_get(balance_sheet_map_m, simMap, key);
|
||||||
|
if (k != kh_end(simMap)) {
|
||||||
|
*out = kh_value(simMap, k);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BalanceSheet_Lookup((uint8_t*)address, out)) {
|
||||||
|
int ret = 0;
|
||||||
|
k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||||
|
if (k == kh_end(simMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
kh_value(simMap, k) = *out;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
memcpy(out->address, address, 32);
|
||||||
|
out->balance = uint256_from_u64(0);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||||
|
if (k == kh_end(simMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
kh_value(simMap, k) = *out;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BalanceSheet_StoreSimEntry(
|
||||||
|
khash_t(balance_sheet_map_m)* simMap,
|
||||||
|
const balance_sheet_entry_t* entry
|
||||||
|
) {
|
||||||
|
if (!simMap || !entry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
key32_t key;
|
||||||
|
memcpy(key.bytes, entry->address, 32);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
khiter_t k = kh_put(balance_sheet_map_m, simMap, key, &ret);
|
||||||
|
if (k == kh_end(simMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
kh_value(simMap, k) = *entry;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BalanceSheet_ApplyCandidateTransaction(
|
||||||
|
khash_t(balance_sheet_map_m)* simMap,
|
||||||
|
const signed_transaction_t* tx,
|
||||||
|
uint64_t* outFee
|
||||||
|
) {
|
||||||
|
if (!simMap || !tx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Transaction_Verify(tx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
balance_sheet_entry_t senderEntry;
|
||||||
|
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.senderAddress, &senderEntry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256_t spend = uint256_from_u64(0);
|
||||||
|
if (uint256_add_u64(&spend, tx->transaction.amount1) ||
|
||||||
|
uint256_add_u64(&spend, tx->transaction.amount2) ||
|
||||||
|
uint256_add_u64(&spend, tx->transaction.fee)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uint256_cmp(&senderEntry.balance, &spend) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uint256_subtract(&senderEntry.balance, &spend)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!BalanceSheet_StoreSimEntry(simMap, &senderEntry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
balance_sheet_entry_t recipient1Entry;
|
||||||
|
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress1, &recipient1Entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (uint256_add_u64(&recipient1Entry.balance, tx->transaction.amount1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!BalanceSheet_StoreSimEntry(simMap, &recipient1Entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx->transaction.amount2 > 0) {
|
||||||
|
balance_sheet_entry_t recipient2Entry;
|
||||||
|
if (!BalanceSheet_GetSimEntry(simMap, tx->transaction.recipientAddress2, &recipient2Entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (uint256_add_u64(&recipient2Entry.balance, tx->transaction.amount2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!BalanceSheet_StoreSimEntry(simMap, &recipient2Entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outFee) {
|
||||||
|
*outFee = tx->transaction.fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int BalanceSheet_InsertLocked(balance_sheet_entry_t entry) {
|
static int BalanceSheet_InsertLocked(balance_sheet_entry_t entry) {
|
||||||
if (!sheetMap) {
|
if (!sheetMap) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -143,3 +277,64 @@ void BalanceSheet_Destroy() {
|
|||||||
sheetMap = NULL;
|
sheetMap = NULL;
|
||||||
pthread_mutex_destroy(&g_sheetLock);
|
pthread_mutex_destroy(&g_sheetLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BalanceSheet_SelectSpendableTransactions(
|
||||||
|
const signed_transaction_t* candidates,
|
||||||
|
size_t candidateCount,
|
||||||
|
signed_transaction_t** outAccepted,
|
||||||
|
size_t* outAcceptedCount,
|
||||||
|
uint64_t* outTotalFees
|
||||||
|
) {
|
||||||
|
if (!outAccepted || !outAcceptedCount || !outTotalFees) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outAccepted = NULL;
|
||||||
|
*outAcceptedCount = 0;
|
||||||
|
*outTotalFees = 0;
|
||||||
|
|
||||||
|
if (!candidates || candidateCount == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
signed_transaction_t* accepted = (signed_transaction_t*)calloc(candidateCount, sizeof(signed_transaction_t));
|
||||||
|
if (!accepted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
khash_t(balance_sheet_map_m)* simMap = kh_init(balance_sheet_map_m);
|
||||||
|
if (!simMap) {
|
||||||
|
free(accepted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t acceptedCount = 0;
|
||||||
|
uint64_t totalFees = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < candidateCount; ++i) {
|
||||||
|
const signed_transaction_t* tx = &candidates[i];
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fee = 0;
|
||||||
|
if (!BalanceSheet_ApplyCandidateTransaction(simMap, tx, &fee)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted[acceptedCount++] = *tx;
|
||||||
|
totalFees += fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
kh_destroy(balance_sheet_map_m, simMap);
|
||||||
|
|
||||||
|
if (acceptedCount == 0) {
|
||||||
|
free(accepted);
|
||||||
|
accepted = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outAccepted = accepted;
|
||||||
|
*outAcceptedCount = acceptedCount;
|
||||||
|
*outTotalFees = totalFees;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
+68
-3
@@ -214,21 +214,86 @@ bool Block_AllTransactionsValid(const block_t* block) {
|
|||||||
|
|
||||||
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
for (size_t i = 0; i < DynArr_size(block->transactions); i++) {
|
||||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||||
|
if (!Transaction_Verify(tx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
if (hasCoinbase) {
|
if (hasCoinbase) {
|
||||||
return false; // More than one coinbase transaction
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCoinbase = true;
|
hasCoinbase = true;
|
||||||
continue; // Coinbase transactions are valid since the miner has the right to create coins. Only rule is one per block.
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Block_ValidateCoinbaseAndFees(const block_t* block, uint64_t expectedCoinbaseAmount, uint64_t* outTotalFees) {
|
||||||
|
if (!block || !block->transactions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasCoinbase = false;
|
||||||
|
uint64_t totalFees = 0;
|
||||||
|
uint8_t zeroAddress[32] = {0};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < DynArr_size(block->transactions); ++i) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||||
|
if (!tx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
if (hasCoinbase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCoinbase = true;
|
||||||
|
|
||||||
|
if (!Transaction_Verify(tx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx->transaction.fee != 0 || tx->transaction.amount2 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx->transaction.amount1 != expectedCoinbaseAmount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.recipientAddress1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(tx->transaction.recipientAddress2, zeroAddress, sizeof(zeroAddress)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Transaction_Verify(tx)) {
|
if (!Transaction_Verify(tx)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UINT64_MAX - totalFees < tx->transaction.fee) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
totalFees += tx->transaction.fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase)
|
if (!hasCoinbase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outTotalFees) {
|
||||||
|
*outTotalFees = totalFees;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Block_IsFullyValid(const block_t* block) {
|
bool Block_IsFullyValid(const block_t* block) {
|
||||||
|
|||||||
+131
-20
@@ -1,6 +1,7 @@
|
|||||||
#include <block/chain.h>
|
#include <block/chain.h>
|
||||||
#include <constants.h>
|
#include <constants.h>
|
||||||
#include <runtime_state.h>
|
#include <runtime_state.h>
|
||||||
|
#include <txmempool.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -97,6 +98,37 @@ static bool DebitAddress(const uint8_t address[32], const uint256_t* amount) {
|
|||||||
return BalanceSheet_Insert(entry) >= 0;
|
return BalanceSheet_Insert(entry) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Chain_RecomputeRuntimeState(blockchain_t* chain) {
|
||||||
|
if (!chain) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256_t rebuiltSupply = uint256_from_u64(0);
|
||||||
|
for (size_t i = 0; i < chain->size; ++i) {
|
||||||
|
block_t* blk = (block_t*)DynArr_at(chain->blocks, i);
|
||||||
|
if (!blk || !blk->transactions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < DynArr_size(blk->transactions); ++j) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, j);
|
||||||
|
if (!tx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
if (uint256_add_u64(&rebuiltSupply, tx->transaction.amount1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSupply = rebuiltSupply;
|
||||||
|
currentReward = CalculateBlockReward(currentSupply, chain);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void Chain_ClearBlocks(blockchain_t* chain) {
|
static void Chain_ClearBlocks(blockchain_t* chain) {
|
||||||
if (!chain || !chain->blocks) {
|
if (!chain || !chain->blocks) {
|
||||||
return;
|
return;
|
||||||
@@ -161,34 +193,90 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// First pass: ensure all non-coinbase senders can cover the full spend
|
|
||||||
// (amount1 + amount2 + fee) before mutating the chain or balance sheet.
|
|
||||||
size_t txCount = DynArr_size(block->transactions);
|
size_t txCount = DynArr_size(block->transactions);
|
||||||
|
signed_transaction_t* candidateTxs = (signed_transaction_t*)calloc(txCount, sizeof(signed_transaction_t));
|
||||||
|
if (!candidateTxs) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nonCoinbaseCount = 0;
|
||||||
for (size_t i = 0; i < txCount; ++i) {
|
for (size_t i = 0; i < txCount; ++i) {
|
||||||
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
ok = false; break;
|
ok = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
candidateTxs[i] = *tx;
|
||||||
continue;
|
if (!Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
}
|
++nonCoinbaseCount;
|
||||||
|
|
||||||
uint256_t spend;
|
|
||||||
if (!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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.
|
// Push the block only after validation succeeds.
|
||||||
block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block);
|
block_t* blk = (block_t*)DynArr_push_back(chain->blocks, block);
|
||||||
@@ -232,6 +320,21 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove mined non-coinbase transactions from the mempool so they are not re-mined or re-broadcast.
|
||||||
|
if (blk->transactions) {
|
||||||
|
for (size_t i = 0; i < DynArr_size(blk->transactions); ++i) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i);
|
||||||
|
if (!tx) continue;
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) continue;
|
||||||
|
uint8_t txHash[32];
|
||||||
|
Transaction_CalculateHash(tx, txHash);
|
||||||
|
if (TxMempool_Remove(txHash)) {
|
||||||
|
// optional: log removal
|
||||||
|
// printf("TxMempool_Remove: removed tx from mempool: "); PrintHexBytes(txHash, 32); printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// ok remains true if no failures
|
// ok remains true if no failures
|
||||||
} while (0);
|
} while (0);
|
||||||
|
|
||||||
@@ -242,6 +345,8 @@ bool Chain_AddBlock(blockchain_t* chain, block_t* block) {
|
|||||||
printf("Added new block to chain:\n");
|
printf("Added new block to chain:\n");
|
||||||
Block_ShortPrint(block);
|
Block_ShortPrint(block);
|
||||||
|
|
||||||
|
/* Debug proof removed: coinbase == baseReward + totalFees was printed here during debugging. */
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,6 +539,12 @@ bool Chain_RollbackToHeight(blockchain_t* chain, size_t height) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Chain_RecomputeRuntimeState(chain)) {
|
||||||
|
pthread_mutex_unlock(&balanceSheetLock);
|
||||||
|
pthread_rwlock_unlock(&chainLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&balanceSheetLock);
|
pthread_mutex_unlock(&balanceSheetLock);
|
||||||
pthread_rwlock_unlock(&chainLock);
|
pthread_rwlock_unlock(&chainLock);
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -42,7 +42,23 @@ bool Transaction_Verify(const signed_transaction_t* tx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
// Coinbase transactions are valid if the signature is correct for the block (handled in Block_Verify)
|
if (tx->transaction.amount1 == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx->transaction.amount2 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.recipientAddress1) || Address_IsCoinbase(tx->transaction.recipientAddress2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t zeroAddress[32] = {0};
|
||||||
|
if (memcmp(tx->transaction.recipientAddress2, zeroAddress, 32) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+251
-8
@@ -12,13 +12,14 @@
|
|||||||
#include <balance_sheet.h>
|
#include <balance_sheet.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <txmempool.h>
|
||||||
|
|
||||||
#include <constants.h>
|
#include <constants.h>
|
||||||
#include <runtime_state.h>
|
#include <runtime_state.h>
|
||||||
#include <autolykos2/autolykos2.h>
|
#include <autolykos2/autolykos2.h>
|
||||||
|
|
||||||
#include <nets/net_node.h>
|
#include <nets/net_node.h>
|
||||||
|
#include <nets/nodediscovery.h>
|
||||||
#include <nets/fetch_scheduler.h>
|
#include <nets/fetch_scheduler.h>
|
||||||
#include <nets/orphan_pool.h>
|
#include <nets/orphan_pool.h>
|
||||||
|
|
||||||
@@ -147,6 +148,63 @@ static void AddCoinbaseTransaction(block_t* block, const uint8_t minerAddress[32
|
|||||||
Block_AddTransaction(block, &coinbaseTx);
|
Block_AddTransaction(block, &coinbaseTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int CompareTransactionPriority(const void* lhs, const void* rhs) {
|
||||||
|
const signed_transaction_t* left = (const signed_transaction_t*)lhs;
|
||||||
|
const signed_transaction_t* right = (const signed_transaction_t*)rhs;
|
||||||
|
|
||||||
|
if (left->transaction.fee > right->transaction.fee) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (left->transaction.fee < right->transaction.fee) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t leftHash[32];
|
||||||
|
uint8_t rightHash[32];
|
||||||
|
Transaction_CalculateHash(left, leftHash);
|
||||||
|
Transaction_CalculateHash(right, rightHash);
|
||||||
|
return memcmp(leftHash, rightHash, sizeof(leftHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BuildSpendableMempoolSelection(
|
||||||
|
signed_transaction_t** outAcceptedTxs,
|
||||||
|
size_t* outAcceptedCount,
|
||||||
|
uint64_t* outTotalFees
|
||||||
|
) {
|
||||||
|
if (!outAcceptedTxs || !outAcceptedCount || !outTotalFees) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outAcceptedTxs = NULL;
|
||||||
|
*outAcceptedCount = 0;
|
||||||
|
*outTotalFees = 0;
|
||||||
|
|
||||||
|
signed_transaction_t* snapshot = NULL;
|
||||||
|
size_t snapshotCount = 0;
|
||||||
|
if (!TxMempool_Snapshot(&snapshot, &snapshotCount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot && snapshotCount > 1) {
|
||||||
|
qsort(snapshot, snapshotCount, sizeof(signed_transaction_t), CompareTransactionPriority);
|
||||||
|
}
|
||||||
|
|
||||||
|
signed_transaction_t* acceptedTxs = NULL;
|
||||||
|
size_t acceptedCount = 0;
|
||||||
|
uint64_t totalFees = 0;
|
||||||
|
bool ok = BalanceSheet_SelectSpendableTransactions(snapshot, snapshotCount, &acceptedTxs, &acceptedCount, &totalFees);
|
||||||
|
free(snapshot);
|
||||||
|
if (!ok) {
|
||||||
|
free(acceptedTxs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outAcceptedTxs = acceptedTxs;
|
||||||
|
*outAcceptedCount = acceptedCount;
|
||||||
|
*outTotalFees = totalFees;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void PrintBlockDetail(const block_t* block, size_t txCount, const uint8_t canonicalHash[32], const uint8_t powHash[32]) {
|
static void PrintBlockDetail(const block_t* block, size_t txCount, const uint8_t canonicalHash[32], const uint8_t powHash[32]) {
|
||||||
if (!block) {
|
if (!block) {
|
||||||
return;
|
return;
|
||||||
@@ -301,6 +359,46 @@ static bool ComputeHistoricalAutolykosHashFromDisk(const char* chainDataDir, uin
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool Block_GetCoinbaseAndFeeTotals(const block_t* block, uint64_t* outCoinbaseAmount, uint64_t* outTotalFees) {
|
||||||
|
if (!block || !block->transactions || !outCoinbaseAmount || !outTotalFees) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasCoinbase = false;
|
||||||
|
uint64_t coinbaseAmount = 0;
|
||||||
|
uint64_t totalFees = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < DynArr_size(block->transactions); ++i) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i);
|
||||||
|
if (!tx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
if (hasCoinbase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCoinbase = true;
|
||||||
|
coinbaseAmount = tx->transaction.amount1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UINT64_MAX - totalFees < tx->transaction.fee) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
totalFees += tx->transaction.fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCoinbase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outCoinbaseAmount = coinbaseAmount;
|
||||||
|
*outTotalFees = totalFees;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool MineAndAppendBlock(blockchain_t* chain,
|
static bool MineAndAppendBlock(blockchain_t* chain,
|
||||||
block_t* block,
|
block_t* block,
|
||||||
uint256_t* currentSupply,
|
uint256_t* currentSupply,
|
||||||
@@ -332,6 +430,8 @@ static bool MineAndAppendBlock(blockchain_t* chain,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Debug proof removed: miner printed proof that coinbase == baseReward + totalFees during debugging. */
|
||||||
|
|
||||||
// After successfully appending a block, attempt to attach any orphans.
|
// After successfully appending a block, attempt to attach any orphans.
|
||||||
size_t attached = OrphanPool_AttemptAttach(chain);
|
size_t attached = OrphanPool_AttemptAttach(chain);
|
||||||
if (attached > 0) {
|
if (attached > 0) {
|
||||||
@@ -404,6 +504,7 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
|||||||
blockchain_t* prevChain = Chain_Create();
|
blockchain_t* prevChain = Chain_Create();
|
||||||
if (!prevChain) { return false; }
|
if (!prevChain) { return false; }
|
||||||
|
|
||||||
|
uint256_t replaySupply = uint256_from_u64(0);
|
||||||
uint32_t expectedDifficulty = INITIAL_DIFFICULTY;
|
uint32_t expectedDifficulty = INITIAL_DIFFICULTY;
|
||||||
for (size_t i = 0; i < chainSize; ++i) {
|
for (size_t i = 0; i < chainSize; ++i) {
|
||||||
block_t* blk = NULL;
|
block_t* blk = NULL;
|
||||||
@@ -480,12 +581,31 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t expectedReward = 0;
|
||||||
|
uint64_t savedReward = currentReward;
|
||||||
|
expectedReward = CalculateBlockReward(replaySupply, prevChain);
|
||||||
|
currentReward = savedReward;
|
||||||
|
|
||||||
if (!Block_AllTransactionsValid(blk)) {
|
if (!Block_AllTransactionsValid(blk)) {
|
||||||
Block_Destroy(blk);
|
Block_Destroy(blk);
|
||||||
Chain_Destroy(prevChain);
|
Chain_Destroy(prevChain);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t coinbaseAmount = 0;
|
||||||
|
uint64_t totalFees = 0;
|
||||||
|
if (!Block_GetCoinbaseAndFeeTotals(blk, &coinbaseAmount, &totalFees)) {
|
||||||
|
Block_Destroy(blk);
|
||||||
|
Chain_Destroy(prevChain);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UINT64_MAX - expectedReward < totalFees || coinbaseAmount != (expectedReward + totalFees)) {
|
||||||
|
Block_Destroy(blk);
|
||||||
|
Chain_Destroy(prevChain);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t expectedMerkle[32];
|
uint8_t expectedMerkle[32];
|
||||||
Block_CalculateMerkleRoot(blk, expectedMerkle);
|
Block_CalculateMerkleRoot(blk, expectedMerkle);
|
||||||
if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) {
|
if (memcmp(blk->header.merkleRoot, expectedMerkle, sizeof(expectedMerkle)) != 0) {
|
||||||
@@ -508,6 +628,8 @@ static bool VerifyChainFully(blockchain_t* chain) {
|
|||||||
headerOnly.transactions = NULL;
|
headerOnly.transactions = NULL;
|
||||||
(void)DynArr_push_back(prevChain->blocks, &headerOnly);
|
(void)DynArr_push_back(prevChain->blocks, &headerOnly);
|
||||||
|
|
||||||
|
(void)uint256_add_u64(&replaySupply, coinbaseAmount);
|
||||||
|
|
||||||
Block_Destroy(blk);
|
Block_Destroy(blk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,6 +674,10 @@ int main(int argc, char* argv[]) {
|
|||||||
ApplyRuntimeConfigFromEnv();
|
ApplyRuntimeConfigFromEnv();
|
||||||
|
|
||||||
signal(SIGINT, handle_sigint);
|
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));
|
srand((unsigned int)time(NULL));
|
||||||
|
|
||||||
// Initialize runtime locks before any thread or helper can touch chain state.
|
// Initialize runtime locks before any thread or helper can touch chain state.
|
||||||
@@ -579,6 +705,11 @@ int main(int argc, char* argv[]) {
|
|||||||
uint8_t lastSavedHash[32] = {0};
|
uint8_t lastSavedHash[32] = {0};
|
||||||
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget, ¤tReward, lastSavedHash, false)) {
|
if (!Chain_LoadFromFile(chain, chainDataDir, ¤tSupply, &difficultyTarget, ¤tReward, lastSavedHash, false)) {
|
||||||
printf("No existing chain loaded from %s\n", chainDataDir);
|
printf("No existing chain loaded from %s\n", chainDataDir);
|
||||||
|
} else {
|
||||||
|
// Recompute runtime supply/reward from loaded blocks to avoid trusting stale meta values.
|
||||||
|
if (!Chain_RecomputeRuntimeState(chain)) {
|
||||||
|
fprintf(stderr, "Failed to recompute runtime state from loaded chain\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BalanceSheet_LoadFromFile(chainDataDir)) {
|
if (!BalanceSheet_LoadFromFile(chainDataDir)) {
|
||||||
@@ -708,7 +839,7 @@ int main(int argc, char* argv[]) {
|
|||||||
char supplyStr[80];
|
char supplyStr[80];
|
||||||
Uint256ToDecimal(¤tSupply, supplyStr, sizeof(supplyStr));
|
Uint256ToDecimal(¤tSupply, supplyStr, sizeof(supplyStr));
|
||||||
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
|
printf("Current chain has %zu blocks, total supply %s\n", Chain_Size(chain), supplyStr);
|
||||||
printf("Commands: mine <x>, send <address> <amount>, balance [address], connect <ipv4>, sync (requires nodes), flushchain, fullverify, blockdetail <block number>, wipechain, genaddr, exit\n");
|
printf("Commands: mine <x>, send <address> <amount> [fee], txpooldetail <txhash>, balance [address], connect <ipv4>, sync (requires nodes), flushchain, fullverify, blockdetail <block number>, wipechain, genaddr, exit\n");
|
||||||
|
|
||||||
char line[1024];
|
char line[1024];
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -746,14 +877,38 @@ int main(int argc, char* argv[]) {
|
|||||||
printf("Mining %llu block(s)...\n", requested);
|
printf("Mining %llu block(s)...\n", requested);
|
||||||
bool minedAll = true;
|
bool minedAll = true;
|
||||||
for (unsigned long long i = 0; i < requested; ++i) {
|
for (unsigned long long i = 0; i < requested; ++i) {
|
||||||
block_t* block = BuildNextBlock(chain, difficultyTarget);
|
signed_transaction_t* acceptedTxs = NULL;
|
||||||
if (!block) {
|
size_t acceptedTxCount = 0;
|
||||||
fprintf(stderr, "failed to create block\n");
|
uint64_t totalFees = 0;
|
||||||
|
if (!BuildSpendableMempoolSelection(&acceptedTxs, &acceptedTxCount, &totalFees)) {
|
||||||
|
fprintf(stderr, "failed to select spendable transactions from mempool\n");
|
||||||
minedAll = false;
|
minedAll = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCoinbaseTransaction(block, minerAddress, currentReward);
|
block_t* block = BuildNextBlock(chain, difficultyTarget);
|
||||||
|
if (!block) {
|
||||||
|
fprintf(stderr, "failed to create block\n");
|
||||||
|
free(acceptedTxs);
|
||||||
|
minedAll = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t coinbaseAmount = currentReward;
|
||||||
|
if (UINT64_MAX - coinbaseAmount < totalFees) {
|
||||||
|
free(acceptedTxs);
|
||||||
|
Block_Destroy(block);
|
||||||
|
minedAll = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
coinbaseAmount += totalFees;
|
||||||
|
|
||||||
|
AddCoinbaseTransaction(block, minerAddress, coinbaseAmount);
|
||||||
|
|
||||||
|
for (size_t txIndex = 0; txIndex < acceptedTxCount; ++txIndex) {
|
||||||
|
Block_AddTransaction(block, &acceptedTxs[txIndex]);
|
||||||
|
}
|
||||||
|
free(acceptedTxs);
|
||||||
|
|
||||||
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward, &difficultyTarget)) {
|
if (!MineAndAppendBlock(chain, block, ¤tSupply, ¤tReward, &difficultyTarget)) {
|
||||||
Block_Destroy(block);
|
Block_Destroy(block);
|
||||||
@@ -786,6 +941,7 @@ int main(int argc, char* argv[]) {
|
|||||||
if (strcmp(cmd, "send") == 0) {
|
if (strcmp(cmd, "send") == 0) {
|
||||||
char* addressStr = strtok(NULL, " \t");
|
char* addressStr = strtok(NULL, " \t");
|
||||||
char* amountStr = strtok(NULL, " \t");
|
char* amountStr = strtok(NULL, " \t");
|
||||||
|
char* feeStr = strtok(NULL, " \t");
|
||||||
if (!addressStr || !amountStr) {
|
if (!addressStr || !amountStr) {
|
||||||
printf("usage: send <address> <amount>\n");
|
printf("usage: send <address> <amount>\n");
|
||||||
continue;
|
continue;
|
||||||
@@ -804,6 +960,21 @@ int main(int argc, char* argv[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned long long fee = 0;
|
||||||
|
if (feeStr) {
|
||||||
|
char* endptr2 = NULL;
|
||||||
|
fee = strtoull(feeStr, &endptr2, 10);
|
||||||
|
if (*feeStr == '\0' || feeStr[0] == '-' || (endptr2 && *endptr2 != '\0')) {
|
||||||
|
printf("invalid fee\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fee > UINT64_MAX - amount) {
|
||||||
|
printf("invalid fee: overflow\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
balance_sheet_entry_t senderEntry;
|
balance_sheet_entry_t senderEntry;
|
||||||
if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) {
|
if (!BalanceSheet_Lookup(minerAddress, &senderEntry)) {
|
||||||
printf("send failed: miner address has no balance\n");
|
printf("send failed: miner address has no balance\n");
|
||||||
@@ -822,12 +993,13 @@ int main(int argc, char* argv[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCoinbaseTransaction(block, minerAddress, currentReward);
|
uint64_t coinbaseAmount = currentReward;
|
||||||
|
AddCoinbaseTransaction(block, minerAddress, coinbaseAmount);
|
||||||
|
|
||||||
signed_transaction_t spendTx;
|
signed_transaction_t spendTx;
|
||||||
Transaction_Init(&spendTx);
|
Transaction_Init(&spendTx);
|
||||||
spendTx.transaction.version = 1;
|
spendTx.transaction.version = 1;
|
||||||
spendTx.transaction.fee = 0;
|
spendTx.transaction.fee = (uint64_t)fee;
|
||||||
spendTx.transaction.amount1 = (uint64_t)amount;
|
spendTx.transaction.amount1 = (uint64_t)amount;
|
||||||
spendTx.transaction.amount2 = 0;
|
spendTx.transaction.amount2 = 0;
|
||||||
memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress));
|
memcpy(spendTx.transaction.senderAddress, minerAddress, sizeof(minerAddress));
|
||||||
@@ -836,6 +1008,7 @@ int main(int argc, char* argv[]) {
|
|||||||
memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey));
|
memcpy(spendTx.transaction.compressedPublicKey, minerCompressedPubkey, sizeof(minerCompressedPubkey));
|
||||||
Transaction_Sign(&spendTx, minerPrivateKey);
|
Transaction_Sign(&spendTx, minerPrivateKey);
|
||||||
|
|
||||||
|
/*
|
||||||
Block_AddTransaction(block, &spendTx);
|
Block_AddTransaction(block, &spendTx);
|
||||||
printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount);
|
printf("Created transaction sending %llu pebble(s) to ", (unsigned long long)amount);
|
||||||
char recipientHex[65];
|
char recipientHex[65];
|
||||||
@@ -854,6 +1027,22 @@ int main(int argc, char* argv[]) {
|
|||||||
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
|
Node_BroadcastChainRange(node, Chain_Size(chain) - 1, NULL);
|
||||||
}
|
}
|
||||||
printf("send committed in mined block\n");
|
printf("send committed in mined block\n");
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Insert into txmempool
|
||||||
|
if (TxMempool_Insert(spendTx) < 0) {
|
||||||
|
printf("failed to add transaction to mempool, transaction rejected\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("transaction added to mempool, broadcasting...\n");
|
||||||
|
|
||||||
|
if (Node_BroadcastTransaction(node, &spendTx, NULL) == 0) {
|
||||||
|
printf("transaction broadcast to peers\n");
|
||||||
|
} else {
|
||||||
|
printf("failed to broadcast transaction to peers\n");
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1014,6 +1203,11 @@ int main(int argc, char* argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t reattached = OrphanPool_AttemptAttach(chain);
|
||||||
|
if (reattached > 0) {
|
||||||
|
printf("Reorg rollback attached %zu orphan(s)\n", reattached);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply additional penalty by shrinking end and restart window from current Chain_Size
|
// Apply additional penalty by shrinking end and restart window from current Chain_Size
|
||||||
if (peerHeight > reorgPenalty) {
|
if (peerHeight > reorgPenalty) {
|
||||||
end = peerHeight - reorgPenalty;
|
end = peerHeight - reorgPenalty;
|
||||||
@@ -1115,6 +1309,46 @@ int main(int argc, char* argv[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(cmd, "txpooldetail") == 0) {
|
||||||
|
char* hashStr = strtok(NULL, " \t");
|
||||||
|
if (!hashStr) {
|
||||||
|
printf("usage: txpooldetail <txhash>\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t txHash[32];
|
||||||
|
if (!ParseHexAddress32(hashStr, txHash)) {
|
||||||
|
printf("invalid tx hash: expected 64 hex chars\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
signed_transaction_t tx;
|
||||||
|
if (!TxMempool_Lookup(txHash, &tx)) {
|
||||||
|
printf("transaction not found in mempool\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char senderHex[65];
|
||||||
|
char recip1Hex[65];
|
||||||
|
char recip2Hex[65];
|
||||||
|
AddressToHexString(tx.transaction.senderAddress, senderHex);
|
||||||
|
AddressToHexString(tx.transaction.recipientAddress1, recip1Hex);
|
||||||
|
AddressToHexString(tx.transaction.recipientAddress2, recip2Hex);
|
||||||
|
|
||||||
|
uint8_t calcHash[32];
|
||||||
|
Transaction_CalculateHash(&tx, calcHash);
|
||||||
|
|
||||||
|
printf("Transaction details:\n");
|
||||||
|
printf(" TxHash: "); PrintHexBytes(calcHash, 32); printf("\n");
|
||||||
|
printf(" Sender: %s%s\n", senderHex, Address_IsCoinbase(tx.transaction.senderAddress) ? " (coinbase)" : "");
|
||||||
|
printf(" Recipient1: %s\n", recip1Hex);
|
||||||
|
printf(" Recipient2: %s\n", recip2Hex);
|
||||||
|
printf(" Amount1: %llu\n", (unsigned long long)tx.transaction.amount1);
|
||||||
|
printf(" Amount2: %llu\n", (unsigned long long)tx.transaction.amount2);
|
||||||
|
printf(" Fee: %llu\n", (unsigned long long)tx.transaction.fee);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cmd, "blockdetail") == 0) {
|
if (strcmp(cmd, "blockdetail") == 0) {
|
||||||
@@ -1235,6 +1469,15 @@ int main(int argc, char* argv[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(cmd, "peers") == 0) {
|
||||||
|
if (strtok(NULL, " \t")) {
|
||||||
|
printf("usage: peers\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NodeDiscovery_PrintPeers(node->discovery);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(cmd, "flushchain") == 0) {
|
if (strcmp(cmd, "flushchain") == 0) {
|
||||||
if (FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward)) {
|
if (FlushChainAndSheet(chain, chainDataDir, currentSupply, currentReward)) {
|
||||||
printf("chain flushed\n");
|
printf("chain flushed\n");
|
||||||
|
|||||||
+422
-19
@@ -1,4 +1,5 @@
|
|||||||
#include <nets/net_node.h>
|
#include <nets/net_node.h>
|
||||||
|
#include <nets/nodediscovery.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -11,6 +12,9 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <unistd.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) {
|
static net_node_t* Node_FromConnection(tcp_connection_t* conn) {
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
@@ -28,6 +32,194 @@ static uint64_t Node_GetCurrentBlockHeight(void) {
|
|||||||
return currentBlockHeight;
|
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 {
|
typedef enum {
|
||||||
NODE_BLOCK_REJECTED = 0,
|
NODE_BLOCK_REJECTED = 0,
|
||||||
NODE_BLOCK_ORPHAN_QUEUED = 1,
|
NODE_BLOCK_ORPHAN_QUEUED = 1,
|
||||||
@@ -46,6 +238,10 @@ static void* Node_MaintenanceThread(void* arg) {
|
|||||||
BalanceSheet_SaveToFile(chainDataDir);
|
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);
|
sleep_for_milliseconds((uint64_t)n->maintenanceIntervalMs);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -176,6 +372,20 @@ static node_block_accept_result_t Node_ParseAndAcceptBlock(const unsigned char*
|
|||||||
return NODE_BLOCK_REJECTED;
|
return NODE_BLOCK_REJECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t coinbaseAmount = 0;
|
||||||
|
if (blk->transactions) {
|
||||||
|
for (size_t i = 0; i < DynArr_size(blk->transactions); ++i) {
|
||||||
|
signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(blk->transactions, i);
|
||||||
|
if (tx && Address_IsCoinbase(tx->transaction.senderAddress)) {
|
||||||
|
coinbaseAmount = tx->transaction.amount1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)uint256_add_u64(¤tSupply, coinbaseAmount);
|
||||||
|
currentReward = CalculateBlockReward(currentSupply, currentChain);
|
||||||
|
|
||||||
// Persist on accept if requested
|
// Persist on accept if requested
|
||||||
if (persist) {
|
if (persist) {
|
||||||
Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward);
|
Chain_SaveToFile(currentChain, chainDataDir, currentSupply, currentReward);
|
||||||
@@ -238,8 +448,9 @@ net_node_t* Node_Create() {
|
|||||||
pthread_mutex_init(&node->seenLock, NULL);
|
pthread_mutex_init(&node->seenLock, NULL);
|
||||||
pthread_mutex_init(&node->outboundLock, NULL);
|
pthread_mutex_init(&node->outboundLock, NULL);
|
||||||
node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes
|
node->seenBlocks = DynSet_Create(32); // 32-byte canonical hashes
|
||||||
|
TxMempool_Init();
|
||||||
|
|
||||||
TcpServer_Init(node->server, listenPort, "0.0.0.0");
|
TcpServer_Init(node->server, listenPort, "::");
|
||||||
|
|
||||||
node->server->owner = node;
|
node->server->owner = node;
|
||||||
node->server->on_connect = Node_Server_OnConnect;
|
node->server->on_connect = Node_Server_OnConnect;
|
||||||
@@ -250,6 +461,25 @@ net_node_t* Node_Create() {
|
|||||||
|
|
||||||
OrphanPool_Init();
|
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
|
// Start maintenance thread
|
||||||
node->maintenanceRunning = 1;
|
node->maintenanceRunning = 1;
|
||||||
node->maintenanceIntervalMs = 1000; // 1s
|
node->maintenanceIntervalMs = 1000; // 1s
|
||||||
@@ -276,13 +506,28 @@ void Node_Destroy(net_node_t* node) {
|
|||||||
TcpServer_Destroy(node->server);
|
TcpServer_Destroy(node->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop maintenance thread
|
// Stop maintenance thread (no more discovery ticks after this)
|
||||||
if (node->maintenanceRunning) {
|
if (node->maintenanceRunning) {
|
||||||
node->maintenanceRunning = 0;
|
node->maintenanceRunning = 0;
|
||||||
pthread_join(node->maintenanceThread, NULL);
|
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();
|
OrphanPool_Destroy();
|
||||||
|
TxMempool_Destroy();
|
||||||
|
|
||||||
if (node->seenBlocks) {
|
if (node->seenBlocks) {
|
||||||
DynSet_Destroy(node->seenBlocks);
|
DynSet_Destroy(node->seenBlocks);
|
||||||
@@ -316,6 +561,14 @@ int Node_ConnectPeer(net_node_t* node, const char* ip, unsigned short port) {
|
|||||||
return -1;
|
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) {
|
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||||
if (node->outboundClients[i].connection == NULL) {
|
if (node->outboundClients[i].connection == NULL) {
|
||||||
if (TcpClient_Connect(
|
if (TcpClient_Connect(
|
||||||
@@ -384,6 +637,33 @@ int Node_SendPacket(net_node_t* node, tcp_connection_t* conn, packet_type_t pack
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Node_BroadcastTransaction(net_node_t* node, signed_transaction_t* tx, tcp_connection_t* excludeNode) {
|
||||||
|
if (!node || !tx) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize transaction into payload
|
||||||
|
size_t payloadLen = sizeof(signed_transaction_t);
|
||||||
|
unsigned char* payload = (unsigned char*)malloc(payloadLen);
|
||||||
|
if (!payload) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(payload, tx, sizeof(signed_transaction_t));
|
||||||
|
|
||||||
|
// Broadcast to all outbound peers
|
||||||
|
pthread_mutex_lock(&node->outboundLock);
|
||||||
|
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||||
|
tcp_connection_t* connection = node->outboundClients[i].connection;
|
||||||
|
if (connection && connection != excludeNode) {
|
||||||
|
(void)Node_SendPacket(node, connection, PACKET_TYPE_BROADCAST_TX, payload, payloadLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&node->outboundLock);
|
||||||
|
|
||||||
|
free(payload);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Node_Server_OnConnect(tcp_connection_t* client) {
|
void Node_Server_OnConnect(tcp_connection_t* client) {
|
||||||
net_node_t* node = Node_FromConnection(client);
|
net_node_t* node = Node_FromConnection(client);
|
||||||
Node_ForwardConnect(node, client);
|
Node_ForwardConnect(node, client);
|
||||||
@@ -392,8 +672,8 @@ void Node_Server_OnConnect(tcp_connection_t* client) {
|
|||||||
if (echoPeersEnabled && node && client) {
|
if (echoPeersEnabled && node && client) {
|
||||||
// Attempt to create an outbound connection back to the peer's IP on our configured port.
|
// Attempt to create an outbound connection back to the peer's IP on our configured port.
|
||||||
// We avoid connecting if we already have an outbound to the same IP.
|
// We avoid connecting if we already have an outbound to the same IP.
|
||||||
char ipbuf[INET_ADDRSTRLEN];
|
char ipbuf[INET6_ADDRSTRLEN];
|
||||||
if (inet_ntop(AF_INET, &client->peerAddr.sin_addr, ipbuf, sizeof(ipbuf))) {
|
if (TcpConnection_GetPeerAddrStr(client, ipbuf, sizeof(ipbuf))) {
|
||||||
// Use the configured port as the target port for the peer's listening service.
|
// Use the configured port as the target port for the peer's listening service.
|
||||||
unsigned short targetPort = listenPort;
|
unsigned short targetPort = listenPort;
|
||||||
|
|
||||||
@@ -401,8 +681,7 @@ void Node_Server_OnConnect(tcp_connection_t* client) {
|
|||||||
pthread_mutex_lock(&node->outboundLock);
|
pthread_mutex_lock(&node->outboundLock);
|
||||||
for (size_t i = 0; i < MAX_CONS; ++i) {
|
for (size_t i = 0; i < MAX_CONS; ++i) {
|
||||||
if (node->outboundClients[i].connection) {
|
if (node->outboundClients[i].connection) {
|
||||||
struct in_addr otherAddr = node->outboundClients[i].connection->peerAddr.sin_addr;
|
if (TcpConnection_PeerAddrEqual(node->outboundClients[i].connection, client)) {
|
||||||
if (otherAddr.s_addr == client->peerAddr.sin_addr.s_addr) {
|
|
||||||
shouldConnect = 0;
|
shouldConnect = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -439,11 +718,36 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
|||||||
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
||||||
memcpy(&blockHeight, payload + sizeof(protoVersion), sizeof(blockHeight));
|
memcpy(&blockHeight, payload + sizeof(protoVersion), sizeof(blockHeight));
|
||||||
|
|
||||||
// TODO: Save these somewhere and maybe respond
|
// Optional trailing listen port. This inbound peer's source port is ephemeral,
|
||||||
printf("Received HELLO from node %u: protoVersion=%u, blockHeight=%" PRIu64 "\n",
|
// so we record the port it actually listens on to make it discoverable/reachable.
|
||||||
client ? client->connectionId : 0U, protoVersion, blockHeight);
|
// 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 ackBuf[100];
|
||||||
uint8_t* ackData = ackBuf;
|
uint8_t* ackData = ackBuf;
|
||||||
size_t ackOffset = 0;
|
size_t ackOffset = 0;
|
||||||
@@ -452,6 +756,9 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
|||||||
uint64_t currentHeight = Node_GetCurrentBlockHeight();
|
uint64_t currentHeight = Node_GetCurrentBlockHeight();
|
||||||
memcpy(ackData + ackOffset, ¤tHeight, sizeof(currentHeight));
|
memcpy(ackData + ackOffset, ¤tHeight, sizeof(currentHeight));
|
||||||
ackOffset += 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);
|
Node_SendPacket(Node_FromConnection(client), client, PACKET_TYPE_ACK_HELLO, ackData, ackOffset);
|
||||||
|
|
||||||
@@ -593,7 +900,48 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PACKET_TYPE_ACK_BLOCK:
|
case PACKET_TYPE_ACK_BLOCK:
|
||||||
case PACKET_TYPE_BROADCAST_TX:
|
case PACKET_TYPE_BROADCAST_TX: {
|
||||||
|
// Decode the block or transaction data inside
|
||||||
|
if (payloadLen == sizeof(signed_transaction_t)) {
|
||||||
|
signed_transaction_t tx;
|
||||||
|
memcpy(&tx, payload, sizeof(tx));
|
||||||
|
uint8_t txHash[32];
|
||||||
|
char txHashHex[65];
|
||||||
|
Transaction_CalculateHash(&tx, txHash);
|
||||||
|
to_hex(txHash, txHashHex);
|
||||||
|
printf("Received packet type %u from node %u with transaction sending %llu pebble(s)\n",
|
||||||
|
(unsigned int)packetType, client ? client->connectionId : 0U, (unsigned long long)tx.transaction.amount1);
|
||||||
|
|
||||||
|
if (!Transaction_Verify(&tx)) {
|
||||||
|
printf("Received invalid transaction from node %u\n", client ? client->connectionId : 0U);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push to mempool if it's not already present
|
||||||
|
if (!TxMempool_Lookup(txHash, &tx)) {
|
||||||
|
if (TxMempool_Insert(tx) >= 0) {
|
||||||
|
printf("Added transaction %s from node %u to mempool\n", txHashHex, client ? client->connectionId : 0U);
|
||||||
|
// Broadcast to other peers
|
||||||
|
net_node_t* node = Node_FromConnection(client);
|
||||||
|
if (node) {
|
||||||
|
Node_BroadcastTransaction(node, &tx, client);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Failed to add transaction %s from node %u to mempool\n", txHashHex, client ? client->connectionId : 0U);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Transaction %s from node %u already seen!\n", txHashHex, client ? client->connectionId : 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("Received packet type %u from node %u with invalid payload length %zu\n",
|
||||||
|
(unsigned int)packetType, client ? client->connectionId : 0U, payloadLen);
|
||||||
|
|
||||||
|
// TODO: Ignoring for now, might error node later if we want to be strict about malformed messages
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case PACKET_TYPE_ACK_TX:
|
case PACKET_TYPE_ACK_TX:
|
||||||
case PACKET_TYPE_ERROR: {
|
case PACKET_TYPE_ERROR: {
|
||||||
// Decode the message inside as text
|
// Decode the message inside as text
|
||||||
@@ -609,6 +957,20 @@ void Node_Server_OnData(tcp_connection_t* client) {
|
|||||||
|
|
||||||
break;
|
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:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -634,12 +996,16 @@ void Node_Client_OnConnect(tcp_connection_t* client) {
|
|||||||
uint8_t* data = buf;
|
uint8_t* data = buf;
|
||||||
|
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
uint32_t protoVersion = 1; // little-endian
|
uint32_t protoVersion = PROTO_VERSION; // little-endian
|
||||||
uint64_t blockHeight = Node_GetCurrentBlockHeight();
|
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
|
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);
|
offset += sizeof(protoVersion);
|
||||||
memcpy((unsigned char*)data + offset, &blockHeight, sizeof(blockHeight));
|
memcpy((unsigned char*)data + offset, &blockHeight, sizeof(blockHeight));
|
||||||
offset += 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);
|
Node_SendPacket(node, client, PACKET_TYPE_HELLO, data, offset);
|
||||||
}
|
}
|
||||||
@@ -676,6 +1042,14 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
|||||||
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
memcpy(&protoVersion, payload, sizeof(protoVersion));
|
||||||
memcpy(&blockHeight, payload + sizeof(protoVersion), sizeof(blockHeight));
|
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);
|
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
|
// Store peer-advertised height on matching outbound client
|
||||||
@@ -765,7 +1139,12 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PACKET_TYPE_ACK_BLOCK:
|
case PACKET_TYPE_ACK_BLOCK:
|
||||||
case PACKET_TYPE_BROADCAST_TX:
|
case PACKET_TYPE_BROADCAST_TX: {
|
||||||
|
// Client can't receive these!
|
||||||
|
printf("Received unexpected packet type %u from node %u\n", (unsigned int)packetType, client ? client->connectionId : 0U);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case PACKET_TYPE_ACK_TX:
|
case PACKET_TYPE_ACK_TX:
|
||||||
case PACKET_TYPE_ERROR: {
|
case PACKET_TYPE_ERROR: {
|
||||||
// Decode the message inside as text
|
// Decode the message inside as text
|
||||||
@@ -781,6 +1160,20 @@ void Node_Client_OnData(tcp_connection_t* client) {
|
|||||||
|
|
||||||
break;
|
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:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -840,11 +1233,6 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
|||||||
size_t chainSize = Chain_Size(currentChain);
|
size_t chainSize = Chain_Size(currentChain);
|
||||||
if (startHeightInclusive >= chainSize) return;
|
if (startHeightInclusive >= chainSize) return;
|
||||||
|
|
||||||
uint32_t sourceIp = 0;
|
|
||||||
if (sourceConn) {
|
|
||||||
sourceIp = sourceConn->peerAddr.sin_addr.s_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t h = startHeightInclusive; h < chainSize; ++h) {
|
for (size_t h = startHeightInclusive; h < chainSize; ++h) {
|
||||||
block_t* blk = NULL;
|
block_t* blk = NULL;
|
||||||
if (!Chain_GetBlockCopy(currentChain, h, &blk) || !blk) {
|
if (!Chain_GetBlockCopy(currentChain, h, &blk) || !blk) {
|
||||||
@@ -907,7 +1295,7 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
|||||||
tcp_connection_t* conn = node->outboundClients[i].connection;
|
tcp_connection_t* conn = node->outboundClients[i].connection;
|
||||||
if (!conn) continue;
|
if (!conn) continue;
|
||||||
if (conn == sourceConn) continue;
|
if (conn == sourceConn) continue;
|
||||||
if (sourceIp != 0 && conn->peerAddr.sin_addr.s_addr == sourceIp) continue;
|
if (sourceConn && TcpConnection_PeerAddrEqual(conn, sourceConn)) continue;
|
||||||
Node_SendPacket(node, conn, PACKET_TYPE_BROADCAST_BLOCK, payload, off);
|
Node_SendPacket(node, conn, PACKET_TYPE_BROADCAST_BLOCK, payload, off);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&node->outboundLock);
|
pthread_mutex_unlock(&node->outboundLock);
|
||||||
@@ -916,3 +1304,18 @@ void Node_BroadcastChainRange(net_node_t* node, size_t startHeightInclusive, tcp
|
|||||||
Block_Destroy(blk);
|
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);
|
||||||
|
}
|
||||||
@@ -197,13 +197,8 @@ size_t OrphanPool_AttemptAttach(blockchain_t* chain) {
|
|||||||
i = (size_t)-1; // reset outer loop
|
i = (size_t)-1; // reset outer loop
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// Chain_AddBlock rejected it (maybe invalid). Drop it.
|
// Keep the orphan around; rejection may be temporary while the local tip is being reorged.
|
||||||
Block_Destroy(e->block);
|
continue;
|
||||||
DynArr_remove(g_orphans, i);
|
|
||||||
n = DynArr_size(g_orphans);
|
|
||||||
i = (size_t)-1;
|
|
||||||
madeProgress = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-11
@@ -3,6 +3,7 @@
|
|||||||
#include <tcpd/tcpclient.h>
|
#include <tcpd/tcpclient.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <numgen.h>
|
#include <numgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -82,18 +83,34 @@ int TcpClient_Connect(
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
|
// Detect address family from the IP string
|
||||||
if (sockFd < 0) {
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in peerAddr;
|
int sockFd = socket(af, SOCK_STREAM, 0);
|
||||||
memset(&peerAddr, 0, sizeof(peerAddr));
|
if (sockFd < 0) {
|
||||||
peerAddr.sin_family = AF_INET;
|
|
||||||
peerAddr.sin_port = htons(peerPort);
|
|
||||||
|
|
||||||
if (inet_pton(AF_INET, peerIp, &peerAddr.sin_addr) <= 0) {
|
|
||||||
close(sockFd);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +119,7 @@ int TcpClient_Connect(
|
|||||||
if (flags == -1) flags = 0;
|
if (flags == -1) flags = 0;
|
||||||
fcntl(sockFd, F_SETFL, flags | O_NONBLOCK);
|
fcntl(sockFd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
int rc = connect(sockFd, (struct sockaddr*)&peerAddr, sizeof(peerAddr));
|
int rc = connect(sockFd, pSockAddr, sockAddrLen);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
if (errno != EINPROGRESS) {
|
if (errno != EINPROGRESS) {
|
||||||
close(sockFd);
|
close(sockFd);
|
||||||
@@ -143,13 +160,18 @@ int TcpClient_Connect(
|
|||||||
// Restore blocking mode
|
// Restore blocking mode
|
||||||
fcntl(sockFd, F_SETFL, flags & ~O_NONBLOCK);
|
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));
|
tcp_connection_t* conn = (tcp_connection_t*)malloc(sizeof(*conn));
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
close(sockFd);
|
close(sockFd);
|
||||||
return -1;
|
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);
|
free(conn);
|
||||||
close(sockFd);
|
close(sockFd);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.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) {
|
if (!conn || sockFd < 0 || !peerAddr) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ int TcpConnection_Init(tcp_connection_t* conn, int sockFd, const struct sockaddr
|
|||||||
memset(conn, 0, sizeof(*conn));
|
memset(conn, 0, sizeof(*conn));
|
||||||
conn->sockFd = sockFd;
|
conn->sockFd = sockFd;
|
||||||
conn->peerAddr = *peerAddr;
|
conn->peerAddr = *peerAddr;
|
||||||
|
conn->addrFamily = peerAddr->ss_family;
|
||||||
conn->role = role;
|
conn->role = role;
|
||||||
|
|
||||||
if (pthread_mutex_init(&conn->sendLock, NULL) != 0) {
|
if (pthread_mutex_init(&conn->sendLock, NULL) != 0) {
|
||||||
@@ -261,4 +262,58 @@ bool TcpConnection_IsDisconnectNotified(tcp_connection_t* conn) {
|
|||||||
return notified;
|
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
|
#endif
|
||||||
|
|||||||
+105
-19
@@ -3,6 +3,7 @@
|
|||||||
#include <tcpd/tcpserver.h>
|
#include <tcpd/tcpserver.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <numgen.h>
|
#include <numgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -10,6 +11,11 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.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) {
|
static void TcpServer_RemoveClientByPtrUnlocked(tcp_server_t* svr, tcp_connection_t* cli) {
|
||||||
if (!svr || !svr->clientsArrPtr || !cli) {
|
if (!svr || !svr->clientsArrPtr || !cli) {
|
||||||
return;
|
return;
|
||||||
@@ -70,15 +76,20 @@ static void* TcpServer_clientthreadprocess(void* ptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void* TcpServer_threadprocess(void* ptr) {
|
static void* TcpServer_threadprocess(void* ptr) {
|
||||||
tcp_server_t* svr = (tcp_server_t*)ptr;
|
tcpaccept_thread_args_t* args = (tcpaccept_thread_args_t*)ptr;
|
||||||
if (!svr) {
|
if (!args || !args->serverPtr) {
|
||||||
|
free(args);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tcp_server_t* svr = args->serverPtr;
|
||||||
|
int listenFd = args->listenFd;
|
||||||
|
free(args);
|
||||||
|
|
||||||
while (svr->isRunning) {
|
while (svr->isRunning) {
|
||||||
struct sockaddr_in clientAddr;
|
struct sockaddr_storage clientAddr;
|
||||||
socklen_t clientSize = sizeof(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 (clientFd < 0) {
|
||||||
if (!svr->isRunning) {
|
if (!svr->isRunning) {
|
||||||
@@ -168,7 +179,9 @@ tcp_server_t* TcpServer_Create() {
|
|||||||
|
|
||||||
memset(svr, 0, sizeof(*svr));
|
memset(svr, 0, sizeof(*svr));
|
||||||
svr->sockFd = -1;
|
svr->sockFd = -1;
|
||||||
|
svr->sockFdV4 = -1;
|
||||||
svr->svrThread = 0;
|
svr->svrThread = 0;
|
||||||
|
svr->svrThreadV4 = 0;
|
||||||
svr->isRunning = 0;
|
svr->isRunning = 0;
|
||||||
svr->maxClients = 0;
|
svr->maxClients = 0;
|
||||||
svr->clientsArrPtr = NULL;
|
svr->clientsArrPtr = NULL;
|
||||||
@@ -200,31 +213,65 @@ void TcpServer_Init(tcp_server_t* ptr, unsigned short port, const char* addr) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->sockFd = socket(AF_INET, SOCK_STREAM, 0);
|
ptr->opt = 1;
|
||||||
if (ptr->sockFd < 0) {
|
|
||||||
return;
|
// 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;
|
// IPv4 (always attempted regardless of IPv6 result)
|
||||||
setsockopt(ptr->sockFd, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(int));
|
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));
|
struct sockaddr_in a4;
|
||||||
ptr->addr.sin_family = AF_INET;
|
memset(&a4, 0, sizeof(a4));
|
||||||
ptr->addr.sin_port = htons(port);
|
a4.sin_family = AF_INET;
|
||||||
inet_pton(AF_INET, addr, &ptr->addr.sin_addr);
|
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) {
|
if (bind(fd4, (struct sockaddr*)&a4, sizeof(a4)) == 0) {
|
||||||
close(ptr->sockFd);
|
ptr->sockFdV4 = fd4;
|
||||||
ptr->sockFd = -1;
|
} else {
|
||||||
|
close(fd4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
|
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;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +292,35 @@ void TcpServer_Start(tcp_server_t* ptr, int maxcons) {
|
|||||||
ptr->isRunning = 1;
|
ptr->isRunning = 1;
|
||||||
pthread_mutex_unlock(&ptr->clientsMutex);
|
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);
|
pthread_mutex_lock(&ptr->clientsMutex);
|
||||||
ptr->isRunning = 0;
|
ptr->isRunning = 0;
|
||||||
free(ptr->clientsArrPtr);
|
free(ptr->clientsArrPtr);
|
||||||
@@ -268,11 +343,22 @@ void TcpServer_Stop(tcp_server_t* ptr) {
|
|||||||
ptr->sockFd = -1;
|
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())) {
|
if (ptr->svrThread != 0 && !pthread_equal(ptr->svrThread, pthread_self())) {
|
||||||
pthread_join(ptr->svrThread, NULL);
|
pthread_join(ptr->svrThread, NULL);
|
||||||
}
|
}
|
||||||
ptr->svrThread = 0;
|
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);
|
pthread_mutex_lock(&ptr->clientsMutex);
|
||||||
size_t maxClients = ptr->maxClients;
|
size_t maxClients = ptr->maxClients;
|
||||||
tcp_connection_t** local = ptr->clientsArrPtr;
|
tcp_connection_t** local = ptr->clientsArrPtr;
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
static pthread_mutex_t g_txMempoolLock;
|
||||||
|
static bool g_txMempoolLockInitialized = false;
|
||||||
|
|
||||||
khash_t(tx_mempool_map_m)* txMempool = NULL;
|
khash_t(tx_mempool_map_m)* txMempool = NULL;
|
||||||
|
|
||||||
void TxMempool_Init() {
|
void TxMempool_Init() {
|
||||||
txMempool = kh_init(tx_mempool_map_m);
|
txMempool = kh_init(tx_mempool_map_m);
|
||||||
|
pthread_mutex_init(&g_txMempoolLock, NULL);
|
||||||
|
g_txMempoolLockInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TxMempool_Insert(signed_transaction_t tx) {
|
int TxMempool_Insert(signed_transaction_t tx) {
|
||||||
if (!txMempool) { return -1; }
|
if (!txMempool) { return -1; }
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
uint8_t txHash[32];
|
uint8_t txHash[32];
|
||||||
Transaction_CalculateHash(&tx, txHash);
|
Transaction_CalculateHash(&tx, txHash);
|
||||||
|
|
||||||
@@ -18,17 +25,21 @@ int TxMempool_Insert(signed_transaction_t tx) {
|
|||||||
int ret;
|
int ret;
|
||||||
khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret);
|
khiter_t k = kh_put(tx_mempool_map_m, txMempool, key, &ret);
|
||||||
if (k == kh_end(txMempool)) {
|
if (k == kh_end(txMempool)) {
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
kh_value(txMempool, k) = tx;
|
kh_value(txMempool, k) = tx;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
|
bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
|
||||||
if (!txMempool || !txHash || !out) { return false; }
|
if (!txMempool || !txHash || !out) { return false; }
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
key32_t key;
|
key32_t key;
|
||||||
memcpy(key.bytes, txHash, 32);
|
memcpy(key.bytes, txHash, 32);
|
||||||
|
|
||||||
@@ -36,15 +47,65 @@ bool TxMempool_Lookup(uint8_t* txHash, signed_transaction_t* out) {
|
|||||||
if (k != kh_end(txMempool)) {
|
if (k != kh_end(txMempool)) {
|
||||||
signed_transaction_t tx = kh_value(txMempool, k);
|
signed_transaction_t tx = kh_value(txMempool, k);
|
||||||
memcpy(out, &tx, sizeof(signed_transaction_t));
|
memcpy(out, &tx, sizeof(signed_transaction_t));
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TxMempool_Snapshot(signed_transaction_t** outTxs, size_t* outCount) {
|
||||||
|
if (!outTxs || !outCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outTxs = NULL;
|
||||||
|
*outCount = 0;
|
||||||
|
|
||||||
|
if (!txMempool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
khiter_t k;
|
||||||
|
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||||
|
if (kh_exist(txMempool, k)) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
signed_transaction_t* snapshot = (signed_transaction_t*)malloc(count * sizeof(signed_transaction_t));
|
||||||
|
if (!snapshot) {
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||||
|
if (kh_exist(txMempool, k)) {
|
||||||
|
snapshot[index++] = kh_value(txMempool, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
|
||||||
|
*outTxs = snapshot;
|
||||||
|
*outCount = count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void TxMempool_Print() {
|
void TxMempool_Print() {
|
||||||
if (!txMempool) { return; }
|
if (!txMempool) { return; }
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
khiter_t k;
|
khiter_t k;
|
||||||
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
for (k = kh_begin(txMempool); k != kh_end(txMempool); ++k) {
|
||||||
if (kh_exist(txMempool, k)) {
|
if (kh_exist(txMempool, k)) {
|
||||||
@@ -62,10 +123,37 @@ void TxMempool_Print() {
|
|||||||
(unsigned long long)tx.transaction.fee);
|
(unsigned long long)tx.transaction.fee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxMempool_Destroy() {
|
void TxMempool_Destroy() {
|
||||||
if (txMempool) {
|
if (txMempool) {
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
kh_destroy(tx_mempool_map_m, txMempool);
|
kh_destroy(tx_mempool_map_m, txMempool);
|
||||||
|
txMempool = NULL;
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_txMempoolLockInitialized) {
|
||||||
|
pthread_mutex_destroy(&g_txMempoolLock);
|
||||||
|
g_txMempoolLockInitialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TxMempool_Remove(const uint8_t* txHash) {
|
||||||
|
if (!txMempool || !txHash) { return false; }
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_txMempoolLock);
|
||||||
|
key32_t key;
|
||||||
|
memcpy(key.bytes, txHash, 32);
|
||||||
|
|
||||||
|
khiter_t k = kh_get(tx_mempool_map_m, txMempool, key);
|
||||||
|
if (k == kh_end(txMempool)) {
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
kh_del(tx_mempool_map_m, txMempool, k);
|
||||||
|
pthread_mutex_unlock(&g_txMempoolLock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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