@ethernity-cloud/sdk-js 1.5.14 → 1.5.15

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/info.js CHANGED
@@ -40,17 +40,17 @@ const NETWORKS = {
40
40
  type: 'mainnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
41
41
  protocol: '0x549A6E06BB2084100148D50F51CF77a3436C3Ae7',
42
42
  imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
43
- esr: '0x39d6f2A98f7FFF9866AAaa0EAa9aEd8bcf6f728E',
43
+ esr: '0xDaFa1e3CAF370765275d853cd86dDEd671Ce29Dd',
44
44
  },
45
45
  BLOXBERG_TESTNET: {
46
46
  type: 'testnet', chainId: 8995, rpc: 'https://core.bloxberg.org',
47
47
  protocol: '0x02882F03097fE8cD31afbdFbB5D72a498B41112c',
48
48
  imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
49
- esr: '0xdfDD088b9cB998280685aF4E93DC0b37952aB08e',
49
+ esr: '0xD7a7Cb9cbb0Ca1adFb2B8405382f299EA1c6132f',
50
50
  },
51
51
  LITVM_LITEFORGE: {
52
52
  type: 'testnet', chainId: 0, rpc: 'https://liteforge.rpc.caldera.xyz/infra-partner-http',
53
- protocol: '', imageRegistry: '', esr: '0x71a4bb5AC4c8F37ea0d9394fB29bBC06eEC28000',
53
+ protocol: '', imageRegistry: '', esr: '0x213aA794F29EA717B9226dF81F7317334Ac36169',
54
54
  },
55
55
  };
56
56
 
@@ -202,8 +202,9 @@ async function esrQuery(prov, name, net, args) {
202
202
  return {
203
203
  enclave: args.enclave, key: args.key, nonce: n,
204
204
  note: 'last accepted idempotency nonce (PUBLIC data, free eth_call); '
205
- + '0 = no guarded commit yet. Pass any greater value to commit() to '
206
- + 'guard against duplicates; the contract enforces in-order per key',
205
+ + '0 = no guarded commit yet. Pass EXACTLY nonce + 1 to commit() to '
206
+ + 'guard against duplicates; the contract enforces the sequence '
207
+ + 'strictly per key (no gaps, no reuse)',
207
208
  };
208
209
  }
209
210
  if (sub === 'state') {
@@ -435,14 +435,14 @@ class StateRegistry {
435
435
 
436
436
  /**
437
437
  * The last accepted idempotency nonce for `key` (0 if none was ever used).
438
- * A dApp that wants duplicate suppression reads this, picks a greater value
439
- * (e.g. +1, or a timestamp), and passes it to commit(..., { nonce }).
438
+ * A dApp that wants duplicate suppression reads this and passes
439
+ * getNonce(key) + 1 to commit(..., { nonce }) -- the contract accepts
440
+ * EXACTLY the next value in sequence, nothing else.
440
441
  *
441
442
  * The nonce is PUBLIC data: the registry records it on-chain next to the
442
443
  * version, and anyone can read it with a free eth_call (getNonce) -- so no
443
444
  * read-ACL applies here, and web3 clients see the same value via the
444
- * runner's esrNonce. Use opaque monotonic values (a counter or a
445
- * timestamp), never secret-derived ones.
445
+ * runner's esrNonce.
446
446
  *
447
447
  * Reads the chain (authoritative, post-relay), merged with this task's
448
448
  * own accepted commits so `commit(..., { nonce: N }); getNonce()` returns N
@@ -463,9 +463,10 @@ class StateRegistry {
463
463
  async commit(key, mutate, opts = 3) {
464
464
  // Back-compat: the third arg used to be `attempts` (a number); it may now
465
465
  // also be { attempts, nonce }. `nonce` is an idempotency guard the dApp
466
- // controls: it must be STRICTLY GREATER than the last accepted nonce for
467
- // the key (see getNonce). A duplicate or stale nonce throws
468
- // StateNonceError and the state is NOT changed.
466
+ // controls: it must be EXACTLY the last accepted nonce for the key + 1
467
+ // (see getNonce; the sequence is 1, 2, 3, ... with no gaps and no
468
+ // reuse). Anything else throws StateNonceError and the state is NOT
469
+ // changed.
469
470
  const { attempts = 3, nonce = null } =
470
471
  typeof opts === 'number' ? { attempts: opts } : (opts || {});
471
472
  const transform = async (acl, data) => {
@@ -502,10 +503,11 @@ class StateRegistry {
502
503
  const n = Math.trunc(nonce);
503
504
  // eslint-disable-next-line no-await-in-loop
504
505
  const storedNonce = await this.getNonce(key);
505
- if (n <= storedNonce) {
506
+ if (n !== storedNonce + 1) {
506
507
  throw new StateNonceError(
507
- `nonce ${n} was already used for state key '${key}' (last accepted: ` +
508
- `${storedNonce}); duplicate commit suppressed, state unchanged`
508
+ `nonce ${n} is out of sequence for state key '${key}' (last accepted: ` +
509
+ `${storedNonce}, expected: ${storedNonce + 1}); commit suppressed, ` +
510
+ 'state unchanged'
509
511
  );
510
512
  }
511
513
  }
@@ -128,9 +128,9 @@ class MemContract {
128
128
  }
129
129
 
130
130
  // ---- the on-chain commitFor, applied locally ----
131
- // Idempotency nonces are enforced IN ORDER per (enclave, key), exactly like
132
- // the contract: nonce != 0 must be strictly greater than the stored value
133
- // (gaps allowed) or NonceOutOfOrder is thrown; nonce == 0 preserves it.
131
+ // Idempotency nonces are enforced strictly sequentially per (enclave, key),
132
+ // exactly like the contract: nonce != 0 must be EXACTLY the stored value + 1
133
+ // (no gaps, no reuse) or NonceOutOfOrder is thrown; nonce == 0 preserves it.
134
134
  applyCommit(enclave, keyHash, cid, expectedVersion, nonce = 0) {
135
135
  const k = key(enclave, keyHash);
136
136
  const entry = this._entries[k] || {};
@@ -141,7 +141,7 @@ class MemContract {
141
141
  let storedNonce = Number(entry.nonce || 0);
142
142
  const n = Number(nonce || 0);
143
143
  if (n !== 0) {
144
- if (n <= storedNonce) {
144
+ if (n !== storedNonce + 1) {
145
145
  throw new Error(`NonceOutOfOrder: stored ${storedNonce}, given ${n}`);
146
146
  }
147
147
  storedNonce = n;
@@ -38,43 +38,55 @@ async function main() {
38
38
  st = await r.get('counter');
39
39
  if (st.n !== 1) throw new Error('duplicate mutated state');
40
40
 
41
- // 3. next nonce and a gap both accepted (in order, gaps allowed)
41
+ // 3. exactly +1 accepted; a gap (skip-ahead) is rejected
42
42
  await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 2 });
43
- await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 10 });
44
43
  st = await r.get('counter');
45
- if (st.n !== 3 || (await r.getNonce('counter')) !== 10) throw new Error('step3');
46
- console.log('3. in-order nonces 2 then 10 (gap) ok, getNonce()==10');
44
+ if (st.n !== 2 || (await r.getNonce('counter')) !== 2) throw new Error('step3a');
45
+ threw = false;
46
+ try {
47
+ await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 10 });
48
+ } catch (e) {
49
+ if (!(e instanceof StateNonceError)) throw e;
50
+ threw = true;
51
+ }
52
+ if (!threw) throw new Error('gap accepted');
53
+ console.log('3. nonce=2 (+1) accepted, gap nonce=10 rejected ok');
47
54
 
48
55
  // 4. nonce-less commit preserves the stored nonce
49
56
  await r.commit('counter', (s) => ({ ...s, note: 'free' }));
50
- if ((await r.getNonce('counter')) !== 10) throw new Error('step4');
57
+ if ((await r.getNonce('counter')) !== 2) throw new Error('step4');
51
58
  console.log('4. nonce-less commit preserves stored nonce ok');
52
59
 
53
- // 5. out-of-order (lower, non-zero) nonce rejected
60
+ // 5. stale (reused) nonce rejected; the exact next one still works
54
61
  threw = false;
55
62
  try {
56
- await r.commit('counter', (s) => s, { nonce: 5 });
63
+ await r.commit('counter', (s) => s, { nonce: 1 });
57
64
  } catch (e) {
58
65
  if (!(e instanceof StateNonceError)) throw e;
59
66
  threw = true;
60
67
  }
61
68
  if (!threw) throw new Error('stale accepted');
62
- console.log('5. out-of-order nonce=5 rejected ok');
69
+ await r.commit('counter', (s) => ({ ...s, step: 3 }), { nonce: 3 });
70
+ if ((await r.getNonce('counter')) !== 3) throw new Error('step5');
71
+ console.log('5. stale nonce=1 rejected, next nonce=3 accepted ok');
63
72
 
64
73
  // 6. contract-side (registry) enforcement, bypassing the SDK pre-check
65
74
  const mc = new MemContract(null);
66
75
  const kh = '0x' + '11'.repeat(32);
67
- mc.applyCommit('0xE', kh, 'cid1', 0, 7);
68
- threw = false;
69
- try {
70
- mc.applyCommit('0xE', kh, 'cid2', 1, 7);
71
- } catch (e) {
72
- if (!/NonceOutOfOrder/.test(String(e.message))) throw e;
73
- threw = true;
74
- console.log('6. contract-side in-order enforcement ok:', e.message);
76
+ mc.applyCommit('0xE', kh, 'cid1', 0, 1);
77
+ for (const bad of [1, 3]) { // reuse and gap both violate the +1 rule
78
+ threw = false;
79
+ try {
80
+ mc.applyCommit('0xE', kh, 'cid2', 1, bad);
81
+ } catch (e) {
82
+ if (!/NonceOutOfOrder/.test(String(e.message))) throw e;
83
+ threw = true;
84
+ }
85
+ if (!threw) throw new Error('registry accepted nonce ' + bad);
75
86
  }
76
- if (!threw) throw new Error('registry accepted stale nonce');
77
- if (Number(await mc.getNonce('0xE', kh)) !== 7) throw new Error('getNonce view');
87
+ mc.applyCommit('0xE', kh, 'cid2', 1, 2);
88
+ if (Number(await mc.getNonce('0xE', kh)) !== 2) throw new Error('getNonce view');
89
+ console.log('6. contract-side +1 enforcement ok (reuse and gap both revert)');
78
90
 
79
91
  // 7. chain-first getNonce after a task-memo wipe (fresh process/other task)
80
92
  delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
@@ -82,8 +94,8 @@ async function main() {
82
94
  esrLocal.install(ecld2, { caller: A, filePrefix: 'st' });
83
95
  const r2 = new ecld2.StateRegistry();
84
96
  const n2 = await r2.getNonce('counter');
85
- if (n2 !== 10) throw new Error('step7 got ' + n2);
86
- console.log('7. chain-first getNonce()==10 after reload (public read, no memo) ok');
97
+ if (n2 !== 3) throw new Error('step7 got ' + n2);
98
+ console.log('7. chain-first getNonce()==3 after reload (public read, no memo) ok');
87
99
 
88
100
  // 8. public: nonce read needs no ACL -- content read stays denied for B
89
101
  delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
@@ -435,14 +435,14 @@ class StateRegistry {
435
435
 
436
436
  /**
437
437
  * The last accepted idempotency nonce for `key` (0 if none was ever used).
438
- * A dApp that wants duplicate suppression reads this, picks a greater value
439
- * (e.g. +1, or a timestamp), and passes it to commit(..., { nonce }).
438
+ * A dApp that wants duplicate suppression reads this and passes
439
+ * getNonce(key) + 1 to commit(..., { nonce }) -- the contract accepts
440
+ * EXACTLY the next value in sequence, nothing else.
440
441
  *
441
442
  * The nonce is PUBLIC data: the registry records it on-chain next to the
442
443
  * version, and anyone can read it with a free eth_call (getNonce) -- so no
443
444
  * read-ACL applies here, and web3 clients see the same value via the
444
- * runner's esrNonce. Use opaque monotonic values (a counter or a
445
- * timestamp), never secret-derived ones.
445
+ * runner's esrNonce.
446
446
  *
447
447
  * Reads the chain (authoritative, post-relay), merged with this task's
448
448
  * own accepted commits so `commit(..., { nonce: N }); getNonce()` returns N
@@ -463,9 +463,10 @@ class StateRegistry {
463
463
  async commit(key, mutate, opts = 3) {
464
464
  // Back-compat: the third arg used to be `attempts` (a number); it may now
465
465
  // also be { attempts, nonce }. `nonce` is an idempotency guard the dApp
466
- // controls: it must be STRICTLY GREATER than the last accepted nonce for
467
- // the key (see getNonce). A duplicate or stale nonce throws
468
- // StateNonceError and the state is NOT changed.
466
+ // controls: it must be EXACTLY the last accepted nonce for the key + 1
467
+ // (see getNonce; the sequence is 1, 2, 3, ... with no gaps and no
468
+ // reuse). Anything else throws StateNonceError and the state is NOT
469
+ // changed.
469
470
  const { attempts = 3, nonce = null } =
470
471
  typeof opts === 'number' ? { attempts: opts } : (opts || {});
471
472
  const transform = async (acl, data) => {
@@ -502,10 +503,11 @@ class StateRegistry {
502
503
  const n = Math.trunc(nonce);
503
504
  // eslint-disable-next-line no-await-in-loop
504
505
  const storedNonce = await this.getNonce(key);
505
- if (n <= storedNonce) {
506
+ if (n !== storedNonce + 1) {
506
507
  throw new StateNonceError(
507
- `nonce ${n} was already used for state key '${key}' (last accepted: ` +
508
- `${storedNonce}); duplicate commit suppressed, state unchanged`
508
+ `nonce ${n} is out of sequence for state key '${key}' (last accepted: ` +
509
+ `${storedNonce}, expected: ${storedNonce + 1}); commit suppressed, ` +
510
+ 'state unchanged'
509
511
  );
510
512
  }
511
513
  }
@@ -128,9 +128,9 @@ class MemContract {
128
128
  }
129
129
 
130
130
  // ---- the on-chain commitFor, applied locally ----
131
- // Idempotency nonces are enforced IN ORDER per (enclave, key), exactly like
132
- // the contract: nonce != 0 must be strictly greater than the stored value
133
- // (gaps allowed) or NonceOutOfOrder is thrown; nonce == 0 preserves it.
131
+ // Idempotency nonces are enforced strictly sequentially per (enclave, key),
132
+ // exactly like the contract: nonce != 0 must be EXACTLY the stored value + 1
133
+ // (no gaps, no reuse) or NonceOutOfOrder is thrown; nonce == 0 preserves it.
134
134
  applyCommit(enclave, keyHash, cid, expectedVersion, nonce = 0) {
135
135
  const k = key(enclave, keyHash);
136
136
  const entry = this._entries[k] || {};
@@ -141,7 +141,7 @@ class MemContract {
141
141
  let storedNonce = Number(entry.nonce || 0);
142
142
  const n = Number(nonce || 0);
143
143
  if (n !== 0) {
144
- if (n <= storedNonce) {
144
+ if (n !== storedNonce + 1) {
145
145
  throw new Error(`NonceOutOfOrder: stored ${storedNonce}, given ${n}`);
146
146
  }
147
147
  storedNonce = n;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethernity-cloud/sdk-js",
3
- "version": "1.5.14",
3
+ "version": "1.5.15",
4
4
  "description": "A package to run ethernity cloud build scripts",
5
5
  "scripts": {
6
6
  "ecld-build": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/build.js",