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