@blinklabs/dingo 0.46.4 → 0.81.0

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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "dingo-v0.81.0-darwin-amd64.tar.gz": "90cd6decee1ba4c9fe971e0297bf57d7c633e9cd260b236be32cbe61b4c4492d",
3
+ "dingo-v0.81.0-darwin-arm64.tar.gz": "f5d82dc58aff89046ca7fc0b499431c82b5c6c13a54306ccda41927af5bc93f7",
4
+ "dingo-v0.81.0-freebsd-amd64.tar.gz": "0a996c980a003ae1df4470610ccb5686a4b5fbb74b93ad89b138535fe3c05a38",
5
+ "dingo-v0.81.0-freebsd-arm64.tar.gz": "1382172f08dff9a067a072c3a647fe142055a70cda3fe8f82e6d82e20c49fb3f",
6
+ "dingo-v0.81.0-linux-amd64.tar.gz": "e04e15896de454b2e6e3911c4eb923b0bf2f0331d3b1e22274e6a6576964c691",
7
+ "dingo-v0.81.0-linux-arm64.tar.gz": "4334de18168bd79eb87fa4dad015694c275e45ced99dc3643330f57b6a7ac512"
8
+ }
package/npm/install.js ADDED
@@ -0,0 +1,211 @@
1
+ // Copyright 2026 Blink Labs Software
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ 'use strict';
14
+
15
+ const crypto = require('node:crypto');
16
+ const fs = require('node:fs');
17
+ const fsp = require('node:fs/promises');
18
+ const https = require('node:https');
19
+ const os = require('node:os');
20
+ const path = require('node:path');
21
+ const { spawn } = require('node:child_process');
22
+ const { Transform } = require('node:stream');
23
+ const { pipeline } = require('node:stream/promises');
24
+
25
+ const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
26
+ const MAX_BINARY_BYTES = 512 * 1024 * 1024;
27
+ const RETRY_DELAYS_MS = [100, 500];
28
+
29
+ function releaseTarget(version, platform = process.platform, arch = process.arch) {
30
+ const targets = {
31
+ linux: { x64: 'amd64', arm64: 'arm64' },
32
+ freebsd: { x64: 'amd64', arm64: 'arm64' },
33
+ darwin: { x64: 'amd64', arm64: 'arm64' },
34
+ };
35
+ const goArch = targets[platform]?.[arch];
36
+ if (!goArch) throw new Error(`No Dingo release binary for ${platform}/${arch}`);
37
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
38
+ throw new Error(`Invalid Dingo package version: ${version}`);
39
+ }
40
+ const filename = `dingo-v${version}-${platform}-${goArch}.tar.gz`;
41
+ return {
42
+ filename,
43
+ url: `https://github.com/blinklabs-io/dingo/releases/download/v${version}/${filename}`,
44
+ };
45
+ }
46
+
47
+ function releaseURL(version, platform = process.platform, arch = process.arch) {
48
+ return releaseTarget(version, platform, arch).url;
49
+ }
50
+
51
+ function byteLimit(maxBytes, description) {
52
+ let received = 0;
53
+ return new Transform({
54
+ transform(chunk, encoding, callback) {
55
+ received += chunk.length;
56
+ if (received > maxBytes) {
57
+ callback(new Error(`${description} exceeds ${maxBytes} bytes`));
58
+ return;
59
+ }
60
+ callback(null, chunk);
61
+ },
62
+ });
63
+ }
64
+
65
+ function downloadOnce(url, destination, get, redirects, maxBytes) {
66
+ return new Promise((resolve, reject) => {
67
+ const parsed = new URL(url);
68
+ if (parsed.protocol !== 'https:') {
69
+ reject(new Error('Dingo release downloads require HTTPS'));
70
+ return;
71
+ }
72
+ const request = get(parsed, (response) => {
73
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
74
+ response.resume();
75
+ if (!response.headers.location || redirects >= 5) {
76
+ reject(new Error('Invalid or excessive Dingo release redirects'));
77
+ return;
78
+ }
79
+ let redirect;
80
+ try {
81
+ redirect = new URL(response.headers.location, parsed).href;
82
+ } catch (error) {
83
+ reject(error);
84
+ return;
85
+ }
86
+ downloadOnce(redirect, destination, get, redirects + 1, maxBytes)
87
+ .then(resolve, reject);
88
+ return;
89
+ }
90
+ if (response.statusCode !== 200) {
91
+ response.resume();
92
+ const error = new Error(`Dingo release download returned HTTP ${response.statusCode}`);
93
+ error.retryable = response.statusCode >= 500 && response.statusCode <= 599;
94
+ reject(error);
95
+ return;
96
+ }
97
+ response.on('error', (error) => { error.retryable = true; });
98
+ pipeline(
99
+ response,
100
+ byteLimit(maxBytes, 'Dingo release archive'),
101
+ fs.createWriteStream(destination, { flags: 'wx' }),
102
+ ).then(resolve, reject);
103
+ });
104
+ request.on('error', (error) => {
105
+ error.retryable = true;
106
+ reject(error);
107
+ });
108
+ request.setTimeout(30_000, () => request.destroy(new Error('Dingo release download timed out')));
109
+ });
110
+ }
111
+
112
+ async function download(url, destination, get = https.get, options = {}) {
113
+ const delays = options.retryDelays ?? RETRY_DELAYS_MS;
114
+ const wait = options.wait ?? ((milliseconds) => new Promise((resolve) => {
115
+ setTimeout(resolve, milliseconds);
116
+ }));
117
+ const maxBytes = options.maxBytes ?? MAX_ARCHIVE_BYTES;
118
+ for (let attempt = 0; ; attempt += 1) {
119
+ await fsp.rm(destination, { force: true });
120
+ try {
121
+ await downloadOnce(url, destination, get, 0, maxBytes);
122
+ return;
123
+ } catch (error) {
124
+ await fsp.rm(destination, { force: true });
125
+ if (!error.retryable || attempt >= delays.length) throw error;
126
+ await wait(delays[attempt]);
127
+ }
128
+ }
129
+ }
130
+
131
+ async function sha256(filename) {
132
+ const digest = crypto.createHash('sha256');
133
+ await pipeline(fs.createReadStream(filename), digest);
134
+ return digest.digest('hex');
135
+ }
136
+
137
+ async function extractBinary(archive, destination, maxBytes = MAX_BINARY_BYTES) {
138
+ // Stream only the shipped member: archive paths never become filesystem paths.
139
+ const child = spawn('tar', ['-xzOf', archive, 'dingo'], { stdio: ['ignore', 'pipe', 'pipe'] });
140
+ let diagnostic = '';
141
+ child.stderr.on('data', (chunk) => { diagnostic = (diagnostic + chunk).slice(-8192); });
142
+ const exited = new Promise((resolve) => {
143
+ child.once('error', (error) => resolve({ error }));
144
+ child.once('close', (code, signal) => {
145
+ resolve({ code, signal });
146
+ });
147
+ });
148
+ try {
149
+ await pipeline(
150
+ child.stdout,
151
+ byteLimit(maxBytes, 'Dingo release binary'),
152
+ fs.createWriteStream(destination, { flags: 'wx', mode: 0o755 }),
153
+ );
154
+ const result = await exited;
155
+ if (result.error) throw result.error;
156
+ if (result.code !== 0) {
157
+ throw new Error(
158
+ `Cannot extract Dingo release (${result.signal || result.code}): ${diagnostic.trim()}`,
159
+ );
160
+ }
161
+ } catch (error) {
162
+ child.kill();
163
+ await exited;
164
+ throw error;
165
+ }
166
+ if ((await fsp.stat(destination)).size === 0) throw new Error('Dingo release binary is empty');
167
+ }
168
+
169
+ function binaryPath(packageRoot) {
170
+ return path.join(packageRoot, 'npm', 'vendor', 'dingo');
171
+ }
172
+
173
+ async function install(packageRoot, options = {}) {
174
+ const { version } = JSON.parse(await fsp.readFile(path.join(packageRoot, 'package.json'), 'utf8'));
175
+ const targetRelease = releaseTarget(version, options.platform, options.arch);
176
+ const checksums = options.checksums ?? JSON.parse(
177
+ await fsp.readFile(path.join(packageRoot, 'npm', 'checksums.json'), 'utf8'),
178
+ );
179
+ const expected = checksums[targetRelease.filename];
180
+ if (typeof expected !== 'string' || !/^[0-9a-f]{64}$/.test(expected)) {
181
+ throw new Error(`No valid checksum for ${targetRelease.filename}`);
182
+ }
183
+ const directory = await fsp.mkdtemp(path.join(os.tmpdir(), 'dingo-npm-'));
184
+ let pending;
185
+ try {
186
+ const archive = path.join(directory, 'release.tar.gz');
187
+ await (options.download || download)(targetRelease.url, archive);
188
+ const archiveSize = (await fsp.stat(archive)).size;
189
+ const maxArchiveBytes = options.maxArchiveBytes ?? MAX_ARCHIVE_BYTES;
190
+ if (archiveSize > maxArchiveBytes) {
191
+ throw new Error(`Dingo release archive exceeds ${maxArchiveBytes} bytes`);
192
+ }
193
+ const actual = await sha256(archive);
194
+ if (!crypto.timingSafeEqual(Buffer.from(actual, 'hex'), Buffer.from(expected, 'hex'))) {
195
+ throw new Error(`Checksum mismatch for ${targetRelease.filename}`);
196
+ }
197
+ const target = binaryPath(packageRoot);
198
+ await fsp.mkdir(path.dirname(target), { recursive: true });
199
+ pending = await fsp.mkdtemp(path.join(path.dirname(target), '.install-'));
200
+ const extracted = path.join(pending, 'dingo');
201
+ await extractBinary(archive, extracted, options.maxBinaryBytes);
202
+ await fsp.chmod(extracted, 0o755);
203
+ await fsp.rename(extracted, target);
204
+ return target;
205
+ } finally {
206
+ await fsp.rm(directory, { recursive: true, force: true });
207
+ if (pending) await fsp.rm(pending, { recursive: true, force: true });
208
+ }
209
+ }
210
+
211
+ module.exports = { binaryPath, download, install, releaseURL };
@@ -0,0 +1,21 @@
1
+ // Copyright 2026 Blink Labs Software
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ 'use strict';
14
+
15
+ const path = require('node:path');
16
+ const { install } = require('./install');
17
+
18
+ install(path.join(__dirname, '..')).catch((error) => {
19
+ console.error(`Cannot install Dingo: ${error.message}`);
20
+ process.exitCode = 1;
21
+ });
package/package.json CHANGED
@@ -1,15 +1,32 @@
1
1
  {
2
2
  "name": "@blinklabs/dingo",
3
- "version": "0.46.4",
3
+ "version": "0.81.0",
4
4
  "description": "Dingo is a Cardano blockchain data node",
5
- "main": "index.js",
6
5
  "bin": {
7
- "dingo": "dingo"
6
+ "dingo": "npm/bin/dingo.js"
8
7
  },
8
+ "files": [
9
+ "npm/install.js",
10
+ "npm/postinstall.js",
11
+ "npm/bin/dingo.js",
12
+ "npm/checksums.json"
13
+ ],
9
14
  "scripts": {
10
- "postinstall": "golang-npm install",
11
- "preuninstall": "golang-npm uninstall"
15
+ "postinstall": "node npm/postinstall.js",
16
+ "test": "node --test npm/test/*.test.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=22"
12
20
  },
21
+ "os": [
22
+ "linux",
23
+ "freebsd",
24
+ "darwin"
25
+ ],
26
+ "cpu": [
27
+ "x64",
28
+ "arm64"
29
+ ],
13
30
  "repository": {
14
31
  "type": "git",
15
32
  "url": "git+https://github.com/blinklabs-io/dingo.git"
@@ -23,13 +40,5 @@
23
40
  "bugs": {
24
41
  "url": "https://github.com/blinklabs-io/dingo/issues"
25
42
  },
26
- "homepage": "https://github.com/blinklabs-io/dingo#readme",
27
- "dependencies": {
28
- "golang-npm": "^0.0.6"
29
- },
30
- "goBinary": {
31
- "name": "dingo",
32
- "path": "./bin",
33
- "url": "https://github.com/blinklabs-io/dingo/releases/download/v{{version}}/dingo-v{{version}}-{{platform}}-{{arch}}.tar.gz"
34
- }
43
+ "homepage": "https://github.com/blinklabs-io/dingo#readme"
35
44
  }
package/.dockerignore DELETED
@@ -1,9 +0,0 @@
1
- .claude/
2
- .codex
3
- .dingo/
4
- .github/
5
- .worktrees/
6
- dingo
7
- Dockerfile
8
- README.md
9
- internal/test/archive-demo/tmp/
package/AGENTS.md DELETED
@@ -1,52 +0,0 @@
1
- # AGENTS.md
2
-
3
- Go Cardano node (Ouroboros). See `CLAUDE.md` for detailed rules; this file has additional content (Build/test section, `make golines` in pre-commit, Key events table) and a different structure — the two are related but not exact copies. Package layout, targets, and flags are derivable from `Makefile`, `go.mod`, `node.go`.
4
-
5
- ## Build / test
6
-
7
- ```
8
- make # fmt, test, build
9
- make test # tests with -race
10
- go test -v -race -run TestName ./path/to/pkg/
11
- ```
12
-
13
- ## Pre-commit
14
-
15
- ```
16
- golangci-lint run ./...
17
- nilaway ./...
18
- modernize ./...
19
- make golines
20
- ```
21
-
22
- ## Testing rules
23
-
24
- - No `time.Sleep()` for sync — use `internal/test/testutil/` (`WaitForCondition`, `RequireReceive`, `context.WithTimeout`).
25
- - Integration tests: `internal/integration/` + `database/immutable/testdata/` (real blocks, slots 0–1.3M).
26
- - Mock fixtures come from `github.com/blinklabs-io/ouroboros-mock` (`fixtures/`, `ledger/`, `conformance/`). Never duplicate mocks inside dingo — extend the shared library so every Blink Labs app (dingo, gouroboros, adder, ...) reuses the same test surface.
27
- - DevNet end-to-end (`internal/test/devnet/run-tests.sh`): validate any change touching consensus, block production, header/VRF/KES/OpCert verification, chain selection, mempool, tx submission, NtN/NtC protocols, epoch boundaries, or nonce computation. Brings up Dingo + cardano-node side by side with `txpump` driving the mempool, so it catches divergence from the reference implementation that unit tests miss. Conformance tests in `internal/test/conformance/` are still mandatory after every change — DevNet is the additional bar for consensus-affecting work.
28
-
29
- ## Non-obvious invariants
30
-
31
- - EventBus for async cross-component notifications: use `event.EventBus.SubscribeFunc()` for block/chain/mempool/peer events. Synchronous state queries between components still use direct method calls.
32
- - CBOR offsets: UTxOs/txs stored as 52-byte refs (magic `"DOFF"` + slot + hash + offset + length), resolved by `TieredCborCache` (hot → block LRU → cold extract). See `database/cbor_offset.go`.
33
- - Cert ordering: `Order("added_slot DESC, block_index DESC, cert_index DESC")` — `cert_index` resets per tx, so `block_index` is required to disambiguate across txs in the same block.
34
- - Rollbacks: delivered on `chain.update` as a `chain.ChainRollbackEvent` payload (no separate `chain.rollback` topic); also subscribe to `chain.fork_detected` for fork metrics. Check `TransactionEvent.Rollback` for undo.
35
- - Stake snapshots: mark/set/go rotation at epoch boundaries (Praos). `LedgerView.GetStakeDistribution(epoch)` for leader election. Per-pool stake in `PoolStakeSnapshot`; aggregates in `EpochSummary`.
36
- - Plugins (`database/plugin/`): blob = `badger` | `gcs` | `s3`; metadata = `sqlite` | `mysql` | `postgres`.
37
-
38
- ## Key events
39
-
40
- | Event | Meaning |
41
- |---|---|
42
- | `chain.update` | block added |
43
- | `chain.fork_detected` | fork |
44
- | `chainselection.chain_switch` | active peer changed |
45
- | `epoch.transition` | epoch boundary — triggers stake snapshot |
46
- | `mempool.add_tx` / `mempool.remove_tx` | tx lifecycle |
47
- | `connmanager.conn_closed` | connection closed |
48
- | `peergov.peer_churn` | peer rotation |
49
-
50
- ## Config
51
-
52
- Priority: CLI > env > YAML > defaults. Key env vars: `CARDANO_NETWORK`, `CARDANO_DATABASE_PATH`, `DINGO_DATABASE_BLOB_PLUGIN`, `DINGO_DATABASE_METADATA_PLUGIN`.