envio 3.3.0-alpha.1 → 3.3.0-alpha.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/package.json +6 -6
- package/src/Batch.res +0 -6
- package/src/Batch.res.mjs +0 -11
- package/src/BatchProcessing.res +2 -12
- package/src/BatchProcessing.res.mjs +2 -7
- package/src/ChainFetching.res +17 -6
- package/src/ChainFetching.res.mjs +11 -11
- package/src/ChainMetadata.res +1 -11
- package/src/ChainMetadata.res.mjs +1 -10
- package/src/ChainState.res +150 -1
- package/src/ChainState.res.mjs +138 -9
- package/src/ChainState.resi +51 -1
- package/src/Config.res +3 -6
- package/src/Config.res.mjs +2 -3
- package/src/Core.res +7 -0
- package/src/CrossChainState.res +9 -26
- package/src/CrossChainState.res.mjs +7 -16
- package/src/Ecosystem.res +6 -1
- package/src/Envio.res +3 -2
- package/src/EventConfigBuilder.res +19 -5
- package/src/EventConfigBuilder.res.mjs +6 -4
- package/src/EventProcessing.res +26 -0
- package/src/EventProcessing.res.mjs +20 -0
- package/src/Internal.res +43 -4
- package/src/Internal.res.mjs +18 -0
- package/src/Main.res +3 -50
- package/src/Main.res.mjs +2 -23
- package/src/Rollback.res +1 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/SimulateItems.res +3 -0
- package/src/SimulateItems.res.mjs +2 -1
- package/src/sources/Evm.res +52 -38
- package/src/sources/Evm.res.mjs +48 -36
- package/src/sources/Fuel.res +3 -1
- package/src/sources/Fuel.res.mjs +1 -0
- package/src/sources/HyperFuelSource.res +5 -0
- package/src/sources/HyperFuelSource.res.mjs +2 -0
- package/src/sources/HyperSync.res +4 -1
- package/src/sources/HyperSync.res.mjs +5 -3
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +9 -78
- package/src/sources/HyperSyncClient.res.mjs +1 -22
- package/src/sources/HyperSyncSource.res +3 -4
- package/src/sources/HyperSyncSource.res.mjs +2 -1
- package/src/sources/RpcSource.res +7 -2
- package/src/sources/RpcSource.res.mjs +3 -1
- package/src/sources/SimulateSource.res +3 -1
- package/src/sources/SimulateSource.res.mjs +1 -0
- package/src/sources/Source.res +4 -0
- package/src/sources/Svm.res +25 -2
- package/src/sources/Svm.res.mjs +27 -2
- package/src/sources/SvmHyperSyncClient.res +3 -29
- package/src/sources/SvmHyperSyncSource.res +60 -84
- package/src/sources/SvmHyperSyncSource.res.mjs +59 -78
- package/src/sources/TransactionStore.res +111 -0
- package/src/sources/TransactionStore.res.mjs +77 -0
- package/src/tui/Tui.res +13 -16
- package/src/tui/Tui.res.mjs +12 -14
|
@@ -188,7 +188,6 @@ let toSvmInstruction = (
|
|
|
188
188
|
instr: SvmHyperSyncClient.ResponseTypes.instruction,
|
|
189
189
|
~programName,
|
|
190
190
|
~instructionName,
|
|
191
|
-
~transaction,
|
|
192
191
|
~logs,
|
|
193
192
|
~block,
|
|
194
193
|
): Envio.svmInstruction => {
|
|
@@ -204,35 +203,10 @@ let toSvmInstruction = (
|
|
|
204
203
|
d4: ?instr.d4,
|
|
205
204
|
d8: ?instr.d8,
|
|
206
205
|
params: ?(instr.decoded->Option.map(parseDecoded)),
|
|
207
|
-
?transaction,
|
|
208
206
|
?logs,
|
|
209
207
|
block,
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
let toSvmTransaction = (tx: SvmHyperSyncClient.ResponseTypes.transaction): Envio.svmTransaction => {
|
|
213
|
-
signatures: tx.signatures,
|
|
214
|
-
accountKeys: tx.accountKeys->SvmTypes.Pubkey.fromStringsUnsafe,
|
|
215
|
-
feePayer: ?(tx.feePayer->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
216
|
-
success: ?tx.success,
|
|
217
|
-
err: ?tx.err,
|
|
218
|
-
// u64 lamports / compute units arrive as `int` over napi. Convert to
|
|
219
|
-
// `bigint` so the public type stays defensible even for pathological values.
|
|
220
|
-
fee: ?(tx.fee->Option.map(BigInt.fromInt)),
|
|
221
|
-
computeUnitsConsumed: ?(tx.computeUnitsConsumed->Option.map(BigInt.fromInt)),
|
|
222
|
-
recentBlockhash: ?tx.recentBlockhash,
|
|
223
|
-
version: ?tx.version,
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let toSvmTokenBalance = (
|
|
227
|
-
tb: SvmHyperSyncClient.ResponseTypes.tokenBalance,
|
|
228
|
-
): Envio.svmTokenBalance => {
|
|
229
|
-
account: ?(tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
230
|
-
mint: ?(tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
231
|
-
owner: ?(tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
|
|
232
|
-
preAmount: ?tb.preAmount,
|
|
233
|
-
postAmount: ?tb.postAmount,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
210
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
237
211
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
238
212
|
// no discriminator-keyed handler matches.
|
|
@@ -273,6 +247,27 @@ let probeRouter = (
|
|
|
273
247
|
}
|
|
274
248
|
}
|
|
275
249
|
|
|
250
|
+
// Map a selected transaction field to the extra query-side column it needs.
|
|
251
|
+
// `transactionIndex` is always fetched as the store key, and `tokenBalances`
|
|
252
|
+
// lives in a separate table (requested via `needsTokenBalances`), so neither
|
|
253
|
+
// adds a transaction column here.
|
|
254
|
+
let toQueryTxField = (field: Internal.svmTransactionField): option<
|
|
255
|
+
SvmHyperSyncClient.QueryTypes.transactionField,
|
|
256
|
+
> =>
|
|
257
|
+
switch field {
|
|
258
|
+
| TransactionIndex => None
|
|
259
|
+
| Signatures => Some(Signatures)
|
|
260
|
+
| FeePayer => Some(FeePayer)
|
|
261
|
+
| Success => Some(Success)
|
|
262
|
+
| Err => Some(Err)
|
|
263
|
+
| Fee => Some(Fee)
|
|
264
|
+
| ComputeUnitsConsumed => Some(ComputeUnitsConsumed)
|
|
265
|
+
| AccountKeys => Some(AccountKeys)
|
|
266
|
+
| RecentBlockhash => Some(RecentBlockhash)
|
|
267
|
+
| Version => Some(Version)
|
|
268
|
+
| TokenBalances => None
|
|
269
|
+
}
|
|
270
|
+
|
|
276
271
|
let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: options): t => {
|
|
277
272
|
let name = "SvmHyperSync"
|
|
278
273
|
let chainId = chain->ChainMap.Chain.toChainId
|
|
@@ -298,9 +293,38 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
298
293
|
orderingByProgram->Dict.set(o.programId->SvmTypes.Pubkey.toString, o.byteLengthsDesc)
|
|
299
294
|
)
|
|
300
295
|
|
|
301
|
-
let needsTransactions = eventConfigs->Array.some(cfg => cfg.includeTransaction)
|
|
302
296
|
let needsLogs = eventConfigs->Array.some(cfg => cfg.includeLogs)
|
|
303
|
-
|
|
297
|
+
|
|
298
|
+
// Union of selected transaction fields across the chain's events. Drives both
|
|
299
|
+
// the query column selection (fetch only what's used) and the materialisation
|
|
300
|
+
// mask. `slot`/`transactionIndex` are always fetched as the store key.
|
|
301
|
+
let selectedTxFields = Utils.Set.make()
|
|
302
|
+
eventConfigs->Array.forEach(cfg =>
|
|
303
|
+
cfg.selectedTransactionFields
|
|
304
|
+
->(Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.svmTransactionField>)
|
|
305
|
+
->Utils.Set.forEach(field => selectedTxFields->Utils.Set.add(field)->ignore)
|
|
306
|
+
)
|
|
307
|
+
let needsTokenBalances = selectedTxFields->Utils.Set.has(Internal.TokenBalances)
|
|
308
|
+
let txQueryFields = {
|
|
309
|
+
// Slot + TransactionIndex are always fetched so the store can be keyed by
|
|
310
|
+
// (slot, transactionIndex).
|
|
311
|
+
let fields: array<SvmHyperSyncClient.QueryTypes.transactionField> = [Slot, TransactionIndex]
|
|
312
|
+
selectedTxFields
|
|
313
|
+
->Utils.Set.toArray
|
|
314
|
+
->Array.forEach(field =>
|
|
315
|
+
switch toQueryTxField(field) {
|
|
316
|
+
| Some(queryField) => fields->Array.push(queryField)
|
|
317
|
+
| None => ()
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
fields
|
|
321
|
+
}
|
|
322
|
+
// Every selected field except `tokenBalances` (its own table) is read off a
|
|
323
|
+
// stored transaction record, so the transaction table must be fetched to
|
|
324
|
+
// populate the store — even when only `transactionIndex` (a key column) is
|
|
325
|
+
// selected.
|
|
326
|
+
let txFieldCount = selectedTxFields->Utils.Set.size
|
|
327
|
+
let needsTransactions = txFieldCount > 1 || (txFieldCount === 1 && !needsTokenBalances)
|
|
304
328
|
|
|
305
329
|
let getItemsOrThrow = async (
|
|
306
330
|
~fromBlock,
|
|
@@ -323,23 +347,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
323
347
|
// opted-into table needs its columns spelled out here.
|
|
324
348
|
let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
|
|
325
349
|
block: [Slot, Blockhash, BlockTime],
|
|
326
|
-
transaction: ?(
|
|
327
|
-
needsTransactions
|
|
328
|
-
? Some([
|
|
329
|
-
Slot,
|
|
330
|
-
TransactionIndex,
|
|
331
|
-
Signatures,
|
|
332
|
-
FeePayer,
|
|
333
|
-
Success,
|
|
334
|
-
Err,
|
|
335
|
-
Fee,
|
|
336
|
-
ComputeUnitsConsumed,
|
|
337
|
-
AccountKeys,
|
|
338
|
-
RecentBlockhash,
|
|
339
|
-
Version,
|
|
340
|
-
])
|
|
341
|
-
: None
|
|
342
|
-
),
|
|
350
|
+
transaction: ?(needsTransactions ? Some(txQueryFields) : None),
|
|
343
351
|
log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
|
|
344
352
|
tokenBalance: ?(
|
|
345
353
|
needsTokenBalances
|
|
@@ -358,7 +366,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
358
366
|
|
|
359
367
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getInstructions")
|
|
360
368
|
|
|
361
|
-
let resp = try await client.get(~query) catch {
|
|
369
|
+
let (resp, transactionStore) = try await client.get(~query) catch {
|
|
362
370
|
| exn =>
|
|
363
371
|
throw(
|
|
364
372
|
Source.GetItemsError(
|
|
@@ -390,13 +398,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
390
398
|
}
|
|
391
399
|
})
|
|
392
400
|
|
|
393
|
-
// Per (slot, transaction_index) lookup for parent transactions.
|
|
394
|
-
let txByKey = Dict.make()
|
|
395
|
-
resp.data.transactions->Array.forEach(tx => {
|
|
396
|
-
let key = tx.slot->Int.toString ++ ":" ++ tx.transactionIndex->Int.toString
|
|
397
|
-
txByKey->Dict.set(key, tx)
|
|
398
|
-
})
|
|
399
|
-
|
|
400
401
|
// Per (slot, transaction_index, instruction_address) lookup for logs
|
|
401
402
|
// scoped to a single instruction. `instructionAddress: None` logs are
|
|
402
403
|
// attached to no instruction (rare; usually only system messages).
|
|
@@ -418,21 +419,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
418
419
|
}
|
|
419
420
|
})
|
|
420
421
|
|
|
421
|
-
let tokenBalancesByTx = Dict.make()
|
|
422
|
-
if needsTokenBalances {
|
|
423
|
-
resp.data.tokenBalances->Array.forEach(tb => {
|
|
424
|
-
switch tb.transactionIndex {
|
|
425
|
-
| Some(txIdx) =>
|
|
426
|
-
let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
|
|
427
|
-
switch tokenBalancesByTx->Dict.get(key) {
|
|
428
|
-
| Some(existing) => existing->Array.push(tb)
|
|
429
|
-
| None => tokenBalancesByTx->Dict.set(key, [tb])
|
|
430
|
-
}
|
|
431
|
-
| None => ()
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
|
|
436
422
|
let parsedQueueItems = []
|
|
437
423
|
resp.data.instructions->Array.forEach(instr => {
|
|
438
424
|
let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
|
|
@@ -454,20 +440,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
454
440
|
switch maybeConfig {
|
|
455
441
|
| None => ()
|
|
456
442
|
| Some(eventConfig) =>
|
|
457
|
-
let txKey = instr.slot->Int.toString ++ ":" ++ instr.transactionIndex->Int.toString
|
|
458
|
-
let maybeTx =
|
|
459
|
-
txByKey->Utils.Dict.dangerouslyGetNonOption(txKey)->Option.map(toSvmTransaction)
|
|
460
|
-
let maybeTx = if eventConfig.includeTokenBalances {
|
|
461
|
-
maybeTx->Option.map(tx => {
|
|
462
|
-
let maybeBalances =
|
|
463
|
-
tokenBalancesByTx
|
|
464
|
-
->Utils.Dict.dangerouslyGetNonOption(txKey)
|
|
465
|
-
->Option.map(bals => bals->Array.map(toSvmTokenBalance))
|
|
466
|
-
{...tx, tokenBalances: ?maybeBalances}
|
|
467
|
-
})
|
|
468
|
-
} else {
|
|
469
|
-
maybeTx
|
|
470
|
-
}
|
|
471
443
|
let logKey =
|
|
472
444
|
instr.slot->Int.toString ++
|
|
473
445
|
":" ++
|
|
@@ -492,7 +464,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
492
464
|
instr,
|
|
493
465
|
~programName=eventConfig.contractName,
|
|
494
466
|
~instructionName=eventConfig.name,
|
|
495
|
-
~transaction=eventConfig.includeTransaction ? maybeTx : None,
|
|
496
467
|
~logs=eventConfig.includeLogs ? maybeLogs : None,
|
|
497
468
|
~block={
|
|
498
469
|
slot: instr.slot,
|
|
@@ -510,6 +481,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
510
481
|
blockNumber: instr.slot,
|
|
511
482
|
blockHash: "",
|
|
512
483
|
logIndex: synthLogIndex(instr),
|
|
484
|
+
// The parent transaction is materialised from the store at batch prep.
|
|
485
|
+
transactionIndex: instr.transactionIndex,
|
|
513
486
|
payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
|
|
514
487
|
}),
|
|
515
488
|
)
|
|
@@ -539,6 +512,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
539
512
|
{
|
|
540
513
|
latestFetchedBlockTimestamp: latestBlockTime,
|
|
541
514
|
parsedQueueItems,
|
|
515
|
+
// Raw transactions kept in Rust; materialised (selected fields) at batch prep.
|
|
516
|
+
transactionStore: Some(transactionStore),
|
|
542
517
|
latestFetchedBlockNumber: highestSlot,
|
|
543
518
|
stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
|
|
544
519
|
knownHeight,
|
|
@@ -563,7 +538,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
|
|
|
563
538
|
maxNumBlocks: 1000,
|
|
564
539
|
}
|
|
565
540
|
Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getBlockHashes")
|
|
566
|
-
|
|
541
|
+
// Block-only query; the store page is empty.
|
|
542
|
+
let (resp, _) = await client.get(~query)
|
|
567
543
|
resp.data.blocks->Array.forEach(b =>
|
|
568
544
|
blockDatas
|
|
569
545
|
->Array.push({
|
|
@@ -166,7 +166,7 @@ function parseDecoded(d) {
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
function toSvmInstruction(instr, programName, instructionName,
|
|
169
|
+
function toSvmInstruction(instr, programName, instructionName, logs, block) {
|
|
170
170
|
return {
|
|
171
171
|
programName: programName,
|
|
172
172
|
instructionName: instructionName,
|
|
@@ -180,36 +180,11 @@ function toSvmInstruction(instr, programName, instructionName, transaction, logs
|
|
|
180
180
|
d4: instr.d4,
|
|
181
181
|
d8: instr.d8,
|
|
182
182
|
params: Stdlib_Option.map(instr.decoded, parseDecoded),
|
|
183
|
-
transaction: transaction,
|
|
184
183
|
logs: logs,
|
|
185
184
|
block: block
|
|
186
185
|
};
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
function toSvmTransaction(tx) {
|
|
190
|
-
return {
|
|
191
|
-
signatures: tx.signatures,
|
|
192
|
-
feePayer: Stdlib_Option.map(tx.feePayer, prim => prim),
|
|
193
|
-
success: tx.success,
|
|
194
|
-
err: tx.err,
|
|
195
|
-
fee: Stdlib_Option.map(tx.fee, prim => BigInt(prim)),
|
|
196
|
-
computeUnitsConsumed: Stdlib_Option.map(tx.computeUnitsConsumed, prim => BigInt(prim)),
|
|
197
|
-
accountKeys: tx.accountKeys,
|
|
198
|
-
recentBlockhash: tx.recentBlockhash,
|
|
199
|
-
version: tx.version
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function toSvmTokenBalance(tb) {
|
|
204
|
-
return {
|
|
205
|
-
account: Stdlib_Option.map(tb.account, prim => prim),
|
|
206
|
-
mint: Stdlib_Option.map(tb.mint, prim => prim),
|
|
207
|
-
owner: Stdlib_Option.map(tb.owner, prim => prim),
|
|
208
|
-
preAmount: tb.preAmount,
|
|
209
|
-
postAmount: tb.postAmount
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
188
|
function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
|
|
214
189
|
let probe = dN => {
|
|
215
190
|
let tag = EventRouter.getSvmEventId(programId, dN);
|
|
@@ -247,6 +222,32 @@ function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress,
|
|
|
247
222
|
}
|
|
248
223
|
}
|
|
249
224
|
|
|
225
|
+
function toQueryTxField(field) {
|
|
226
|
+
switch (field) {
|
|
227
|
+
case "signatures" :
|
|
228
|
+
return "signatures";
|
|
229
|
+
case "feePayer" :
|
|
230
|
+
return "fee_payer";
|
|
231
|
+
case "success" :
|
|
232
|
+
return "success";
|
|
233
|
+
case "err" :
|
|
234
|
+
return "err";
|
|
235
|
+
case "fee" :
|
|
236
|
+
return "fee";
|
|
237
|
+
case "computeUnitsConsumed" :
|
|
238
|
+
return "compute_units_consumed";
|
|
239
|
+
case "accountKeys" :
|
|
240
|
+
return "account_keys";
|
|
241
|
+
case "recentBlockhash" :
|
|
242
|
+
return "recent_blockhash";
|
|
243
|
+
case "version" :
|
|
244
|
+
return "version";
|
|
245
|
+
case "transactionIndex" :
|
|
246
|
+
case "tokenBalances" :
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
250
251
|
function make(param) {
|
|
251
252
|
let eventConfigs = param.eventConfigs;
|
|
252
253
|
let chain = param.chain;
|
|
@@ -259,9 +260,27 @@ function make(param) {
|
|
|
259
260
|
match[1].forEach(o => {
|
|
260
261
|
orderingByProgram[o.programId] = o.byteLengthsDesc;
|
|
261
262
|
});
|
|
262
|
-
let needsTransactions = eventConfigs.some(cfg => cfg.includeTransaction);
|
|
263
263
|
let needsLogs = eventConfigs.some(cfg => cfg.includeLogs);
|
|
264
|
-
let
|
|
264
|
+
let selectedTxFields = new Set();
|
|
265
|
+
eventConfigs.forEach(cfg => {
|
|
266
|
+
cfg.selectedTransactionFields.forEach(field => {
|
|
267
|
+
selectedTxFields.add(field);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
let needsTokenBalances = selectedTxFields.has("tokenBalances");
|
|
271
|
+
let fields = [
|
|
272
|
+
"slot",
|
|
273
|
+
"transaction_index"
|
|
274
|
+
];
|
|
275
|
+
Array.from(selectedTxFields).forEach(field => {
|
|
276
|
+
let queryField = toQueryTxField(field);
|
|
277
|
+
if (queryField !== undefined) {
|
|
278
|
+
fields.push(queryField);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
let txFieldCount = selectedTxFields.size;
|
|
283
|
+
let needsTransactions = txFieldCount > 1 || txFieldCount === 1 && !needsTokenBalances;
|
|
265
284
|
let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
|
|
266
285
|
let totalTimeRef = Hrtime.makeTimer();
|
|
267
286
|
let pageFetchRef = Hrtime.makeTimer();
|
|
@@ -271,19 +290,7 @@ function make(param) {
|
|
|
271
290
|
"blockhash",
|
|
272
291
|
"block_time"
|
|
273
292
|
];
|
|
274
|
-
let fields_transaction = needsTransactions ?
|
|
275
|
-
"slot",
|
|
276
|
-
"transaction_index",
|
|
277
|
-
"signatures",
|
|
278
|
-
"fee_payer",
|
|
279
|
-
"success",
|
|
280
|
-
"err",
|
|
281
|
-
"fee",
|
|
282
|
-
"compute_units_consumed",
|
|
283
|
-
"account_keys",
|
|
284
|
-
"recent_blockhash",
|
|
285
|
-
"version"
|
|
286
|
-
] : undefined;
|
|
293
|
+
let fields_transaction = needsTransactions ? fields : undefined;
|
|
287
294
|
let fields_log = needsLogs ? [
|
|
288
295
|
"slot",
|
|
289
296
|
"transaction_index",
|
|
@@ -300,7 +307,7 @@ function make(param) {
|
|
|
300
307
|
"pre_amount",
|
|
301
308
|
"post_amount"
|
|
302
309
|
] : undefined;
|
|
303
|
-
let fields = {
|
|
310
|
+
let fields$1 = {
|
|
304
311
|
block: fields_block,
|
|
305
312
|
transaction: fields_transaction,
|
|
306
313
|
log: fields_log,
|
|
@@ -308,7 +315,7 @@ function make(param) {
|
|
|
308
315
|
};
|
|
309
316
|
let query_toSlot = Stdlib_Option.map(toBlock, toBlock => toBlock + 1 | 0);
|
|
310
317
|
let query_instructions = instructionSelections;
|
|
311
|
-
let query_fields = fields;
|
|
318
|
+
let query_fields = fields$1;
|
|
312
319
|
let query = {
|
|
313
320
|
fromSlot: fromBlock,
|
|
314
321
|
toSlot: query_toSlot,
|
|
@@ -316,9 +323,9 @@ function make(param) {
|
|
|
316
323
|
fields: query_fields
|
|
317
324
|
};
|
|
318
325
|
Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
|
|
319
|
-
let
|
|
326
|
+
let match;
|
|
320
327
|
try {
|
|
321
|
-
|
|
328
|
+
match = await client.get(query);
|
|
322
329
|
} catch (raw_exn) {
|
|
323
330
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
324
331
|
throw {
|
|
@@ -336,6 +343,7 @@ function make(param) {
|
|
|
336
343
|
Error: new Error()
|
|
337
344
|
};
|
|
338
345
|
}
|
|
346
|
+
let resp = match[0];
|
|
339
347
|
let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef));
|
|
340
348
|
let parsingRef = Hrtime.makeTimer();
|
|
341
349
|
let blockTimeBySlot = {};
|
|
@@ -346,11 +354,6 @@ function make(param) {
|
|
|
346
354
|
return;
|
|
347
355
|
}
|
|
348
356
|
});
|
|
349
|
-
let txByKey = {};
|
|
350
|
-
resp.data.transactions.forEach(tx => {
|
|
351
|
-
let key = tx.slot.toString() + ":" + tx.transactionIndex.toString();
|
|
352
|
-
txByKey[key] = tx;
|
|
353
|
-
});
|
|
354
357
|
let logsByKey = {};
|
|
355
358
|
resp.data.logs.forEach(log => {
|
|
356
359
|
let match = log.transactionIndex;
|
|
@@ -369,22 +372,6 @@ function make(param) {
|
|
|
369
372
|
logsByKey[key] = [log];
|
|
370
373
|
}
|
|
371
374
|
});
|
|
372
|
-
let tokenBalancesByTx = {};
|
|
373
|
-
if (needsTokenBalances) {
|
|
374
|
-
resp.data.tokenBalances.forEach(tb => {
|
|
375
|
-
let txIdx = tb.transactionIndex;
|
|
376
|
-
if (txIdx === undefined) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
let key = tb.slot.toString() + ":" + txIdx.toString();
|
|
380
|
-
let existing = tokenBalancesByTx[key];
|
|
381
|
-
if (existing !== undefined) {
|
|
382
|
-
existing.push(tb);
|
|
383
|
-
} else {
|
|
384
|
-
tokenBalancesByTx[key] = [tb];
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
375
|
let parsedQueueItems = [];
|
|
389
376
|
resp.data.instructions.forEach(instr => {
|
|
390
377
|
let programId = instr.programId;
|
|
@@ -392,14 +379,6 @@ function make(param) {
|
|
|
392
379
|
let contractAddress = instr.programId;
|
|
393
380
|
let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses);
|
|
394
381
|
if (maybeConfig !== undefined) {
|
|
395
|
-
let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString();
|
|
396
|
-
let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction);
|
|
397
|
-
let maybeTx$1 = maybeConfig.includeTokenBalances ? Stdlib_Option.map(maybeTx, tx => {
|
|
398
|
-
let maybeBalances = Stdlib_Option.map(tokenBalancesByTx[txKey], bals => bals.map(toSvmTokenBalance));
|
|
399
|
-
let newrecord = {...tx};
|
|
400
|
-
newrecord.tokenBalances = maybeBalances;
|
|
401
|
-
return newrecord;
|
|
402
|
-
}) : maybeTx;
|
|
403
382
|
let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
|
|
404
383
|
let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
|
|
405
384
|
kind: Stdlib_Option.getOr(log.kind, ""),
|
|
@@ -407,7 +386,7 @@ function make(param) {
|
|
|
407
386
|
})));
|
|
408
387
|
let slotKey = instr.slot.toString();
|
|
409
388
|
let blockTime = blockTimeBySlot[slotKey];
|
|
410
|
-
let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.
|
|
389
|
+
let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeLogs ? maybeLogs : undefined, {
|
|
411
390
|
slot: instr.slot,
|
|
412
391
|
time: Stdlib_Option.getOr(blockTime, 0),
|
|
413
392
|
hash: ""
|
|
@@ -420,6 +399,7 @@ function make(param) {
|
|
|
420
399
|
blockNumber: instr.slot,
|
|
421
400
|
blockHash: "",
|
|
422
401
|
logIndex: synthLogIndex(instr),
|
|
402
|
+
transactionIndex: instr.transactionIndex,
|
|
423
403
|
payload: payload
|
|
424
404
|
});
|
|
425
405
|
}
|
|
@@ -436,6 +416,7 @@ function make(param) {
|
|
|
436
416
|
knownHeight: knownHeight,
|
|
437
417
|
blockHashes: blockHashes,
|
|
438
418
|
parsedQueueItems: parsedQueueItems,
|
|
419
|
+
transactionStore: Primitive_option.some(match[1]),
|
|
439
420
|
fromBlockQueried: fromBlock,
|
|
440
421
|
latestFetchedBlockNumber: highestSlot,
|
|
441
422
|
latestFetchedBlockTimestamp: latestBlockTime,
|
|
@@ -469,7 +450,8 @@ function make(param) {
|
|
|
469
450
|
maxNumBlocks: query_maxNumBlocks
|
|
470
451
|
};
|
|
471
452
|
Prometheus.SourceRequestCount.increment(name, chain, "getBlockHashes");
|
|
472
|
-
let
|
|
453
|
+
let match = await client.get(query);
|
|
454
|
+
let resp = match[0];
|
|
473
455
|
resp.data.blocks.forEach(b => {
|
|
474
456
|
blockDatas.push({
|
|
475
457
|
blockHash: b.blockhash,
|
|
@@ -549,9 +531,8 @@ export {
|
|
|
549
531
|
buildProgramSchemas,
|
|
550
532
|
parseDecoded,
|
|
551
533
|
toSvmInstruction,
|
|
552
|
-
toSvmTransaction,
|
|
553
|
-
toSvmTokenBalance,
|
|
554
534
|
probeRouter,
|
|
535
|
+
toQueryTxField,
|
|
555
536
|
make,
|
|
556
537
|
}
|
|
557
538
|
/* Prometheus Not a pure module */
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Binding to the Rust `TransactionStore` napi class. Transactions are kept in
|
|
2
|
+
// Rust as raw structs (their large fields never enter JS until read) keyed by
|
|
3
|
+
// (blockNumber, transactionIndex). One store lives per chain on `ChainState`;
|
|
4
|
+
// each fetch response contributes a page that is merged in. At batch
|
|
5
|
+
// preparation the selected fields are materialised in bulk, off the JS thread,
|
|
6
|
+
// in columnar form and zipped into plain JS objects on the main thread.
|
|
7
|
+
type t
|
|
8
|
+
|
|
9
|
+
@send external classNew: Core.transactionStoreCtor => t = "new"
|
|
10
|
+
let make = (): t => Core.getAddon().transactionStore->classNew
|
|
11
|
+
|
|
12
|
+
// Field-name → bit-index map from an ordered field-name array. The index is the
|
|
13
|
+
// field code shared with the Rust store (`EvmTxField`/`SvmTxField`).
|
|
14
|
+
let fieldCodes = (fields: array<string>): dict<int> => {
|
|
15
|
+
let codes = Dict.make()
|
|
16
|
+
fields->Array.forEachWithIndex((name, i) => codes->Dict.set(name, i))
|
|
17
|
+
codes
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let pow2: int => float = %raw(`c => Math.pow(2, c)`)
|
|
21
|
+
|
|
22
|
+
// Union of an ecosystem's selected transaction fields as a bitmask float (bit
|
|
23
|
+
// `code` set ⇔ selected). Built arithmetically to dodge 32-bit JS bitwise ops.
|
|
24
|
+
let mask = (eventConfigs: array<Internal.eventConfig>, ~codes: dict<int>): float => {
|
|
25
|
+
let selected = Utils.Set.make()
|
|
26
|
+
eventConfigs->Array.forEach(eventConfig =>
|
|
27
|
+
eventConfig.selectedTransactionFields->Utils.Set.forEach(name =>
|
|
28
|
+
switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
|
|
29
|
+
| Some(code) => selected->Utils.Set.add(code)->ignore
|
|
30
|
+
| None => ()
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
selected->Utils.Set.toArray->Array.reduce(0., (mask, code) => mask +. pow2(code))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Drain another store (a fetch-response page) into this one.
|
|
38
|
+
@send external merge: (t, t) => unit = "merge"
|
|
39
|
+
|
|
40
|
+
// Bulk-materialise the fields selected by `mask` (one bit per field code) for
|
|
41
|
+
// the given transactions, off the JS thread. Result is aligned with the input.
|
|
42
|
+
@send
|
|
43
|
+
external materialize: (
|
|
44
|
+
t,
|
|
45
|
+
~blockNumbers: array<int>,
|
|
46
|
+
~transactionIndices: array<int>,
|
|
47
|
+
~mask: float,
|
|
48
|
+
) => promise<array<Internal.eventTransaction>> = "materialize"
|
|
49
|
+
|
|
50
|
+
// Drop transactions for blocks at or below the given block (already processed).
|
|
51
|
+
@send external prune: (t, int) => unit = "prune"
|
|
52
|
+
|
|
53
|
+
// Drop transactions for blocks above the given block (rolled back).
|
|
54
|
+
@send external rollback: (t, int) => unit = "rollback"
|
|
55
|
+
|
|
56
|
+
// Materialise the mask-selected fields for the store-backed items and write the
|
|
57
|
+
// resulting transaction onto each item's payload. Items that already carry an
|
|
58
|
+
// inline transaction (RPC/simulate/Fuel) are skipped. Store-backed items always
|
|
59
|
+
// get a transaction object — the selected fields, or `{}` when the chain
|
|
60
|
+
// selected none — so `event.transaction` is never `undefined` (matching the
|
|
61
|
+
// inline sources). Deduped per (blockNumber, transactionIndex).
|
|
62
|
+
let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: float) => {
|
|
63
|
+
// Store-backed items arrive in (block, logIndex) order, and a transaction's
|
|
64
|
+
// logs are contiguous within a block, so events sharing a (blockNumber,
|
|
65
|
+
// transactionIndex) are adjacent. Group them by extending the current run
|
|
66
|
+
// rather than hashing a string key per item. A key recurring non-adjacently
|
|
67
|
+
// just splits into two groups (one redundant decode) — never incorrect.
|
|
68
|
+
let blockNumbers = []
|
|
69
|
+
let transactionIndices = []
|
|
70
|
+
let payloadGroups = []
|
|
71
|
+
|
|
72
|
+
items->Array.forEach(item =>
|
|
73
|
+
switch item {
|
|
74
|
+
| Internal.Event(_) =>
|
|
75
|
+
let eventItem = item->Internal.castUnsafeEventItem
|
|
76
|
+
switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
|
|
77
|
+
| Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
|
|
78
|
+
| None =>
|
|
79
|
+
let {blockNumber, transactionIndex} = eventItem
|
|
80
|
+
let last = payloadGroups->Array.length - 1
|
|
81
|
+
if (
|
|
82
|
+
last >= 0 &&
|
|
83
|
+
blockNumbers->Array.getUnsafe(last) == blockNumber &&
|
|
84
|
+
transactionIndices->Array.getUnsafe(last) == transactionIndex
|
|
85
|
+
) {
|
|
86
|
+
payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
|
|
87
|
+
} else {
|
|
88
|
+
blockNumbers->Array.push(blockNumber)
|
|
89
|
+
transactionIndices->Array.push(transactionIndex)
|
|
90
|
+
payloadGroups->Array.push([eventItem.payload])
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
| Internal.Block(_) => ()
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if payloadGroups->Utils.Array.notEmpty {
|
|
98
|
+
// A zero mask selects no fields, so there's nothing to decode — each
|
|
99
|
+
// store-backed item still gets an empty transaction object, so
|
|
100
|
+
// `event.transaction` is never undefined (matching the inline sources).
|
|
101
|
+
let txs = if mask == 0. {
|
|
102
|
+
payloadGroups->Array.map((_): Internal.eventTransaction => %raw(`{}`))
|
|
103
|
+
} else {
|
|
104
|
+
await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
|
|
105
|
+
}
|
|
106
|
+
payloadGroups->Array.forEachWithIndex((payloads, i) => {
|
|
107
|
+
let tx = txs->Array.getUnsafe(i)
|
|
108
|
+
payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Core from "../Core.res.mjs";
|
|
4
|
+
import * as Utils from "../Utils.res.mjs";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
+
|
|
7
|
+
function make() {
|
|
8
|
+
return Core.getAddon().TransactionStore.new();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function fieldCodes(fields) {
|
|
12
|
+
let codes = {};
|
|
13
|
+
fields.forEach((name, i) => {
|
|
14
|
+
codes[name] = i;
|
|
15
|
+
});
|
|
16
|
+
return codes;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let pow2 = (c => Math.pow(2, c));
|
|
20
|
+
|
|
21
|
+
function mask(eventConfigs, codes) {
|
|
22
|
+
let selected = new Set();
|
|
23
|
+
eventConfigs.forEach(eventConfig => {
|
|
24
|
+
eventConfig.selectedTransactionFields.forEach(name => {
|
|
25
|
+
let code = codes[name];
|
|
26
|
+
if (code !== undefined) {
|
|
27
|
+
selected.add(code);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return Stdlib_Array.reduce(Array.from(selected), 0, (mask, code) => mask + pow2(code));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function materializeItems(store, items, mask) {
|
|
36
|
+
let blockNumbers = [];
|
|
37
|
+
let transactionIndices = [];
|
|
38
|
+
let payloadGroups = [];
|
|
39
|
+
items.forEach(item => {
|
|
40
|
+
if (item.kind !== 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let match = item.payload.transaction;
|
|
44
|
+
if (!(match == null)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let transactionIndex = item.transactionIndex;
|
|
48
|
+
let blockNumber = item.blockNumber;
|
|
49
|
+
let last = payloadGroups.length - 1 | 0;
|
|
50
|
+
if (last >= 0 && blockNumbers[last] === blockNumber && transactionIndices[last] === transactionIndex) {
|
|
51
|
+
payloadGroups[last].push(item.payload);
|
|
52
|
+
} else {
|
|
53
|
+
blockNumbers.push(blockNumber);
|
|
54
|
+
transactionIndices.push(transactionIndex);
|
|
55
|
+
payloadGroups.push([item.payload]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (!Utils.$$Array.notEmpty(payloadGroups)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let txs = mask === 0 ? payloadGroups.map(param => ({})) : await store.materialize(blockNumbers, transactionIndices, mask);
|
|
62
|
+
payloadGroups.forEach((payloads, i) => {
|
|
63
|
+
let tx = txs[i];
|
|
64
|
+
payloads.forEach(payload => {
|
|
65
|
+
payload.transaction = tx;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
make,
|
|
72
|
+
fieldCodes,
|
|
73
|
+
pow2,
|
|
74
|
+
mask,
|
|
75
|
+
materializeItems,
|
|
76
|
+
}
|
|
77
|
+
/* Core Not a pure module */
|