@novasamatech/statement-store 0.8.7-3 → 0.8.7-5
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/adapter/rpc.js
CHANGED
|
@@ -97,9 +97,9 @@ export function createPapiStatementStoreAdapter(lazyClient) {
|
|
|
97
97
|
case 'dataTooLarge':
|
|
98
98
|
return errAsync(new DataTooLargeError(result.submitted_size, result.available_size));
|
|
99
99
|
case 'channelPriorityTooLow':
|
|
100
|
-
return errAsync(new ExpiryTooLowError(result.submitted_expiry, result.min_expiry));
|
|
100
|
+
return errAsync(new ExpiryTooLowError(BigInt(result.submitted_expiry), BigInt(result.min_expiry)));
|
|
101
101
|
case 'accountFull':
|
|
102
|
-
return errAsync(new AccountFullError(result.submitted_expiry, result.min_expiry));
|
|
102
|
+
return errAsync(new AccountFullError(BigInt(result.submitted_expiry), BigInt(result.min_expiry)));
|
|
103
103
|
case 'storeFull':
|
|
104
104
|
return errAsync(new StorageFullError());
|
|
105
105
|
case 'noAllowance':
|
package/dist/session/session.js
CHANGED
|
@@ -99,12 +99,19 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
99
99
|
// latest is live — a retry for an older one must not resurrect it).
|
|
100
100
|
let lastResponseRequestId = null;
|
|
101
101
|
// Encrypt, then submit on `channel`/`topicSessionId` at the allocator's next (strictly
|
|
102
|
-
// increasing) expiry.
|
|
103
|
-
//
|
|
102
|
+
// increasing) expiry. A priority rejection does NOT resync the allocator here — each caller
|
|
103
|
+
// raises the floor to the chain-reported minimum (the submitWithRetry `onPriorityError` hooks
|
|
104
|
+
// below, and the one-shot clear in clearOutgoingStatement), so the retry — and every later
|
|
105
|
+
// submit — clears it.
|
|
104
106
|
function submitStatementData(channel, topicSessionId, data) {
|
|
105
|
-
return encryption
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
return encryption.encrypt(data).asyncAndThen(encrypted => submitStatementOnce({
|
|
108
|
+
statementStore,
|
|
109
|
+
prover,
|
|
110
|
+
allocator,
|
|
111
|
+
channel,
|
|
112
|
+
topics: [topicSessionId],
|
|
113
|
+
data: encrypted,
|
|
114
|
+
}));
|
|
108
115
|
}
|
|
109
116
|
// Settle and remove the pending-delivery entries for the given tokens.
|
|
110
117
|
function settleTokens(tokens, settle) {
|
|
@@ -118,8 +125,8 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
118
125
|
}
|
|
119
126
|
// Session retry policy (this and every submitWithRetry call below): priority errors
|
|
120
127
|
// (ExpiryTooLow / AccountFull) are retried with `priorityAttempts: 'unbounded'` — they never
|
|
121
|
-
// consume the transient-failure budget, because
|
|
122
|
-
//
|
|
128
|
+
// consume the transient-failure budget, because the `onPriorityError` hook raises the allocator
|
|
129
|
+
// floor above the chain-reported minimum, so the next attempt submits higher. We keep at it
|
|
123
130
|
// until the statement lands or the submission is superseded; once superseded, a priority
|
|
124
131
|
// rejection is swallowed as success (it merely lost the channel race to a newer, higher-priority
|
|
125
132
|
// statement). The upshot: priority errors never surface to session callers. Other errors keep
|
|
@@ -132,6 +139,8 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
132
139
|
attempts: MAX_SUBMIT_RETRIES,
|
|
133
140
|
priorityAttempts: 'unbounded',
|
|
134
141
|
delaysMs: RETRY_DELAY_MS,
|
|
142
|
+
// Adopt the chain-reported floor so the next attempt submits strictly above it.
|
|
143
|
+
onPriorityError: error => allocator.raiseFloor(error.min),
|
|
135
144
|
// Only keep retrying while this is still the live submission (not superseded by a
|
|
136
145
|
// newer retransmit, aborted via clearOutgoingStatement, or disposed).
|
|
137
146
|
shouldRetry: () => !disposed && state.outgoingRequest?.requestIds.at(-1) === requestId,
|
|
@@ -454,6 +463,8 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
454
463
|
attempts: MAX_SUBMIT_RETRIES,
|
|
455
464
|
priorityAttempts: 'unbounded',
|
|
456
465
|
delaysMs: RETRY_DELAY_MS,
|
|
466
|
+
// Adopt the chain-reported floor so the next attempt submits strictly above it.
|
|
467
|
+
onPriorityError: error => allocator.raiseFloor(error.min),
|
|
457
468
|
// Stop retrying once a newer response supersedes this one (shared response channel) or disposed.
|
|
458
469
|
shouldRetry: () => !disposed && lastResponseRequestId === requestId,
|
|
459
470
|
})
|
|
@@ -576,8 +587,14 @@ export function createSession({ localAccount, remoteAccount, statementStore, enc
|
|
|
576
587
|
// would leave the original request live on-chain. One shot, no retry (clearing is a
|
|
577
588
|
// supersede, not a request that must land); a priority rejection (ExpiryTooLow /
|
|
578
589
|
// AccountFull) means the channel already advanced past us, so the clear already
|
|
579
|
-
// happened → absorb it as success.
|
|
580
|
-
|
|
590
|
+
// happened → absorb it as success. No retry loop here means no onPriorityError hook, so
|
|
591
|
+
// resync the allocator inline: adopt the chain floor before absorbing, so later submits stay above it.
|
|
592
|
+
return submitStatementData(requestChannel, outgoingSessionId, encoded.value).orElse(error => {
|
|
593
|
+
if (!isPriorityTooLow(error))
|
|
594
|
+
return errAsync(error);
|
|
595
|
+
allocator.raiseFloor(error.min);
|
|
596
|
+
return okAsync(undefined);
|
|
597
|
+
});
|
|
581
598
|
},
|
|
582
599
|
dispose() {
|
|
583
600
|
disposed = true;
|
package/dist/submit/retry.d.ts
CHANGED
|
@@ -28,5 +28,13 @@ export type SubmitRetryOptions = {
|
|
|
28
28
|
delayMs: number;
|
|
29
29
|
error: Error;
|
|
30
30
|
}) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Invoked on every priority rejection (ExpiryTooLow / AccountFull), before the retry
|
|
33
|
+
* decision — so it fires even when the priority budget is exhausted or the submission is
|
|
34
|
+
* no longer live. This is where the caller adopts the chain-reported floor (`error.min`)
|
|
35
|
+
* into its allocator, so the NEXT attempt — here or via an outer retry/outbox — submits
|
|
36
|
+
* strictly above it and clears in one step rather than climbing.
|
|
37
|
+
*/
|
|
38
|
+
onPriorityError?: (error: ExpiryTooLowError | AccountFullError) => void;
|
|
31
39
|
};
|
|
32
40
|
export declare function submitWithRetry(submit: () => ResultAsync<void, Error>, options: SubmitRetryOptions): ResultAsync<void, Error>;
|
package/dist/submit/retry.js
CHANGED
|
@@ -15,7 +15,7 @@ function delayFor(delaysMs, attempt) {
|
|
|
15
15
|
return delaysMs[Math.min(attempt, delaysMs.length - 1)] ?? 0;
|
|
16
16
|
}
|
|
17
17
|
export function submitWithRetry(submit, options) {
|
|
18
|
-
const { attempts, priorityAttempts, delaysMs, shouldRetry = () => true, onRetry } = options;
|
|
18
|
+
const { attempts, priorityAttempts, delaysMs, shouldRetry = () => true, onRetry, onPriorityError } = options;
|
|
19
19
|
// How to settle once we stop retrying: under the 'unbounded' policy a
|
|
20
20
|
// no-longer-live submission rejected with a priority error simply lost the
|
|
21
21
|
// channel race to a newer, higher-priority statement — benign, so report success.
|
|
@@ -30,13 +30,17 @@ export function submitWithRetry(submit, options) {
|
|
|
30
30
|
if (result.isOk())
|
|
31
31
|
return result;
|
|
32
32
|
const error = result.error;
|
|
33
|
-
const
|
|
34
|
-
|
|
33
|
+
const priorityError = isPriorityTooLow(error) ? error : null;
|
|
34
|
+
// Adopt the chain-reported floor before deciding whether to retry, so an exhausted or
|
|
35
|
+
// no-longer-live priority rejection still raises it for any outer retry/outbox.
|
|
36
|
+
if (priorityError)
|
|
37
|
+
onPriorityError?.(priorityError);
|
|
38
|
+
const budgetLeft = priorityError ? priorityLeft : attemptsLeft;
|
|
35
39
|
if (!shouldRetry() || (typeof budgetLeft === 'number' && budgetLeft <= 0))
|
|
36
40
|
return settle(error);
|
|
37
41
|
const delayMs = delayFor(delaysMs, attempt);
|
|
38
42
|
onRetry?.({ attempt, delayMs, error });
|
|
39
|
-
if (
|
|
43
|
+
if (priorityError) {
|
|
40
44
|
if (priorityLeft !== 'unbounded')
|
|
41
45
|
priorityLeft -= 1;
|
|
42
46
|
}
|
|
@@ -36,6 +36,26 @@ describe('submitWithRetry', () => {
|
|
|
36
36
|
expect(result._unsafeUnwrapErr()).toBeInstanceOf(AccountFullError);
|
|
37
37
|
expect(submit).toHaveBeenCalledTimes(4); // 1 initial + 3 retries
|
|
38
38
|
});
|
|
39
|
+
it('onPriorityError fires for every priority rejection, including the terminal one once the budget is exhausted', async () => {
|
|
40
|
+
const seen = [];
|
|
41
|
+
let calls = 0;
|
|
42
|
+
const submit = vi.fn(() => errAsync(new AccountFullError(0n, BigInt(++calls))));
|
|
43
|
+
const result = await submitWithRetry(submit, {
|
|
44
|
+
...FAST,
|
|
45
|
+
attempts: 0,
|
|
46
|
+
priorityAttempts: 2,
|
|
47
|
+
onPriorityError: error => seen.push(error.min),
|
|
48
|
+
});
|
|
49
|
+
expect(result.isErr()).toBe(true);
|
|
50
|
+
expect(submit).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
|
|
51
|
+
expect(seen).toEqual([1n, 2n, 3n]); // adopted the floor on all three, including the terminal rejection
|
|
52
|
+
});
|
|
53
|
+
it('onPriorityError is not called for non-priority errors', async () => {
|
|
54
|
+
const onPriorityError = vi.fn();
|
|
55
|
+
const submit = vi.fn(() => errAsync(new Error('store rejected')));
|
|
56
|
+
await submitWithRetry(submit, { ...FAST, attempts: 2, priorityAttempts: 'unbounded', onPriorityError });
|
|
57
|
+
expect(onPriorityError).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
39
59
|
it('attempts 0: a non-priority error propagates immediately', async () => {
|
|
40
60
|
const submit = vi.fn(() => errAsync(new Error('store rejected')));
|
|
41
61
|
const result = await submitWithRetry(submit, { ...FAST, attempts: 0, priorityAttempts: 3 });
|
|
@@ -24,5 +24,9 @@ export function submitStatementOnce(params) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
export function signAndSubmitStatement(params) {
|
|
27
|
-
return submitWithRetry(() => submitStatementOnce(params),
|
|
27
|
+
return submitWithRetry(() => submitStatementOnce(params), {
|
|
28
|
+
...params.retry,
|
|
29
|
+
// Adopt the chain-reported floor so the next retry submits strictly above it and clears.
|
|
30
|
+
onPriorityError: error => params.allocator.raiseFloor(error.min),
|
|
31
|
+
});
|
|
28
32
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novasamatech/statement-store",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.8.7-
|
|
4
|
+
"version": "0.8.7-5",
|
|
5
5
|
"description": "Statement store integration",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@novasamatech/scale": "0.8.7-
|
|
28
|
+
"@novasamatech/scale": "0.8.7-5",
|
|
29
29
|
"@novasamatech/sdk-statement": "^0.6.0",
|
|
30
|
-
"@novasamatech/substrate-slot-sr25519-wasm": "0.8.7-
|
|
30
|
+
"@novasamatech/substrate-slot-sr25519-wasm": "0.8.7-5",
|
|
31
31
|
"@polkadot-api/substrate-bindings": "^0.20.3",
|
|
32
32
|
"@polkadot-api/substrate-client": "^0.7.0",
|
|
33
33
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|