@yz-social/kdht 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dht/kbucket.js +1 -1
- package/dht/node.js +2 -2
- package/dht/nodeProbe.js +2 -2
- package/dht/nodeStorage.js +4 -1
- package/dht/nodeUtilities.js +4 -2
- package/package.json +2 -2
- package/portals/node.js +0 -1
- package/spec/bots.js +7 -9
- package/spec/dhtImplementation.js +1 -1
- package/spec/portal.js +1 -0
- package/transports/contact.js +1 -2
- package/transports/webrtc.js +4 -10
package/dht/kbucket.js
CHANGED
|
@@ -36,7 +36,7 @@ export class KBucket {
|
|
|
36
36
|
}
|
|
37
37
|
async refresh() { // Refresh specified bucket using LocateNodes for a random key in the specified bucket's range.
|
|
38
38
|
if (this.node.isStopped() || !this.contacts.length) return false; // fixme skip isStopped?
|
|
39
|
-
this.node.
|
|
39
|
+
this.node.ilog('refresh bucket', this.index);
|
|
40
40
|
const targetKey = this.randomTarget;
|
|
41
41
|
await this.node.locateNodes(targetKey); // Side-effect is to update this bucket.
|
|
42
42
|
return true;
|
package/dht/node.js
CHANGED
|
@@ -106,7 +106,7 @@ export class Node extends NodeProbe {
|
|
|
106
106
|
return k - remaining;
|
|
107
107
|
}
|
|
108
108
|
async join(contact) {
|
|
109
|
-
this.
|
|
109
|
+
this.ilog('joining', contact.sname);
|
|
110
110
|
contact = this.ensureContact(contact);
|
|
111
111
|
await contact.connect();
|
|
112
112
|
await this.addToRoutingTable(contact);
|
|
@@ -126,7 +126,7 @@ export class Node extends NodeProbe {
|
|
|
126
126
|
// if (!started) started = true;
|
|
127
127
|
// else if (!bucket?.contacts.length) await this.ensureBucket(index).refresh();
|
|
128
128
|
// }
|
|
129
|
-
this.
|
|
129
|
+
this.ilog('joined', contact.sname);
|
|
130
130
|
return this.contact; // Answering this node's home contact is handy for chaining or keeping track of contacts being made and joined.
|
|
131
131
|
}
|
|
132
132
|
}
|
package/dht/nodeProbe.js
CHANGED
|
@@ -86,7 +86,7 @@ export class NodeProbe extends NodeMessages {
|
|
|
86
86
|
let iterationFinished = false;
|
|
87
87
|
|
|
88
88
|
let resolveIteration;
|
|
89
|
-
const iterationPromise = new Promise((resolve) => resolveIteration = (...args) => { iterationFinished = true; resolve(...args) }); // to be resolved with a value result, or undefined
|
|
89
|
+
const iterationPromise = new Promise((resolve) => resolveIteration = (...args) => { iterationFinished = true; resolve(...args); }); // to be resolved with a value result, or undefined
|
|
90
90
|
|
|
91
91
|
// Check if termination condition is met:
|
|
92
92
|
// We're complete when we've found k 'responded' nodes with no unresolved nodes
|
|
@@ -166,7 +166,7 @@ export class NodeProbe extends NodeMessages {
|
|
|
166
166
|
|
|
167
167
|
// Result is array of Helpers (may be empty if node had no new contacts)
|
|
168
168
|
// Merge new helpers into allNodesSeen and track progress
|
|
169
|
-
if (result
|
|
169
|
+
if (result?.length > 0) {
|
|
170
170
|
allNodesSeen.push(...result);
|
|
171
171
|
allNodesSeen.sort(Helper.compare); // Keep sorted by distance (best-first).
|
|
172
172
|
responsesWithoutNewNodes = 0; // reset counter
|
package/dht/nodeStorage.js
CHANGED
|
@@ -12,7 +12,10 @@ export class NodeStorage extends NodeRefresh {
|
|
|
12
12
|
}
|
|
13
13
|
// TODO: The paper says this can be optimized.
|
|
14
14
|
// Claude.ai suggests just writing to the next in line, but that doesn't work.
|
|
15
|
-
this.schedule(key, 'storage', () =>
|
|
15
|
+
this.schedule(key, 'storage', () => {
|
|
16
|
+
this.ilog('refresh value', value, 'at key', key);
|
|
17
|
+
this.storeValue(key, value);
|
|
18
|
+
});
|
|
16
19
|
}
|
|
17
20
|
retrieveLocally(key) { // Retrieve from memory.
|
|
18
21
|
return this.storage.get(key);
|
package/dht/nodeUtilities.js
CHANGED
|
@@ -12,11 +12,13 @@ export class NodeUtilities {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
debug = false;
|
|
15
|
+
info = true;
|
|
15
16
|
get sname() { // The home contact sname, or just name if no contact
|
|
16
17
|
return this.contact?.sname || this.name;
|
|
17
18
|
}
|
|
18
|
-
log(...rest) { if (this.debug)
|
|
19
|
-
|
|
19
|
+
log(...rest) { if (this.debug) this.xlog(new Date(), this.sname, ...rest); }
|
|
20
|
+
ilog(...rest) { if (this.info || this.debug) this.xlog(...rest); }
|
|
21
|
+
xlog(...rest) { console.log(new Date().toISOString(), this.sname, ...rest); }
|
|
20
22
|
static assert(ok, ...rest) { // If !ok, log rests and exit.
|
|
21
23
|
if (ok) return;
|
|
22
24
|
console.error(...rest, new Error("Assert failure").stack); // Not throwing error, because we want to exit. But we are grabbing stack.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yz-social/kdht",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Pure Kademlia base, for testing variations.",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"start": "node spec/portal.js --externalBaseURL https://
|
|
12
|
+
"start": "node spec/portal.js --externalBaseURL https://ki1r0y.com/kdht",
|
|
13
13
|
"stop": "pkill kdht",
|
|
14
14
|
"background": "npm stop; (npm start 1>server.log 2>&1 &); sleep 1",
|
|
15
15
|
"bots": "node spec/bots.js",
|
package/portals/node.js
CHANGED
|
@@ -28,7 +28,6 @@ export async function setup({baseURL, externalBaseURL = '', verbose, fixedSpacin
|
|
|
28
28
|
const bootstrap = joinURL && await contact.ensureRemoteContact(bootstrapName, joinURL);
|
|
29
29
|
process.send(contact.sname); // Report in to server as available for others to bootstrap through.
|
|
30
30
|
if (bootstrap) await contact.join(bootstrap);
|
|
31
|
-
contact.host.xlog('joined');
|
|
32
31
|
process.on('SIGINT', async () => {
|
|
33
32
|
console.log(process.title, 'Shutdown for Ctrl+C');
|
|
34
33
|
await contact.disconnect();
|
package/spec/bots.js
CHANGED
|
@@ -45,8 +45,10 @@ const argv = yargs(hideBin(process.argv))
|
|
|
45
45
|
.parse();
|
|
46
46
|
|
|
47
47
|
const host = uuidv4();
|
|
48
|
+
process.title = 'kdht-bot-' + host;
|
|
48
49
|
|
|
49
50
|
if (cluster.isPrimary) {
|
|
51
|
+
console.log(`${cpus()[0].model}, ${logicalCores} logical cores. Starting ${argv.nBots} over ${Node.refreshTimeIntervalMS/1000} seconds.`);
|
|
50
52
|
for (let i = 1; i < argv.nBots; i++) { // The cluster primary becomes bot 0.
|
|
51
53
|
cluster.fork();
|
|
52
54
|
}
|
|
@@ -56,7 +58,6 @@ if (cluster.isPrimary) {
|
|
|
56
58
|
launchWriteRead(argv.nWrites, argv.baseURL, Node.refreshTimeIntervalMS, argv.verbose);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
|
-
process.title = 'kdht-bot-' + host;
|
|
60
61
|
|
|
61
62
|
await Node.delay(Node.randomInteger(Node.refreshTimeIntervalMS));
|
|
62
63
|
console.log(cluster.worker?.id || 0, host);
|
|
@@ -64,19 +65,17 @@ let contact = await WebContact.create({name: host, debug: argv.verbose});
|
|
|
64
65
|
let bootstrapName = await contact.fetchBootstrap(argv.baseURL);
|
|
65
66
|
let bootstrapContact = await contact.ensureRemoteContact(bootstrapName, argv.baseURL);
|
|
66
67
|
await contact.join(bootstrapContact);
|
|
67
|
-
contact.host.xlog('joined');
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
process.on('SIGINT', async () => {
|
|
70
|
+
console.log(process.title, 'Shutdown for Ctrl+C');
|
|
71
|
+
await contact.disconnect();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
74
|
|
|
75
75
|
while (argv.thrash) {
|
|
76
76
|
await Node.delay(contact.host.fuzzyInterval(Node.refreshTimeIntervalMS));
|
|
77
77
|
const old = contact;
|
|
78
78
|
const next = uuidv4();
|
|
79
|
-
contact.host.xlog('disconnecting');
|
|
80
79
|
await contact.disconnect();
|
|
81
80
|
await Node.delay(1e3); // TODO: remove?
|
|
82
81
|
|
|
@@ -84,6 +83,5 @@ while (argv.thrash) {
|
|
|
84
83
|
bootstrapName = await contact.fetchBootstrap(argv.baseURL);
|
|
85
84
|
bootstrapContact = await contact.ensureRemoteContact(bootstrapName, argv.baseURL);
|
|
86
85
|
await contact.join(bootstrapContact);
|
|
87
|
-
old.host.xlog('rejoined as', next);
|
|
88
86
|
}
|
|
89
87
|
|
|
@@ -16,7 +16,7 @@ export { Node, Contact };
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
export async function start1(name, bootstrapContact, refreshTimeIntervalMS, isServerNode = false) {
|
|
19
|
-
const contact = await Contact.create({name, refreshTimeIntervalMS, isServerNode});
|
|
19
|
+
const contact = await Contact.create({name, refreshTimeIntervalMS, isServerNode, info: false});
|
|
20
20
|
if (bootstrapContact) await contact.join(bootstrapContact);
|
|
21
21
|
return contact;
|
|
22
22
|
}
|
package/spec/portal.js
CHANGED
|
@@ -79,6 +79,7 @@ const argv = yargs(hideBin(process.argv))
|
|
|
79
79
|
if (cluster.isPrimary) { // Parent process with portal webserver through which clienta can bootstrap
|
|
80
80
|
// Our job is to launch some kdht nodes to which clients can connect by signaling through
|
|
81
81
|
// a little web server operated here.
|
|
82
|
+
console.log(`${cpus()[0].model}, ${logicalCores} logical cores.`);
|
|
82
83
|
process.title = 'kdht-portal-server';
|
|
83
84
|
const __filename = fileURLToPath(import.meta.url);
|
|
84
85
|
const __dirname = path.dirname(__filename);
|
package/transports/contact.js
CHANGED
|
@@ -83,7 +83,7 @@ export class Contact {
|
|
|
83
83
|
this.host.removeLooseTransport(this.key); // If any.
|
|
84
84
|
}
|
|
85
85
|
bye() { // The sender is disconnecting from the network
|
|
86
|
-
this.host.
|
|
86
|
+
this.host.ilog('removing disconnected contact', this.sname);
|
|
87
87
|
this.host.removeContact(this).then(bucket => bucket?.resetRefresh('now')); // Accelerate the bucket refresh
|
|
88
88
|
}
|
|
89
89
|
distance(key) { return this.host.constructor.distance(this.key, key); }
|
|
@@ -158,7 +158,6 @@ export class Contact {
|
|
|
158
158
|
const deserialized = await this.deserializeRequest(...data);
|
|
159
159
|
let response = await this.host.receiveRPC(...deserialized);
|
|
160
160
|
response = this.serializeResponse(response);
|
|
161
|
-
//if (messageTag.startsWith('X')) this.host.xlog(this.counter, 'responding', messageTag, response, 'to', this.sname);
|
|
162
161
|
await this.send([messageTag, response]);
|
|
163
162
|
}
|
|
164
163
|
}
|
package/transports/webrtc.js
CHANGED
|
@@ -76,7 +76,7 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
|
|
|
76
76
|
return `@${this.host.contact.sname} ==> ${this.sname}`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
ensureWebRTC(initiate = false, timeoutMS = this.host.timeoutMS ||
|
|
79
|
+
ensureWebRTC(initiate = false, timeoutMS = this.host.timeoutMS || 30e3) { // Ensure we are connected, if possible.
|
|
80
80
|
// If not already configured, sets up contact to have properties:
|
|
81
81
|
// - connection - a promise for an open webrtc data channel:
|
|
82
82
|
// this.send(string) puts data on the channel
|
|
@@ -118,7 +118,7 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
|
|
|
118
118
|
clearTimeout(timeout);
|
|
119
119
|
dataChannel.addEventListener('close', onclose);
|
|
120
120
|
dataChannel.addEventListener('message', event => this.receiveWebRTC(event.data));
|
|
121
|
-
|
|
121
|
+
await webrtc.reportConnection(true);
|
|
122
122
|
if (webrtc.statsElapsed > 500) this.host.xlog(`** slow connection to ${this.sname} took ${webrtc.statsElapsed.toLocaleString()} ms. **`);
|
|
123
123
|
this.unsafeData = dataChannel;
|
|
124
124
|
return dataChannel;
|
|
@@ -130,7 +130,7 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
|
|
|
130
130
|
const timerPromise = new Promise(expired => {
|
|
131
131
|
timeout = setTimeout(async () => {
|
|
132
132
|
const now = Date.now();
|
|
133
|
-
this.host.
|
|
133
|
+
this.host.ilog('Unable to connect to', this.sname);
|
|
134
134
|
// this.host.xlog('**** connection timeout', this.sname, now - start,
|
|
135
135
|
// 'status:', webrtc.pc.connectionState, 'signaling:', webrtc.pc.signalingState,
|
|
136
136
|
// 'last signal:', now - webrtc.lastOutboundSignal,
|
|
@@ -161,7 +161,7 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
|
|
|
161
161
|
async send(message) { // Promise to send through previously opened connection promise.
|
|
162
162
|
let channel = await this.connection;
|
|
163
163
|
if (channel?.readyState === 'open') channel.send(JSON.stringify(message));
|
|
164
|
-
else this.host.xlog('
|
|
164
|
+
else this.host.xlog('Tried to send on unopen channel on', this.sname, message);
|
|
165
165
|
}
|
|
166
166
|
synchronousSend(message) { // this.send awaits channel open promise. This is if we know it has been opened.
|
|
167
167
|
if (this.unsafeData?.readyState !== 'open') return; // But it may have since been closed.
|
|
@@ -232,12 +232,6 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
|
|
|
232
232
|
// The message could the start of an RPC sent from the peer, or it could be a response to an RPC that we made.
|
|
233
233
|
// As we do the latter, we generate and note (in transmitRPC) a message tag included in the message.
|
|
234
234
|
// If we find that in our messageResolvers tags, then the message is a response.
|
|
235
|
-
if (dataString === '"bye"') { // Special messsage that the other side is disconnecting, so we can clean up early.
|
|
236
|
-
this.webrtc.close();
|
|
237
|
-
this.host.xlog('removing disconnected contact', this.sname);
|
|
238
|
-
await this.host.removeContact(this); // TODO: Make sure we're not invoking this in maxTransports cases.
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
235
|
const [messageTag, ...data] = JSON.parse(dataString);
|
|
242
236
|
await this.receiveRPC(messageTag, ...data);
|
|
243
237
|
}
|