wao 0.40.2 → 0.41.1
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/cjs/accounts-web.js +25 -0
- package/cjs/accounts.js +38 -0
- package/cjs/adaptor-base.js +504 -287
- package/cjs/adaptor-cf.js +42 -0
- package/cjs/ao-loader.js +1 -1
- package/cjs/ao.js +18 -6
- package/cjs/aoconnect-base.js +1017 -546
- package/cjs/aoconnect-cf.js +24 -0
- package/cjs/ar-remote.js +277 -0
- package/cjs/armem-base.js +822 -211
- package/cjs/armem-cf.js +128 -0
- package/cjs/armem.js +11 -5
- package/cjs/bar.js +511 -173
- package/cjs/car.js +37 -0
- package/cjs/cf-env.js +54 -0
- package/cjs/cf.js +76 -0
- package/cjs/cli.js +12 -6
- package/cjs/create.js +1 -1
- package/cjs/devs.js +53 -0
- package/cjs/dodb.js +116 -0
- package/cjs/hb.js +136 -53
- package/cjs/hyperbeam.js +85 -44
- package/cjs/keygen.js +94 -0
- package/cjs/run.js +4 -1
- package/cjs/server.js +40 -9
- package/cjs/storage-multi.js +525 -0
- package/cjs/tgql-d1.js +664 -0
- package/cjs/tgql.js +293 -172
- package/cjs/workspace/.claude/agents/tester.md +2 -2
- package/cjs/workspace/.claude/skills/build/SKILL.md +2 -2
- package/cjs/workspace/.claude/skills/test/SKILL.md +3 -2
- package/cjs/workspace/CLAUDE.md +2 -2
- package/cjs/workspace/README.md +1 -1
- package/cjs/workspace/docs/debug.md +9 -4
- package/cjs/workspace/docs/hyperbeam-devices.md +50 -1
- package/cjs/workspace/package.json +3 -3
- package/esm/accounts-web.js +14 -0
- package/esm/accounts.js +27 -0
- package/esm/adaptor-base.js +129 -31
- package/esm/adaptor-cf.js +11 -0
- package/esm/ao-loader.js +1 -1
- package/esm/ao.js +21 -2
- package/esm/aoconnect-base.js +255 -7
- package/esm/aoconnect-cf.js +9 -0
- package/esm/ar-remote.js +87 -0
- package/esm/armem-base.js +304 -53
- package/esm/armem-cf.js +67 -0
- package/esm/armem.js +7 -2
- package/esm/bar.js +126 -16
- package/esm/car.js +10 -0
- package/esm/cf-env.js +29 -0
- package/esm/cf.js +11 -0
- package/esm/cli.js +6 -2
- package/esm/create.js +1 -1
- package/esm/devs.js +15 -0
- package/esm/dodb.js +26 -0
- package/esm/hb.js +93 -16
- package/esm/hyperbeam.js +68 -30
- package/esm/keygen.js +47 -0
- package/esm/run.js +4 -1
- package/esm/server.js +29 -9
- package/esm/storage-multi.js +183 -0
- package/esm/tgql-d1.js +407 -0
- package/esm/tgql.js +29 -10
- package/esm/workspace/.claude/agents/tester.md +2 -2
- package/esm/workspace/.claude/skills/build/SKILL.md +2 -2
- package/esm/workspace/.claude/skills/test/SKILL.md +3 -2
- package/esm/workspace/CLAUDE.md +2 -2
- package/esm/workspace/README.md +1 -1
- package/esm/workspace/docs/debug.md +9 -4
- package/esm/workspace/docs/hyperbeam-devices.md +50 -1
- package/esm/workspace/package.json +3 -3
- package/package.json +10 -3
- package/postinstall.cjs +84 -0
- package/wao-0.41.1.tgz +0 -0
package/esm/hyperbeam.js
CHANGED
|
@@ -36,6 +36,7 @@ export default class HyperBEAM {
|
|
|
36
36
|
genesis_wasm = false,
|
|
37
37
|
arweave_gateway,
|
|
38
38
|
force_signed = false,
|
|
39
|
+
linkify_mode, // v0.9-FINAL: HB linkify mode. undefined => HB default; pass "false" for hbsig-style inline-only responses
|
|
39
40
|
rebar3, // Use rebar3 shell (true) or direct erl (false). Defaults to HB_REBAR3 env or true
|
|
40
41
|
} = {}) {
|
|
41
42
|
// Determine rebar3 mode: option > env var > default (true)
|
|
@@ -49,6 +50,7 @@ export default class HyperBEAM {
|
|
|
49
50
|
}
|
|
50
51
|
this.genesis_wasm = genesis_wasm
|
|
51
52
|
this.force_signed = force_signed
|
|
53
|
+
this.linkify_mode = linkify_mode
|
|
52
54
|
this.cu_port = cu_port
|
|
53
55
|
this.arweave_gateway = arweave_gateway || process.env.ARWEAVE_GATEWAY
|
|
54
56
|
this.devices = devices
|
|
@@ -84,7 +86,7 @@ export default class HyperBEAM {
|
|
|
84
86
|
this.c = c
|
|
85
87
|
this.cmake = cmake
|
|
86
88
|
this.port = port
|
|
87
|
-
this.url = `http://
|
|
89
|
+
this.url = `http://127.0.0.1:${this.port}`
|
|
88
90
|
if (bundler) this.bundler = `http://localhost::${bundler}`
|
|
89
91
|
this.bundler_ans104 = bundler_ans104
|
|
90
92
|
if (bundler_httpsig) this.bundler = bundler_httpsig
|
|
@@ -102,6 +104,16 @@ export default class HyperBEAM {
|
|
|
102
104
|
if (shell) this.shell()
|
|
103
105
|
}
|
|
104
106
|
shell() {
|
|
107
|
+
// Kill any stale beam.smp / process listening on our HB port before
|
|
108
|
+
// spawning a new shell. Without this, sequential test runs can hit a
|
|
109
|
+
// lingering Erlang VM from the previous test (kill() returned but the
|
|
110
|
+
// OS hadn't released the port yet) and end up either failing to bind
|
|
111
|
+
// 10001 or talking to the stale node with stale process registry —
|
|
112
|
+
// surface symptom is 400 "No scheduler information provided." on the
|
|
113
|
+
// first schedule of an otherwise-known process.
|
|
114
|
+
try {
|
|
115
|
+
spawnSync("bash", ["-c", `lsof -ti:${this.port} | xargs -r kill -9 2>/dev/null`], { stdio: "ignore" })
|
|
116
|
+
} catch (_e) {}
|
|
105
117
|
const evalCmd = this.genEval({ gateway: this.gateway, wallet: this.wallet })
|
|
106
118
|
const cwd = resolve(process.cwd(), this.cwd)
|
|
107
119
|
const env = this.genEnv() // genEnv() returns filtered process.env without proxy vars
|
|
@@ -257,6 +269,18 @@ export default class HyperBEAM {
|
|
|
257
269
|
// Ensure DB directory exists
|
|
258
270
|
spawnSync("mkdir", ["-p", dbDir])
|
|
259
271
|
|
|
272
|
+
// Kill any stale CU process listening on cu_port before spawning a new
|
|
273
|
+
// one. Sequential test runs in the same OS share port 6363; if a prior
|
|
274
|
+
// run's CU lingered (e.g. detached but its parent died before SIGKILL
|
|
275
|
+
// could propagate), the new CU's bind silently fails and HB ends up
|
|
276
|
+
// talking to the stale CU, which has a different process registry and
|
|
277
|
+
// throws confusing 500/400s on the next spawn/schedule.
|
|
278
|
+
try {
|
|
279
|
+
spawnSync("bash", ["-c", `lsof -ti:${this.cu_port} | xargs -r kill -9 2>/dev/null`], { stdio: "ignore" })
|
|
280
|
+
} catch (_e) {}
|
|
281
|
+
// Brief settle so the OS can release the port.
|
|
282
|
+
await new Promise(r => setTimeout(r, 200))
|
|
283
|
+
|
|
260
284
|
// Use arweave_gateway option or ARWEAVE_GATEWAY env var for proxy environments
|
|
261
285
|
const gatewayUrl = this.arweave_gateway || process.env.GATEWAY_URL || "https://arweave.net"
|
|
262
286
|
const graphqlUrl = process.env.GRAPHQL_URL || `${gatewayUrl}/graphql`
|
|
@@ -285,7 +309,11 @@ export default class HyperBEAM {
|
|
|
285
309
|
CHECKPOINT_GRAPHQL_URL: graphqlUrl,
|
|
286
310
|
}
|
|
287
311
|
|
|
288
|
-
|
|
312
|
+
// Node 26 enables wasm-memory64 by default and rejects the experimental
|
|
313
|
+
// flag; older Node versions still need it. Detect from process.versions.
|
|
314
|
+
const nodeMajor = parseInt((process.versions.node || "0").split(".")[0], 10)
|
|
315
|
+
const memory64Flag = nodeMajor >= 24 ? [] : ["--experimental-wasm-memory64"]
|
|
316
|
+
this.cuProc = spawn("node", [...memory64Flag, "-r", "dotenv/config", "src/app.js"], {
|
|
289
317
|
cwd: cuDir,
|
|
290
318
|
env,
|
|
291
319
|
detached: true,
|
|
@@ -341,6 +369,12 @@ export default class HyperBEAM {
|
|
|
341
369
|
const asyncThreads = "+A 4"
|
|
342
370
|
if (!_env.ERL_ZFLAGS) _env.ERL_ZFLAGS = asyncThreads
|
|
343
371
|
else if (!_env.ERL_ZFLAGS.includes("+A")) _env.ERL_ZFLAGS += ` ${asyncThreads}`
|
|
372
|
+
// Skip the hb application's default-port HTTP server start (8734). We call
|
|
373
|
+
// hb_http_server:start_node/1 explicitly with the actual port via genEval,
|
|
374
|
+
// so the default binding is redundant; skipping it avoids eaddrinuse and
|
|
375
|
+
// the downstream case_clause crashes when multiple HyperBEAM instances
|
|
376
|
+
// run side-by-side (p4.test.js, p4-lua.test.js).
|
|
377
|
+
_env.WAO_NO_DEFAULT_HTTP_SERVER = "1"
|
|
344
378
|
return _env
|
|
345
379
|
}
|
|
346
380
|
|
|
@@ -360,14 +394,14 @@ export default class HyperBEAM {
|
|
|
360
394
|
}
|
|
361
395
|
}
|
|
362
396
|
if (_devs.length > 0) {
|
|
363
|
-
_devices = `,
|
|
397
|
+
_devices = `, <<"preloaded-devices">> => [${_devs.join(", ")}]`
|
|
364
398
|
}
|
|
365
|
-
const _wallet = `,
|
|
399
|
+
const _wallet = `, <<"priv-key-location">> => <<"${wallet}">>`
|
|
366
400
|
// Use arweave_gateway (Cloudflare proxy) if set, otherwise local gateway port, otherwise default
|
|
367
401
|
const _gateway = this.arweave_gateway
|
|
368
|
-
? `, gateway => <<"${this.arweave_gateway}">>`
|
|
402
|
+
? `, <<"gateway">> => <<"${this.arweave_gateway}">>`
|
|
369
403
|
: gateway
|
|
370
|
-
? `, gateway => <<"http://localhost:${gateway}">>`
|
|
404
|
+
? `, <<"gateway">> => <<"http://localhost:${gateway}">>`
|
|
371
405
|
: ""
|
|
372
406
|
|
|
373
407
|
// Store config: use single hb_store_fs matching HyperBEAM eunit test pattern.
|
|
@@ -375,35 +409,35 @@ export default class HyperBEAM {
|
|
|
375
409
|
// because list_numbered/resolve interactions across stores break symlink following.
|
|
376
410
|
// The wao@1.0 device handles Arweave TX resolution independently via HTTP.
|
|
377
411
|
const _store = this.store_prefix
|
|
378
|
-
? `, store => #{ <<"store-module">> => hb_store_fs, <<"name">> => <<"${this.store_prefix}">> }`
|
|
412
|
+
? `, <<"store">> => #{ <<"store-module">> => hb_store_fs, <<"name">> => <<"${this.store_prefix}">> }`
|
|
379
413
|
: ""
|
|
380
414
|
let _bundler = this.bundler
|
|
381
|
-
? `,
|
|
415
|
+
? `, <<"bundler-httpsig">> => <<"${this.bundler}">>`
|
|
382
416
|
: ""
|
|
383
417
|
// Only include bundler_ans104 if it's a truthy value (port number or URL)
|
|
384
418
|
// When false or omitted, don't include it - Erlang code expects either no option or a valid URL
|
|
385
419
|
let _bundler_ans104 = this.bundler_ans104 && this.bundler_ans104 !== false
|
|
386
|
-
? `,
|
|
420
|
+
? `, <<"bundler-ans104">> => <<"http://localhost:${this.bundler_ans104}">>`
|
|
387
421
|
: ""
|
|
388
422
|
/*
|
|
389
423
|
const _routes = `, routes => [#{ <<"template">> => <<"/result/.*">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${this.cu}">> } }, #{ <<\"template\">> => <<\"/dry-run\">>, <<\"node\">> => #{ <<\"prefix\">> => <<\"http://localhost:${this.cu}\">> } }, #{ <<"template">> => <<"/graphql">>, <<"nodes">> => [#{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => httpc, protocol => http2 } }, #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } }] }, #{ <<"template">> => <<"/raw">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${gateway}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } } }]`
|
|
390
424
|
*/
|
|
391
425
|
const _p4_non_chargable = this.p4_non_chargable
|
|
392
|
-
? `,
|
|
426
|
+
? `, <<"p4-non-chargable-routes">> => [${this.p4_non_chargable_routes
|
|
393
427
|
.map(() => `#{ <<"template">> => <<"/*~node-process@1.0/*">> }`)
|
|
394
428
|
.join(", ")}]`
|
|
395
429
|
: this.p4_lua
|
|
396
|
-
? `,
|
|
430
|
+
? `, <<"p4-non-chargable-routes">> => [#{ <<"template">> => <<"/*~node-process@1.0/*">> }, #{ <<"template">> => <<"/~wao@1.0/*">> }, #{ <<"template">> => <<"/~p4@1.0/balance">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }]`
|
|
397
431
|
: !this.simple_pay
|
|
398
432
|
? ""
|
|
399
|
-
: `,
|
|
433
|
+
: `, <<"p4-non-chargable-routes">> => [#{ <<"template">> => <<"/~simple-pay@1.0/topup">> }, #{ <<"template">> => <<"/~meta@1.0/*">> }, #{ <<"template">> => <<"/~simple-pay@1.0/balance">> }]`
|
|
400
434
|
|
|
401
435
|
const _operator = this.operator
|
|
402
|
-
? `, operator => <<"${this.operator}">>`
|
|
436
|
+
? `, <<"operator">> => <<"${this.operator}">>`
|
|
403
437
|
: ""
|
|
404
|
-
const _spp = this.spp ? `,
|
|
405
|
-
const _genesis_wasm_port = this.genesis_wasm ? `,
|
|
406
|
-
const _force_signed = this.force_signed ? `,
|
|
438
|
+
const _spp = this.spp ? `, <<"simple-pay-price">> => ${this.spp}` : ""
|
|
439
|
+
const _genesis_wasm_port = this.genesis_wasm ? `, <<"genesis-wasm-port">> => ${this.cu_port}` : ""
|
|
440
|
+
const _force_signed = this.force_signed ? `, <<"force-signed-requests">> => true, <<"force-signed">> => true` : ""
|
|
407
441
|
|
|
408
442
|
// Helper to format module(s) for Erlang - supports ID string, inline object, or array
|
|
409
443
|
const formatModule = (mod) => {
|
|
@@ -427,30 +461,30 @@ export default class HyperBEAM {
|
|
|
427
461
|
}
|
|
428
462
|
|
|
429
463
|
const _node_processes = this.p4_lua
|
|
430
|
-
? `,
|
|
464
|
+
? `, <<"node-processes">> => #{ <<"ledger">> => #{ <<"device">> => <<"process@1.0">>, <<"execution-device">> => <<"lua@5.3a">>, <<"scheduler-device">> => <<"scheduler@1.0">>, <<"module">> => ${formatModule(this.p4_lua.processor)}, <<"operator">> => <<"${this.operator}">>${this.p4_lua.admin ? `, <<"admin">> => <<"${this.p4_lua.admin}">>` : ""}${this.p4_lua.balance ? `, <<"balance">> => #{ ${Object.entries(this.p4_lua.balance).map(([k, v]) => `<<"${k}">> => ${v}`).join(", ")} }` : ""} } }`
|
|
431
465
|
: ""
|
|
432
466
|
const processor = this.p4_lua
|
|
433
467
|
? `#{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"lua@5.3a">>, <<"module">> => ${formatModule(this.p4_lua.client)}, <<"ledger-path">> => <<"/ledger~node-process@1.0">> }`
|
|
434
468
|
: ""
|
|
435
|
-
const _port =
|
|
469
|
+
const _port = `<<"port">> => ${this.port}`
|
|
436
470
|
const _faff = isNil(this.faff)
|
|
437
471
|
? ""
|
|
438
|
-
: `,
|
|
472
|
+
: `, <<"faff-allow-list">> => [ ${map(addr => `<<"${addr}">>`)(this.faff).join(", ")} ]`
|
|
439
473
|
|
|
440
474
|
const _on = this.p4_lua
|
|
441
|
-
? `, on => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
|
|
475
|
+
? `, <<"on">> => #{ <<"request">> => ${processor}, <<"response">> => ${processor} }`
|
|
442
476
|
: this.simple_pay
|
|
443
|
-
? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> } }`
|
|
477
|
+
? `, <<"on">> => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"simple-pay@1.0">>, <<"ledger-device">> => <<"simple-pay@1.0">> } }`
|
|
444
478
|
: !isNil(this.faff)
|
|
445
|
-
? `, on => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> } }`
|
|
479
|
+
? `, <<"on">> => #{ <<"request">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> }, <<"response">> => #{ <<"device">> => <<"p4@1.0">>, <<"pricing-device">> => <<"faff@1.0">>, <<"ledger-device">> => <<"faff@1.0">> } }`
|
|
446
480
|
: ""
|
|
447
481
|
// Add cache_writers to allow the wallet to write to cache (needed for WASM module uploads)
|
|
448
482
|
// Use the wallet address (this.addr) which is always available from the wallet file
|
|
449
|
-
const _cache_writers = `,
|
|
483
|
+
const _cache_writers = `, <<"cache-writers">> => [<<"${this.addr}">>]`
|
|
450
484
|
|
|
451
485
|
// Use gun HTTP client for relay calls instead of httpc
|
|
452
486
|
// gun doesn't use system proxy settings, avoiding the proxy issue with localhost CU
|
|
453
|
-
const _relay_http_client = `,
|
|
487
|
+
const _relay_http_client = `, <<"relay-http-client">> => gun, <<"http-client">> => gun`
|
|
454
488
|
|
|
455
489
|
// Custom routes using Cloudflare proxy instead of arweave.net
|
|
456
490
|
// Also add CU routes for genesis_wasm when enabled
|
|
@@ -460,7 +494,7 @@ export default class HyperBEAM {
|
|
|
460
494
|
#{ <<"template">> => <<"/dry-run">>, <<"node">> => #{ <<"prefix">> => <<"http://localhost:${this.cu_port}">> } },`
|
|
461
495
|
: ""
|
|
462
496
|
const _routes = this.arweave_gateway || this.genesis_wasm
|
|
463
|
-
? `, routes => [
|
|
497
|
+
? `, <<"routes">> => [
|
|
464
498
|
${cuRoutes}
|
|
465
499
|
#{ <<"template">> => <<"/graphql">>, <<"nodes">> => [
|
|
466
500
|
#{ <<"prefix">> => <<"${this.arweave_gateway || 'https://arweave.net'}">>, <<"opts">> => #{ http_client => gun, protocol => http2 } }
|
|
@@ -487,7 +521,7 @@ export default class HyperBEAM {
|
|
|
487
521
|
|
|
488
522
|
// Pre-register device name atoms so hb_util:atom/1 (which uses list_to_existing_atom)
|
|
489
523
|
// doesn't crash with badarg when resolving device names from HTTP headers/binaries
|
|
490
|
-
const preRegisterAtoms = `lists:foreach(fun list_to_atom/1, ["wao@1.0", "hbsig@1.0", "stack@1.0", "patch@1.0", "inc@1.0", "double@1.0", "add@1.0", "mul@1.0", "inc2@1.0", "square@1.0", "mydev@1.0", "lua@5.3a", "process@1.0", "scheduler@1.0", "message@1.0", "meta@1.0", "cache@1.0", "json@1.0", "structured@1.0", "httpsig@1.0", "flat@1.0", "genesis-wasm@1.0", "compute@1.0", "delegated-compute@1.0", "relay@1.0", "router@1.0", "cron@1.0", "node-process@1.0", "p4@1.0", "simple-pay@1.0", "faff@1.0", "ans104@1.0", "test-device@1.0", "lookup@1.0", "local-name@1.0", "upload@1.0", "hook@1.0", "auth-hook@1.0", "http-auth@1.0", "greenzone@1.0", "apply@1.0", "dedup@1.0", "cookie@1.0", "push@1.0", "query@1.0", "manifest@1.0", "name@1.0", "profile@1.0", "monitor@1.0", "multipass@1.0", "poda@1.0", "snp@1.0", "trie@1.0", "volume@1.0", "secret@1.0", "wasi@1.0", "wasm-64@1.0", "whois@1.0", "cacheviz@1.0", "hyperbuddy@1.0", "copycat@1.0", "json-iface@1.0", "arweave@2.9-
|
|
524
|
+
const preRegisterAtoms = `lists:foreach(fun list_to_atom/1, ["wao@1.0", "hbsig@1.0", "stack@1.0", "patch@1.0", "inc@1.0", "double@1.0", "add@1.0", "mul@1.0", "inc2@1.0", "square@1.0", "mydev@1.0", "lua@5.3a", "process@1.0", "scheduler@1.0", "message@1.0", "meta@1.0", "cache@1.0", "json@1.0", "structured@1.0", "httpsig@1.0", "flat@1.0", "genesis-wasm@1.0", "compute@1.0", "delegated-compute@1.0", "relay@1.0", "router@1.0", "cron@1.0", "node-process@1.0", "p4@1.0", "simple-pay@1.0", "faff@1.0", "ans104@1.0", "test-device@1.0", "lookup@1.0", "local-name@1.0", "upload@1.0", "hook@1.0", "auth-hook@1.0", "http-auth@1.0", "greenzone@1.0", "apply@1.0", "dedup@1.0", "cookie@1.0", "push@1.0", "query@1.0", "manifest@1.0", "name@1.0", "profile@1.0", "monitor@1.0", "multipass@1.0", "poda@1.0", "snp@1.0", "trie@1.0", "volume@1.0", "secret@1.0", "wasi@1.0", "wasm-64@1.0", "whois@1.0", "cacheviz@1.0", "hyperbuddy@1.0", "copycat@1.0", "json-iface@1.0", "arweave@2.9", "b32-name@1.0", "blacklist@1.0", "bundler@1.0", "gzip@1.0", "location@1.0", "metering@1.0", "rate-limit@1.0", "tx@1.0"]), `
|
|
491
525
|
|
|
492
526
|
// Pre-create prometheus ETS tables owned by the shell process.
|
|
493
527
|
// dev_hbsig on_load also does this, but the module loads lazily so this
|
|
@@ -506,13 +540,13 @@ export default class HyperBEAM {
|
|
|
506
540
|
// This affects both node_processes definitions (which include 'authority' via
|
|
507
541
|
// augment_definition) and push device re-scheduling (which signs outbox messages
|
|
508
542
|
// with httpsig). Disable verification until upstream fixes the codec.
|
|
509
|
-
const _verify_assignments = (this.p4_lua || this.genesis_wasm) ? `,
|
|
543
|
+
const _verify_assignments = (this.p4_lua || this.genesis_wasm) ? `, <<"verify-assignments">> => false` : ""
|
|
510
544
|
|
|
511
545
|
// Use hb_http_server:start_node directly instead of hb:start_mainnet.
|
|
512
546
|
// start_mainnet always overwrites the store config with a single hb_store_fs,
|
|
513
547
|
// which prevents hb_store_gateway from resolving Arweave TX IDs.
|
|
514
548
|
// start_node preserves user-provided store via set_default_opts.
|
|
515
|
-
const _priv_wallet = `,
|
|
549
|
+
const _priv_wallet = `, <<"priv-wallet">> => hb:wallet(<<"${wallet}">>)`
|
|
516
550
|
// cache_control => <<"always">> ensures compute results/snapshots are cached.
|
|
517
551
|
// process_snapshot_slots => 1 takes a snapshot every slot (not just every 60s).
|
|
518
552
|
// process_async_cache => false writes snapshots synchronously before returning
|
|
@@ -521,9 +555,13 @@ export default class HyperBEAM {
|
|
|
521
555
|
// Without these, hb_cache:write strips uncommitted keys (like device-stack)
|
|
522
556
|
// from the cached process state, and subsequent computes fail with
|
|
523
557
|
// {error, no_valid_device_stack} when loading from the corrupted cache.
|
|
524
|
-
const _cache_control = `,
|
|
558
|
+
const _cache_control = `, <<"cache-control">> => <<"always">>, <<"process-snapshot-slots">> => 1, <<"process-async-cache">> => false`
|
|
525
559
|
|
|
526
|
-
const
|
|
560
|
+
const _linkify =
|
|
561
|
+
this.linkify_mode === undefined
|
|
562
|
+
? ""
|
|
563
|
+
: `, <<"linkify-mode">> => ${this.linkify_mode === false ? "false" : (this.linkify_mode === true ? "true" : this.linkify_mode)}`
|
|
564
|
+
const start = `${clearProxy}${initPrometheus}${preRegisterAtoms}${loadHbsig}${ensureInit}hb_http_server:start_node(#{ ${_port}${_gateway}${_priv_wallet}${_faff}${_bundler}${_bundler_ans104}${_on}${_p4_non_chargable}${_operator}${_spp}${_genesis_wasm_port}${_force_signed}${_devices}${_node_processes}${_cache_writers}${_relay_http_client}${_routes}${_store}${_verify_assignments}${_cache_control}, <<"prometheus">> => false${_linkify}}).`
|
|
527
565
|
|
|
528
566
|
return start
|
|
529
567
|
}
|
package/esm/keygen.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import _Arweave from "arweave"
|
|
2
|
+
const Arweave = _Arweave.default ?? _Arweave
|
|
3
|
+
import { toAddr } from "./utils.js"
|
|
4
|
+
import { mkdirSync, writeFileSync, existsSync, readdirSync } from "fs"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
import { dirname, resolve } from "path"
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = dirname(__filename)
|
|
10
|
+
const walletsDir = resolve(__dirname, "../devnet/.wallets")
|
|
11
|
+
const names = ["su", "cu", "mu", "acc0", "acc1", "acc2"]
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const force = process.argv.includes("--force")
|
|
15
|
+
|
|
16
|
+
if (!force && existsSync(walletsDir)) {
|
|
17
|
+
const files = readdirSync(walletsDir).filter(f => f.endsWith(".json"))
|
|
18
|
+
if (files.length > 0) {
|
|
19
|
+
console.log("Wallets already exist in", walletsDir)
|
|
20
|
+
console.log("Use --force to regenerate")
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
mkdirSync(walletsDir, { recursive: true })
|
|
26
|
+
|
|
27
|
+
const arweave = Arweave.init({})
|
|
28
|
+
|
|
29
|
+
console.log("Generating wallets...\n")
|
|
30
|
+
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
const jwk = await arweave.wallets.generate()
|
|
33
|
+
const addr = toAddr(jwk.n)
|
|
34
|
+
writeFileSync(
|
|
35
|
+
resolve(walletsDir, `${name}.json`),
|
|
36
|
+
JSON.stringify(jwk, null, 2),
|
|
37
|
+
)
|
|
38
|
+
console.log(` ${name.padEnd(5)} ${addr}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`\nWallets written to ${walletsDir}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main().catch(err => {
|
|
45
|
+
console.error(err)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
})
|
package/esm/run.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Note: this script is normally launched by src/cli.js via pm2, which
|
|
3
|
+
// gates --experimental-wasm-memory64 to Node <24. If you run this file
|
|
4
|
+
// directly on Node <24, prefix with the flag: NODE_OPTIONS=--experimental-wasm-memory64
|
|
2
5
|
import yargs from "yargs"
|
|
3
6
|
import { resolve } from "path"
|
|
4
7
|
import { unlinkSync } from "fs"
|
package/esm/server.js
CHANGED
|
@@ -16,8 +16,10 @@ class Server {
|
|
|
16
16
|
aoconnect,
|
|
17
17
|
log = false,
|
|
18
18
|
db,
|
|
19
|
+
storage,
|
|
19
20
|
port,
|
|
20
21
|
adaptor,
|
|
22
|
+
units,
|
|
21
23
|
} = {}) {
|
|
22
24
|
if (port) {
|
|
23
25
|
ar = port
|
|
@@ -27,18 +29,25 @@ class Server {
|
|
|
27
29
|
cu = port + 4
|
|
28
30
|
aoconnect = optAO(5000)
|
|
29
31
|
}
|
|
32
|
+
// Parse enabled units (default: all)
|
|
33
|
+
const enabled = units
|
|
34
|
+
? new Set(units.map(u => u.toLowerCase()))
|
|
35
|
+
: new Set(["ar", "bd", "mu", "su", "cu"])
|
|
36
|
+
if (enabled.has("ar")) enabled.add("bd")
|
|
37
|
+
this._enabledUnits = enabled
|
|
38
|
+
|
|
30
39
|
if (!aoconnect) {
|
|
31
|
-
const { mem } = connect(aoconnect, { log, cache: db })
|
|
40
|
+
const { mem } = connect(aoconnect, { log, cache: db, storage })
|
|
32
41
|
aoconnect = mem
|
|
33
42
|
}
|
|
34
|
-
this.adaptor = adaptor ?? new Adaptor({ hb_url, aoconnect, log, db })
|
|
43
|
+
this.adaptor = adaptor ?? new Adaptor({ hb_url, aoconnect, log, db, storage })
|
|
35
44
|
this.ports = { ar, mu, su, cu, bd }
|
|
36
45
|
this.servers = []
|
|
37
|
-
this.ar()
|
|
38
|
-
this.bd()
|
|
39
|
-
this.mu()
|
|
40
|
-
this.su()
|
|
41
|
-
this.cu()
|
|
46
|
+
if (enabled.has("ar")) this.ar()
|
|
47
|
+
if (enabled.has("bd")) this.bd()
|
|
48
|
+
if (enabled.has("mu")) this.mu()
|
|
49
|
+
if (enabled.has("su")) this.su()
|
|
50
|
+
if (enabled.has("cu")) this.cu()
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
ar() {
|
|
@@ -93,6 +102,9 @@ class Server {
|
|
|
93
102
|
res(res) {
|
|
94
103
|
return data => {
|
|
95
104
|
if (data.status) res.status(data.status)
|
|
105
|
+
if (data.headers) {
|
|
106
|
+
for (const [k, v] of Object.entries(data.headers)) res.set(k, v)
|
|
107
|
+
}
|
|
96
108
|
if (data.error) {
|
|
97
109
|
res.json({ error: data.error })
|
|
98
110
|
} else if (data.json) {
|
|
@@ -137,15 +149,23 @@ class Server {
|
|
|
137
149
|
|
|
138
150
|
end() {
|
|
139
151
|
return new Promise(res => {
|
|
152
|
+
if (this.servers.length === 0) return res()
|
|
140
153
|
let count = 0
|
|
141
|
-
for (const v of this.servers)
|
|
154
|
+
for (const v of this.servers) {
|
|
155
|
+
// close() waits for active connections to drain; force-close any
|
|
156
|
+
// hanging keepalive sockets so the test process can exit when a
|
|
157
|
+
// dependent HyperBEAM instance dies mid-request.
|
|
158
|
+
if (typeof v.closeAllConnections === "function") {
|
|
159
|
+
try { v.closeAllConnections() } catch (_e) {}
|
|
160
|
+
}
|
|
142
161
|
v.close(() => {
|
|
143
162
|
count += 1
|
|
144
|
-
if (count >=
|
|
163
|
+
if (count >= this.servers.length) {
|
|
145
164
|
console.log("servers closed!")
|
|
146
165
|
res()
|
|
147
166
|
}
|
|
148
167
|
})
|
|
168
|
+
}
|
|
149
169
|
})
|
|
150
170
|
}
|
|
151
171
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Multi-backend storage adapter
|
|
2
|
+
// Routes data to D1 (indexed queries), R2 (large blobs), or DO (hot compute state)
|
|
3
|
+
// based on key prefix.
|
|
4
|
+
|
|
5
|
+
function sanitize(val) {
|
|
6
|
+
if (val == null) return val
|
|
7
|
+
if (typeof val === "function") return null
|
|
8
|
+
if (typeof val !== "object") return val
|
|
9
|
+
if (val instanceof Uint8Array || val instanceof ArrayBuffer) return val
|
|
10
|
+
if (ArrayBuffer.isView(val)) return val
|
|
11
|
+
if (Array.isArray(val)) return val.map(sanitize)
|
|
12
|
+
const out = {}
|
|
13
|
+
for (const k of Object.keys(val)) {
|
|
14
|
+
const v = val[k]
|
|
15
|
+
if (typeof v !== "function") out[k] = sanitize(v)
|
|
16
|
+
}
|
|
17
|
+
return out
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default class StorageMulti {
|
|
21
|
+
constructor({ storage, d1, r2, kv }) {
|
|
22
|
+
this.storage = storage // DO storage (always available)
|
|
23
|
+
this.d1 = d1 || null // D1 database (optional)
|
|
24
|
+
this.r2 = r2 || null // R2 bucket (optional)
|
|
25
|
+
this.kv = kv || null // KV namespace (optional)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --- D1 helpers ---
|
|
29
|
+
|
|
30
|
+
async d1WriteTx(tx, blockId, blockHeight) {
|
|
31
|
+
if (!this.d1) return
|
|
32
|
+
const tags = tx.tags || []
|
|
33
|
+
let dataSize = "0"
|
|
34
|
+
let dataType = ""
|
|
35
|
+
if (tx._data) {
|
|
36
|
+
dataSize = tx._data.size || "0"
|
|
37
|
+
dataType = tx._data.type || ""
|
|
38
|
+
} else if (tx.data_size) {
|
|
39
|
+
dataSize = tx.data_size
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await this.d1.batch([
|
|
43
|
+
this.d1.prepare(
|
|
44
|
+
`INSERT OR REPLACE INTO txs (id, block_id, block_height, owner, recipient, anchor, signature, data_size, data_type, bundle_id, parent_id)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
46
|
+
).bind(
|
|
47
|
+
tx.id,
|
|
48
|
+
blockId,
|
|
49
|
+
blockHeight,
|
|
50
|
+
tx.owner || "",
|
|
51
|
+
tx.recipient || "",
|
|
52
|
+
tx.anchor || "",
|
|
53
|
+
tx.signature || null,
|
|
54
|
+
dataSize,
|
|
55
|
+
dataType,
|
|
56
|
+
tx.bundledIn?.id || tx.parent?.id || null,
|
|
57
|
+
tx.parent?.id || null
|
|
58
|
+
),
|
|
59
|
+
...tags.map(t =>
|
|
60
|
+
this.d1.prepare(
|
|
61
|
+
`INSERT INTO tx_tags (tx_id, name, value) VALUES (?, ?, ?)`
|
|
62
|
+
).bind(tx.id, t.name, t.value)
|
|
63
|
+
),
|
|
64
|
+
])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async d1WriteBlock(block) {
|
|
68
|
+
if (!this.d1) return
|
|
69
|
+
await this.d1.prepare(
|
|
70
|
+
`INSERT OR REPLACE INTO blocks (id, height, timestamp, previous)
|
|
71
|
+
VALUES (?, ?, ?, ?)`
|
|
72
|
+
).bind(block.id, block.height, block.timestamp, block.previous || "")
|
|
73
|
+
.run()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async d1WriteAddrmap(address, entry) {
|
|
77
|
+
if (!this.d1) return
|
|
78
|
+
await this.d1.prepare(
|
|
79
|
+
`INSERT OR REPLACE INTO addrmap (address, key) VALUES (?, ?)`
|
|
80
|
+
).bind(address, entry.key || entry.address || "")
|
|
81
|
+
.run()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async d1WriteModule(name, wasmId) {
|
|
85
|
+
if (!this.d1) return
|
|
86
|
+
await this.d1.prepare(
|
|
87
|
+
`INSERT OR REPLACE INTO modules (name, wasm_id) VALUES (?, ?)`
|
|
88
|
+
).bind(name, wasmId)
|
|
89
|
+
.run()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async d1WriteWasm(id, entry) {
|
|
93
|
+
if (!this.d1) return
|
|
94
|
+
await this.d1.prepare(
|
|
95
|
+
`INSERT OR REPLACE INTO wasms (id, format, file, variant) VALUES (?, ?, ?, ?)`
|
|
96
|
+
).bind(
|
|
97
|
+
id,
|
|
98
|
+
entry.format || "wasm64-unknown-emscripten-draft_2024_02_15",
|
|
99
|
+
entry.file || null,
|
|
100
|
+
entry.variant || null
|
|
101
|
+
).run()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- R2 helpers ---
|
|
105
|
+
|
|
106
|
+
async r2PutMemory(pid, memory) {
|
|
107
|
+
if (!this.r2) return false
|
|
108
|
+
await this.r2.put(`env/${pid}/memory`, memory)
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async r2GetMemory(pid) {
|
|
113
|
+
if (!this.r2) return null
|
|
114
|
+
const obj = await this.r2.get(`env/${pid}/memory`)
|
|
115
|
+
if (!obj) return null
|
|
116
|
+
return new Uint8Array(await obj.arrayBuffer())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async r2PutTxData(txId, data) {
|
|
120
|
+
if (!this.r2) return false
|
|
121
|
+
await this.r2.put(`txdata/${txId}`, data)
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async r2GetTxData(txId) {
|
|
126
|
+
if (!this.r2) return null
|
|
127
|
+
const obj = await this.r2.get(`txdata/${txId}`)
|
|
128
|
+
if (!obj) return null
|
|
129
|
+
return await obj.text()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async r2PutWasm(id, data) {
|
|
133
|
+
if (!this.r2) return false
|
|
134
|
+
await this.r2.put(`wasms/${id}`, data)
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async r2GetWasm(id) {
|
|
139
|
+
if (!this.r2) return null
|
|
140
|
+
const obj = await this.r2.get(`wasms/${id}`)
|
|
141
|
+
if (!obj) return null
|
|
142
|
+
return new Uint8Array(await obj.arrayBuffer())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- D1 read helpers ---
|
|
146
|
+
|
|
147
|
+
async d1GetTxById(id) {
|
|
148
|
+
if (!this.d1) return null
|
|
149
|
+
const row = await this.d1.prepare("SELECT * FROM txs WHERE id = ?").bind(id).first()
|
|
150
|
+
if (!row) return null
|
|
151
|
+
const { results: tags } = await this.d1.prepare(
|
|
152
|
+
"SELECT name, value FROM tx_tags WHERE tx_id = ?"
|
|
153
|
+
).bind(id).all()
|
|
154
|
+
const tx = {
|
|
155
|
+
id: row.id,
|
|
156
|
+
owner: row.owner || "",
|
|
157
|
+
recipient: row.recipient || "",
|
|
158
|
+
anchor: row.anchor || "",
|
|
159
|
+
signature: row.signature || "",
|
|
160
|
+
tags,
|
|
161
|
+
_data: { size: row.data_size || "0", type: row.data_type || "" },
|
|
162
|
+
block: row.block_id || "",
|
|
163
|
+
}
|
|
164
|
+
if (row.bundle_id) tx.bundledIn = { id: row.bundle_id }
|
|
165
|
+
if (row.parent_id) tx.parent = { id: row.parent_id }
|
|
166
|
+
return tx
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// --- DO storage pass-through (dodb compatible interface) ---
|
|
170
|
+
|
|
171
|
+
async put(key, val) {
|
|
172
|
+
await this.storage.put(key, sanitize(val))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async get(key) {
|
|
176
|
+
return (await this.storage.get(key)) ?? null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getKeys({ start, end }) {
|
|
180
|
+
const map = await this.storage.list({ start, end })
|
|
181
|
+
return [...map.keys()]
|
|
182
|
+
}
|
|
183
|
+
}
|