envio 3.7.0 → 3.8.0
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/index.d.ts +331 -142
- package/package.json +6 -6
- package/src/ChainState.res +9 -1
- package/src/ChainState.res.mjs +6 -1
- package/src/Config.res +19 -10
- package/src/Config.res.mjs +17 -7
- package/src/Envio.res +54 -76
- package/src/EventConfigBuilder.res +159 -30
- package/src/EventConfigBuilder.res.mjs +120 -24
- package/src/HandlerRegister.res +20 -11
- package/src/HandlerRegister.res.mjs +22 -5
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +1 -6
- package/src/InMemoryStore.res.mjs +1 -3
- package/src/Internal.res +33 -13
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +2 -5
- package/src/MemoryStorage.res +40 -8
- package/src/MemoryStorage.res.mjs +38 -16
- package/src/Persistence.res +4 -2
- package/src/PgStorage.res +6 -1
- package/src/PgStorage.res.mjs +1 -1
- package/src/SimulateItems.res +261 -9
- package/src/SimulateItems.res.mjs +218 -47
- package/src/TestIndexer.res +26 -10
- package/src/TestIndexer.res.mjs +34 -12
- package/src/bindings/ClickHouse.res +33 -6
- package/src/bindings/ClickHouse.res.mjs +25 -4
- package/src/db/InternalTable.res +11 -0
- package/src/db/InternalTable.res.mjs +7 -0
- package/src/sources/EvmHyperSyncSource.res +3 -2
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +34 -7
- package/src/sources/Svm.res.mjs +32 -4
- package/src/sources/SvmHyperSyncClient.res +10 -12
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +91 -34
- package/src/sources/SvmHyperSyncSource.res.mjs +77 -37
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/svm.schema.json +27 -78
package/src/Internal.res.mjs
CHANGED
|
@@ -88,20 +88,10 @@ let allSvmTransactionFields = [
|
|
|
88
88
|
"accountKeys",
|
|
89
89
|
"recentBlockhash",
|
|
90
90
|
"version",
|
|
91
|
-
"
|
|
92
|
-
"
|
|
91
|
+
"allSignatures",
|
|
92
|
+
"accountActivities"
|
|
93
93
|
];
|
|
94
94
|
|
|
95
|
-
let svmTransactionFieldSchema = S$RescriptSchema.$$enum(allSvmTransactionFields);
|
|
96
|
-
|
|
97
|
-
let allSvmBlockFields = [
|
|
98
|
-
"height",
|
|
99
|
-
"parentSlot",
|
|
100
|
-
"parentHash"
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
let svmBlockFieldSchema = S$RescriptSchema.$$enum(allSvmBlockFields);
|
|
104
|
-
|
|
105
95
|
let evmNullableBlockFields = new Set([
|
|
106
96
|
"nonce",
|
|
107
97
|
"difficulty",
|
|
@@ -141,10 +131,16 @@ let evmNullableTransactionFields = new Set([
|
|
|
141
131
|
"type"
|
|
142
132
|
]);
|
|
143
133
|
|
|
144
|
-
function makeFieldSelection(blockFields, transactionFields, blockMaskFn, transactionMaskFn) {
|
|
134
|
+
function makeFieldSelection(blockFields, transactionFields, instructionFieldsOpt, accountActivityFieldsOpt, logFieldsOpt, blockMaskFn, transactionMaskFn) {
|
|
135
|
+
let instructionFields = instructionFieldsOpt !== undefined ? Primitive_option.valFromOption(instructionFieldsOpt) : new Set();
|
|
136
|
+
let accountActivityFields = accountActivityFieldsOpt !== undefined ? Primitive_option.valFromOption(accountActivityFieldsOpt) : new Set();
|
|
137
|
+
let logFields = logFieldsOpt !== undefined ? Primitive_option.valFromOption(logFieldsOpt) : new Set();
|
|
145
138
|
return {
|
|
146
139
|
blockFields: blockFields,
|
|
147
140
|
transactionFields: transactionFields,
|
|
141
|
+
instructionFields: instructionFields,
|
|
142
|
+
accountActivityFields: accountActivityFields,
|
|
143
|
+
logFields: logFields,
|
|
148
144
|
blockMask: blockMaskFn(blockFields),
|
|
149
145
|
transactionMask: transactionMaskFn(transactionFields)
|
|
150
146
|
};
|
|
@@ -162,6 +158,9 @@ function unionFieldSelection(a, b) {
|
|
|
162
158
|
return {
|
|
163
159
|
blockFields: unionFields(a.blockFields, b.blockFields),
|
|
164
160
|
transactionFields: unionFields(a.transactionFields, b.transactionFields),
|
|
161
|
+
instructionFields: unionFields(a.instructionFields, b.instructionFields),
|
|
162
|
+
accountActivityFields: unionFields(a.accountActivityFields, b.accountActivityFields),
|
|
163
|
+
logFields: unionFields(a.logFields, b.logFields),
|
|
165
164
|
blockMask: FieldMask.orMask(a.blockMask, b.blockMask),
|
|
166
165
|
transactionMask: FieldMask.orMask(a.transactionMask, b.transactionMask)
|
|
167
166
|
};
|
|
@@ -329,9 +328,6 @@ export {
|
|
|
329
328
|
allEvmTransactionFields,
|
|
330
329
|
evmTransactionFieldSchema,
|
|
331
330
|
allSvmTransactionFields,
|
|
332
|
-
svmTransactionFieldSchema,
|
|
333
|
-
allSvmBlockFields,
|
|
334
|
-
svmBlockFieldSchema,
|
|
335
331
|
evmNullableBlockFields,
|
|
336
332
|
evmNullableTransactionFields,
|
|
337
333
|
makeFieldSelection,
|
package/src/Main.res
CHANGED
|
@@ -239,7 +239,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
239
239
|
"event": unknown,
|
|
240
240
|
"wildcard": option<bool>,
|
|
241
241
|
"where": option<JSON.t>,
|
|
242
|
-
"fields": option<
|
|
242
|
+
"fields": option<unknown>,
|
|
243
243
|
}
|
|
244
244
|
)
|
|
245
245
|
// Detect format: if "contract" is a string, it's the TS format
|
|
@@ -295,7 +295,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
295
295
|
"program": unknown,
|
|
296
296
|
"instruction": unknown,
|
|
297
297
|
"where": option<JSON.t>,
|
|
298
|
-
"fields": option<
|
|
298
|
+
"fields": option<unknown>,
|
|
299
299
|
}
|
|
300
300
|
)
|
|
301
301
|
let (programName, instructionName) = if typeof(raw["program"]) === #string {
|
|
@@ -308,9 +308,6 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
308
308
|
(inst["contract"], inst["_0"])
|
|
309
309
|
}
|
|
310
310
|
let where = raw["where"]
|
|
311
|
-
// SVM takes its selection from the config, so `fields` is carried through
|
|
312
|
-
// only to be rejected by the registration — dropping it here would leave a
|
|
313
|
-
// plain-JS caller with a silently ignored option.
|
|
314
311
|
let fields = raw["fields"]
|
|
315
312
|
let eventOptions: option<Internal.eventOptions<_>> = switch (where, fields) {
|
|
316
313
|
| (None, None) => None
|
package/src/MemoryStorage.res
CHANGED
|
@@ -232,11 +232,24 @@ let toInitialState = (state: t, ~cleanRun): Persistence.initialState => {
|
|
|
232
232
|
let handleLoad = (state: t, ~tableName: string, ~filter: EntityFilter.t): array<
|
|
233
233
|
Internal.entity,
|
|
234
234
|
> => {
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
235
|
+
// Effect caches (`envio_effect_<name>`) are loaded through the same call, and
|
|
236
|
+
// they have no entity config — they're served from the cache `writeBatch`
|
|
237
|
+
// filled, so a resumed indexer reuses cached outputs instead of recomputing
|
|
238
|
+
// them the way Postgres would.
|
|
238
239
|
switch state.entityConfigs->Dict.get(tableName) {
|
|
239
|
-
| None =>
|
|
240
|
+
| None =>
|
|
241
|
+
switch state.effectCache->Dict.get(tableName) {
|
|
242
|
+
| None => []
|
|
243
|
+
| Some(cacheDict) =>
|
|
244
|
+
cacheDict
|
|
245
|
+
->Dict.valuesToArray
|
|
246
|
+
->Array.filter(item =>
|
|
247
|
+
filter->EntityFilter.matches(
|
|
248
|
+
~entity=item->(Utils.magic: Internal.effectCacheItem => dict<EntityFilter.FieldValue.t>),
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
->(Utils.magic: array<Internal.effectCacheItem> => array<Internal.entity>)
|
|
252
|
+
}
|
|
240
253
|
| Some(entityConfig) =>
|
|
241
254
|
let entityDict = state.entities->Dict.get(entityConfig.name)->Option.getOr(Dict.make())
|
|
242
255
|
let matched =
|
|
@@ -320,6 +333,17 @@ let writeBatch = (
|
|
|
320
333
|
| None => ()
|
|
321
334
|
}
|
|
322
335
|
|
|
336
|
+
// The rollback diff restates what the reverted state already is, so it is not
|
|
337
|
+
// a change history should record — and an id it touches needs no backfill
|
|
338
|
+
// either, because the diff already carries its reverted value. Postgres draws
|
|
339
|
+
// the same line, keyed on the diff's checkpoint id.
|
|
340
|
+
let diffCheckpointId = rollback->Option.map(({diffCheckpointId}) => diffCheckpointId)
|
|
341
|
+
let isDiff = (change: Change.t<Internal.entity>) =>
|
|
342
|
+
switch diffCheckpointId {
|
|
343
|
+
| Some(diffCheckpointId) => change->Change.getCheckpointId === diffCheckpointId
|
|
344
|
+
| None => false
|
|
345
|
+
}
|
|
346
|
+
|
|
323
347
|
updatedEntities->Array.forEach(({entityConfig, scope, changes}: Persistence.updatedEntity) => {
|
|
324
348
|
let entityDict = state->getEntityDict(~name=entityConfig.name)
|
|
325
349
|
let historyRows = state->getHistory(~name=entityConfig.name)
|
|
@@ -327,9 +351,17 @@ let writeBatch = (
|
|
|
327
351
|
// onto the stored entity the same way the Postgres write path does.
|
|
328
352
|
let chainIdField = entityConfig.table->Table.getChainIdField
|
|
329
353
|
|
|
354
|
+
let idsWithDiff = Utils.Set.make()
|
|
355
|
+
changes->Array.forEach(change =>
|
|
356
|
+
if isDiff(change) {
|
|
357
|
+
idsWithDiff->Utils.Set.add(change->Change.getEntityId->EntityId.toKey)->ignore
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
|
|
330
361
|
changes->Array.forEach(change => {
|
|
331
362
|
let entityId = change->Change.getEntityId
|
|
332
|
-
|
|
363
|
+
let shouldSaveChangeHistory = shouldSaveHistory && !isDiff(change)
|
|
364
|
+
if shouldSaveHistory && !(idsWithDiff->Utils.Set.has(entityId->EntityId.toKey)) {
|
|
333
365
|
state->backfillHistory(~entityConfig, ~scope, ~entityId, ~rows=historyRows)
|
|
334
366
|
}
|
|
335
367
|
switch change {
|
|
@@ -340,7 +372,7 @@ let writeBatch = (
|
|
|
340
372
|
| _ => entity
|
|
341
373
|
}
|
|
342
374
|
entityDict->Dict.set(rowKey(~scope, ~entityId), storedEntity)
|
|
343
|
-
if
|
|
375
|
+
if shouldSaveChangeHistory {
|
|
344
376
|
historyRows
|
|
345
377
|
->Array.push({
|
|
346
378
|
entityId,
|
|
@@ -353,7 +385,7 @@ let writeBatch = (
|
|
|
353
385
|
}
|
|
354
386
|
| Delete({checkpointId}) =>
|
|
355
387
|
entityDict->Utils.Dict.deleteInPlace(rowKey(~scope, ~entityId))
|
|
356
|
-
if
|
|
388
|
+
if shouldSaveChangeHistory {
|
|
357
389
|
historyRows
|
|
358
390
|
->Array.push({
|
|
359
391
|
entityId,
|
|
@@ -472,7 +504,7 @@ let getRollbackData = (
|
|
|
472
504
|
| Some({action: DELETE, entityId, scope}) =>
|
|
473
505
|
removals->Array.push({Persistence.entityId, scope})->ignore
|
|
474
506
|
| Some({action: SET, entity: Some(entity)}) =>
|
|
475
|
-
restored->Array.push(entity
|
|
507
|
+
restored->Array.push(entity)->ignore
|
|
476
508
|
| Some({action: SET, entity: None}) => ()
|
|
477
509
|
}
|
|
478
510
|
})
|
|
@@ -187,20 +187,25 @@ function toInitialState(state, cleanRun) {
|
|
|
187
187
|
|
|
188
188
|
function handleLoad(state, tableName, filter) {
|
|
189
189
|
let entityConfig = state.entityConfigs[tableName];
|
|
190
|
-
if (entityConfig
|
|
191
|
-
|
|
190
|
+
if (entityConfig !== undefined) {
|
|
191
|
+
let entityDict = Stdlib_Option.getOr(state.entities[entityConfig.name], {});
|
|
192
|
+
let matched = Object.values(entityDict).filter(entity => EntityFilter.matches(filter, entity));
|
|
193
|
+
let field = Table.getChainIdField(entityConfig.table);
|
|
194
|
+
if (field !== undefined) {
|
|
195
|
+
return matched.map(entity => {
|
|
196
|
+
let copy = Utils.Dict.shallowCopy(entity);
|
|
197
|
+
Utils.Dict.deleteInPlace(copy, field.fieldName);
|
|
198
|
+
return copy;
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
return matched;
|
|
202
|
+
}
|
|
192
203
|
}
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (field !== undefined) {
|
|
197
|
-
return matched.map(entity => {
|
|
198
|
-
let copy = Utils.Dict.shallowCopy(entity);
|
|
199
|
-
Utils.Dict.deleteInPlace(copy, field.fieldName);
|
|
200
|
-
return copy;
|
|
201
|
-
});
|
|
204
|
+
let cacheDict = state.effectCache[tableName];
|
|
205
|
+
if (cacheDict !== undefined) {
|
|
206
|
+
return Object.values(cacheDict).filter(item => EntityFilter.matches(filter, item));
|
|
202
207
|
} else {
|
|
203
|
-
return
|
|
208
|
+
return [];
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
|
|
@@ -234,15 +239,32 @@ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedE
|
|
|
234
239
|
if (rollback !== undefined) {
|
|
235
240
|
applyRollback(state, rollback.targetCheckpointId);
|
|
236
241
|
}
|
|
242
|
+
let diffCheckpointId = Stdlib_Option.map(rollback, param => param.diffCheckpointId);
|
|
243
|
+
let isDiff = change => {
|
|
244
|
+
if (diffCheckpointId !== undefined) {
|
|
245
|
+
return change.checkpointId === diffCheckpointId;
|
|
246
|
+
} else {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
237
250
|
updatedEntities.forEach(param => {
|
|
251
|
+
let changes = param.changes;
|
|
238
252
|
let scope = param.scope;
|
|
239
253
|
let entityConfig = param.entityConfig;
|
|
240
254
|
let entityDict = getEntityDict(state, entityConfig.name);
|
|
241
255
|
let historyRows = getHistory(state, entityConfig.name);
|
|
242
256
|
let chainIdField = Table.getChainIdField(entityConfig.table);
|
|
243
|
-
|
|
257
|
+
let idsWithDiff = new Set();
|
|
258
|
+
changes.forEach(change => {
|
|
259
|
+
if (isDiff(change)) {
|
|
260
|
+
idsWithDiff.add(EntityId.toKey(change.entityId));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
changes.forEach(change => {
|
|
244
265
|
let entityId = change.entityId;
|
|
245
|
-
|
|
266
|
+
let shouldSaveChangeHistory = shouldSaveHistory && !isDiff(change);
|
|
267
|
+
if (shouldSaveHistory && !idsWithDiff.has(EntityId.toKey(entityId))) {
|
|
246
268
|
backfillHistory(state, entityConfig, scope, entityId, historyRows);
|
|
247
269
|
}
|
|
248
270
|
if (change.type === "SET") {
|
|
@@ -250,7 +272,7 @@ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedE
|
|
|
250
272
|
let match = Internal.chainScopeChainId(scope);
|
|
251
273
|
let storedEntity = chainIdField !== undefined && match !== undefined ? Internal.stampChainId(entity, chainIdField.fieldName, Primitive_option.valFromOption(match)) : entity;
|
|
252
274
|
entityDict[rowKey(scope, entityId)] = storedEntity;
|
|
253
|
-
if (
|
|
275
|
+
if (shouldSaveChangeHistory) {
|
|
254
276
|
historyRows.push({
|
|
255
277
|
entityId: entityId,
|
|
256
278
|
scope: scope,
|
|
@@ -264,7 +286,7 @@ function writeBatch(state, batch, rollback, isInReorgThreshold, config, updatedE
|
|
|
264
286
|
}
|
|
265
287
|
}
|
|
266
288
|
Utils.Dict.deleteInPlace(entityDict, rowKey(scope, entityId));
|
|
267
|
-
if (
|
|
289
|
+
if (shouldSaveChangeHistory) {
|
|
268
290
|
historyRows.push({
|
|
269
291
|
entityId: entityId,
|
|
270
292
|
scope: scope,
|
package/src/Persistence.res
CHANGED
|
@@ -147,11 +147,13 @@ type storage = {
|
|
|
147
147
|
"new_progress_block_number": int,
|
|
148
148
|
}>,
|
|
149
149
|
>,
|
|
150
|
-
//
|
|
150
|
+
// Rollback data for an entity, as decoded entities rather than storage rows:
|
|
151
|
+
// only the storage knows how it encoded them, so each one decodes its own
|
|
152
|
+
// before handing them back.
|
|
151
153
|
getRollbackData: (
|
|
152
154
|
~entityConfig: Internal.entityConfig,
|
|
153
155
|
~rollbackTargetCheckpointId: Internal.checkpointId,
|
|
154
|
-
) => promise<(array<rollbackRemoval>, array<
|
|
156
|
+
) => promise<(array<rollbackRemoval>, array<Internal.entity>)>,
|
|
155
157
|
// Write batch to storage
|
|
156
158
|
writeBatch: (
|
|
157
159
|
~batch: Batch.t,
|
package/src/PgStorage.res
CHANGED
|
@@ -2308,7 +2308,12 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2308
2308
|
}
|
|
2309
2309
|
})
|
|
2310
2310
|
|
|
2311
|
-
(
|
|
2311
|
+
(
|
|
2312
|
+
removals,
|
|
2313
|
+
restoredEntitiesResult
|
|
2314
|
+
->S.parseOrThrow(entityConfig.table->Table.pgRowsSchema)
|
|
2315
|
+
->(Utils.magic: array<unknown> => array<Internal.entity>),
|
|
2316
|
+
)
|
|
2312
2317
|
}
|
|
2313
2318
|
|
|
2314
2319
|
let writeBatchMethod = async (
|
package/src/PgStorage.res.mjs
CHANGED
|
@@ -1464,7 +1464,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
|
|
|
1464
1464
|
});
|
|
1465
1465
|
return [
|
|
1466
1466
|
removals,
|
|
1467
|
-
restoredEntitiesResult
|
|
1467
|
+
S$RescriptSchema.parseOrThrow(restoredEntitiesResult, Table.pgRowsSchema(entityConfig.table))
|
|
1468
1468
|
];
|
|
1469
1469
|
};
|
|
1470
1470
|
let writeBatchMethod = async (batch, rollback, isInReorgThreshold, config, allEntities, updatedEffectsCache, updatedEntities, chainMetaData, onWrite) => {
|
package/src/SimulateItems.res
CHANGED
|
@@ -174,6 +174,8 @@ type rawSimulateItem
|
|
|
174
174
|
|
|
175
175
|
@get external getContract: rawSimulateItem => option<string> = "contract"
|
|
176
176
|
@get external getEvent: rawSimulateItem => option<string> = "event"
|
|
177
|
+
@get external getProgram: rawSimulateItem => option<string> = "program"
|
|
178
|
+
@get external getInstruction: rawSimulateItem => option<string> = "instruction"
|
|
177
179
|
|
|
178
180
|
let findEventConfig = (~config: Config.t, ~contractName: string, ~eventName: string) => {
|
|
179
181
|
let found = ref(None)
|
|
@@ -240,18 +242,71 @@ let deriveSrcAddress = (
|
|
|
240
242
|
}
|
|
241
243
|
}
|
|
242
244
|
|
|
245
|
+
type parseResult = {
|
|
246
|
+
items: array<Internal.item>,
|
|
247
|
+
transactionStore: option<TransactionStore.t>,
|
|
248
|
+
blockStore: option<BlockStore.t>,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
type svmSimActivity = {
|
|
252
|
+
address: string,
|
|
253
|
+
transactionAccountIndex?: int,
|
|
254
|
+
isSigner?: bool,
|
|
255
|
+
isWritable?: bool,
|
|
256
|
+
lamports?: {pre?: bigint, post?: bigint},
|
|
257
|
+
token?: {
|
|
258
|
+
mint?: string,
|
|
259
|
+
owner?: string,
|
|
260
|
+
decimals?: int,
|
|
261
|
+
preAmount?: bigint,
|
|
262
|
+
postAmount?: bigint,
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
type svmSimTransaction = {
|
|
267
|
+
transactionIndex?: int,
|
|
268
|
+
signature?: string,
|
|
269
|
+
allSignatures?: array<string>,
|
|
270
|
+
feePayer?: string,
|
|
271
|
+
success?: bool,
|
|
272
|
+
err?: string,
|
|
273
|
+
fee?: bigint,
|
|
274
|
+
computeUnitsConsumed?: bigint,
|
|
275
|
+
accountKeys?: array<string>,
|
|
276
|
+
recentBlockhash?: string,
|
|
277
|
+
version?: string,
|
|
278
|
+
accountActivities?: array<svmSimActivity>,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let liveRegistrationsFor = (
|
|
282
|
+
~config: Config.t,
|
|
283
|
+
~chainId: ChainId.t,
|
|
284
|
+
~eventConfig: Internal.eventConfig,
|
|
285
|
+
) =>
|
|
286
|
+
HandlerRegister.getSimulateOnEventRegistrations(
|
|
287
|
+
~config,
|
|
288
|
+
~chainId,
|
|
289
|
+
~eventConfig,
|
|
290
|
+
)->Array.filter(reg =>
|
|
291
|
+
(reg.handler->Option.isSome || reg.contractRegister->Option.isSome) &&
|
|
292
|
+
!HandlerRegister.isDroppedByWhere(~config, reg)
|
|
293
|
+
)
|
|
294
|
+
|
|
243
295
|
let parse = (
|
|
244
296
|
~simulateItems: array<JSON.t>,
|
|
245
297
|
~config: Config.t,
|
|
246
298
|
~chainConfig: Config.chain,
|
|
247
299
|
~onEventRegistrations: array<Internal.onEventRegistration>,
|
|
248
|
-
):
|
|
300
|
+
): parseResult => {
|
|
249
301
|
let chainId = chainConfig.id
|
|
250
302
|
let startBlock = chainConfig.startBlock
|
|
251
303
|
let currentBlock = ref(startBlock)
|
|
252
304
|
let currentLogIndex = ref(0)
|
|
253
305
|
|
|
254
306
|
let items = []
|
|
307
|
+
let svmTxs: array<TransactionStore.svmTxInput> = []
|
|
308
|
+
let svmActivities: array<TransactionStore.svmActivityInput> = []
|
|
309
|
+
let svmBlocks: array<BlockStore.inputBlock> = []
|
|
255
310
|
// Coordinate "block:logIndex" -> the index of the first item that claimed it,
|
|
256
311
|
// used to reject two items resolving to the same (block, logIndex).
|
|
257
312
|
let seenCoordinates = Dict.make()
|
|
@@ -259,8 +314,189 @@ let parse = (
|
|
|
259
314
|
simulateItems->Array.forEachWithIndex((rawJson, itemIndex) => {
|
|
260
315
|
let raw = rawJson->(Utils.magic: JSON.t => rawSimulateItem)
|
|
261
316
|
|
|
262
|
-
switch (raw->
|
|
263
|
-
| (Some(
|
|
317
|
+
switch (config.ecosystem.name, raw->getProgram, raw->getInstruction) {
|
|
318
|
+
| (Svm, Some(programName), Some(instructionName)) =>
|
|
319
|
+
let eventConfig = switch findEventConfig(
|
|
320
|
+
~config,
|
|
321
|
+
~contractName=programName,
|
|
322
|
+
~eventName=instructionName,
|
|
323
|
+
) {
|
|
324
|
+
| Some(ec) => ec
|
|
325
|
+
| None =>
|
|
326
|
+
JsError.throwWithMessage(
|
|
327
|
+
`simulate: Instruction "${instructionName}" not found on program "${programName}". ` ++ `Check that the program and instruction names match your config.yaml.`,
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
let svmEventConfig =
|
|
331
|
+
eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
332
|
+
let item = rawJson->(Utils.magic: JSON.t => Envio.svmSimulateItem)
|
|
333
|
+
let rawItem = rawJson->(Utils.magic: JSON.t => {..})
|
|
334
|
+
let blockJson: option<JSON.t> =
|
|
335
|
+
rawItem["block"]->(Utils.magic: 'a => Nullable.t<JSON.t>)->Nullable.toOption
|
|
336
|
+
let slot = switch item.slot {
|
|
337
|
+
| Some(s) => s
|
|
338
|
+
| None =>
|
|
339
|
+
switch blockJson {
|
|
340
|
+
| Some(bj) =>
|
|
341
|
+
switch (bj->(Utils.magic: JSON.t => dict<JSON.t>))->Dict.get("slot") {
|
|
342
|
+
| Some(v) =>
|
|
343
|
+
v->(Utils.magic: JSON.t => Nullable.t<int>)->Nullable.toOption->Option.getOr(
|
|
344
|
+
currentBlock.contents,
|
|
345
|
+
)
|
|
346
|
+
| None => currentBlock.contents
|
|
347
|
+
}
|
|
348
|
+
| None => currentBlock.contents
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
currentBlock := slot
|
|
352
|
+
let transaction = switch item.transaction {
|
|
353
|
+
| Some(tx) => tx->(Utils.magic: unknown => svmSimTransaction)
|
|
354
|
+
| None => {}
|
|
355
|
+
}
|
|
356
|
+
let transactionIndex = transaction.transactionIndex->Option.getOr(0)
|
|
357
|
+
let path = item.path->Option.getOr([0])
|
|
358
|
+
let programId =
|
|
359
|
+
item.programId->Option.getOr(svmEventConfig.programId->SvmTypes.Pubkey.toString)
|
|
360
|
+
let accountArguments = switch item.accountArguments {
|
|
361
|
+
| Some(args) => args
|
|
362
|
+
| None =>
|
|
363
|
+
switch item.accounts {
|
|
364
|
+
| Some(named) =>
|
|
365
|
+
svmEventConfig.accounts->Array.map(name =>
|
|
366
|
+
switch named->Dict.get(name) {
|
|
367
|
+
| Some({address}) => address
|
|
368
|
+
| None => ""
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
| None => []
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
let data = item.data->Option.getOr(svmEventConfig.discriminator->Option.getOr("0x"))
|
|
375
|
+
let decoded = item.args->Option.map(args => (
|
|
376
|
+
{
|
|
377
|
+
SvmHyperSyncClient.ResponseTypes.name: instructionName,
|
|
378
|
+
argsJson: args->JSON.stringify,
|
|
379
|
+
accountsJson: "{}",
|
|
380
|
+
extraAccounts: [],
|
|
381
|
+
}: SvmHyperSyncClient.ResponseTypes.decodedInstruction
|
|
382
|
+
))
|
|
383
|
+
let logs = item.logs->Option.map(logs =>
|
|
384
|
+
logs->Array.map((log): SvmHyperSyncClient.EventItems.log => {
|
|
385
|
+
kind: ?log.kind,
|
|
386
|
+
message: ?log.message,
|
|
387
|
+
})
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
let liveRegistrations = liveRegistrationsFor(~config, ~chainId, ~eventConfig)
|
|
391
|
+
if liveRegistrations->Utils.Array.isEmpty {
|
|
392
|
+
JsError.throwWithMessage(
|
|
393
|
+
`simulate: no handler runs for instruction "${instructionName}" on program "${programName}". Register a handler with indexer.onInstruction before simulating it.`,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
svmTxs
|
|
398
|
+
->Array.push({
|
|
399
|
+
slot,
|
|
400
|
+
transactionIndex,
|
|
401
|
+
signature: ?transaction.signature,
|
|
402
|
+
allSignatures: ?transaction.allSignatures,
|
|
403
|
+
feePayer: ?transaction.feePayer,
|
|
404
|
+
success: ?transaction.success,
|
|
405
|
+
err: ?transaction.err,
|
|
406
|
+
fee: ?transaction.fee,
|
|
407
|
+
computeUnitsConsumed: ?transaction.computeUnitsConsumed,
|
|
408
|
+
accountKeys: ?transaction.accountKeys,
|
|
409
|
+
recentBlockhash: ?transaction.recentBlockhash,
|
|
410
|
+
version: ?transaction.version,
|
|
411
|
+
})
|
|
412
|
+
->ignore
|
|
413
|
+
switch transaction.accountActivities {
|
|
414
|
+
| Some(rows) =>
|
|
415
|
+
rows->Array.forEach(activity =>
|
|
416
|
+
svmActivities
|
|
417
|
+
->Array.push({
|
|
418
|
+
TransactionStore.slot,
|
|
419
|
+
transactionIndex,
|
|
420
|
+
account: activity.address,
|
|
421
|
+
accountIndex: ?activity.transactionAccountIndex,
|
|
422
|
+
isSigner: ?activity.isSigner,
|
|
423
|
+
isWritable: ?activity.isWritable,
|
|
424
|
+
preBalance: ?activity.lamports->Option.flatMap(l => l.pre),
|
|
425
|
+
postBalance: ?activity.lamports->Option.flatMap(l => l.post),
|
|
426
|
+
mint: ?activity.token->Option.flatMap(t => t.mint),
|
|
427
|
+
owner: ?activity.token->Option.flatMap(t => t.owner),
|
|
428
|
+
decimals: ?activity.token->Option.flatMap(t => t.decimals),
|
|
429
|
+
preAmount: ?activity.token->Option.flatMap(t => t.preAmount),
|
|
430
|
+
postAmount: ?activity.token->Option.flatMap(t => t.postAmount),
|
|
431
|
+
})
|
|
432
|
+
->ignore
|
|
433
|
+
)
|
|
434
|
+
| None => ()
|
|
435
|
+
}
|
|
436
|
+
let blockTime = switch item.block {
|
|
437
|
+
| Some(block) => block.time
|
|
438
|
+
| None => None
|
|
439
|
+
}
|
|
440
|
+
let blockHash = switch item.block {
|
|
441
|
+
| Some(block) => block.hash
|
|
442
|
+
| None => None
|
|
443
|
+
}
|
|
444
|
+
svmBlocks
|
|
445
|
+
->Array.push({
|
|
446
|
+
blockNumber: slot,
|
|
447
|
+
?blockHash,
|
|
448
|
+
blockTimestamp: ?blockTime,
|
|
449
|
+
})
|
|
450
|
+
->ignore
|
|
451
|
+
|
|
452
|
+
liveRegistrations->Array.forEach(reg => {
|
|
453
|
+
let onEventRegistrationIndex = onEventRegistrations->Array.length
|
|
454
|
+
let onEventRegistration = {...reg, index: onEventRegistrationIndex}
|
|
455
|
+
onEventRegistrations->Array.push(onEventRegistration)->ignore
|
|
456
|
+
let payload = SvmHyperSyncSource.toSvmInstruction(
|
|
457
|
+
{
|
|
458
|
+
onEventRegistrationIndex,
|
|
459
|
+
slot,
|
|
460
|
+
transactionIndex,
|
|
461
|
+
path,
|
|
462
|
+
programId,
|
|
463
|
+
accounts: accountArguments,
|
|
464
|
+
data,
|
|
465
|
+
isInner: item.isInner->Option.getOr(false),
|
|
466
|
+
?decoded,
|
|
467
|
+
?logs,
|
|
468
|
+
},
|
|
469
|
+
~programName,
|
|
470
|
+
~instructionName,
|
|
471
|
+
~eventConfig=svmEventConfig,
|
|
472
|
+
~fieldSelection=onEventRegistration.fieldSelection,
|
|
473
|
+
)
|
|
474
|
+
let payloadDict = payload->(Utils.magic: Envio.svmInstruction => dict<unknown>)
|
|
475
|
+
payloadDict->Dict.set("srcAddress", programId->(Utils.magic: string => unknown))
|
|
476
|
+
items
|
|
477
|
+
->Array.push(
|
|
478
|
+
Internal.Event({
|
|
479
|
+
onEventRegistration,
|
|
480
|
+
chainId,
|
|
481
|
+
blockNumber: slot,
|
|
482
|
+
logIndex: transactionIndex,
|
|
483
|
+
orderPath: path,
|
|
484
|
+
transactionIndex,
|
|
485
|
+
payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
|
|
486
|
+
}),
|
|
487
|
+
)
|
|
488
|
+
->ignore
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
| (Svm, _, _) =>
|
|
492
|
+
JsError.throwWithMessage(`simulate: Invalid item. Each item must have "program" and "instruction" fields.`)
|
|
493
|
+
|
|
494
|
+
| (_, _, _) =>
|
|
495
|
+
let (contractName, eventName) = switch (raw->getContract, raw->getEvent) {
|
|
496
|
+
| (Some(c), Some(e)) => (c, e)
|
|
497
|
+
| _ =>
|
|
498
|
+
JsError.throwWithMessage(`simulate: Invalid item. Each item must have "contract" and "event" fields.`)
|
|
499
|
+
}
|
|
264
500
|
// Event simulate item
|
|
265
501
|
let eventConfig = switch findEventConfig(~config, ~contractName, ~eventName) {
|
|
266
502
|
| Some(ec) => ec
|
|
@@ -399,13 +635,21 @@ let parse = (
|
|
|
399
635
|
)
|
|
400
636
|
->ignore
|
|
401
637
|
})
|
|
402
|
-
|
|
403
|
-
| _ =>
|
|
404
|
-
JsError.throwWithMessage(`simulate: Invalid item. Each item must have "contract" and "event" fields.`)
|
|
405
638
|
}
|
|
406
639
|
})
|
|
407
640
|
|
|
408
|
-
|
|
641
|
+
switch config.ecosystem.name {
|
|
642
|
+
| Svm => {
|
|
643
|
+
items,
|
|
644
|
+
transactionStore: Some(TransactionStore.fromSvmJs(svmTxs, svmActivities)),
|
|
645
|
+
blockStore: Some(BlockStore.fromJs(svmBlocks, ~ecosystem=Svm, ~shouldChecksum=false)),
|
|
646
|
+
}
|
|
647
|
+
| _ => {
|
|
648
|
+
items,
|
|
649
|
+
transactionStore: None,
|
|
650
|
+
blockStore: None,
|
|
651
|
+
}
|
|
652
|
+
}
|
|
409
653
|
}
|
|
410
654
|
|
|
411
655
|
// Apply simulate source config from processConfig JSON to a Config.t
|
|
@@ -444,13 +688,21 @@ let patchConfig = (
|
|
|
444
688
|
// Parse with the process's startBlock so items default into the range
|
|
445
689
|
// the source will be queried over; the source now filters by range.
|
|
446
690
|
let chainConfig = {...chainConfig, startBlock, endBlock}
|
|
447
|
-
let items = parse(
|
|
691
|
+
let {items, transactionStore, blockStore} = parse(
|
|
448
692
|
~simulateItems,
|
|
449
693
|
~config,
|
|
450
694
|
~chainConfig,
|
|
451
695
|
~onEventRegistrations=chainRegistrations.onEventRegistrations,
|
|
452
696
|
)
|
|
453
|
-
{
|
|
697
|
+
{
|
|
698
|
+
...chainConfig,
|
|
699
|
+
sourceConfig: Config.SimulateSourceConfig({
|
|
700
|
+
items,
|
|
701
|
+
endBlock,
|
|
702
|
+
?transactionStore,
|
|
703
|
+
?blockStore,
|
|
704
|
+
}),
|
|
705
|
+
}
|
|
454
706
|
| None => chainConfig
|
|
455
707
|
}
|
|
456
708
|
| None => chainConfig
|