@render-foundation/utils 0.0.59 → 0.0.61

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.
Files changed (37) hide show
  1. package/lib/cjs/client/{pg.js → pg/v1.js} +18 -214
  2. package/lib/cjs/client/pg/v1.js.map +1 -0
  3. package/lib/cjs/client/pg/v2.js +714 -0
  4. package/lib/cjs/client/pg/v2.js.map +1 -0
  5. package/lib/cjs/client/render.js +26 -4
  6. package/lib/cjs/client/render.js.map +1 -1
  7. package/lib/cjs/dbTypesV2.js +3 -0
  8. package/lib/cjs/dbTypesV2.js.map +1 -0
  9. package/lib/cjs/index.js +15 -1
  10. package/lib/cjs/index.js.map +1 -1
  11. package/lib/esm/src/client/{pg.js → pg/v1.js} +13 -192
  12. package/lib/esm/src/client/pg/v1.js.map +1 -0
  13. package/lib/esm/src/client/pg/v2.js +653 -0
  14. package/lib/esm/src/client/pg/v2.js.map +1 -0
  15. package/lib/esm/src/client/render.js +17 -0
  16. package/lib/esm/src/client/render.js.map +1 -1
  17. package/lib/esm/src/dbTypesV2.js +2 -0
  18. package/lib/esm/src/dbTypesV2.js.map +1 -0
  19. package/lib/esm/src/index.js +2 -1
  20. package/lib/esm/src/index.js.map +1 -1
  21. package/lib/esm/tsconfig.esm.tsbuildinfo +1 -1
  22. package/lib/types/src/client/{pg.d.ts → pg/v1.d.ts} +12 -51
  23. package/lib/types/src/client/pg/v1.d.ts.map +1 -0
  24. package/lib/types/src/client/pg/v2.d.ts +35 -0
  25. package/lib/types/src/client/pg/v2.d.ts.map +1 -0
  26. package/lib/types/src/client/render.d.ts +3 -0
  27. package/lib/types/src/client/render.d.ts.map +1 -1
  28. package/lib/types/src/dbTypes.d.ts +5 -0
  29. package/lib/types/src/dbTypes.d.ts.map +1 -1
  30. package/lib/types/src/dbTypesV2.d.ts +83 -0
  31. package/lib/types/src/dbTypesV2.d.ts.map +1 -0
  32. package/lib/types/src/index.d.ts +2 -1
  33. package/lib/types/src/index.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/lib/cjs/client/pg.js.map +0 -1
  36. package/lib/esm/src/client/pg.js.map +0 -1
  37. package/lib/types/src/client/pg.d.ts.map +0 -1
@@ -0,0 +1,653 @@
1
+ import { Pool } from 'pg';
2
+ import { Kysely, NoResultError, PostgresDialect, sql, } from 'kysely';
3
+ import { consoleLogger } from '../../logger';
4
+ const transferTypToChan = (t) => {
5
+ if (t == 'open_compute_rewards') {
6
+ return 'open_compute';
7
+ }
8
+ if (t == 'node_operator_rewards') {
9
+ return 'node_operator';
10
+ }
11
+ return 'upgrade';
12
+ };
13
+ const transferFromDBV2 = (o) => {
14
+ return {
15
+ id: o.id,
16
+ solKey: o.sol_key,
17
+ solTx: o.sig,
18
+ sqTx: o.sq_tx,
19
+ createdAt: o.created_at,
20
+ payedAt: o.executed_at,
21
+ amountDue: BigInt(o.amount_due),
22
+ amountPayed: o.amount_payed ? BigInt(o.amount_payed) : undefined,
23
+ operatorRewards: o.channel == 'node_operator'
24
+ ? {
25
+ epochId: o.ui,
26
+ startTime: o.start_time,
27
+ endTime: o.end_time,
28
+ }
29
+ : undefined,
30
+ computeClientRewards: o.transfer_type == 'open_compute'
31
+ ? {
32
+ epochId: o.ui,
33
+ deviceId: o.device_id,
34
+ }
35
+ : undefined,
36
+ upgradeRewards: o.transfer_type == 'upgrade'
37
+ ? {
38
+ epochId: o.ui,
39
+ epochStartTime: o.start_time,
40
+ points: BigInt(o.points),
41
+ }
42
+ : undefined,
43
+ };
44
+ };
45
+ export const SOL_TX_TABLE = 'sol_tx';
46
+ export const EPOCH_TABLE = 'epoch';
47
+ export const MANUAL_BURN_TABLE = 'manual_burn';
48
+ export const ENTITY_TABLE = 'entity';
49
+ export const SOL_TRANSFER_TABLE = 'sol_transfer';
50
+ export const SOL_TRANSFER_INFO_TABLE = 'sol_transfer_info';
51
+ export const JOB_ID_TABLE = 'job_id';
52
+ export const JOB_TABLE = 'job';
53
+ export const BURN_TABLE = 'burn';
54
+ export const pgClient = (config) => {
55
+ const dialect = new PostgresDialect({
56
+ pool: new Pool({
57
+ ...config,
58
+ port: config.port ?? 5432,
59
+ max: config.max ?? 10,
60
+ }),
61
+ });
62
+ // Database interface is passed to Kysely's constructor, and from now on, Kysely
63
+ // knows your database structure.
64
+ // Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
65
+ // to communicate with your database.
66
+ const db = new Kysely({
67
+ dialect,
68
+ });
69
+ const getOrInsertEntity = async (db, p, log = consoleLogger) => {
70
+ const res = (await db
71
+ .selectFrom(ENTITY_TABLE)
72
+ .select('id')
73
+ .where('sol_key', '=', p.solKey)
74
+ .executeTakeFirst());
75
+ if (res) {
76
+ log.debug(`already found entity ${res.id}`);
77
+ return res.id;
78
+ }
79
+ const { id } = (await db
80
+ .insertInto(ENTITY_TABLE)
81
+ .values({
82
+ sol_key: p.solKey,
83
+ })
84
+ .onConflict((oc) => oc.column('sol_key').doNothing())
85
+ .returning((eb) => ['id as id'])
86
+ .executeTakeFirst());
87
+ log.debug(`inserted entity ${id}`);
88
+ return id;
89
+ };
90
+ const getOrInsertTx = async (db, p, log = consoleLogger) => {
91
+ const res = await db
92
+ .selectFrom(SOL_TX_TABLE)
93
+ .select('id')
94
+ .where('sig', '=', p.sig)
95
+ .executeTakeFirst();
96
+ if (res) {
97
+ log.debug(`already found tx ${res.id}`);
98
+ return res.id;
99
+ }
100
+ const { id } = (await db
101
+ .insertInto(SOL_TX_TABLE)
102
+ .values({
103
+ sig: p.sig,
104
+ executed_at: p.executedAt,
105
+ })
106
+ .onConflict((oc) => oc.column('sig').doNothing())
107
+ .returning((eb) => ['id as id'])
108
+ .executeTakeFirst());
109
+ log.debug(`inserted tx ${id}`);
110
+ return id;
111
+ };
112
+ const getOrInsertEpoch = async (db, p, log = consoleLogger) => {
113
+ const res = await db
114
+ .selectFrom(EPOCH_TABLE)
115
+ .select('id')
116
+ .where('ui', '=', String(p.uiEpochId))
117
+ .where('channel', '=', p.channel)
118
+ .executeTakeFirst();
119
+ if (res) {
120
+ log.debug(`already found epoch ${res.id}`);
121
+ return res.id;
122
+ }
123
+ const { id } = (await db
124
+ .insertInto(EPOCH_TABLE)
125
+ .values({
126
+ ui: p.uiEpochId,
127
+ channel: p.channel,
128
+ start_time: p.startTime,
129
+ end_time: p.endTime,
130
+ total_rndr: p.totalRndr,
131
+ })
132
+ .onConflict((oc) => oc.columns(['ui', 'channel']).doNothing())
133
+ .returning((eb) => ['id as id'])
134
+ .executeTakeFirst());
135
+ log.debug(`inserted epoch ${id}`);
136
+ return id;
137
+ };
138
+ const createTransfers = async (db, transfers, log = consoleLogger, totalRndr = BigInt(0)) => {
139
+ const epochIds = [];
140
+ for (const t of transfers) {
141
+ if (!t.computeClientRewards && !t.upgradeRewards && !t.operatorRewards) {
142
+ log.warn(`malformed transfer doesnt have type ${JSON.stringify(t, (key, value) => typeof value === 'bigint' ? value.toString() : value)}`);
143
+ continue;
144
+ }
145
+ let solTxId;
146
+ if (t.solTx) {
147
+ solTxId = await getOrInsertTx(db, {
148
+ sig: t.solTx,
149
+ executedAt: t.payedAt,
150
+ }, log);
151
+ }
152
+ let entityId;
153
+ if (t.solKey) {
154
+ entityId = await getOrInsertEntity(db, { solKey: t.solKey }, log);
155
+ }
156
+ log.info(`inserting transfer ${JSON.stringify(t, (key, value) => typeof value === 'bigint' ? value.toString() : value)}`);
157
+ const { id: transferId } = (await db
158
+ .insertInto(SOL_TRANSFER_TABLE)
159
+ .values({
160
+ sol_tx_id: solTxId,
161
+ entity_id: entityId,
162
+ channel: transferTypToChan((() => {
163
+ if (t.operatorRewards) {
164
+ return 'node_operator_rewards';
165
+ }
166
+ else if (t.upgradeRewards) {
167
+ return 'upgrade_rewards';
168
+ }
169
+ return 'open_compute_rewards';
170
+ })()),
171
+ created_at: t.createdAt,
172
+ amount_due: BigInt(t.amountDue),
173
+ amount_payed: t.amountPayed ? BigInt(t.amountPayed) : 0,
174
+ })
175
+ .returning((eb) => ['id as id'])
176
+ .executeTakeFirst());
177
+ let epochId;
178
+ let deviceId;
179
+ let points;
180
+ let startTime;
181
+ let endTime;
182
+ let channel;
183
+ if (t.operatorRewards) {
184
+ log.info(`inserting operator rewards ${transferId}`);
185
+ epochId = t.operatorRewards.epochId;
186
+ startTime = t.operatorRewards.startTime;
187
+ endTime = t.operatorRewards.endTime;
188
+ channel = 'node_operator';
189
+ }
190
+ else if (t.computeClientRewards) {
191
+ log.info(`inserting compute client rewards ${transferId}`);
192
+ epochId = t.computeClientRewards.epochId;
193
+ deviceId = t.computeClientRewards.deviceId;
194
+ channel = 'open_compute';
195
+ }
196
+ else if (t.upgradeRewards) {
197
+ log.info(`inserting upgrade rewards ${transferId}`);
198
+ channel = 'upgrade';
199
+ epochId = t.upgradeRewards.epochId;
200
+ points = t.upgradeRewards.points;
201
+ }
202
+ let epochIdDb;
203
+ if (epochId != null) {
204
+ epochIdDb = await getOrInsertEpoch(db, {
205
+ channel,
206
+ startTime,
207
+ endTime,
208
+ uiEpochId: epochId,
209
+ totalRndr,
210
+ }, log);
211
+ }
212
+ await db
213
+ .insertInto(SOL_TRANSFER_INFO_TABLE)
214
+ .values({
215
+ transfer_id: transferId,
216
+ device_id: deviceId,
217
+ points,
218
+ epoch_id: epochIdDb ? BigInt(epochIdDb) : null,
219
+ })
220
+ .executeTakeFirst();
221
+ epochIds.push({ epochDbId: epochIdDb });
222
+ }
223
+ return epochIds;
224
+ };
225
+ return {
226
+ db,
227
+ async saveNodeOperatorEpoch(epochId, transfers, jobIds, totalRndr, log = consoleLogger) {
228
+ await db
229
+ .transaction()
230
+ .setIsolationLevel('repeatable read')
231
+ .execute(async (trx) => {
232
+ const epochDbIds = await createTransfers(trx, transfers, log, totalRndr);
233
+ log.info(`inserted transfers`);
234
+ for (const id of jobIds) {
235
+ await trx
236
+ .insertInto(JOB_ID_TABLE)
237
+ .values({
238
+ id: BigInt(id),
239
+ epoch_id: epochDbIds[0].epochDbId,
240
+ })
241
+ .executeTakeFirstOrThrow();
242
+ }
243
+ log.debug(`saved epoch`);
244
+ /*const now = new Date(Date.now())
245
+ const res = await trx
246
+ .updateTable(EPOCH_TABLE)
247
+ .set({
248
+ saved_at: now,
249
+ total_rndr: totalRndr,
250
+ })
251
+ .where('channel')
252
+ .where('ui', '=', epochId.toString())
253
+ .where('', '=')
254
+ .executeTakeFirstOrThrow()*/
255
+ });
256
+ return;
257
+ },
258
+ async getLatestNodeOperatorEpoch(log = consoleLogger) {
259
+ const now = new Date(Date.now());
260
+ try {
261
+ const res = await db
262
+ .selectFrom(EPOCH_TABLE)
263
+ .selectAll()
264
+ .where('channel', '=', 'node_operator')
265
+ .where('end_time', '<=', now)
266
+ .where('saved_at', 'is', null)
267
+ .orderBy('end_time asc')
268
+ .limit(1)
269
+ .executeTakeFirstOrThrow();
270
+ return {
271
+ epochId: BigInt(res.ui),
272
+ startedAt: res.start_time,
273
+ endedAt: res.end_time,
274
+ savedAt: res.saved_at ?? undefined,
275
+ };
276
+ }
277
+ catch (e) {
278
+ if (e instanceof NoResultError) {
279
+ log.info(`no node operator epoch found`);
280
+ return undefined;
281
+ }
282
+ throw e;
283
+ }
284
+ },
285
+ async getOutstanding(f, log = consoleLogger) {
286
+ const { typ, nullKey, due } = f;
287
+ //select sol_key, sum(amount_due)-sum(amount_payed) from transfer group by sol_key;
288
+ let q = db
289
+ .selectFrom(SOL_TRANSFER_TABLE)
290
+ .innerJoin(ENTITY_TABLE, 'sol_transfer.entity_id', 'entity.id')
291
+ .select(({ fn, val, ref }) => [
292
+ 'entity.sol_key',
293
+ sql `${fn.sum('sol_transfer.amount_due')} -
294
+ ${fn.sum('sol_transfer.amount_payed')}`.as('due'),
295
+ ])
296
+ .where('sol_transfer.channel', '=', transferTypToChan(typ))
297
+ .groupBy(['entity.sol_key']);
298
+ if (!nullKey) {
299
+ q = q.where('entity.sol_key', 'is not', null);
300
+ }
301
+ if (due) {
302
+ q = q.having(({ fn, ref }) => sql `${fn.sum('sol_transfer.amount_due')} -
303
+ ${fn.sum('sol_transfer.amount_payed')}
304
+ >
305
+ 0`);
306
+ }
307
+ const res = await q.execute();
308
+ log.info(`outstanding query ${q.compile().sql} params ${JSON.stringify(f)} ${res.length} rows`);
309
+ const out = {};
310
+ for (const r of res) {
311
+ out[r.sol_key ?? ''] = BigInt(r.due);
312
+ }
313
+ return out;
314
+ },
315
+ async fetchTransfers(f, log = consoleLogger) {
316
+ let base = db
317
+ .selectFrom(SOL_TRANSFER_TABLE)
318
+ .selectAll(SOL_TRANSFER_TABLE)
319
+ .innerJoin(SOL_TRANSFER_INFO_TABLE, 'sol_transfer_info.transfer_id', 'sol_transfer.id')
320
+ .select(['device_id', 'points'])
321
+ .leftJoin(SOL_TX_TABLE, 'sol_tx.id', 'sol_transfer.sol_tx_id')
322
+ .select(['sig', 'executed_at'])
323
+ .innerJoin(ENTITY_TABLE, 'entity.id', 'sol_transfer.entity_id')
324
+ .select(['sol_key'])
325
+ .innerJoin(EPOCH_TABLE, 'epoch.id', 'sol_transfer_info.epoch_id')
326
+ .select(['ui', 'start_time', 'end_time'])
327
+ .where('sol_transfer.channel', '=', transferTypToChan(f.typ));
328
+ if (f.solKey) {
329
+ base = base.where('entity.sol_key', '=', f.solKey);
330
+ }
331
+ if (f.payed != undefined) {
332
+ base = base.where('sol_transfer.sol_tx_id', f.payed ? 'is not' : 'is', null);
333
+ }
334
+ if (f.createdBefore != undefined) {
335
+ base = base.where('sol_transfer.created_at', '<=', f.createdBefore);
336
+ }
337
+ if (f.epochId) {
338
+ base = base.where('epoch.ui', '=', String(f.epochId));
339
+ }
340
+ if (f.sort) {
341
+ base = base.orderBy('entity.sol_key asc');
342
+ }
343
+ const res = await base.execute();
344
+ log.info(`fetched ${base.compile().sql} params ${JSON.stringify(f)} ${res.length} transfers`);
345
+ return res.map((r) => transferFromDBV2(r));
346
+ },
347
+ async payoutTransfers(idToAmt, payedAt, solTx, log = consoleLogger) {
348
+ await db
349
+ .transaction()
350
+ .setIsolationLevel('repeatable read')
351
+ .execute(async (tx) => {
352
+ const alreadyUpdatedIds = (await tx
353
+ .selectFrom(SOL_TRANSFER_TABLE)
354
+ .innerJoin(SOL_TX_TABLE, 'sol_transfer.sol_tx_id', 'sol_tx.id')
355
+ .select(['sol_transfer.id'])
356
+ .where('sol_transfer.id', 'in', Object.keys(idToAmt))
357
+ .where('sol_tx.sig', 'is not', null)
358
+ .execute()).map(({ id }) => id);
359
+ for (const [id, amt] of Object.entries(idToAmt)) {
360
+ if (alreadyUpdatedIds.includes(id)) {
361
+ log.info(`transfer ${id} already marked payed out. skipping`);
362
+ continue;
363
+ }
364
+ const solTxId = await getOrInsertTx(tx, { sig: solTx, executedAt: payedAt }, log);
365
+ const res = await tx
366
+ .updateTable(SOL_TRANSFER_TABLE)
367
+ .set({
368
+ sol_tx_id: solTxId,
369
+ amount_payed: amt,
370
+ })
371
+ .where('id', '=', id.toString())
372
+ .executeTakeFirstOrThrow();
373
+ log.info(`marked ${res.numUpdatedRows} transfer payed out`);
374
+ }
375
+ });
376
+ },
377
+ async payoutTransfer(id, payedAt, solTx, amtPayed, log = consoleLogger) {
378
+ await db
379
+ .transaction()
380
+ .setIsolationLevel('repeatable read')
381
+ .execute(async (tx) => {
382
+ const solTxId = await getOrInsertTx(tx, { sig: solTx, executedAt: payedAt }, log);
383
+ const res = await tx
384
+ .updateTable(SOL_TRANSFER_TABLE)
385
+ .set({
386
+ sol_tx_id: solTxId,
387
+ amount_payed: amtPayed,
388
+ })
389
+ .where('id', '=', id.toString())
390
+ .executeTakeFirst();
391
+ log.info(`marked ${res.numUpdatedRows} transfer payed out`);
392
+ });
393
+ },
394
+ async createTransfers(transfers, log = consoleLogger) {
395
+ await db
396
+ .transaction()
397
+ .setIsolationLevel('repeatable read')
398
+ .execute(async (trx) => {
399
+ await createTransfers(trx, transfers, log);
400
+ });
401
+ },
402
+ async fetchInfoForEpoch(epochId, log = consoleLogger) {
403
+ const q = db
404
+ .selectFrom(JOB_ID_TABLE)
405
+ .innerJoin(EPOCH_TABLE, 'epoch.id', 'job_id.epoch_id')
406
+ .leftJoin(MANUAL_BURN_TABLE, 'manual_burn.epoch_id', 'epoch.id')
407
+ .where('epoch.ui', '=', String(epochId))
408
+ .where('epoch.channel', '=', 'node_operator')
409
+ .select([
410
+ 'job_id.id',
411
+ 'epoch.total_rndr',
412
+ 'epoch.start_time',
413
+ 'epoch.end_time',
414
+ 'manual_burn.burned',
415
+ 'manual_burn.tx_sig',
416
+ ])
417
+ .orderBy('job_id.id asc');
418
+ log.debug(`fetch epoch info q ${q.compile().sql} epochId ${epochId}`);
419
+ const res = await q.execute();
420
+ if (res.length == 0) {
421
+ throw new Error(`epoch ${epochId} not found`);
422
+ }
423
+ return {
424
+ jobIds: res.map((r) => Number(r.id)),
425
+ totalRndr: BigInt(res[0].total_rndr),
426
+ startTime: res[0].start_time,
427
+ endTime: res[0].end_time,
428
+ burned: BigInt(res[0].burned ?? 0),
429
+ burnTx: res[0].tx_sig,
430
+ };
431
+ },
432
+ async updateComputeClientSolKey(deviceId, solKey, log = consoleLogger) {
433
+ const q = db
434
+ .updateTable(`${ENTITY_TABLE} as e`)
435
+ .set({
436
+ sol_key: solKey,
437
+ })
438
+ .from(`${SOL_TRANSFER_TABLE} as t`)
439
+ .innerJoin(SOL_TRANSFER_INFO_TABLE, 'sol_transfer_info.transfer_id', 't.id')
440
+ //.whereRef('t.id', '=', 'o.transfer_id')
441
+ .where('e.sol_key', 'is', null)
442
+ .where('sol_transfer_info.device_id', '=', deviceId);
443
+ const sql = await q.compile();
444
+ log.info(`sql ${sql.sql} params ${sql.parameters}`);
445
+ const res = await q.executeTakeFirstOrThrow();
446
+ log.info(`updated ${res.numUpdatedRows} device-id sol_keys`);
447
+ },
448
+ async markJobsBurnt(trx, p, log = consoleLogger) {
449
+ const solTxId = await getOrInsertTx(trx, { sig: p.solTx, executedAt: p.executedAt }, log);
450
+ const { id } = await trx
451
+ .insertInto(BURN_TABLE)
452
+ .values({
453
+ sol_tx_id: solTxId,
454
+ priced_at: p.pricedAt,
455
+ usdc_spent: p.usdcSpent,
456
+ quote_amt: p.quoteAmt,
457
+ burned: p.burned,
458
+ })
459
+ //.onConflict((oc) => oc.column('sol_tx').doNothing())
460
+ .returning((eb) => ['id as id'])
461
+ .executeTakeFirstOrThrow();
462
+ const jobIdsStr = p.jobsIds.map(String);
463
+ const res = await trx
464
+ .updateTable(JOB_TABLE)
465
+ .where('id', 'in', jobIdsStr)
466
+ .set('burn_id', id)
467
+ .executeTakeFirstOrThrow();
468
+ log.debug(`set burn_id on ${res.numUpdatedRows} jobs`);
469
+ return;
470
+ },
471
+ async produceBurnedJobs(jobs, log = consoleLogger) {
472
+ await db.transaction().execute(async (trx) => {
473
+ let num = BigInt(0);
474
+ for (const { amount, completedAt, jobId } of jobs) {
475
+ const res = await trx
476
+ .insertInto(JOB_TABLE)
477
+ .values({
478
+ id: jobId,
479
+ render_amt: amount,
480
+ completed_at: completedAt,
481
+ inserted_at: new Date(Date.now()),
482
+ })
483
+ .onConflict((oc) => oc.column('id').doNothing())
484
+ .executeTakeFirstOrThrow();
485
+ num += res.numInsertedOrUpdatedRows ?? BigInt(0);
486
+ }
487
+ log.debug(`inserted ${num} of ${jobs.length} burn jobs`);
488
+ });
489
+ },
490
+ async markJobsBurned(p, log = consoleLogger) {
491
+ let { markedAt, solTx } = p;
492
+ let res = await db
493
+ .updateTable(BURN_TABLE)
494
+ .set({ marked_burned_at: markedAt })
495
+ .from(SOL_TX_TABLE)
496
+ .whereRef('burn.sol_tx_id', '=', 'sol_tx.id')
497
+ .where('sol_tx.sig', '=', solTx)
498
+ .executeTakeFirstOrThrow();
499
+ log.debug(`set ${res.numUpdatedRows} ${solTx} -> marked_burned_at ${markedAt.toISOString()}`);
500
+ },
501
+ async consumeAndLockBurnJobs(limit, lowerRndrThreshold, upperRndrThreshold, log = consoleLogger) {
502
+ const trx = await begin(db);
503
+ const q = trx.trx
504
+ .selectFrom(trx.trx
505
+ .selectFrom(JOB_TABLE)
506
+ .select(({ fn, val, ref }) => [
507
+ 'id',
508
+ fn
509
+ .sum('render_amt')
510
+ .over((ob) => ob.orderBy(`completed_at`, `asc`))
511
+ .as('total'),
512
+ ])
513
+ .where('burn_id', 'is', null)
514
+ .as('t'))
515
+ .select('id')
516
+ .where('total', '>', lowerRndrThreshold)
517
+ .where('total', '<', upperRndrThreshold);
518
+ log.debug(`outstanding jobs q: ${q.compile().sql} ${JSON.stringify({
519
+ limit,
520
+ lowerRndrThreshold,
521
+ upperRndrThreshold,
522
+ })}`);
523
+ const idRs = await q.execute();
524
+ const ids = idRs.map((x) => x.id);
525
+ log.debug(`${ids.length} candidate jobs for burn. locking up to ${limit} jobs`);
526
+ if (ids.length == 0) {
527
+ return { trx, jobs: [] };
528
+ }
529
+ const rows = await trx.trx
530
+ .selectFrom(JOB_TABLE)
531
+ .selectAll()
532
+ .where('id', 'in', ids)
533
+ .limit(limit)
534
+ .forUpdate()
535
+ .skipLocked()
536
+ .execute();
537
+ log.debug(`locked ${rows.length} jobs`);
538
+ const jobs = [];
539
+ for (const r of rows) {
540
+ jobs.push({
541
+ id: Number(r.id),
542
+ completedAt: r.completed_at,
543
+ rndrUsed: BigInt(r.render_amt),
544
+ });
545
+ }
546
+ return { trx, jobs };
547
+ },
548
+ async incrBuyBurnMetrics(log = consoleLogger) {
549
+ const res = await db
550
+ .selectFrom(JOB_TABLE)
551
+ .select('completed_at')
552
+ .orderBy('completed_at asc')
553
+ .where('burn_id', 'is', null)
554
+ .execute();
555
+ if (res.length == 0) {
556
+ return { outstandingJobs: 0 };
557
+ }
558
+ return {
559
+ outstandingJobs: res.length,
560
+ oldestJobFinishedAt: res[0].completed_at ?? undefined,
561
+ };
562
+ },
563
+ async deleteAll() {
564
+ if (config.database.includes('render')) {
565
+ throw new Error('cannot nuke from deployed database. call from tests only');
566
+ }
567
+ await Promise.all([
568
+ db
569
+ .deleteFrom(SOL_TX_TABLE)
570
+ .where('id', 'is not', null)
571
+ .executeTakeFirst(),
572
+ db
573
+ .deleteFrom(SOL_TRANSFER_TABLE)
574
+ .where('id', 'is not', null)
575
+ .executeTakeFirst(),
576
+ db
577
+ .deleteFrom(SOL_TRANSFER_INFO_TABLE)
578
+ .where('transfer_id', 'is not', null)
579
+ .executeTakeFirst(),
580
+ db
581
+ .deleteFrom(EPOCH_TABLE)
582
+ .where('id', 'is not', null)
583
+ .executeTakeFirst(),
584
+ db
585
+ .deleteFrom(ENTITY_TABLE)
586
+ .where('id', 'is not', null)
587
+ .executeTakeFirst(),
588
+ db
589
+ .deleteFrom(MANUAL_BURN_TABLE)
590
+ .where('epoch_id', 'is not', null)
591
+ .executeTakeFirst(),
592
+ db
593
+ .deleteFrom(JOB_ID_TABLE)
594
+ .where('id', 'is not', null)
595
+ .executeTakeFirst(),
596
+ db.deleteFrom(JOB_TABLE).where('id', 'is not', null).executeTakeFirst(),
597
+ db
598
+ .deleteFrom(BURN_TABLE)
599
+ .where('id', 'is not', null)
600
+ .executeTakeFirst(),
601
+ ]);
602
+ },
603
+ };
604
+ };
605
+ async function begin(db) {
606
+ const connection = new Deferred();
607
+ const result = new Deferred();
608
+ // Do NOT await this line.
609
+ db.transaction()
610
+ .setIsolationLevel('repeatable read')
611
+ .execute((trx) => {
612
+ connection.resolve(trx);
613
+ return result.promise;
614
+ })
615
+ .catch((err) => {
616
+ // Don't do anything here. Just swallow the exception.
617
+ });
618
+ const trx = await connection.promise;
619
+ return {
620
+ trx,
621
+ commit() {
622
+ result.resolve(null);
623
+ },
624
+ rollback() {
625
+ result.reject(new Error('rollback'));
626
+ },
627
+ };
628
+ }
629
+ export class Deferred {
630
+ #promise;
631
+ #resolve;
632
+ #reject;
633
+ constructor() {
634
+ this.#promise = new Promise((resolve, reject) => {
635
+ this.#reject = reject;
636
+ this.#resolve = resolve;
637
+ });
638
+ }
639
+ get promise() {
640
+ return this.#promise;
641
+ }
642
+ resolve = (value) => {
643
+ if (this.#resolve) {
644
+ this.#resolve(value);
645
+ }
646
+ };
647
+ reject = (reason) => {
648
+ if (this.#reject) {
649
+ this.#reject(reason);
650
+ }
651
+ };
652
+ }
653
+ //# sourceMappingURL=v2.js.map