Skip to content

Commit da61305

Browse files
authored
chore(cbind): nim-ffi migration [8/9] — relay, peerstore, metrics (API parity) (#2779)
1 parent 1ce12fb commit da61305

7 files changed

Lines changed: 670 additions & 41 deletions

File tree

cbind/cbind.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ task examples, "Build and run the C bindings examples":
151151
cborObjs.add obj
152152
let cborObjsStr = cborObjs.join(" ")
153153

154-
for example in ["echo", "gossipsub", "kad"]:
154+
for example in ["echo", "gossipsub", "kad", "relay", "peerstore", "metrics"]:
155155
let outBin = "../build/" & example
156156
exec "gcc -std=c11 -O2 -I c_bindings -I " & vendor & " examples/" & example & ".c " &
157157
cborObjsStr & " " & lib & " -pthread -Wl,-rpath,'$ORIGIN' -o " & outBin

cbind/examples/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ set_property(TARGET tinycbor PROPERTY POSITION_INDEPENDENT_CODE ON)
6262

6363
find_package(Threads REQUIRED)
6464

65-
foreach(example echo gossipsub kad)
65+
foreach(example echo gossipsub kad relay peerstore metrics)
6666
add_executable(${example} "${example}.c")
6767
target_include_directories(${example} PRIVATE "${GEN_DIR}")
6868
target_link_libraries(${example} PRIVATE "${LIB_FILE}" tinycbor Threads::Threads)

cbind/examples/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ The bindings are a header-only C API over a C ABI. Every generated method is asy
77
- `echo.c` — two TCP nodes; the server mounts a custom `/cbind/echo/1.0.0` protocol and echoes the bytes it reads, the client dials it and verifies the round-trip. The incoming-stream handler only hands the stream id to `main`, which serves it inline, since calling back into the library from the dispatch thread would deadlock.
88
- `gossipsub.c` — two TCP nodes join a shared topic and exchange one message, delivered to the subscriber through its pub/sub-message listener.
99
- `kad.c` — two TCP nodes form a Kademlia DHT (a server, and a client that lists the server as its bootstrap node), then round-trip a value (`kad_put_value` / `kad_get_value`) and a provider record (`create_cid`, `kad_start_providing` / `kad_get_providers`) over the wire.
10+
- `relay.c` — three TCP nodes: a relay, a destination and a source. The destination reserves a slot on the relay (`circuit_relay_reserve`); the source reaches it over a `/p2p-circuit` address (`dial_circuit_relay`) and sends a message the destination reads off its relayed stream.
11+
- `peerstore.c` — drives one node's peerstore directly from a second node's PeerInfo: `peerstore_add_peer`, `peerstore_get_peers`, `peerstore_get_peer_info`, then `peerstore_delete_peer`. No dialing required.
12+
- `metrics.c` — a running node dumps the process-wide Prometheus registry as JSON via `collect_metrics`.
1013

1114
## Build
1215

@@ -38,6 +41,9 @@ cmake -S . -B build -DTINYCBOR_VENDOR=$(nimble path ffi)/ffi/codegen/templates/c
3841
./build/echo
3942
./build/gossipsub
4043
./build/kad
44+
./build/relay
45+
./build/peerstore
46+
./build/metrics
4147
```
4248

4349
Each program exits `0` on success and prints the values it exchanged over the wire.

cbind/examples/metrics.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Metrics: a running node dumps the process-wide Prometheus registry as JSON
2+
// via libp2p_ctx_collect_metrics. The registry is global, so one started node
3+
// is enough to populate it. Calls are made blocking with the helpers in
4+
// common.h.
5+
#include "common.h"
6+
7+
// TransportType / MuxerType ordinals, mirrored from libp2p_ffi/config.nim.
8+
static const int64_t TransportTcp = 1;
9+
static const int64_t MuxerMplex = 0;
10+
11+
// collect_metrics replies with a JSON string (Result[string]).
12+
typedef struct {
13+
atomic_int done;
14+
int err_code;
15+
char err[256];
16+
char json[1 << 16];
17+
size_t len;
18+
} MetricsWaiter;
19+
20+
static void on_metrics(int ec, const NimFfiStr *reply, const char *em,
21+
void *ud) {
22+
MetricsWaiter *w = (MetricsWaiter *)ud;
23+
w->err_code = ec;
24+
if (reply && reply->data) {
25+
w->len =
26+
reply->len < sizeof(w->json) - 1 ? reply->len : sizeof(w->json) - 1;
27+
memcpy(w->json, reply->data, w->len);
28+
w->json[w->len] = '\0';
29+
}
30+
if (em)
31+
snprintf(w->err, sizeof(w->err), "%s", em);
32+
atomic_store(&w->done, 1);
33+
}
34+
35+
int main(void) {
36+
NimFfiStr addr = nimffi_str("/ip4/127.0.0.1/tcp/5041");
37+
Libp2pConfig cfg;
38+
memset(&cfg, 0, sizeof(cfg));
39+
cfg.addrs.data = &addr;
40+
cfg.addrs.len = 1;
41+
cfg.muxer = MuxerMplex;
42+
cfg.transport = TransportTcp;
43+
44+
LibP2PCtx *node = await_create(&cfg, "node");
45+
if (!node)
46+
return 1;
47+
48+
int status = 1;
49+
BoolWaiter bw;
50+
if (!AWAIT_BOOL(bw, libp2p_ctx_start(node, on_bool, &bw), "start"))
51+
goto cleanup;
52+
53+
MetricsWaiter mw;
54+
memset(&mw, 0, sizeof(mw));
55+
libp2p_ctx_collect_metrics(node, on_metrics, &mw);
56+
if (!wait_done(&mw.done) || mw.err_code != 0) {
57+
fprintf(stderr, "collect_metrics: %s\n", mw.err[0] ? mw.err : "unknown");
58+
goto cleanup;
59+
}
60+
61+
// A JSON array of {name,type,help,labels,value,timestamp} objects; print a
62+
// prefix rather than the whole document.
63+
printf("Collected %zu bytes of metrics\n", mw.len);
64+
printf("%.400s%s\n", mw.json, mw.len > 400 ? " ..." : "");
65+
66+
if (mw.json[0] == '[' && strstr(mw.json, "\"name\""))
67+
status = 0;
68+
else
69+
fprintf(stderr, "Error: metrics payload was empty or malformed\n");
70+
71+
cleanup:
72+
AWAIT_BOOL(bw, libp2p_ctx_stop(node, on_bool, &bw), "stop");
73+
libp2p_ctx_destroy(node);
74+
return status;
75+
}

cbind/examples/peerstore.c

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Peerstore: a node's local record of other peers. This drives the store
2+
// directly — no dialing — using a second node's real PeerInfo: add the peer,
3+
// list it, read it back, then delete it. Calls are made blocking via common.h.
4+
#include "common.h"
5+
6+
// TransportType / MuxerType ordinals, mirrored from libp2p_ffi/config.nim.
7+
static const int64_t TransportTcp = 1;
8+
static const int64_t MuxerMplex = 0;
9+
10+
static const char *Proto = "/cbind/peerstore/1.0.0";
11+
12+
// get_peers replies with a PeersResponse of peer-id strings.
13+
#define MAX_PEERS 16
14+
typedef struct {
15+
atomic_int done;
16+
int err_code;
17+
char err[256];
18+
char peerIds[MAX_PEERS][128];
19+
size_t n;
20+
} PeersWaiter;
21+
22+
static void on_peers(int ec, const PeersResponse *reply, const char *em,
23+
void *ud) {
24+
PeersWaiter *w = (PeersWaiter *)ud;
25+
w->err_code = ec;
26+
if (reply) {
27+
w->n = reply->peerIds.len < MAX_PEERS ? reply->peerIds.len : MAX_PEERS;
28+
for (size_t i = 0; i < w->n; i++)
29+
if (reply->peerIds.data[i].data)
30+
snprintf(w->peerIds[i], sizeof(w->peerIds[i]), "%s",
31+
reply->peerIds.data[i].data);
32+
}
33+
if (em)
34+
snprintf(w->err, sizeof(w->err), "%s", em);
35+
atomic_store(&w->done, 1);
36+
}
37+
38+
static bool get_peers(LibP2PCtx *ctx, PeersWaiter *w) {
39+
memset(w, 0, sizeof(*w));
40+
libp2p_ctx_peerstore_get_peers(ctx, on_peers, w);
41+
if (!wait_done(&w->done) || w->err_code != 0) {
42+
fprintf(stderr, "get_peers: %s\n", w->err[0] ? w->err : "unknown");
43+
return false;
44+
}
45+
return true;
46+
}
47+
48+
static bool holds(const PeersWaiter *w, const char *peerId) {
49+
for (size_t i = 0; i < w->n; i++)
50+
if (strcmp(w->peerIds[i], peerId) == 0)
51+
return true;
52+
return false;
53+
}
54+
55+
// get_peer_info replies with the full entry; we only report its counts here.
56+
typedef struct {
57+
atomic_int done;
58+
int err_code;
59+
char err[256];
60+
size_t naddrs, nprotocols;
61+
} EntryWaiter;
62+
63+
static void on_entry(int ec, const PeerStoreEntryResponse *reply,
64+
const char *em, void *ud) {
65+
EntryWaiter *w = (EntryWaiter *)ud;
66+
w->err_code = ec;
67+
if (reply) {
68+
w->naddrs = reply->addrs.len;
69+
w->nprotocols = reply->protocols.len;
70+
}
71+
if (em)
72+
snprintf(w->err, sizeof(w->err), "%s", em);
73+
atomic_store(&w->done, 1);
74+
}
75+
76+
static LibP2PCtx *createNode(const char *addr, const char *label) {
77+
NimFfiStr slot = nimffi_str(addr);
78+
Libp2pConfig cfg;
79+
memset(&cfg, 0, sizeof(cfg));
80+
cfg.addrs.data = &slot;
81+
cfg.addrs.len = 1;
82+
cfg.muxer = MuxerMplex;
83+
cfg.transport = TransportTcp;
84+
return await_create(&cfg, label);
85+
}
86+
87+
int main(void) {
88+
int status = 1;
89+
BoolWaiter bw;
90+
91+
// `local` owns the peerstore we drive; `other` just hands us a real peer id
92+
// and listen addresses to store.
93+
LibP2PCtx *local = createNode("/ip4/127.0.0.1/tcp/5051", "local");
94+
LibP2PCtx *other = createNode("/ip4/127.0.0.1/tcp/5052", "other");
95+
if (!local || !other) {
96+
libp2p_ctx_destroy(local);
97+
libp2p_ctx_destroy(other);
98+
return 1;
99+
}
100+
101+
// Start `other` so its listen addresses are bound and reported by peer_info.
102+
if (!AWAIT_BOOL(bw, libp2p_ctx_start(other, on_bool, &bw), "start other"))
103+
goto cleanup;
104+
PeerInfoWaiter oi;
105+
if (!await_peerinfo(other, &oi, "other peerinfo"))
106+
goto cleanup;
107+
printf("Peer to store: %s\n", oi.peerId);
108+
109+
// add_peer: merge the peer's addresses and a protocol into the store.
110+
NimFfiStr addrs[MAX_ADDRS];
111+
for (size_t i = 0; i < oi.naddrs; i++)
112+
addrs[i] = nimffi_str(oi.addrs[i]);
113+
NimFfiStr proto = nimffi_str(Proto);
114+
AddPeerRequest addReq = {
115+
nimffi_str(oi.peerId), {addrs, oi.naddrs}, {&proto, 1}};
116+
if (!AWAIT_BOOL(bw,
117+
libp2p_ctx_peerstore_add_peer(local, &addReq, on_bool, &bw),
118+
"add_peer"))
119+
goto cleanup;
120+
121+
// get_peers + get_peer_info: the peer is now listed and readable.
122+
PeersWaiter peers;
123+
if (!get_peers(local, &peers))
124+
goto cleanup;
125+
EntryWaiter entry;
126+
memset(&entry, 0, sizeof(entry));
127+
libp2p_ctx_peerstore_get_peer_info(local, nimffi_str(oi.peerId), on_entry,
128+
&entry);
129+
if (!wait_done(&entry.done) || entry.err_code != 0) {
130+
fprintf(stderr, "get_peer_info: %s\n",
131+
entry.err[0] ? entry.err : "unknown");
132+
goto cleanup;
133+
}
134+
printf("Stored: %zu peer(s), entry has %zu address(es) and %zu protocol(s)\n",
135+
peers.n, entry.naddrs, entry.nprotocols);
136+
if (!holds(&peers, oi.peerId) || entry.naddrs == 0 || entry.nprotocols == 0) {
137+
fprintf(stderr, "Error: peer was not stored correctly\n");
138+
goto cleanup;
139+
}
140+
141+
// delete_peer: the store no longer holds it.
142+
if (!AWAIT_BOOL(bw,
143+
libp2p_ctx_peerstore_delete_peer(local, nimffi_str(oi.peerId),
144+
on_bool, &bw),
145+
"delete_peer"))
146+
goto cleanup;
147+
if (!get_peers(local, &peers))
148+
goto cleanup;
149+
if (holds(&peers, oi.peerId)) {
150+
fprintf(stderr, "Error: peer still present after delete_peer\n");
151+
goto cleanup;
152+
}
153+
printf("Deleted %s\n", oi.peerId);
154+
status = 0;
155+
156+
cleanup:
157+
AWAIT_BOOL(bw, libp2p_ctx_stop(other, on_bool, &bw), "stop other");
158+
libp2p_ctx_destroy(other);
159+
libp2p_ctx_destroy(local);
160+
return status;
161+
}

0 commit comments

Comments
 (0)