@did-btcr2/cli 0.12.1 → 0.12.3

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/dist/cjs/index.js CHANGED
@@ -63,7 +63,7 @@ var import_utils2 = require("@noble/hashes/utils.js");
63
63
 
64
64
  // src/config.ts
65
65
  var import_api = require("@did-btcr2/api");
66
- var import_node_fs4 = require("fs");
66
+ var import_node_fs5 = require("fs");
67
67
  var import_node_os2 = require("os");
68
68
  var import_node_path4 = require("path");
69
69
 
@@ -142,7 +142,7 @@ function assertSecurePerms(path) {
142
142
  var import_key_manager = require("@did-btcr2/key-manager");
143
143
 
144
144
  // src/keystore/file-key-store.ts
145
- var import_node_fs2 = require("fs");
145
+ var import_node_fs3 = require("fs");
146
146
  var import_node_path3 = require("path");
147
147
  var import_base2 = require("@scure/base");
148
148
 
@@ -230,6 +230,90 @@ function decryptSecret(env, passphrase) {
230
230
  }
231
231
  }
232
232
 
233
+ // src/keystore/lock.ts
234
+ var import_node_fs2 = require("fs");
235
+ var DEFAULT_TIMEOUT_MS = 1e4;
236
+ var DEFAULT_STALE_MS = 3e4;
237
+ var DEFAULT_RETRY_MS = 50;
238
+ var tokenCounter = 0;
239
+ function sleepSync(ms) {
240
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
241
+ }
242
+ function isProcessAlive(pid) {
243
+ if (!Number.isInteger(pid) || pid <= 0) return false;
244
+ try {
245
+ process.kill(pid, 0);
246
+ return true;
247
+ } catch (error) {
248
+ return error.code === "EPERM";
249
+ }
250
+ }
251
+ function breakIfStale(lockPath, staleMs) {
252
+ let ageMs;
253
+ let pid;
254
+ try {
255
+ const stat = (0, import_node_fs2.statSync)(lockPath);
256
+ ageMs = Date.now() - stat.mtimeMs;
257
+ pid = Number.parseInt((0, import_node_fs2.readFileSync)(lockPath, "utf-8").split(".")[0] ?? "", 10);
258
+ } catch {
259
+ return true;
260
+ }
261
+ if (ageMs > staleMs || !isProcessAlive(pid)) {
262
+ try {
263
+ (0, import_node_fs2.rmSync)(lockPath, { force: true });
264
+ } catch {
265
+ }
266
+ return true;
267
+ }
268
+ return false;
269
+ }
270
+ function releaseIfOwner(lockPath, token) {
271
+ try {
272
+ if ((0, import_node_fs2.readFileSync)(lockPath, "utf-8") === token) (0, import_node_fs2.rmSync)(lockPath, { force: true });
273
+ } catch {
274
+ }
275
+ }
276
+ function withFileLock(lockPath, fn, options = {}) {
277
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
278
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
279
+ const retryMs = options.retryMs ?? DEFAULT_RETRY_MS;
280
+ const token = `${process.pid}.${tokenCounter++}`;
281
+ const deadline = Date.now() + timeoutMs;
282
+ for (; ; ) {
283
+ try {
284
+ const fd = (0, import_node_fs2.openSync)(lockPath, "wx", 384);
285
+ try {
286
+ (0, import_node_fs2.writeSync)(fd, token);
287
+ } finally {
288
+ (0, import_node_fs2.closeSync)(fd);
289
+ }
290
+ break;
291
+ } catch (error) {
292
+ if (error.code !== "EEXIST") {
293
+ throw new KeyStoreError(
294
+ `Failed to acquire keystore lock at ${lockPath}.`,
295
+ "KEYSTORE_LOCK_ERROR",
296
+ { lockPath, cause: error instanceof Error ? error.message : String(error) }
297
+ );
298
+ }
299
+ if (breakIfStale(lockPath, staleMs)) continue;
300
+ if (Date.now() >= deadline) {
301
+ throw new KeyStoreError(
302
+ `Timed out after ${timeoutMs}ms waiting for the keystore lock at ${lockPath}. Another btcr2 process may be writing; retry, or remove the lock file if no other process is running.`,
303
+ "KEYSTORE_LOCKED_ERROR",
304
+ { lockPath, timeoutMs }
305
+ );
306
+ }
307
+ sleepSync(retryMs);
308
+ }
309
+ }
310
+ try {
311
+ return fn();
312
+ } finally {
313
+ releaseIfOwner(lockPath, token);
314
+ }
315
+ }
316
+
233
317
  // src/keystore/paths.ts
234
318
  var import_node_os = require("os");
235
319
  var import_node_path2 = require("path");
@@ -242,23 +326,27 @@ function defaultKeystorePath() {
242
326
  var KEYSTORE_VERSION = 1;
243
327
  var FileKeyStore = class {
244
328
  #path;
329
+ #lockPath;
330
+ #lockOptions;
245
331
  #getPassphrase;
246
332
  #argonParams;
247
333
  #cache = /* @__PURE__ */ new Map();
248
334
  #active;
249
335
  constructor(options) {
250
336
  this.#path = options.path ?? defaultKeystorePath();
337
+ this.#lockPath = `${this.#path}.lock`;
338
+ this.#lockOptions = options.lock ?? {};
251
339
  this.#getPassphrase = options.getPassphrase;
252
340
  this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
253
341
  ensureDir((0, import_node_path3.dirname)(this.#path), 448);
254
- this.#load();
342
+ this.#loadFromDisk();
255
343
  }
256
- #load() {
257
- if (!(0, import_node_fs2.existsSync)(this.#path)) return;
344
+ #loadFromDisk() {
345
+ if (!(0, import_node_fs3.existsSync)(this.#path)) return;
258
346
  assertSecurePerms(this.#path);
259
347
  let parsed;
260
348
  try {
261
- parsed = JSON.parse((0, import_node_fs2.readFileSync)(this.#path, "utf-8"));
349
+ parsed = JSON.parse((0, import_node_fs3.readFileSync)(this.#path, "utf-8"));
262
350
  } catch {
263
351
  throw new KeyStoreError(
264
352
  `Keystore at ${this.#path} is corrupt or unreadable.`,
@@ -317,6 +405,42 @@ var FileKeyStore = class {
317
405
  writeFileAtomic(this.#path, `${JSON.stringify(file, null, 2)}
318
406
  `, 384);
319
407
  }
408
+ /**
409
+ * Re-reads the file into the cache, discarding the prior in-memory view so a
410
+ * mutation applies on top of whatever other processes have written. Secrets
411
+ * already decrypted this session are carried over for entries whose sealed
412
+ * envelope is byte-identical on disk, so a mid-session write does not force a
413
+ * re-prompt for keys it did not touch.
414
+ */
415
+ #reload() {
416
+ const carried = /* @__PURE__ */ new Map();
417
+ for (const [id, entry] of this.#cache) {
418
+ if (entry.secret && entry.decrypted) carried.set(id, { secret: entry.secret, decrypted: entry.decrypted });
419
+ }
420
+ this.#cache.clear();
421
+ this.#active = void 0;
422
+ this.#loadFromDisk();
423
+ for (const [id, prior] of carried) {
424
+ const entry = this.#cache.get(id);
425
+ if (entry?.secret && JSON.stringify(entry.secret) === JSON.stringify(prior.secret)) {
426
+ entry.decrypted = prior.decrypted;
427
+ }
428
+ }
429
+ }
430
+ /**
431
+ * Runs a cache mutation under the exclusive write lock, reloading the file
432
+ * first so the change merges with any concurrent writer's change rather than
433
+ * overwriting it, then flushing the result atomically. Callers must do any
434
+ * expensive work (such as sealing a secret with argon2id) before calling this,
435
+ * so the locked critical section stays short.
436
+ */
437
+ #mutate(apply) {
438
+ withFileLock(this.#lockPath, () => {
439
+ this.#reload();
440
+ apply();
441
+ this.#flush();
442
+ }, this.#lockOptions);
443
+ }
320
444
  get(id) {
321
445
  const entry = this.#cache.get(id);
322
446
  if (!entry) return void 0;
@@ -342,26 +466,28 @@ var FileKeyStore = class {
342
466
  }
343
467
  set(id, value) {
344
468
  const secret = value.secretKey ? encryptSecret(value.secretKey, this.#getPassphrase(), this.#argonParams) : void 0;
345
- this.#cache.set(id, {
346
- publicKey: value.publicKey,
347
- ...value.tags && { tags: value.tags },
348
- ...secret && { secret },
349
- ...value.secretKey && { decrypted: value.secretKey }
469
+ this.#mutate(() => {
470
+ this.#cache.set(id, {
471
+ publicKey: value.publicKey,
472
+ ...value.tags && { tags: value.tags },
473
+ ...secret && { secret },
474
+ ...value.secretKey && { decrypted: value.secretKey }
475
+ });
350
476
  });
351
- this.#flush();
352
477
  }
353
478
  delete(id) {
354
- const existed = this.#cache.delete(id);
355
- if (existed) {
356
- if (this.#active === id) this.#active = void 0;
357
- this.#flush();
358
- }
479
+ let existed = false;
480
+ this.#mutate(() => {
481
+ existed = this.#cache.delete(id);
482
+ if (existed && this.#active === id) this.#active = void 0;
483
+ });
359
484
  return existed;
360
485
  }
361
486
  clear() {
362
- this.#cache.clear();
363
- this.#active = void 0;
364
- this.#flush();
487
+ this.#mutate(() => {
488
+ this.#cache.clear();
489
+ this.#active = void 0;
490
+ });
365
491
  }
366
492
  /** All stored values with secret keys omitted. Never decrypts, never prompts. */
367
493
  list() {
@@ -400,11 +526,12 @@ var FileKeyStore = class {
400
526
  * clears it. Throws if the identifier is not a known key.
401
527
  */
402
528
  setActive(id) {
403
- if (id !== void 0 && !this.#cache.has(id)) {
404
- throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, "KEY_NOT_FOUND_ERROR", { keyId: id });
405
- }
406
- this.#active = id;
407
- this.#flush();
529
+ this.#mutate(() => {
530
+ if (id !== void 0 && !this.#cache.has(id)) {
531
+ throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, "KEY_NOT_FOUND_ERROR", { keyId: id });
532
+ }
533
+ this.#active = id;
534
+ });
408
535
  }
409
536
  };
410
537
 
@@ -464,13 +591,13 @@ var FileBackedKeyManager = class {
464
591
  };
465
592
 
466
593
  // src/keystore/passphrase.ts
467
- var import_node_fs3 = require("fs");
594
+ var import_node_fs4 = require("fs");
468
595
  var ENV_KEYSTORE_PASSPHRASE = "BTCR2_KEYSTORE_PASSPHRASE";
469
596
  function acquirePassphrase(options = {}) {
470
597
  const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
471
598
  if (fromEnv) return assertNonEmpty(fromEnv.replace(/\r?\n$/, ""));
472
599
  if (options.passphraseFile) {
473
- return assertNonEmpty((0, import_node_fs3.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
600
+ return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
474
601
  }
475
602
  if (!process.stdin.isTTY) {
476
603
  throw new KeyStoreError(
@@ -504,7 +631,7 @@ function promptHidden(label) {
504
631
  for (; ; ) {
505
632
  let read = 0;
506
633
  try {
507
- read = (0, import_node_fs3.readSync)(stdin.fd, byte, 0, 1, null);
634
+ read = (0, import_node_fs4.readSync)(stdin.fd, byte, 0, 1, null);
508
635
  } catch (error) {
509
636
  const code = error.code;
510
637
  if (code === "EAGAIN") continue;
@@ -601,7 +728,7 @@ function defaultConfigPath() {
601
728
  }
602
729
  function readConfigFile(path) {
603
730
  try {
604
- const content = (0, import_node_fs4.readFileSync)(path, "utf-8");
731
+ const content = (0, import_node_fs5.readFileSync)(path, "utf-8");
605
732
  return JSON.parse(content);
606
733
  } catch {
607
734
  return void 0;
@@ -1034,7 +1161,7 @@ function parseJsonArg2(flagName) {
1034
1161
  // src/commands/key.ts
1035
1162
  var import_keypair = require("@did-btcr2/keypair");
1036
1163
  var import_utils3 = require("@noble/hashes/utils.js");
1037
- var import_node_fs5 = require("fs");
1164
+ var import_node_fs6 = require("fs");
1038
1165
  function registerKeyCommand(program, factory, globals) {
1039
1166
  const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
1040
1167
  const print = (result) => console.log(formatResult(result, globals()));
@@ -1132,7 +1259,7 @@ function parseHex(hex, expectedBytes, label) {
1132
1259
  function readHexFile(path, expectedBytes, label) {
1133
1260
  let content;
1134
1261
  try {
1135
- content = (0, import_node_fs5.readFileSync)(path, "utf-8");
1262
+ content = (0, import_node_fs6.readFileSync)(path, "utf-8");
1136
1263
  } catch {
1137
1264
  throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
1138
1265
  }
@@ -1141,7 +1268,7 @@ function readHexFile(path, expectedBytes, label) {
1141
1268
  function writeSecretFile(path, contents) {
1142
1269
  let fd;
1143
1270
  try {
1144
- fd = (0, import_node_fs5.openSync)(path, "wx", 384);
1271
+ fd = (0, import_node_fs6.openSync)(path, "wx", 384);
1145
1272
  } catch (error) {
1146
1273
  if (error.code === "EEXIST") {
1147
1274
  throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
@@ -1149,14 +1276,14 @@ function writeSecretFile(path, contents) {
1149
1276
  throw error;
1150
1277
  }
1151
1278
  try {
1152
- (0, import_node_fs5.writeFileSync)(fd, contents);
1279
+ (0, import_node_fs6.writeFileSync)(fd, contents);
1153
1280
  } finally {
1154
- (0, import_node_fs5.closeSync)(fd);
1281
+ (0, import_node_fs6.closeSync)(fd);
1155
1282
  }
1156
1283
  }
1157
1284
 
1158
1285
  // src/commands/config.ts
1159
- var import_node_fs6 = require("fs");
1286
+ var import_node_fs7 = require("fs");
1160
1287
  var import_node_path5 = require("path");
1161
1288
  function registerConfigCommand(program, globals) {
1162
1289
  const config = program.command("config").description("Read and write CLI configuration.");
@@ -1164,7 +1291,7 @@ function registerConfigCommand(program, globals) {
1164
1291
  const print = (result) => console.log(formatResult(result, globals()));
1165
1292
  config.command("init").description("Create a default config file with one profile per network.").option("--force", "Overwrite an existing config file.", false).action((options) => {
1166
1293
  const p = path();
1167
- if ((0, import_node_fs6.existsSync)(p) && !options.force) {
1294
+ if ((0, import_node_fs7.existsSync)(p) && !options.force) {
1168
1295
  throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
1169
1296
  }
1170
1297
  const scaffold = {
@@ -1276,14 +1403,14 @@ function completionScript(shell) {
1276
1403
  }
1277
1404
 
1278
1405
  // src/version.ts
1279
- var import_node_fs7 = require("fs");
1406
+ var import_node_fs8 = require("fs");
1280
1407
  var import_node_path6 = require("path");
1281
1408
  var import_node_url = require("url");
1282
1409
  function readVersion() {
1283
1410
  let dir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
1284
1411
  for (let i = 0; i < 5; i++) {
1285
1412
  try {
1286
- const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
1413
+ const pkg = JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
1287
1414
  if (pkg.name === "@did-btcr2/cli") return pkg.version;
1288
1415
  } catch {
1289
1416
  }
@@ -4,6 +4,7 @@ import { base64urlnopad } from '@scure/base';
4
4
  import { assertSecurePerms, ensureDir, writeFileAtomic } from './atomic.js';
5
5
  import { DEFAULT_ARGON_PARAMS, decryptSecret, encryptSecret } from './envelope.js';
6
6
  import { KeyStoreError } from './error.js';
7
+ import { withFileLock } from './lock.js';
7
8
  import { defaultKeystorePath } from './paths.js';
8
9
  /** Current on-disk keystore file format version. */
9
10
  export const KEYSTORE_VERSION = 1;
@@ -13,6 +14,15 @@ export const KEYSTORE_VERSION = 1;
13
14
  * in memory at construction and flushing the whole file atomically on every
14
15
  * mutation.
15
16
  *
17
+ * Every mutation runs under an exclusive cross-process lock and, inside that
18
+ * lock, reloads the file from disk before applying its change and flushing.
19
+ * Caching at construction and flushing the whole file is otherwise a lost-update
20
+ * race: two `btcr2` processes that each load, then each write, would have the
21
+ * second overwrite the first. The lock serializes the writers and the reload
22
+ * merges any change the other made, so concurrent invocations compose instead of
23
+ * clobbering. Reads stay lock-free: an atomic rename means a concurrent reader
24
+ * always sees a complete file, old or new.
25
+ *
16
26
  * Secrets are materialized only through {@link FileKeyStore.get}. The
17
27
  * {@link FileKeyStore.list} and {@link FileKeyStore.entries} projections omit
18
28
  * secret keys and never decrypt, so enumerating the store never triggers a
@@ -20,18 +30,22 @@ export const KEYSTORE_VERSION = 1;
20
30
  */
21
31
  export class FileKeyStore {
22
32
  #path;
33
+ #lockPath;
34
+ #lockOptions;
23
35
  #getPassphrase;
24
36
  #argonParams;
25
37
  #cache = new Map();
26
38
  #active;
27
39
  constructor(options) {
28
40
  this.#path = options.path ?? defaultKeystorePath();
41
+ this.#lockPath = `${this.#path}.lock`;
42
+ this.#lockOptions = options.lock ?? {};
29
43
  this.#getPassphrase = options.getPassphrase;
30
44
  this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
31
45
  ensureDir(dirname(this.#path), 0o700);
32
- this.#load();
46
+ this.#loadFromDisk();
33
47
  }
34
- #load() {
48
+ #loadFromDisk() {
35
49
  if (!existsSync(this.#path))
36
50
  return;
37
51
  assertSecurePerms(this.#path);
@@ -82,6 +96,43 @@ export class FileKeyStore {
82
96
  };
83
97
  writeFileAtomic(this.#path, `${JSON.stringify(file, null, 2)}\n`, 0o600);
84
98
  }
99
+ /**
100
+ * Re-reads the file into the cache, discarding the prior in-memory view so a
101
+ * mutation applies on top of whatever other processes have written. Secrets
102
+ * already decrypted this session are carried over for entries whose sealed
103
+ * envelope is byte-identical on disk, so a mid-session write does not force a
104
+ * re-prompt for keys it did not touch.
105
+ */
106
+ #reload() {
107
+ const carried = new Map();
108
+ for (const [id, entry] of this.#cache) {
109
+ if (entry.secret && entry.decrypted)
110
+ carried.set(id, { secret: entry.secret, decrypted: entry.decrypted });
111
+ }
112
+ this.#cache.clear();
113
+ this.#active = undefined;
114
+ this.#loadFromDisk();
115
+ for (const [id, prior] of carried) {
116
+ const entry = this.#cache.get(id);
117
+ if (entry?.secret && JSON.stringify(entry.secret) === JSON.stringify(prior.secret)) {
118
+ entry.decrypted = prior.decrypted;
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Runs a cache mutation under the exclusive write lock, reloading the file
124
+ * first so the change merges with any concurrent writer's change rather than
125
+ * overwriting it, then flushing the result atomically. Callers must do any
126
+ * expensive work (such as sealing a secret with argon2id) before calling this,
127
+ * so the locked critical section stays short.
128
+ */
129
+ #mutate(apply) {
130
+ withFileLock(this.#lockPath, () => {
131
+ this.#reload();
132
+ apply();
133
+ this.#flush();
134
+ }, this.#lockOptions);
135
+ }
85
136
  get(id) {
86
137
  const entry = this.#cache.get(id);
87
138
  if (!entry)
@@ -112,30 +163,36 @@ export class FileKeyStore {
112
163
  return this.#cache.has(id);
113
164
  }
114
165
  set(id, value) {
166
+ // Seal the secret before taking the lock: argon2id is deliberately slow and
167
+ // must not extend the critical section that blocks other processes.
115
168
  const secret = value.secretKey
116
169
  ? encryptSecret(value.secretKey, this.#getPassphrase(), this.#argonParams)
117
170
  : undefined;
118
- this.#cache.set(id, {
119
- publicKey: value.publicKey,
120
- ...(value.tags && { tags: value.tags }),
121
- ...(secret && { secret }),
122
- ...(value.secretKey && { decrypted: value.secretKey }),
171
+ this.#mutate(() => {
172
+ this.#cache.set(id, {
173
+ publicKey: value.publicKey,
174
+ ...(value.tags && { tags: value.tags }),
175
+ ...(secret && { secret }),
176
+ ...(value.secretKey && { decrypted: value.secretKey }),
177
+ });
123
178
  });
124
- this.#flush();
125
179
  }
126
180
  delete(id) {
127
- const existed = this.#cache.delete(id);
128
- if (existed) {
129
- if (this.#active === id)
181
+ // `existed` reflects the freshly-reloaded state inside the lock, so a key a
182
+ // concurrent process already removed reads as absent rather than resurrected.
183
+ let existed = false;
184
+ this.#mutate(() => {
185
+ existed = this.#cache.delete(id);
186
+ if (existed && this.#active === id)
130
187
  this.#active = undefined;
131
- this.#flush();
132
- }
188
+ });
133
189
  return existed;
134
190
  }
135
191
  clear() {
136
- this.#cache.clear();
137
- this.#active = undefined;
138
- this.#flush();
192
+ this.#mutate(() => {
193
+ this.#cache.clear();
194
+ this.#active = undefined;
195
+ });
139
196
  }
140
197
  /** All stored values with secret keys omitted. Never decrypts, never prompts. */
141
198
  list() {
@@ -174,11 +231,14 @@ export class FileKeyStore {
174
231
  * clears it. Throws if the identifier is not a known key.
175
232
  */
176
233
  setActive(id) {
177
- if (id !== undefined && !this.#cache.has(id)) {
178
- throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, 'KEY_NOT_FOUND_ERROR', { keyId: id });
179
- }
180
- this.#active = id;
181
- this.#flush();
234
+ this.#mutate(() => {
235
+ // The existence check runs against the reloaded state, so a key another
236
+ // process added in the meantime is a valid active target.
237
+ if (id !== undefined && !this.#cache.has(id)) {
238
+ throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, 'KEY_NOT_FOUND_ERROR', { keyId: id });
239
+ }
240
+ this.#active = id;
241
+ });
182
242
  }
183
243
  }
184
244
  //# sourceMappingURL=file-key-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-key-store.js","sourceRoot":"","sources":["../../../../src/keystore/file-key-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-key-store.js","sourceRoot":"","sources":["../../../../src/keystore/file-key-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import { closeSync, openSync, readFileSync, rmSync, statSync, writeSync } from 'node:fs';
2
+ import { KeyStoreError } from './error.js';
3
+ const DEFAULT_TIMEOUT_MS = 10_000;
4
+ const DEFAULT_STALE_MS = 30_000;
5
+ const DEFAULT_RETRY_MS = 50;
6
+ // A per-process counter so a lock token is unambiguous even when one process
7
+ // runs several stores over the same path (as the tests do): the pid alone would
8
+ // collide, the pid plus counter never does.
9
+ let tokenCounter = 0;
10
+ /**
11
+ * Sleeps synchronously for `ms` without spinning the CPU. The keystore store is
12
+ * a synchronous interface, so the wait must block the thread rather than yield a
13
+ * promise. `Atomics.wait` on a private buffer no other thread can notify always
14
+ * runs the full duration. Node-only, which the keystore already is.
15
+ */
16
+ function sleepSync(ms) {
17
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
18
+ }
19
+ /**
20
+ * Reports whether a process is still running. `process.kill(pid, 0)` sends no
21
+ * signal but performs the existence/permission check: ESRCH means the process
22
+ * is gone, EPERM means it exists under another user (still alive).
23
+ */
24
+ function isProcessAlive(pid) {
25
+ if (!Number.isInteger(pid) || pid <= 0)
26
+ return false;
27
+ try {
28
+ process.kill(pid, 0);
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ return error.code === 'EPERM';
33
+ }
34
+ }
35
+ /**
36
+ * Removes the lock if its writer process is gone or it has aged past `staleMs`,
37
+ * so a process that crashed mid-mutation cannot wedge the keystore permanently.
38
+ * Returns true when the lock was broken or had already vanished (caller should
39
+ * retry the create immediately), false when a live, fresh holder still owns it.
40
+ */
41
+ function breakIfStale(lockPath, staleMs) {
42
+ let ageMs;
43
+ let pid;
44
+ try {
45
+ const stat = statSync(lockPath);
46
+ ageMs = Date.now() - stat.mtimeMs;
47
+ pid = Number.parseInt(readFileSync(lockPath, 'utf-8').split('.')[0] ?? '', 10);
48
+ }
49
+ catch {
50
+ // The lock disappeared between our failed create and this inspection; the
51
+ // caller can race for it again straight away.
52
+ return true;
53
+ }
54
+ if (ageMs > staleMs || !isProcessAlive(pid)) {
55
+ try {
56
+ rmSync(lockPath, { force: true });
57
+ }
58
+ catch {
59
+ // Another waiter broke it first; either way it is gone, so retry.
60
+ }
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ /** Releases the lock only while it still holds our token, never one broken from us as stale. */
66
+ function releaseIfOwner(lockPath, token) {
67
+ try {
68
+ if (readFileSync(lockPath, 'utf-8') === token)
69
+ rmSync(lockPath, { force: true });
70
+ }
71
+ catch {
72
+ // Already removed (broken as stale, or never created); nothing to release.
73
+ }
74
+ }
75
+ /**
76
+ * Runs `fn` while holding an exclusive, cross-process advisory lock on
77
+ * `lockPath`, then releases it.
78
+ *
79
+ * The lock is an `O_EXCL` lockfile: creating it fails when another holder
80
+ * exists, which serializes mutators across separate `btcr2` processes. This is
81
+ * the missing half of a safe read-modify-write on the keystore file: an atomic
82
+ * rename keeps the file from tearing, but only mutual exclusion (paired with a
83
+ * reload inside the lock) keeps two concurrent writers from clobbering each
84
+ * other's changes. A lock whose writer has died, or that has aged past
85
+ * `staleMs`, is broken so a crash cannot deadlock future invocations.
86
+ *
87
+ * @throws {KeyStoreError} `KEYSTORE_LOCKED_ERROR` if the lock cannot be acquired
88
+ * within `timeoutMs`, or `KEYSTORE_LOCK_ERROR` on an unexpected filesystem error.
89
+ */
90
+ export function withFileLock(lockPath, fn, options = {}) {
91
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
92
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
93
+ const retryMs = options.retryMs ?? DEFAULT_RETRY_MS;
94
+ const token = `${process.pid}.${tokenCounter++}`;
95
+ const deadline = Date.now() + timeoutMs;
96
+ for (;;) {
97
+ try {
98
+ const fd = openSync(lockPath, 'wx', 0o600);
99
+ try {
100
+ writeSync(fd, token);
101
+ }
102
+ finally {
103
+ closeSync(fd);
104
+ }
105
+ break;
106
+ }
107
+ catch (error) {
108
+ if (error.code !== 'EEXIST') {
109
+ throw new KeyStoreError(`Failed to acquire keystore lock at ${lockPath}.`, 'KEYSTORE_LOCK_ERROR', { lockPath, cause: error instanceof Error ? error.message : String(error) });
110
+ }
111
+ if (breakIfStale(lockPath, staleMs))
112
+ continue;
113
+ if (Date.now() >= deadline) {
114
+ throw new KeyStoreError(`Timed out after ${timeoutMs}ms waiting for the keystore lock at ${lockPath}. `
115
+ + 'Another btcr2 process may be writing; retry, or remove the lock file if no other process is running.', 'KEYSTORE_LOCKED_ERROR', { lockPath, timeoutMs });
116
+ }
117
+ sleepSync(retryMs);
118
+ }
119
+ }
120
+ try {
121
+ return fn();
122
+ }
123
+ finally {
124
+ releaseIfOwner(lockPath, token);
125
+ }
126
+ }
127
+ //# sourceMappingURL=lock.js.map