@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 +6 -5
- package/nodenithy/build/securelock/.ecld-test-stage/ecld_state.js +12 -10
- package/nodenithy/build/securelock/.ecld-test-stage/esr_local.js +4 -4
- package/nodenithy/build/securelock/.ecld-test-stage/nonce_ladder.js +32 -20
- package/nodenithy/build/securelock/src/ecld_state.js +12 -10
- package/nodenithy/build/securelock/src/esr_local.js +4 -4
- package/package.json +1 -1
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: '
|
|
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: '
|
|
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: '
|
|
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
|
|
206
|
-
+ 'guard against duplicates; the contract enforces
|
|
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
|
|
439
|
-
* (
|
|
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.
|
|
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
|
|
467
|
-
//
|
|
468
|
-
// StateNonceError and the state is NOT
|
|
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
|
|
506
|
+
if (n !== storedNonce + 1) {
|
|
506
507
|
throw new StateNonceError(
|
|
507
|
-
`nonce ${n}
|
|
508
|
-
`${storedNonce});
|
|
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
|
|
132
|
-
// the contract: nonce != 0 must be
|
|
133
|
-
// (gaps
|
|
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
|
|
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.
|
|
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 !==
|
|
46
|
-
|
|
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')) !==
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
if (Number(await mc.getNonce('0xE', kh)) !==
|
|
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 !==
|
|
86
|
-
console.log('7. chain-first getNonce()==
|
|
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
|
|
439
|
-
* (
|
|
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.
|
|
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
|
|
467
|
-
//
|
|
468
|
-
// StateNonceError and the state is NOT
|
|
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
|
|
506
|
+
if (n !== storedNonce + 1) {
|
|
506
507
|
throw new StateNonceError(
|
|
507
|
-
`nonce ${n}
|
|
508
|
-
`${storedNonce});
|
|
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
|
|
132
|
-
// the contract: nonce != 0 must be
|
|
133
|
-
// (gaps
|
|
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
|
|
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