Skip to content
This repository was archived by the owner on Feb 8, 2025. It is now read-only.

Commit f551d0c

Browse files
committed
feat(api): more accurate and simple transaction status tracking
1 parent 23bf1b4 commit f551d0c

8 files changed

+67
-27
lines changed

api/dbschema/edgeql-js/__spec__.ts

+2-2
Large diffs are not rendered by default.

api/dbschema/edgeql-js/modules/default.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ const $Scheduled = $.makeType<$Scheduled>(_.spec, "23c64270-532c-11ef-863f-6be9e
577577
const Scheduled: $.$expr_PathNode<$.TypeSet<$Scheduled, $.Cardinality.Many>, null> = _.syntax.$PathNode($.$toSet($Scheduled, $.Cardinality.Many), null);
578578

579579
export type $SimulatedFailureλShape = $.typeutil.flatten<$FailureλShape & {
580+
"validationErrors": $.PropertyDesc<$.ArrayType<_std.$str>, $.Cardinality.One, false, false, false, true>;
580581
}>;
581582
type $SimulatedFailure = $.ObjectType<"default::SimulatedFailure", $SimulatedFailureλShape, null, [
582583
...$Failure['__exclusives__'],
@@ -656,18 +657,18 @@ const Token: $.$expr_PathNode<$.TypeSet<$Token, $.Cardinality.Many>, null> = _.s
656657
export type $TransactionλShape = $.typeutil.flatten<Omit<$ProposalλShape, "<proposal"> & {
657658
"maxAmount": $.PropertyDesc<_std.$decimal, $.Cardinality.One, false, false, false, false>;
658659
"gasLimit": $.PropertyDesc<$uint256, $.Cardinality.One, false, false, false, true>;
659-
"executable": $.PropertyDesc<_std.$bool, $.Cardinality.One, false, false, false, true>;
660660
"unorderedOperations": $.LinkDesc<$Operation, $.Cardinality.AtLeastOne, {}, true, false, false, false>;
661661
"operations": $.LinkDesc<$Operation, $.Cardinality.AtLeastOne, {}, false, true, false, false>;
662662
"paymaster": $.PropertyDesc<$Address, $.Cardinality.One, false, false, false, false>;
663663
"feeToken": $.LinkDesc<$Token, $.Cardinality.One, {}, false, false, false, false>;
664664
"maxAmountFp": $.PropertyDesc<_std.$bigint, $.Cardinality.One, false, true, false, false>;
665665
"paymasterEthFees": $.LinkDesc<$PaymasterFees, $.Cardinality.One, {}, true, false, false, true>;
666666
"result": $.LinkDesc<$Result, $.Cardinality.AtMostOne, {}, true, false, false, false>;
667-
"status": $.PropertyDesc<$TransactionStatus, $.Cardinality.One, false, true, false, false>;
668667
"systx": $.LinkDesc<$SystemTx, $.Cardinality.AtMostOne, {}, true, false, false, false>;
669668
"results": $.LinkDesc<$Result, $.Cardinality.Many, {}, false, true, false, false>;
670669
"systxs": $.LinkDesc<$SystemTx, $.Cardinality.Many, {}, false, true, false, false>;
670+
"status": $.PropertyDesc<$TransactionStatus, $.Cardinality.One, false, true, false, false>;
671+
"executable": $.PropertyDesc<_std.$bool, $.Cardinality.One, false, true, false, false>;
671672
"<transaction[is ConfirmedSuccess]": $.LinkDesc<$ConfirmedSuccess, $.Cardinality.Many, {}, false, false, false, false>;
672673
"<transaction[is OptimisticSuccess]": $.LinkDesc<$OptimisticSuccess, $.Cardinality.Many, {}, false, false, false, false>;
673674
"<proposal[is PolicyState]": $.LinkDesc<$PolicyState, $.Cardinality.Many, {}, false, false, false, false>;

api/dbschema/interfaces.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ export namespace $default {
241241
"cancelled": boolean;
242242
"scheduledFor": Date;
243243
}
244-
export interface SimulatedFailure extends Failure {}
244+
export interface SimulatedFailure extends Failure {
245+
"validationErrors": string[];
246+
}
245247
export interface SimulatedSuccess extends Success {}
246248
export interface SystemTx extends std.$Object {
247249
"proposal": Transaction;
@@ -269,18 +271,18 @@ export namespace $default {
269271
export interface Transaction extends Proposal {
270272
"maxAmount": string;
271273
"gasLimit": bigint;
272-
"executable": boolean;
273274
"unorderedOperations": Operation[];
274275
"operations": Operation[];
275276
"paymaster": string;
276277
"feeToken": Token;
277278
"maxAmountFp": bigint;
278279
"paymasterEthFees": PaymasterFees;
279280
"result"?: Result | null;
280-
"status": TransactionStatus;
281281
"systx"?: SystemTx | null;
282282
"results": Result[];
283283
"systxs": SystemTx[];
284+
"status": TransactionStatus;
285+
"executable": boolean;
284286
}
285287
export type TransactionStatus = "Pending" | "Scheduled" | "Executing" | "Successful" | "Failed" | "Cancelled";
286288
export interface Transferlike extends Event {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
CREATE MIGRATION m15nf2s2pczdhpncvwih5festgbvzushr5qfunppqaqahdonqo3nba
2+
ONTO m1b4kmwwkkxbx7nv3ze7u3c5td4y35hzgyinz422ny3npt4jpkcc3a
3+
{
4+
ALTER TYPE default::SimulatedFailure {
5+
CREATE REQUIRED PROPERTY validationErrors: array<std::str> {
6+
SET default := (<array<std::str>>[]);
7+
};
8+
};
9+
ALTER TYPE default::Transaction {
10+
ALTER PROPERTY executable {
11+
RESET default;
12+
USING (((.result IS default::SimulatedSuccess) ?? false));
13+
};
14+
ALTER PROPERTY status {
15+
USING (WITH
16+
result :=
17+
.result
18+
SELECT
19+
std::assert_exists((default::TransactionStatus.Pending IF ((NOT (EXISTS (result)) OR (result IS default::SimulatedSuccess)) OR (result IS default::SimulatedFailure)) ELSE (default::TransactionStatus.Executing IF (result IS default::OptimisticSuccess) ELSE (default::TransactionStatus.Successful IF (result IS default::ConfirmedSuccess) ELSE (default::TransactionStatus.Failed IF (result IS default::ConfirmedFailure) ELSE (default::TransactionStatus.Scheduled IF NOT (result[IS default::Scheduled].cancelled) ELSE default::TransactionStatus.Cancelled))))))
20+
);
21+
};
22+
};
23+
};

api/dbschema/transaction.esdl

+11-8
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,22 @@ module default {
2929
on source delete delete target;
3030
default := (insert PaymasterFees {});
3131
}
32-
required executable: bool { default := false; }
3332
multi link systxs := .<proposal[is SystemTx];
3433
link systx: SystemTx { constraint exclusive; } # Latest .timestamp
3534
multi link results := .<transaction[is Result];
3635
link result: Result { constraint exclusive; } # Latest .timestamp
3736
required property status := (
37+
with result := .result
3838
select assert_exists((
39-
TransactionStatus.Pending if (not .executable) else
40-
TransactionStatus.Executing if (not exists .result) else
41-
TransactionStatus.Successful if (.result is ConfirmedSuccess) else
42-
TransactionStatus.Failed if (.result is ConfirmedFailure) else
43-
TransactionStatus.Scheduled if (not .result[is Scheduled].cancelled) else
44-
TransactionStatus.Cancelled
39+
TransactionStatus.Pending if (not exists result or result is SimulatedSuccess or result is SimulatedFailure) else
40+
TransactionStatus.Executing if (result is OptimisticSuccess) else
41+
TransactionStatus.Successful if (result is ConfirmedSuccess) else
42+
TransactionStatus.Failed if (result is ConfirmedFailure) else
43+
TransactionStatus.Scheduled if (not result[is Scheduled].cancelled) else
44+
TransactionStatus.Cancelled # result is Scheduled
4545
))
4646
);
47+
required executable := (.result is SimulatedSuccess) ?? false;
4748
}
4849

4950
type SystemTx {
@@ -93,7 +94,9 @@ module default {
9394

9495
type SimulatedSuccess extending Success {}
9596

96-
type SimulatedFailure extending Failure {}
97+
type SimulatedFailure extending Failure {
98+
required validationErrors: array<str> { default := <array<str>>[]; };
99+
}
97100

98101
type OptimisticSuccess extending Success {}
99102

api/src/feat/simulations/insert-simulated-failure.edgeql

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ insert SimulatedFailure {
22
transaction := <Transaction><uuid>$transaction,
33
response := <Bytes>$response,
44
gasUsed := <bigint>$gasUsed,
5-
reason := <str>$reason
5+
reason := <str>$reason,
6+
validationErrors := <array<str>>$validationErrors
67
}

api/src/feat/simulations/insert-simulated-failure.query.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type InsertSimulatedFailureArgs = {
77
readonly "response": string;
88
readonly "gasUsed": bigint;
99
readonly "reason": string;
10+
readonly "validationErrors": ReadonlyArray<string>;
1011
};
1112

1213
export type InsertSimulatedFailureReturns = {
@@ -19,7 +20,8 @@ insert SimulatedFailure {
1920
transaction := <Transaction><uuid>$transaction,
2021
response := <Bytes>$response,
2122
gasUsed := <bigint>$gasUsed,
22-
reason := <str>$reason
23+
reason := <str>$reason,
24+
validationErrors := <array<str>>$validationErrors
2325
}`, args);
2426

2527
}

api/src/feat/simulations/simulations.worker.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class SimulationsWorker extends Worker<SimulationsQueue> {
8181
);
8282
if (!t) return 'Transaction not found';
8383

84-
const validPromise = this.isValidatable(t);
84+
const validationErrorsPromise = this.getValidationErrors(t);
8585
const account = asUAddress(t.account.address);
8686
const localAccount = asAddress(account);
8787
const chain = asChain(account);
@@ -101,7 +101,10 @@ export class SimulationsWorker extends Worker<SimulationsQueue> {
101101
});
102102

103103
const error = trace.error || trace.revertReason;
104-
const result = await (!error
104+
const validationErrors = await validationErrorsPromise;
105+
const success = !error && !validationErrors.length;
106+
107+
const result = await (success
105108
? this.db.exec(insertSimulatedSuccess, {
106109
transaction: t.id,
107110
response: trace.output,
@@ -111,16 +114,16 @@ export class SimulationsWorker extends Worker<SimulationsQueue> {
111114
transaction: t.id,
112115
response: trace.output,
113116
gasUsed: trace.gasUsed,
114-
reason: error,
117+
reason: error ?? '',
118+
validationErrors,
115119
}));
116120

117121
const logs = await this.simulateEvents(t);
118122
await this.events.processSimulatedAndOptimistic({ chain, logs, result: asUUID(result.id) });
119123

120124
this.proposals.event({ id: asUUID(t.id), account }, ProposalEvent.simulated);
121125

122-
const valid = await validPromise;
123-
return { executable: valid && !error };
126+
return { executable: success };
124127
}
125128

126129
private async simulateEvents(t: TransactionExecutableShape) {
@@ -252,12 +255,14 @@ export class SimulationsWorker extends Worker<SimulationsQueue> {
252255
return logs;
253256
}
254257

255-
private async isValidatable(t: TransactionExecutableShape) {
256-
if (!t.policy.isActive) return false;
257-
if (t.validationErrors.length) return false;
258+
private async getValidationErrors(t: TransactionExecutableShape) {
259+
const errors: string[] = [];
260+
261+
if (!t.policy.isActive) errors.push('Policy not active');
262+
if (t.validationErrors.length) errors.push('Policy validation errors');
258263

259264
const approved = t.policy.threshold <= t.approvals.length;
260-
if (!approved) return false;
265+
if (!approved) errors.push('Insufficient approval');
261266

262267
// Check all limits
263268
const transfers = transactionAsTx(t)
@@ -282,7 +287,10 @@ export class SimulationsWorker extends Worker<SimulationsQueue> {
282287
}),
283288
);
284289

285-
return limitResults.every((r) => r);
290+
const sufficientSpending = limitResults.every((r) => r);
291+
if (!sufficientSpending) errors.push('Greater than allowed spending');
292+
293+
return errors;
286294
}
287295

288296
async bootstrap() {

0 commit comments

Comments
 (0)