Skip to content

Commit 8a515be

Browse files
authored
Merge pull request #107 from TheBlueMatt/main
[TS] Properly export unitary enums such that they're in *.d.mjs
2 parents 711bc9c + e7c70ad commit 8a515be

File tree

18 files changed

+496
-19
lines changed

18 files changed

+496
-19
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ jobs:
9696
- name: Check latest TS files are in git
9797
run: |
9898
git checkout ts/package.json
99+
git checkout node-net/package.json
99100
git diff --exit-code
100101
101102
java_bindings:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ The TypeScript bindings require modern web standards, including support for `Fin
6262
and `WeakRef` (Chrome 84, Firefox 79, Safari 14.1/iOS 14.5 and Node 14.6) and WASM BigInt support
6363
(Chrome 85, Firefox 78, Safari 14.1/iOS 14.5, and Node 15.0).
6464

65+
For users of Node.JS environments you may wish to use the `lightningdevkit-node-net` package as
66+
well to implement the required network handling to bridge the `lightningdevkit` package's
67+
`SocketDescriptor` interface to Node.JS TCP Sockets. For those wishing to run a lightning node in
68+
the browser you will need to provide your own bridge from `SocketDescriptor` to a WebSocket proxy.
69+
6570
## General
6671

6772
The only known issue resulting in a use-after-free bug requires custom a custom ChannelKeys instance

genbindings.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ if [ "$2" != "wasm" ]; then
159159
popd
160160
LDK_LIB="tmp/libldk.bc tmp/libldk.a"
161161
fi
162-
$COMPILE -o liblightningjni_release$LDK_TARGET_SUFFIX.so -flto -O3 -I"$1"/lightning-c-bindings/include/ $2 src/main/jni/bindings.c $LDK_LIB -lm
162+
$COMPILE -o liblightningjni_release$LDK_TARGET_SUFFIX.so -s -flto -O3 -I"$1"/lightning-c-bindings/include/ $2 src/main/jni/bindings.c $LDK_LIB -lm
163163
if [ "$IS_MAC" = "false" -a "$4" = "false" ]; then
164164
GLIBC_SYMBS="$(objdump -T liblightningjni_release$LDK_TARGET_SUFFIX.so | grep GLIBC_ | grep -v "GLIBC_2\.2\." | grep -v "GLIBC_2\.3\(\.\| \)" | grep -v "GLIBC_2.\(14\|17\) " || echo)"
165165
if [ "$GLIBC_SYMBS" != "" ]; then
@@ -190,6 +190,8 @@ else
190190
fi
191191
rm -f ts/bindings.c
192192
sed -i 's/^ "version": .*/ "version": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'",/g' ts/package.json
193+
sed -i 's/^ "version": .*/ "version": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'",/g' node-net/package.json
194+
sed -i 's/^ "lightningdevkit": .*/ "lightningdevkit": "'${LDK_GARBAGECOLLECTED_GIT_OVERRIDE:1:100}'"/g' node-net/package.json
193195
if [ "$3" = "true" ]; then
194196
echo "#define LDK_DEBUG_BUILD" > ts/bindings.c
195197
elif [ "$3" = "leaks" ]; then
@@ -224,11 +226,16 @@ else
224226
tsc --types node --typeRoots .
225227
cp ../$WASM_FILE liblightningjs.wasm
226228
cp ../README.md README.md
229+
cd ../node-net
230+
tsc --types node --typeRoots .
227231
echo Ready to publish!
228232
if [ -x "$(which node)" ]; then
229233
NODE_V="$(node --version)"
230234
if [ "${NODE_V:1:2}" -gt 14 ]; then
235+
cd ../ts
231236
node test/node.mjs
237+
cd ../node-net
238+
node test/test.mjs
232239
fi
233240
fi
234241
fi

node-net/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
LDK Node.JS TypeScript Network Implementation
2+
=============================================
3+
4+
This module bridges the LDK `SocketDescriptor` and `PeerManager` interfaces to Node.JS's `net`
5+
TCP sockets. See the `lightningdevkit` module for more info.

node-net/net.mts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import * as ldk from "lightningdevkit";
2+
import * as net from "net";
3+
4+
/**
5+
* Handles TCP connections using Node.JS's 'net' module given an `ldk.PeerManager`.
6+
*/
7+
export class NodeLDKNet {
8+
private ping_timer;
9+
private servers: net.Server[];
10+
public constructor(public peer_manager: ldk.PeerManager) {
11+
this.ping_timer = setInterval(function() {
12+
peer_manager.timer_tick_occurred();
13+
peer_manager.process_events();
14+
}, 10_000);
15+
this.servers = [];
16+
}
17+
18+
/**
19+
* Disconnects all connections and releases all resources for this net handler.
20+
*/
21+
public stop() {
22+
clearInterval(this.ping_timer);
23+
for (const server of this.servers) {
24+
server.close();
25+
}
26+
this.peer_manager.disconnect_all_peers();
27+
}
28+
29+
/**
30+
* Processes any pending events for the PeerManager, sending queued messages.
31+
* You should call this (or peer_manager.process_events()) any time you take an action which
32+
* is likely to generate messages to send (eg send a payment, processing payment forwards,
33+
* etc).
34+
*/
35+
public process_events() { this.peer_manager.process_events(); }
36+
37+
private descriptor_count = BigInt(0);
38+
private get_descriptor(socket: net.Socket): ldk.SocketDescriptor {
39+
const this_index = this.descriptor_count;
40+
this.descriptor_count += BigInt(1);
41+
42+
socket.setNoDelay(true);
43+
44+
const this_pm = this.peer_manager;
45+
var sock_write_waiting = false;
46+
47+
let descriptor = ldk.SocketDescriptor.new_impl ({
48+
send_data(data: Uint8Array, resume_read: boolean): number {
49+
if (resume_read) socket.resume();
50+
51+
if (sock_write_waiting) return 0;
52+
const written = socket.write(data);
53+
if (!written) sock_write_waiting = true;
54+
return data.length;
55+
},
56+
disconnect_socket(): void {
57+
socket.destroy();
58+
},
59+
eq(other: ldk.SocketDescriptor): boolean {
60+
return other.hash() == this.hash();
61+
},
62+
hash(): bigint {
63+
return this_index;
64+
}
65+
} as ldk.SocketDescriptorInterface);
66+
67+
socket.on("drain", function() {
68+
if (sock_write_waiting) {
69+
if (!this_pm.write_buffer_space_avail(descriptor).is_ok()) {
70+
descriptor.disconnect_socket();
71+
}
72+
}
73+
});
74+
75+
socket.on("data", function(data) {
76+
const res = this_pm.read_event(descriptor, data);
77+
if (!res.is_ok()) descriptor.disconnect_socket();
78+
else if ((res as ldk.Result_boolPeerHandleErrorZ_OK).res) socket.pause();
79+
this_pm.process_events();
80+
});
81+
82+
socket.on("close", function() {
83+
this_pm.socket_disconnected(descriptor);
84+
});
85+
86+
return descriptor;
87+
}
88+
89+
private static v4_addr_from_ip(ip: string, port: number): ldk.NetAddress {
90+
const sockaddr = ip.split(".").map(parseFloat);
91+
return ldk.NetAddress.constructor_ipv4(new Uint8Array(sockaddr), port);
92+
}
93+
private static v6_addr_from_ip(ip: string, port: number): ldk.NetAddress {
94+
const sockaddr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
95+
const halves = ip.split("::"); // either one or two elements
96+
const first_half = halves[0].split(":");
97+
for (var idx = 0; idx < first_half.length; idx++) {
98+
const v = parseInt(first_half[idx], 16);
99+
sockaddr[idx*2] = v >> 8;
100+
sockaddr[idx*2 + 1] = v & 0xff;
101+
}
102+
if (halves.length == 2) {
103+
const second_half = halves[1].split(":");
104+
for (var idx = 0; idx < second_half.length; idx++) {
105+
const v = parseInt(second_half[second_half.length - idx - 1], 16);
106+
sockaddr[14 - idx*2] = v >> 8;
107+
sockaddr[15 - idx*2] = v & 0xff;
108+
}
109+
}
110+
return ldk.NetAddress.constructor_ipv6(new Uint8Array(sockaddr), port);
111+
}
112+
113+
private static get_addr_from_socket(socket: net.Socket): ldk.Option_NetAddressZ {
114+
const addr = socket.remoteAddress;
115+
if (addr === undefined)
116+
return ldk.Option_NetAddressZ.constructor_none();
117+
if (net.isIPv4(addr)) {
118+
return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v4_addr_from_ip(addr, socket.remotePort));
119+
}
120+
if (net.isIPv6(addr)) {
121+
return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v6_addr_from_ip(addr, socket.remotePort));
122+
}
123+
return ldk.Option_NetAddressZ.constructor_none();
124+
}
125+
126+
/**
127+
* Binds a listener on the given host and port, accepting incoming connections.
128+
*/
129+
public async bind_listener(host: string, port: number) {
130+
const this_handler = this;
131+
const server = net.createServer(function(incoming_sock: net.Socket) {
132+
const descriptor = this_handler.get_descriptor(incoming_sock);
133+
const res = this_handler.peer_manager
134+
.new_inbound_connection(descriptor, NodeLDKNet.get_addr_from_socket(incoming_sock));
135+
if (!res.is_ok()) descriptor.disconnect_socket();
136+
});
137+
const servers_list = this.servers;
138+
return new Promise<void>((resolve, reject) => {
139+
server.on("error", function() {
140+
reject();
141+
server.close();
142+
});
143+
server.on("listening", function() {
144+
servers_list.push(server);
145+
resolve();
146+
});
147+
server.listen(port, host);
148+
});
149+
}
150+
151+
/**
152+
* Establishes an outgoing connection to the given peer at the given host and port.
153+
*
154+
* Note that the peer will not appear in the PeerManager peers list until the socket has
155+
* connected and the initial handshake completes.
156+
*/
157+
public async connect_peer(host: string, port: number, peer_node_id: Uint8Array) {
158+
const this_handler = this;
159+
const sock = new net.Socket();
160+
const res = new Promise<void>((resolve, reject) => {
161+
sock.on("connect", function() { resolve(); });
162+
sock.on("error", function() { reject(); });
163+
});
164+
sock.connect(port, host, function() {
165+
const descriptor = this_handler.get_descriptor(sock);
166+
const res = this_handler.peer_manager
167+
.new_outbound_connection(peer_node_id, descriptor, NodeLDKNet.get_addr_from_socket(sock));
168+
if (!res.is_ok()) descriptor.disconnect_socket();
169+
else {
170+
const bytes = (res as ldk.Result_CVec_u8ZPeerHandleErrorZ_OK).res;
171+
const send_res = descriptor.send_data(bytes, true);
172+
console.assert(send_res == bytes.length);
173+
}
174+
});
175+
return res;
176+
}
177+
}

node-net/node_modules/@types/node

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node-net/node_modules/lightningdevkit

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node-net/package.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "lightningdevkit-node-net",
3+
"version": "Set in genbindings.sh automagically",
4+
"description": "Lightning Development Kit Net Implementation for Node.JS",
5+
"main": "net.mjs",
6+
"types": "net.d.mts",
7+
"type": "module",
8+
"files": [
9+
"net.mjs",
10+
"net.d.mts",
11+
"tsconfig.json",
12+
"README.md"
13+
],
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/lightningdevkit/ldk-garbagecollected.git"
17+
},
18+
"keywords": [
19+
"lightning",
20+
"bitcoin"
21+
],
22+
"author": "LDK Developers",
23+
"license": "MIT OR Apache-2.0",
24+
"bugs": {
25+
"url": "https://github.com/lightningdevkit/ldk-garbagecollected/issues"
26+
},
27+
"homepage": "https://github.com/lightningdevkit/ldk-garbagecollected#readme",
28+
"dependencies": {
29+
"lightningdevkit": "Set in genbindings.sh automagically"
30+
},
31+
"devDependencies": {
32+
"@types/node": "^17.0.8"
33+
}
34+
}

node-net/test/test.mts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as ldk from "../../ts/index.mjs";
2+
import * as node_net from '../net.mjs';
3+
4+
import * as fs from 'fs';
5+
6+
const wasm_file = fs.readFileSync('../ts/liblightningjs.wasm');
7+
await ldk.initializeWasmFromBinary(wasm_file);
8+
9+
const logger_a = ldk.Logger.new_impl({
10+
log(record: ldk.Record): void {
11+
console.log(record.get_module_path() + ": " + record.get_args());
12+
}
13+
} as ldk.LoggerInterface);
14+
const logger_b = logger_a;
15+
16+
const node_a_secret = new Uint8Array(32);
17+
for (var i = 0; i < 32; i++) node_a_secret[i] = 42;
18+
// The public key for a secret key of all 42s:
19+
const node_a_pk = new Uint8Array([3, 91, 229, 233, 71, 130, 9, 103, 74, 150, 230, 15, 31, 3, 127, 97, 118, 84, 15, 208, 1, 250, 29, 100, 105, 71, 112, 197, 106, 119, 9, 196, 44]);
20+
21+
const node_b_secret = new Uint8Array(32);
22+
for (var i = 0; i < 32; i++) node_b_secret[i] = 43;
23+
24+
const rng_seed = new Uint8Array(32);
25+
const routing_handler = ldk.IgnoringMessageHandler.constructor_new().as_RoutingMessageHandler();
26+
const chan_handler = ldk.ErroringMessageHandler.constructor_new().as_ChannelMessageHandler();
27+
const cust_handler = ldk.IgnoringMessageHandler.constructor_new().as_CustomMessageHandler();
28+
29+
const a_pm = ldk.PeerManager.constructor_new(chan_handler, routing_handler, node_a_secret, rng_seed, logger_a, cust_handler);
30+
const a_net_handler = new node_net.NodeLDKNet(a_pm);
31+
var port = 10000;
32+
for (; port < 11000; port++) {
33+
try {
34+
// Try ports until we find one we can bind to.
35+
await a_net_handler.bind_listener("127.0.0.1", port);
36+
break;
37+
} catch(_) {}
38+
}
39+
40+
const b_pm = ldk.PeerManager.constructor_new(chan_handler, routing_handler, node_b_secret, rng_seed, logger_b, cust_handler);
41+
const b_net_handler = new node_net.NodeLDKNet(b_pm);
42+
await b_net_handler.connect_peer("127.0.0.1", port, node_a_pk);
43+
44+
try {
45+
// Ensure we get an error if we try to bind the same port twice.
46+
await a_net_handler.bind_listener("127.0.0.1", port);
47+
console.assert(false);
48+
} catch(_) {}
49+
50+
await new Promise<void>(resolve => {
51+
// Wait until the peers are connected and have exchanged the initial handshake
52+
var timer: ReturnType<typeof setInterval>;
53+
timer = setInterval(function() {
54+
if (a_pm.get_peer_node_ids().length == 1 && b_pm.get_peer_node_ids().length == 1) {
55+
resolve();
56+
clearInterval(timer);
57+
}
58+
}, 500);
59+
});
60+
61+
b_pm.disconnect_by_node_id(node_a_pk, false);
62+
await new Promise<void>(resolve => {
63+
// Wait until A learns the connection is closed from the socket closure
64+
var timer: ReturnType<typeof setInterval>;
65+
timer = setInterval(function() {
66+
if (a_pm.get_peer_node_ids().length == 0 && b_pm.get_peer_node_ids().length == 0) {
67+
resolve();
68+
clearInterval(timer);
69+
}
70+
}, 500);
71+
});
72+
73+
a_net_handler.stop();
74+
b_net_handler.stop();
75+
76+
function arr_eq(a: number[]|Uint8Array, b: number[]|Uint8Array): boolean {
77+
return a.length == b.length && a.every((val, idx) => val == b[idx]);
78+
}
79+
80+
const v4_parse = node_net.NodeLDKNet["v4_addr_from_ip"];
81+
console.assert((v4_parse("127.0.0.1", 4242) as ldk.NetAddress_IPv4).port == 4242);
82+
console.assert(arr_eq((v4_parse("127.0.0.1", 4242) as ldk.NetAddress_IPv4).addr, [127,0,0,1]));
83+
console.assert(arr_eq((v4_parse("0.0.0.0", 4242) as ldk.NetAddress_IPv4).addr, [0,0,0,0]));
84+
85+
const v6_parse = node_net.NodeLDKNet["v6_addr_from_ip"];
86+
console.assert((v6_parse("::", 4242) as ldk.NetAddress_IPv4).port == 4242);
87+
console.assert(arr_eq((v6_parse("::", 4242) as ldk.NetAddress_IPv6).addr,
88+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]));
89+
console.assert(arr_eq((v6_parse("fe80::", 4242) as ldk.NetAddress_IPv6).addr,
90+
[0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0]));
91+
console.assert(arr_eq((v6_parse("fe80::42", 4242) as ldk.NetAddress_IPv6).addr,
92+
[0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0x42]));
93+
console.assert(arr_eq((v6_parse("fe80:A:b::", 4242) as ldk.NetAddress_IPv6).addr,
94+
[0xfe,0x80,0,0xa,0,0xb,0,0,0,0,0,0,0,0,0,0]));
95+
console.assert(arr_eq((v6_parse("2001:1:bad::beef:cafe", 4242) as ldk.NetAddress_IPv6).addr,
96+
[0x20, 0x01, 0, 1, 0xb, 0xad, 0, 0, 0, 0, 0, 0, 0xbe, 0xef, 0xca, 0xfe]));

0 commit comments

Comments
 (0)