@openzeppelin/guardian-client 0.18.3 → 0.19.0-rc.2
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/README.md +127 -9
- package/dist/auth-request.d.ts +13 -0
- package/dist/auth-request.d.ts.map +1 -1
- package/dist/auth-request.js +26 -5
- package/dist/auth-request.js.map +1 -1
- package/dist/conversion.d.ts +8 -2
- package/dist/conversion.d.ts.map +1 -1
- package/dist/conversion.js +46 -3
- package/dist/conversion.js.map +1 -1
- package/dist/error-codes.d.ts +3 -5
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +23 -4
- package/dist/error-codes.js.map +1 -1
- package/dist/execution.d.ts +99 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +124 -0
- package/dist/execution.js.map +1 -0
- package/dist/http.d.ts +81 -5
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +179 -27
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/request-envelope.d.ts +22 -0
- package/dist/request-envelope.d.ts.map +1 -0
- package/dist/request-envelope.js +51 -0
- package/dist/request-envelope.js.map +1 -0
- package/dist/server-types.d.ts +33 -0
- package/dist/server-types.d.ts.map +1 -1
- package/dist/session-request.d.ts +104 -0
- package/dist/session-request.d.ts.map +1 -0
- package/dist/session-request.js +164 -0
- package/dist/session-request.js.map +1 -0
- package/dist/types.d.ts +124 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/auth-request.test.ts +12 -0
- package/src/auth-request.ts +37 -5
- package/src/conversion.test.ts +98 -0
- package/src/conversion.ts +59 -3
- package/src/dependencies.test.ts +25 -0
- package/src/error-codes.test.ts +14 -1
- package/src/error-codes.ts +23 -4
- package/src/execution.test.ts +145 -0
- package/src/execution.ts +206 -0
- package/src/http.test.ts +752 -19
- package/src/http.ts +244 -30
- package/src/index.ts +50 -1
- package/src/request-envelope.test.ts +30 -0
- package/src/request-envelope.ts +68 -0
- package/src/server-types.ts +35 -1
- package/src/session-request.test.ts +178 -0
- package/src/session-request.ts +191 -0
- package/src/types.ts +129 -4
package/src/execution.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardian execution of threshold-met proposals, as the base client reports it. Mirrors the
|
|
3
|
+
* `guardian_shared::execution` vocabulary; drift-guard tests keep the two in step.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The five reported states. Closed: adding one is a breaking change. */
|
|
7
|
+
export const EXECUTION_STATES = ['pending', 'proving', 'submitted', 'committed', 'failed'] as const;
|
|
8
|
+
export type ExecutionState = (typeof EXECUTION_STATES)[number];
|
|
9
|
+
|
|
10
|
+
/** Whether the execution has finished, one way or the other. Mirrors `ExecutionState::is_terminal`. */
|
|
11
|
+
export function isTerminalExecutionState(state: ExecutionState): boolean {
|
|
12
|
+
switch (state) {
|
|
13
|
+
case 'committed':
|
|
14
|
+
case 'failed':
|
|
15
|
+
return true;
|
|
16
|
+
case 'pending':
|
|
17
|
+
case 'proving':
|
|
18
|
+
case 'submitted':
|
|
19
|
+
return false;
|
|
20
|
+
default: {
|
|
21
|
+
const unreachable: never = state;
|
|
22
|
+
throw new Error(`Unknown execution state: ${String(unreachable)}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const REQUEST_INVALID_REASONS = [
|
|
28
|
+
'bound_block_not_declared',
|
|
29
|
+
'auth_args_missing',
|
|
30
|
+
'approval_expiration_missing',
|
|
31
|
+
'input_notes_not_pinned',
|
|
32
|
+
'too_many_declared_blocks',
|
|
33
|
+
] as const;
|
|
34
|
+
export type RequestInvalidReason = (typeof REQUEST_INVALID_REASONS)[number];
|
|
35
|
+
|
|
36
|
+
export const EXPIRATION_BOUNDS = ['approval', 'transaction'] as const;
|
|
37
|
+
export type ExpirationBound = (typeof EXPIRATION_BOUNDS)[number];
|
|
38
|
+
|
|
39
|
+
export const FOREIGN_ACCOUNT_UNAVAILABLE_REASONS = ['private', 'unavailable'] as const;
|
|
40
|
+
export type ForeignAccountUnavailableReason = (typeof FOREIGN_ACCOUNT_UNAVAILABLE_REASONS)[number];
|
|
41
|
+
|
|
42
|
+
/** Why a server does not offer Guardian execution, as `GET /status` reports it. */
|
|
43
|
+
export const EXECUTION_UNAVAILABLE_REASONS = ['prover_not_configured', 'disabled', 'canonicalization_disabled'] as const;
|
|
44
|
+
export type ExecutionUnavailableReason = (typeof EXECUTION_UNAVAILABLE_REASONS)[number];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether the server accepts Guardian execution requests. It reflects the server's configuration,
|
|
48
|
+
* not whether its prover is reachable right now.
|
|
49
|
+
*/
|
|
50
|
+
export type ServerExecutionCapability = { enabled: true } | { enabled: false; reason: ExecutionUnavailableReason };
|
|
51
|
+
|
|
52
|
+
/** The capability as `GET /status` sends it, before validation. */
|
|
53
|
+
export interface ServerExecutionCapabilityWire {
|
|
54
|
+
enabled: unknown;
|
|
55
|
+
reason?: unknown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Failure codes that carry no structured meta. */
|
|
59
|
+
export const PLAIN_EXECUTION_FAILURE_CODES = [
|
|
60
|
+
'GUARDIAN_EXECUTION_BINDING_MISMATCH',
|
|
61
|
+
'GUARDIAN_EXECUTION_STATE_MISMATCH',
|
|
62
|
+
'GUARDIAN_EXECUTION_REQUEST_CODEC',
|
|
63
|
+
'GUARDIAN_EXECUTION_PROTOCOL_MISMATCH',
|
|
64
|
+
'GUARDIAN_EXECUTION_CHAIN_BEHIND',
|
|
65
|
+
'GUARDIAN_EXECUTION_CHAIN_INCONSISTENT',
|
|
66
|
+
'GUARDIAN_EXECUTION_NODE_UNAVAILABLE',
|
|
67
|
+
'GUARDIAN_EXECUTION_INSUFFICIENT_FEE',
|
|
68
|
+
'GUARDIAN_EXECUTION_INSUFFICIENT_SIGNATURES',
|
|
69
|
+
'GUARDIAN_EXECUTION_PROVING_FAILED',
|
|
70
|
+
'GUARDIAN_EXECUTION_SEALING_FAILED',
|
|
71
|
+
'GUARDIAN_EXECUTION_ACKNOWLEDGEMENT_FAILED',
|
|
72
|
+
'GUARDIAN_EXECUTION_EXPIRATION_BEYOND_HORIZON',
|
|
73
|
+
'GUARDIAN_EXECUTION_ACCOUNT_INADMISSIBLE',
|
|
74
|
+
'GUARDIAN_EXECUTION_SUBMISSION_REJECTED',
|
|
75
|
+
'GUARDIAN_EXECUTION_SUBMISSION_NOT_PROCESSED',
|
|
76
|
+
'GUARDIAN_EXECUTION_CANDIDATE_DISCARDED',
|
|
77
|
+
'GUARDIAN_EXECUTION_EXPIRED',
|
|
78
|
+
'GUARDIAN_EXECUTION_LEASE_EXPIRED',
|
|
79
|
+
'GUARDIAN_EXECUTION_ABANDONED',
|
|
80
|
+
] as const;
|
|
81
|
+
export type PlainExecutionFailureCode = (typeof PLAIN_EXECUTION_FAILURE_CODES)[number];
|
|
82
|
+
|
|
83
|
+
/** Why an execution failed, discriminated by `code`; codes with meta carry it typed. */
|
|
84
|
+
export type ExecutionFailure =
|
|
85
|
+
| { code: 'GUARDIAN_EXECUTION_REQUEST_INVALID'; message: string; reason: RequestInvalidReason }
|
|
86
|
+
| { code: 'GUARDIAN_EXECUTION_EXPIRATION_REACHED'; message: string; bound: ExpirationBound }
|
|
87
|
+
| {
|
|
88
|
+
code: 'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE';
|
|
89
|
+
message: string;
|
|
90
|
+
reason: ForeignAccountUnavailableReason;
|
|
91
|
+
}
|
|
92
|
+
| { code: PlainExecutionFailureCode; message: string };
|
|
93
|
+
|
|
94
|
+
export type ExecutionFailureCode = ExecutionFailure['code'];
|
|
95
|
+
|
|
96
|
+
export const EXECUTION_FAILURE_CODES: readonly ExecutionFailureCode[] = [
|
|
97
|
+
...PLAIN_EXECUTION_FAILURE_CODES,
|
|
98
|
+
'GUARDIAN_EXECUTION_REQUEST_INVALID',
|
|
99
|
+
'GUARDIAN_EXECUTION_EXPIRATION_REACHED',
|
|
100
|
+
'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE',
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/** One execution of a proposal, identified by `(accountId, proposalId)`. */
|
|
104
|
+
export interface ProposalExecution {
|
|
105
|
+
accountId: string;
|
|
106
|
+
proposalId: string;
|
|
107
|
+
state: ExecutionState;
|
|
108
|
+
/** Present exactly when `state` is `'failed'`. */
|
|
109
|
+
error: ExecutionFailure | null;
|
|
110
|
+
/** The delta nonce the execution committed to, once it crossed the no-retry boundary. */
|
|
111
|
+
deltaNonce: number | null;
|
|
112
|
+
/** Whether this request started the execution, rather than finding it already running. */
|
|
113
|
+
newlyAccepted: boolean;
|
|
114
|
+
/** Whether the proposal is still stored. A fact, not retry advice. */
|
|
115
|
+
proposalExists: boolean;
|
|
116
|
+
/** Stored signatures the server ignored as invalid, duplicate or not from a cosigner. */
|
|
117
|
+
ignoredSignatures: number;
|
|
118
|
+
updatedAt: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface ServerExecutionError {
|
|
122
|
+
code: string;
|
|
123
|
+
message: string;
|
|
124
|
+
meta?: Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ServerProposalExecution {
|
|
128
|
+
account_id: string;
|
|
129
|
+
proposal_id: string;
|
|
130
|
+
state: string;
|
|
131
|
+
error?: ServerExecutionError;
|
|
132
|
+
delta_nonce?: number;
|
|
133
|
+
newly_accepted: boolean;
|
|
134
|
+
proposal_exists: boolean;
|
|
135
|
+
ignored_signatures: number;
|
|
136
|
+
updated_at: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ServerCurrentExecution {
|
|
140
|
+
execution: ServerProposalExecution | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function member<T extends string>(values: readonly T[], value: unknown, what: string): T {
|
|
144
|
+
if (typeof value === 'string' && (values as readonly string[]).includes(value)) {
|
|
145
|
+
return value as T;
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`Guardian returned an unknown ${what}: ${JSON.stringify(value)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Decodes a `GET /status` capability the server sent, refusing a missing flag or an unknown
|
|
152
|
+
* reason. A status without one does not offer Guardian execution.
|
|
153
|
+
*/
|
|
154
|
+
export function fromServerExecutionCapability(server: ServerExecutionCapabilityWire): ServerExecutionCapability {
|
|
155
|
+
if (typeof server !== 'object' || server === null) {
|
|
156
|
+
throw new Error(`Guardian returned a malformed execution capability: ${JSON.stringify(server)}`);
|
|
157
|
+
}
|
|
158
|
+
if (server.enabled === true) {
|
|
159
|
+
if (server.reason !== undefined) {
|
|
160
|
+
throw new Error(`Guardian returned an enabled execution capability with a reason: ${JSON.stringify(server.reason)}`);
|
|
161
|
+
}
|
|
162
|
+
return { enabled: true };
|
|
163
|
+
}
|
|
164
|
+
if (server.enabled === false) {
|
|
165
|
+
return { enabled: false, reason: member(EXECUTION_UNAVAILABLE_REASONS, server.reason, 'execution unavailable reason') };
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Guardian returned an execution capability without a boolean enabled: ${JSON.stringify(server.enabled)}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function fromServerFailure(error: ServerExecutionError): ExecutionFailure {
|
|
171
|
+
const { code, message, meta } = error;
|
|
172
|
+
switch (code) {
|
|
173
|
+
case 'GUARDIAN_EXECUTION_REQUEST_INVALID':
|
|
174
|
+
return { code, message, reason: member(REQUEST_INVALID_REASONS, meta?.reason, 'request reason') };
|
|
175
|
+
case 'GUARDIAN_EXECUTION_EXPIRATION_REACHED':
|
|
176
|
+
return { code, message, bound: member(EXPIRATION_BOUNDS, meta?.bound, 'expiration bound') };
|
|
177
|
+
case 'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE':
|
|
178
|
+
return {
|
|
179
|
+
code,
|
|
180
|
+
message,
|
|
181
|
+
reason: member(FOREIGN_ACCOUNT_UNAVAILABLE_REASONS, meta?.reason, 'foreign account reason'),
|
|
182
|
+
};
|
|
183
|
+
default:
|
|
184
|
+
return { code: member(PLAIN_EXECUTION_FAILURE_CODES, code, 'execution failure code'), message };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Decodes the wire envelope, refusing any state, code or meta outside the vocabulary. */
|
|
189
|
+
export function fromServerExecution(server: ServerProposalExecution): ProposalExecution {
|
|
190
|
+
const state = member(EXECUTION_STATES, server.state, 'execution state');
|
|
191
|
+
const error = server.error ? fromServerFailure(server.error) : null;
|
|
192
|
+
if ((state === 'failed') !== (error !== null)) {
|
|
193
|
+
throw new Error(`Guardian returned an execution in state '${state}' ${error ? 'with' : 'without'} an error`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
accountId: server.account_id,
|
|
197
|
+
proposalId: server.proposal_id,
|
|
198
|
+
state,
|
|
199
|
+
error,
|
|
200
|
+
deltaNonce: server.delta_nonce ?? null,
|
|
201
|
+
newlyAccepted: server.newly_accepted,
|
|
202
|
+
proposalExists: server.proposal_exists,
|
|
203
|
+
ignoredSignatures: server.ignored_signatures,
|
|
204
|
+
updatedAt: server.updated_at,
|
|
205
|
+
};
|
|
206
|
+
}
|