envio 3.2.1 → 3.3.0-alpha.1
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 -7
- package/src/Batch.res +12 -15
- package/src/Batch.res.mjs +4 -5
- package/src/BatchProcessing.res +199 -0
- package/src/BatchProcessing.res.mjs +125 -0
- package/src/ChainFetching.res +373 -0
- package/src/ChainFetching.res.mjs +206 -0
- package/src/ChainMetadata.res +27 -0
- package/src/ChainMetadata.res.mjs +27 -0
- package/src/ChainState.res +643 -0
- package/src/ChainState.res.mjs +476 -0
- package/src/ChainState.resi +87 -0
- package/src/Config.res +13 -4
- package/src/Config.res.mjs +8 -3
- package/src/ContractRegisterContext.res +106 -0
- package/src/ContractRegisterContext.res.mjs +76 -0
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +294 -0
- package/src/CrossChainState.res.mjs +221 -0
- package/src/CrossChainState.resi +39 -0
- package/src/Ecosystem.res +58 -0
- package/src/Ecosystem.res.mjs +43 -1
- package/src/Env.res +0 -2
- package/src/Env.res.mjs +0 -3
- package/src/EventConfigBuilder.res +2 -2
- package/src/EventProcessing.res +60 -46
- package/src/EventProcessing.res.mjs +33 -32
- package/src/EventUtils.res +0 -4
- package/src/EventUtils.res.mjs +0 -4
- package/src/ExitOnCaughtUp.res +10 -0
- package/src/ExitOnCaughtUp.res.mjs +22 -0
- package/src/FetchState.res +205 -73
- package/src/FetchState.res.mjs +242 -103
- package/src/InMemoryStore.res +36 -451
- package/src/InMemoryStore.res.mjs +20 -369
- package/src/IndexerLoop.res +43 -0
- package/src/IndexerLoop.res.mjs +46 -0
- package/src/IndexerState.res +536 -0
- package/src/IndexerState.res.mjs +543 -0
- package/src/IndexerState.resi +128 -0
- package/src/Internal.res +29 -8
- package/src/LoadLayer.res +20 -18
- package/src/LoadLayer.res.mjs +14 -12
- package/src/LoadLayer.resi +6 -3
- package/src/Logging.res +11 -44
- package/src/Logging.res.mjs +10 -42
- package/src/Main.res +54 -79
- package/src/Main.res.mjs +44 -56
- package/src/PgStorage.res +8 -71
- package/src/PgStorage.res.mjs +3 -47
- package/src/Prometheus.res +1 -13
- package/src/Prometheus.res.mjs +84 -96
- package/src/PruneStaleHistory.res +45 -0
- package/src/PruneStaleHistory.res.mjs +46 -0
- package/src/RawEvent.res +73 -0
- package/src/RawEvent.res.mjs +51 -0
- package/src/Rollback.res +204 -0
- package/src/Rollback.res.mjs +118 -0
- package/src/SimulateItems.res +12 -10
- package/src/SimulateItems.res.mjs +1 -1
- package/src/UserContext.res +26 -114
- package/src/UserContext.res.mjs +15 -78
- package/src/Writing.res +242 -0
- package/src/Writing.res.mjs +215 -0
- package/src/db/InternalTable.res +14 -27
- package/src/sources/Evm.res +40 -1
- package/src/sources/Evm.res.mjs +94 -84
- package/src/sources/Fuel.res +40 -1
- package/src/sources/Fuel.res.mjs +36 -26
- package/src/sources/HyperFuel.res +41 -120
- package/src/sources/HyperFuel.res.mjs +42 -80
- package/src/sources/HyperFuel.resi +1 -24
- package/src/sources/HyperFuelClient.res +16 -297
- package/src/sources/HyperFuelClient.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +33 -8
- package/src/sources/HyperFuelSource.res.mjs +56 -10
- package/src/sources/HyperSyncSource.res +5 -3
- package/src/sources/HyperSyncSource.res.mjs +1 -1
- package/src/sources/RpcSource.res +2 -2
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SourceManager.res +14 -23
- package/src/sources/SourceManager.res.mjs +27 -29
- package/src/sources/SourceManager.resi +4 -2
- package/src/sources/Svm.res +18 -1
- package/src/sources/Svm.res.mjs +30 -22
- package/src/sources/SvmHyperSyncSource.res +1 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/tui/Tui.res +38 -36
- package/src/tui/Tui.res.mjs +51 -51
- package/src/ChainFetcher.res +0 -519
- package/src/ChainFetcher.res.mjs +0 -314
- package/src/ChainManager.res +0 -358
- package/src/ChainManager.res.mjs +0 -291
- package/src/Ctx.res +0 -6
- package/src/Ctx.res.mjs +0 -2
- package/src/GlobalState.res +0 -1056
- package/src/GlobalState.res.mjs +0 -1086
- package/src/GlobalStateManager.res +0 -57
- package/src/GlobalStateManager.res.mjs +0 -68
- package/src/GlobalStateManager.resi +0 -7
- package/src/Ports.res +0 -5
- package/src/Ports.res.mjs +0 -9
- package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
- package/src/sources/EnvioApiClient.res +0 -15
- package/src/sources/EnvioApiClient.res.mjs +0 -24
package/src/Rollback.res
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// The reorg rollback state machine. Re-enters fetch/process/rollback through
|
|
2
|
+
// the injected schedule* effects.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Finds the last known valid block number below the reorg block
|
|
6
|
+
If not found, returns the highest block below threshold
|
|
7
|
+
*/
|
|
8
|
+
let getLastKnownValidBlock = async (
|
|
9
|
+
chainState: ChainState.t,
|
|
10
|
+
~reorgBlockNumber: int,
|
|
11
|
+
~isRealtime: bool,
|
|
12
|
+
) => {
|
|
13
|
+
// Don't include the reorg block itself — different source instances
|
|
14
|
+
// may have mismatching hashes at the head, so we always rollback
|
|
15
|
+
// the block where we detected the reorg.
|
|
16
|
+
let scannedBlockNumbers =
|
|
17
|
+
chainState
|
|
18
|
+
->ChainState.reorgDetection
|
|
19
|
+
->ReorgDetection.getThresholdBlockNumbersBelowBlock(
|
|
20
|
+
~blockNumber=reorgBlockNumber,
|
|
21
|
+
~knownHeight=(chainState->ChainState.fetchState).knownHeight,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
switch scannedBlockNumbers {
|
|
25
|
+
| [] => chainState->ChainState.getHighestBlockBelowThreshold
|
|
26
|
+
| _ => {
|
|
27
|
+
let blockNumbersAndHashes = await chainState
|
|
28
|
+
->ChainState.sourceManager
|
|
29
|
+
->SourceManager.getBlockHashes(~blockNumbers=scannedBlockNumbers, ~isRealtime)
|
|
30
|
+
|
|
31
|
+
switch chainState
|
|
32
|
+
->ChainState.reorgDetection
|
|
33
|
+
->ReorgDetection.getLatestValidScannedBlock(~blockNumbersAndHashes) {
|
|
34
|
+
| Some(blockNumber) => blockNumber
|
|
35
|
+
| None => chainState->ChainState.getHighestBlockBelowThreshold
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let rec rollback = async (
|
|
42
|
+
state: IndexerState.t,
|
|
43
|
+
~scheduleFetch,
|
|
44
|
+
~scheduleProcessing,
|
|
45
|
+
~scheduleRollback,
|
|
46
|
+
) =>
|
|
47
|
+
// Owns its error boundary: launch doesn't catch, so a failure mid-rollback
|
|
48
|
+
// must stop the indexer.
|
|
49
|
+
try {
|
|
50
|
+
switch state->IndexerState.rollbackState {
|
|
51
|
+
| NoRollback | RollbackReady(_) =>
|
|
52
|
+
JsError.throwWithMessage("Internal error: Rollback initiated with invalid state")
|
|
53
|
+
| ReorgDetected({chain, blockNumber: reorgBlockNumber}) =>
|
|
54
|
+
let chainState = state->IndexerState.getChainState(~chain)
|
|
55
|
+
|
|
56
|
+
state->IndexerState.enterFindingReorgDepth
|
|
57
|
+
let rollbackTargetBlockNumber = await chainState->getLastKnownValidBlock(
|
|
58
|
+
~reorgBlockNumber,
|
|
59
|
+
~isRealtime=state->IndexerState.isRealtime,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
chainState
|
|
63
|
+
->ChainState.sourceManager
|
|
64
|
+
->SourceManager.onReorg(~rollbackTargetBlock=rollbackTargetBlockNumber)
|
|
65
|
+
|
|
66
|
+
state->IndexerState.foundReorgDepth(~chain, ~rollbackTargetBlockNumber)
|
|
67
|
+
// Rendezvous with the processing loop: whichever of {depth found, loop
|
|
68
|
+
// idle} happens last triggers the rollback; the earlier one finds the
|
|
69
|
+
// other condition unmet and bails here.
|
|
70
|
+
scheduleRollback()
|
|
71
|
+
// Reached when a batch finished (loop idle) while the reorg depth wasn't
|
|
72
|
+
// found yet. Wait for the ReorgDetected branch above to find it and re-kick.
|
|
73
|
+
| FindingReorgDepth => ()
|
|
74
|
+
| FoundReorgDepth(_) if state->IndexerState.isProcessing =>
|
|
75
|
+
Logging.info("Waiting for batch to finish processing before executing rollback")
|
|
76
|
+
| FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber}) =>
|
|
77
|
+
await executeRollback(
|
|
78
|
+
state,
|
|
79
|
+
~reorgChain,
|
|
80
|
+
~rollbackTargetBlockNumber,
|
|
81
|
+
~scheduleFetch,
|
|
82
|
+
~scheduleProcessing,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
| exn =>
|
|
87
|
+
IndexerState.errorExit(state, exn->ErrorHandling.make(~msg=IndexerState.unexpectedErrorMsg))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
and executeRollback = async (
|
|
91
|
+
state: IndexerState.t,
|
|
92
|
+
~reorgChain,
|
|
93
|
+
~rollbackTargetBlockNumber,
|
|
94
|
+
~scheduleFetch,
|
|
95
|
+
~scheduleProcessing,
|
|
96
|
+
) => {
|
|
97
|
+
let startTime = Hrtime.makeTimer()
|
|
98
|
+
|
|
99
|
+
let chainState = state->IndexerState.getChainState(~chain=reorgChain)
|
|
100
|
+
|
|
101
|
+
let logger = Logging.createChildFrom(
|
|
102
|
+
~logger=chainState->ChainState.logger,
|
|
103
|
+
~params={
|
|
104
|
+
"action": "Rollback",
|
|
105
|
+
"reorgChain": reorgChain,
|
|
106
|
+
"targetBlockNumber": rollbackTargetBlockNumber,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
logger->Logging.childInfo("Started rollback on reorg")
|
|
110
|
+
Prometheus.RollbackTargetBlockNumber.set(
|
|
111
|
+
~blockNumber=rollbackTargetBlockNumber,
|
|
112
|
+
~chain=reorgChain,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
let reorgChainId = reorgChain->ChainMap.Chain.toChainId
|
|
116
|
+
|
|
117
|
+
// Finish pending batch writes first: the target checkpoint, the progress
|
|
118
|
+
// diff and the rollback diff below must all be computed from the same db
|
|
119
|
+
// state. Otherwise an in-flight batch lands after the progress reads and
|
|
120
|
+
// its entity changes get reverted without the chain progress being
|
|
121
|
+
// rolled back, so the events are never reprocessed.
|
|
122
|
+
await state->Writing.flush
|
|
123
|
+
|
|
124
|
+
let rollbackTargetCheckpointId = {
|
|
125
|
+
switch await (state->IndexerState.persistence).storage.getRollbackTargetCheckpoint(
|
|
126
|
+
~reorgChainId,
|
|
127
|
+
~lastKnownValidBlockNumber=rollbackTargetBlockNumber,
|
|
128
|
+
) {
|
|
129
|
+
| Some(checkpointId) => checkpointId
|
|
130
|
+
| None => 0n
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let eventsProcessedDiffByChain = Dict.make()
|
|
135
|
+
let newProgressBlockNumberPerChain = Dict.make()
|
|
136
|
+
let rollbackedProcessedEvents = ref(0.)
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
let rollbackProgressDiff = await (
|
|
140
|
+
state->IndexerState.persistence
|
|
141
|
+
).storage.getRollbackProgressDiff(~rollbackTargetCheckpointId)
|
|
142
|
+
for idx in 0 to rollbackProgressDiff->Array.length - 1 {
|
|
143
|
+
let diff = rollbackProgressDiff->Array.getUnsafe(idx)
|
|
144
|
+
eventsProcessedDiffByChain->Utils.Dict.setByInt(
|
|
145
|
+
diff["chain_id"],
|
|
146
|
+
{
|
|
147
|
+
let eventsProcessedDiff =
|
|
148
|
+
Float.fromString(diff["events_processed_diff"])->Option.getOrThrow
|
|
149
|
+
rollbackedProcessedEvents := rollbackedProcessedEvents.contents +. eventsProcessedDiff
|
|
150
|
+
eventsProcessedDiff
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
newProgressBlockNumberPerChain->Utils.Dict.setByInt(
|
|
154
|
+
diff["chain_id"],
|
|
155
|
+
if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChainId {
|
|
156
|
+
Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber)
|
|
157
|
+
} else {
|
|
158
|
+
diff["new_progress_block_number"]
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
state
|
|
165
|
+
->IndexerState.chainStates
|
|
166
|
+
->Utils.Dict.forEach(cs => {
|
|
167
|
+
let chainId = (cs->ChainState.chainConfig).id
|
|
168
|
+
cs->ChainState.rollback(
|
|
169
|
+
~newProgressBlockNumber=newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
170
|
+
chainId,
|
|
171
|
+
),
|
|
172
|
+
~eventsProcessedDiff=eventsProcessedDiffByChain->Utils.Dict.dangerouslyGetByIntNonOption(
|
|
173
|
+
chainId,
|
|
174
|
+
),
|
|
175
|
+
~rollbackTargetBlockNumber,
|
|
176
|
+
~isReorgChain=chainId === reorgChainId,
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
let diff = await state->InMemoryStore.prepareRollbackDiff(
|
|
181
|
+
~rollbackTargetCheckpointId,
|
|
182
|
+
~rollbackDiffCheckpointId=state->IndexerState.committedCheckpointId->BigInt.add(1n),
|
|
183
|
+
~progressBlockNumberByChainId=newProgressBlockNumberPerChain,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
logger->Logging.childTrace({
|
|
187
|
+
"msg": "Finished rollback on reorg",
|
|
188
|
+
"entityChanges": {
|
|
189
|
+
"deleted": diff["deletedEntities"],
|
|
190
|
+
"upserted": diff["setEntities"],
|
|
191
|
+
},
|
|
192
|
+
"rollbackedEvents": rollbackedProcessedEvents.contents,
|
|
193
|
+
"beforeCheckpointId": state->IndexerState.committedCheckpointId,
|
|
194
|
+
"targetCheckpointId": rollbackTargetCheckpointId,
|
|
195
|
+
})
|
|
196
|
+
Prometheus.RollbackSuccess.increment(
|
|
197
|
+
~timeSeconds=Hrtime.timeSince(startTime)->Hrtime.toSecondsFloat,
|
|
198
|
+
~rollbackedProcessedEvents=rollbackedProcessedEvents.contents,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
state->IndexerState.completeRollback(~eventsProcessedDiffByChain)
|
|
202
|
+
scheduleFetch()
|
|
203
|
+
scheduleProcessing()
|
|
204
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "./Utils.res.mjs";
|
|
4
|
+
import * as Hrtime from "./bindings/Hrtime.res.mjs";
|
|
5
|
+
import * as Logging from "./Logging.res.mjs";
|
|
6
|
+
import * as Writing from "./Writing.res.mjs";
|
|
7
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
8
|
+
import * as Prometheus from "./Prometheus.res.mjs";
|
|
9
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
10
|
+
import * as Stdlib_Float from "@rescript/runtime/lib/es6/Stdlib_Float.js";
|
|
11
|
+
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
12
|
+
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
13
|
+
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
14
|
+
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
15
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
16
|
+
import * as ReorgDetection from "./ReorgDetection.res.mjs";
|
|
17
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
18
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
19
|
+
|
|
20
|
+
async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime) {
|
|
21
|
+
let scannedBlockNumbers = ReorgDetection.getThresholdBlockNumbersBelowBlock(ChainState.reorgDetection(chainState), reorgBlockNumber, ChainState.fetchState(chainState).knownHeight);
|
|
22
|
+
if (scannedBlockNumbers.length === 0) {
|
|
23
|
+
return ChainState.getHighestBlockBelowThreshold(chainState);
|
|
24
|
+
}
|
|
25
|
+
let blockNumbersAndHashes = await SourceManager.getBlockHashes(ChainState.sourceManager(chainState), scannedBlockNumbers, isRealtime);
|
|
26
|
+
let blockNumber = ReorgDetection.getLatestValidScannedBlock(ChainState.reorgDetection(chainState), blockNumbersAndHashes);
|
|
27
|
+
if (blockNumber !== undefined) {
|
|
28
|
+
return blockNumber;
|
|
29
|
+
} else {
|
|
30
|
+
return ChainState.getHighestBlockBelowThreshold(chainState);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function executeRollback(state, reorgChain, rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing) {
|
|
35
|
+
let startTime = Hrtime.makeTimer();
|
|
36
|
+
let chainState = IndexerState.getChainState(state, reorgChain);
|
|
37
|
+
let logger = Logging.createChildFrom(ChainState.logger(chainState), {
|
|
38
|
+
action: "Rollback",
|
|
39
|
+
reorgChain: reorgChain,
|
|
40
|
+
targetBlockNumber: rollbackTargetBlockNumber
|
|
41
|
+
});
|
|
42
|
+
Logging.childInfo(logger, "Started rollback on reorg");
|
|
43
|
+
Prometheus.RollbackTargetBlockNumber.set(rollbackTargetBlockNumber, reorgChain);
|
|
44
|
+
await Writing.flush(state);
|
|
45
|
+
let checkpointId = await IndexerState.persistence(state).storage.getRollbackTargetCheckpoint(reorgChain, rollbackTargetBlockNumber);
|
|
46
|
+
let rollbackTargetCheckpointId = checkpointId !== undefined ? checkpointId : 0n;
|
|
47
|
+
let eventsProcessedDiffByChain = {};
|
|
48
|
+
let newProgressBlockNumberPerChain = {};
|
|
49
|
+
let rollbackedProcessedEvents = 0;
|
|
50
|
+
let rollbackProgressDiff = await IndexerState.persistence(state).storage.getRollbackProgressDiff(rollbackTargetCheckpointId);
|
|
51
|
+
for (let idx = 0, idx_finish = rollbackProgressDiff.length; idx < idx_finish; ++idx) {
|
|
52
|
+
let diff = rollbackProgressDiff[idx];
|
|
53
|
+
let eventsProcessedDiff = Stdlib_Option.getOrThrow(Stdlib_Float.fromString(diff.events_processed_diff), undefined);
|
|
54
|
+
rollbackedProcessedEvents = rollbackedProcessedEvents + eventsProcessedDiff;
|
|
55
|
+
eventsProcessedDiffByChain[diff.chain_id] = eventsProcessedDiff;
|
|
56
|
+
newProgressBlockNumberPerChain[diff.chain_id] = rollbackTargetCheckpointId === 0n && diff.chain_id === reorgChain ? Primitive_int.min(diff.new_progress_block_number, rollbackTargetBlockNumber) : diff.new_progress_block_number;
|
|
57
|
+
}
|
|
58
|
+
Utils.Dict.forEach(IndexerState.chainStates(state), cs => {
|
|
59
|
+
let chainId = ChainState.chainConfig(cs).id;
|
|
60
|
+
ChainState.rollback(cs, newProgressBlockNumberPerChain[chainId], eventsProcessedDiffByChain[chainId], rollbackTargetBlockNumber, chainId === reorgChain);
|
|
61
|
+
});
|
|
62
|
+
let diff$1 = await InMemoryStore.prepareRollbackDiff(state, rollbackTargetCheckpointId, IndexerState.committedCheckpointId(state) + 1n, newProgressBlockNumberPerChain);
|
|
63
|
+
Logging.childTrace(logger, {
|
|
64
|
+
msg: "Finished rollback on reorg",
|
|
65
|
+
entityChanges: {
|
|
66
|
+
deleted: diff$1.deletedEntities,
|
|
67
|
+
upserted: diff$1.setEntities
|
|
68
|
+
},
|
|
69
|
+
rollbackedEvents: rollbackedProcessedEvents,
|
|
70
|
+
beforeCheckpointId: IndexerState.committedCheckpointId(state),
|
|
71
|
+
targetCheckpointId: rollbackTargetCheckpointId
|
|
72
|
+
});
|
|
73
|
+
Prometheus.RollbackSuccess.increment(Hrtime.toSecondsFloat(Hrtime.timeSince(startTime)), rollbackedProcessedEvents);
|
|
74
|
+
IndexerState.completeRollback(state, eventsProcessedDiffByChain);
|
|
75
|
+
scheduleFetch();
|
|
76
|
+
return scheduleProcessing();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function rollback(state, scheduleFetch, scheduleProcessing, scheduleRollback) {
|
|
80
|
+
try {
|
|
81
|
+
let match = IndexerState.rollbackState(state);
|
|
82
|
+
if (typeof match !== "object") {
|
|
83
|
+
if (match === "NoRollback") {
|
|
84
|
+
return Stdlib_JsError.throwWithMessage("Internal error: Rollback initiated with invalid state");
|
|
85
|
+
} else {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
switch (match.TAG) {
|
|
90
|
+
case "ReorgDetected" :
|
|
91
|
+
let chain = match.chain;
|
|
92
|
+
let chainState = IndexerState.getChainState(state, chain);
|
|
93
|
+
IndexerState.enterFindingReorgDepth(state);
|
|
94
|
+
let rollbackTargetBlockNumber = await getLastKnownValidBlock(chainState, match.blockNumber, IndexerState.isRealtime(state));
|
|
95
|
+
SourceManager.onReorg(ChainState.sourceManager(chainState), rollbackTargetBlockNumber);
|
|
96
|
+
IndexerState.foundReorgDepth(state, chain, rollbackTargetBlockNumber);
|
|
97
|
+
return scheduleRollback();
|
|
98
|
+
case "FoundReorgDepth" :
|
|
99
|
+
if (IndexerState.isProcessing(state)) {
|
|
100
|
+
return Logging.info("Waiting for batch to finish processing before executing rollback");
|
|
101
|
+
} else {
|
|
102
|
+
return await executeRollback(state, match.chain, match.rollbackTargetBlockNumber, scheduleFetch, scheduleProcessing);
|
|
103
|
+
}
|
|
104
|
+
case "RollbackReady" :
|
|
105
|
+
return Stdlib_JsError.throwWithMessage("Internal error: Rollback initiated with invalid state");
|
|
106
|
+
}
|
|
107
|
+
} catch (raw_exn) {
|
|
108
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
109
|
+
return IndexerState.errorExit(state, ErrorHandling.make(exn, undefined, IndexerState.unexpectedErrorMsg));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
getLastKnownValidBlock,
|
|
115
|
+
rollback,
|
|
116
|
+
executeRollback,
|
|
117
|
+
}
|
|
118
|
+
/* Utils Not a pure module */
|
package/src/SimulateItems.res
CHANGED
|
@@ -288,16 +288,18 @@ let parse = (~simulateItems: array<JSON.t>, ~config: Config.t, ~chainConfig: Con
|
|
|
288
288
|
blockNumber,
|
|
289
289
|
blockHash,
|
|
290
290
|
logIndex,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
payload: (
|
|
292
|
+
{
|
|
293
|
+
contractName: eventConfig.contractName,
|
|
294
|
+
eventName: eventConfig.name,
|
|
295
|
+
params,
|
|
296
|
+
chainId,
|
|
297
|
+
srcAddress,
|
|
298
|
+
logIndex,
|
|
299
|
+
transaction,
|
|
300
|
+
block,
|
|
301
|
+
}: Evm.payload
|
|
302
|
+
)->Evm.fromPayload,
|
|
301
303
|
}),
|
|
302
304
|
)
|
|
303
305
|
->ignore
|
|
@@ -241,7 +241,7 @@ function parse(simulateItems, config, chainConfig) {
|
|
|
241
241
|
blockNumber: blockNumber,
|
|
242
242
|
blockHash: match$3[3],
|
|
243
243
|
logIndex: logIndex,
|
|
244
|
-
|
|
244
|
+
payload: {
|
|
245
245
|
contractName: eventConfig.contractName,
|
|
246
246
|
eventName: eventConfig.name,
|
|
247
247
|
params: params,
|
package/src/UserContext.res
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type contextParams = {
|
|
2
2
|
item: Internal.item,
|
|
3
3
|
checkpointId: Internal.checkpointId,
|
|
4
|
-
|
|
4
|
+
indexerState: IndexerState.t,
|
|
5
5
|
loadManager: LoadManager.t,
|
|
6
6
|
persistence: Persistence.t,
|
|
7
7
|
isPreload: bool,
|
|
@@ -22,7 +22,8 @@ Utils.Object.defineProperty(
|
|
|
22
22
|
{
|
|
23
23
|
// Wrap with toMethod so `this` binds to the EffectContext instance.
|
|
24
24
|
get: Utils.toMethod(() => {
|
|
25
|
-
|
|
25
|
+
let params = paramsByThis->Utils.WeakMap.unsafeGet(%raw(`this`))
|
|
26
|
+
Ecosystem.getItemUserLogger(params.item, ~ecosystem=params.config.ecosystem)
|
|
26
27
|
}),
|
|
27
28
|
},
|
|
28
29
|
)
|
|
@@ -60,9 +61,10 @@ let initEffect = (params: contextParams) => {
|
|
|
60
61
|
~persistence=params.persistence,
|
|
61
62
|
~effect,
|
|
62
63
|
~effectArgs,
|
|
63
|
-
~
|
|
64
|
+
~indexerState=params.indexerState,
|
|
64
65
|
~shouldGroup=params.isPreload,
|
|
65
66
|
~item=params.item,
|
|
67
|
+
~ecosystem=params.config.ecosystem,
|
|
66
68
|
)
|
|
67
69
|
}
|
|
68
70
|
callEffect
|
|
@@ -82,9 +84,10 @@ let getWhereHandler = (params: entityContextParams, filter: dict<dict<unknown>>)
|
|
|
82
84
|
~loadManager=params.loadManager,
|
|
83
85
|
~persistence=params.persistence,
|
|
84
86
|
~entityConfig,
|
|
85
|
-
~
|
|
87
|
+
~indexerState=params.indexerState,
|
|
86
88
|
~shouldGroup=params.isPreload,
|
|
87
89
|
~item=params.item,
|
|
90
|
+
~ecosystem=params.config.ecosystem,
|
|
88
91
|
~filter,
|
|
89
92
|
)
|
|
90
93
|
|
|
@@ -121,10 +124,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
121
124
|
let set = params.isPreload
|
|
122
125
|
? noopSet
|
|
123
126
|
: (entity: Internal.entity) => {
|
|
124
|
-
params.
|
|
127
|
+
params.indexerState
|
|
125
128
|
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig)
|
|
126
129
|
->InMemoryTable.Entity.set(
|
|
127
|
-
~committedCheckpointId=params.
|
|
130
|
+
~committedCheckpointId=params.indexerState->IndexerState.committedCheckpointId,
|
|
128
131
|
Set({
|
|
129
132
|
entityId: entity.id,
|
|
130
133
|
checkpointId: params.checkpointId,
|
|
@@ -146,9 +149,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
146
149
|
~loadManager=params.loadManager,
|
|
147
150
|
~persistence=params.persistence,
|
|
148
151
|
~entityConfig=params.entityConfig,
|
|
149
|
-
~
|
|
152
|
+
~indexerState=params.indexerState,
|
|
150
153
|
~shouldGroup=params.isPreload,
|
|
151
154
|
~item=params.item,
|
|
155
|
+
~ecosystem=params.config.ecosystem,
|
|
152
156
|
~entityId,
|
|
153
157
|
)
|
|
154
158
|
)->(Utils.magic: (string => promise<option<Internal.entity>>) => unknown)
|
|
@@ -177,9 +181,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
177
181
|
~loadManager=params.loadManager,
|
|
178
182
|
~persistence=params.persistence,
|
|
179
183
|
~entityConfig=params.entityConfig,
|
|
180
|
-
~
|
|
184
|
+
~indexerState=params.indexerState,
|
|
181
185
|
~shouldGroup=params.isPreload,
|
|
182
186
|
~item=params.item,
|
|
187
|
+
~ecosystem=params.config.ecosystem,
|
|
183
188
|
~entityId,
|
|
184
189
|
)->Promise.thenResolve(entity => {
|
|
185
190
|
switch entity {
|
|
@@ -206,9 +211,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
206
211
|
~loadManager=params.loadManager,
|
|
207
212
|
~persistence=params.persistence,
|
|
208
213
|
~entityConfig=params.entityConfig,
|
|
209
|
-
~
|
|
214
|
+
~indexerState=params.indexerState,
|
|
210
215
|
~shouldGroup=params.isPreload,
|
|
211
216
|
~item=params.item,
|
|
217
|
+
~ecosystem=params.config.ecosystem,
|
|
212
218
|
~entityId=entity.id,
|
|
213
219
|
)->Promise.thenResolve(storageEntity => {
|
|
214
220
|
switch storageEntity {
|
|
@@ -227,10 +233,10 @@ let entityTraps: Utils.Proxy.traps<entityContextParams> = {
|
|
|
227
233
|
noopDeleteUnsafe
|
|
228
234
|
} else {
|
|
229
235
|
entityId => {
|
|
230
|
-
params.
|
|
236
|
+
params.indexerState
|
|
231
237
|
->InMemoryStore.getInMemTable(~entityConfig=params.entityConfig)
|
|
232
238
|
->InMemoryTable.Entity.set(
|
|
233
|
-
~committedCheckpointId=params.
|
|
239
|
+
~committedCheckpointId=params.indexerState->IndexerState.committedCheckpointId,
|
|
234
240
|
Delete({
|
|
235
241
|
entityId,
|
|
236
242
|
checkpointId: params.checkpointId,
|
|
@@ -250,13 +256,17 @@ let handlerTraps: Utils.Proxy.traps<contextParams> = {
|
|
|
250
256
|
if params.isResolved {
|
|
251
257
|
Utils.Error.make(
|
|
252
258
|
`Impossible to access context.${prop} after the handler is resolved. Make sure you didn't miss an await in the handler.`,
|
|
253
|
-
)->ErrorHandling.mkLogAndRaise(
|
|
259
|
+
)->ErrorHandling.mkLogAndRaise(
|
|
260
|
+
~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
|
|
261
|
+
)
|
|
254
262
|
}
|
|
255
263
|
switch prop {
|
|
256
264
|
| "log" =>
|
|
257
|
-
(
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
(
|
|
266
|
+
params.isPreload
|
|
267
|
+
? Logging.noopLogger
|
|
268
|
+
: Ecosystem.getItemUserLogger(params.item, ~ecosystem=params.config.ecosystem)
|
|
269
|
+
)->(Utils.magic: Envio.logger => unknown)
|
|
260
270
|
|
|
261
271
|
| "effect" =>
|
|
262
272
|
initEffect((params :> contextParams))->(
|
|
@@ -277,7 +287,7 @@ let handlerTraps: Utils.Proxy.traps<contextParams> = {
|
|
|
277
287
|
{
|
|
278
288
|
item: params.item,
|
|
279
289
|
isPreload: params.isPreload,
|
|
280
|
-
|
|
290
|
+
indexerState: params.indexerState,
|
|
281
291
|
loadManager: params.loadManager,
|
|
282
292
|
persistence: params.persistence,
|
|
283
293
|
checkpointId: params.checkpointId,
|
|
@@ -300,101 +310,3 @@ let handlerTraps: Utils.Proxy.traps<contextParams> = {
|
|
|
300
310
|
let getHandlerContext = (params: contextParams): Internal.handlerContext => {
|
|
301
311
|
params->Utils.Proxy.make(handlerTraps)->(Utils.magic: contextParams => Internal.handlerContext)
|
|
302
312
|
}
|
|
303
|
-
|
|
304
|
-
// Contract register context creation
|
|
305
|
-
type contractRegisterParams = {
|
|
306
|
-
item: Internal.item,
|
|
307
|
-
onRegister: (~item: Internal.item, ~contractAddress: Address.t, ~contractName: string) => unit,
|
|
308
|
-
config: Config.t,
|
|
309
|
-
mutable isResolved: bool,
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Helper to create a validated add function for contract registration.
|
|
313
|
-
// The isResolved check has to live inside the returned closure (not just in the
|
|
314
|
-
// outer proxy trap) because users can capture `const add = context.chain.X.add`
|
|
315
|
-
// before awaiting — a later call would otherwise bypass the resolved guard.
|
|
316
|
-
let makeAddFunction = (~params: contractRegisterParams, ~contractName: string): (
|
|
317
|
-
Address.t => unit
|
|
318
|
-
) => {
|
|
319
|
-
(contractAddress: Address.t) => {
|
|
320
|
-
if params.isResolved {
|
|
321
|
-
Utils.Error.make(`Impossible to access context.chain after the contract register is resolved. Make sure you didn't miss an await in the handler.`)->ErrorHandling.mkLogAndRaise(
|
|
322
|
-
~logger=params.item->Logging.getItemLogger,
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
let validatedAddress = if params.config.ecosystem.name === Evm {
|
|
326
|
-
// The value is passed from the user-land,
|
|
327
|
-
// so we need to validate and checksum/lowercase the address.
|
|
328
|
-
if params.config.lowercaseAddresses {
|
|
329
|
-
contractAddress->Address.Evm.fromAddressLowercaseOrThrow
|
|
330
|
-
} else {
|
|
331
|
-
contractAddress->Address.Evm.fromAddressOrThrow
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
// TODO: Ideally we should do the same for other ecosystems
|
|
335
|
-
contractAddress
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
params.onRegister(~item=params.item, ~contractAddress=validatedAddress, ~contractName)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Chain proxy for contractRegister context: context.chain.ContractName.add(address)
|
|
343
|
-
let contractRegisterChainTraps: Utils.Proxy.traps<contractRegisterParams> = {
|
|
344
|
-
get: (~target as params, ~prop: unknown) => {
|
|
345
|
-
let prop = prop->(Utils.magic: unknown => string)
|
|
346
|
-
switch prop {
|
|
347
|
-
| "id" =>
|
|
348
|
-
let eventItem = params.item->Internal.castUnsafeEventItem
|
|
349
|
-
eventItem.chain->ChainMap.Chain.toChainId->(Utils.magic: int => unknown)
|
|
350
|
-
| _ =>
|
|
351
|
-
// Look up the contract name directly in config contracts across all chains.
|
|
352
|
-
let contractName = prop
|
|
353
|
-
let isValidContract =
|
|
354
|
-
params.config.chainMap
|
|
355
|
-
->ChainMap.values
|
|
356
|
-
->Array.some(chain => chain.contracts->Array.some(c => c.name === contractName))
|
|
357
|
-
if isValidContract {
|
|
358
|
-
let addFn = makeAddFunction(~params, ~contractName)
|
|
359
|
-
{"add": addFn}->(Utils.magic: {"add": Address.t => unit} => unknown)
|
|
360
|
-
} else {
|
|
361
|
-
JsError.throwWithMessage(
|
|
362
|
-
`Invalid contract name '${prop}' on context.chain. ${EntityFilter.codegenHelpMessage}`,
|
|
363
|
-
)
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
let contractRegisterTraps: Utils.Proxy.traps<contractRegisterParams> = {
|
|
370
|
-
get: (~target as params, ~prop: unknown) => {
|
|
371
|
-
let prop = prop->(Utils.magic: unknown => string)
|
|
372
|
-
if params.isResolved {
|
|
373
|
-
Utils.Error.make(
|
|
374
|
-
`Impossible to access context.${prop} after the contract register is resolved. Make sure you didn't miss an await in the handler.`,
|
|
375
|
-
)->ErrorHandling.mkLogAndRaise(~logger=params.item->Logging.getItemLogger)
|
|
376
|
-
}
|
|
377
|
-
switch prop {
|
|
378
|
-
| "log" => params.item->Logging.getUserLogger->(Utils.magic: Envio.logger => unknown)
|
|
379
|
-
| "chain" =>
|
|
380
|
-
params
|
|
381
|
-
->Utils.Proxy.make(contractRegisterChainTraps)
|
|
382
|
-
->(Utils.magic: contractRegisterParams => unknown)
|
|
383
|
-
| _ =>
|
|
384
|
-
JsError.throwWithMessage(
|
|
385
|
-
`Invalid context access by '${prop}' property. Use context.chain.ContractName.add(address) to register contracts. ${EntityFilter.codegenHelpMessage}`,
|
|
386
|
-
)
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let getContractRegisterContext = (params: contractRegisterParams) => {
|
|
392
|
-
params
|
|
393
|
-
->Utils.Proxy.make(contractRegisterTraps)
|
|
394
|
-
->(Utils.magic: contractRegisterParams => Internal.contractRegisterContext)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
let getContractRegisterArgs = (params: contractRegisterParams): Internal.contractRegisterArgs => {
|
|
398
|
-
event: (params.item->Internal.castUnsafeEventItem).event,
|
|
399
|
-
context: getContractRegisterContext(params),
|
|
400
|
-
}
|