envio 3.6.1 → 3.7.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 +197 -9
- package/package.json +6 -6
- package/src/Batch.res +64 -25
- package/src/Batch.res.mjs +69 -46
- package/src/ChainFetching.res +13 -17
- package/src/ChainFetching.res.mjs +9 -14
- package/src/ChainState.res +160 -119
- package/src/ChainState.res.mjs +75 -46
- package/src/ChainState.resi +15 -12
- package/src/Config.res +9 -2
- package/src/Config.res.mjs +4 -4
- package/src/Core.res +5 -0
- package/src/Ecosystem.res +0 -1
- package/src/Envio.res +16 -3
- package/src/EventConfigBuilder.res +155 -40
- package/src/EventConfigBuilder.res.mjs +76 -24
- package/src/EventProcessing.res +4 -8
- package/src/EventProcessing.res.mjs +4 -4
- package/src/EventUtils.res +3 -47
- package/src/FetchState.res +124 -104
- package/src/FetchState.res.mjs +116 -100
- package/src/HandlerRegister.res +108 -2
- package/src/HandlerRegister.res.mjs +59 -10
- package/src/HandlerRegister.resi +4 -0
- package/src/Internal.res +84 -24
- package/src/Internal.res.mjs +33 -2
- package/src/LazyLoader.res +12 -11
- package/src/LazyLoader.res.mjs +13 -7
- package/src/Main.res +22 -9
- package/src/Main.res.mjs +18 -5
- package/src/MemoryStorage.res +724 -0
- package/src/MemoryStorage.res.mjs +601 -0
- package/src/PgStorage.res +18 -7
- package/src/PgStorage.res.mjs +14 -11
- package/src/ReorgDetection.res +0 -222
- package/src/ReorgDetection.res.mjs +0 -163
- package/src/Rollback.res +14 -15
- package/src/Rollback.res.mjs +4 -5
- package/src/SimulateItems.res +2 -2
- package/src/Utils.res +3 -0
- package/src/bindings/ClickHouse.res +16 -11
- package/src/bindings/ClickHouse.res.mjs +10 -10
- package/src/bindings/Vitest.res +1 -1
- package/src/sources/BlockStore.res +115 -5
- package/src/sources/BlockStore.res.mjs +25 -0
- package/src/sources/Evm.res +0 -1
- package/src/sources/Evm.res.mjs +0 -1
- package/src/sources/EvmHyperSyncSource.res +19 -84
- package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
- package/src/sources/Fuel.res +24 -4
- package/src/sources/Fuel.res.mjs +23 -3
- package/src/sources/FuelHyperSync.res +8 -3
- package/src/sources/FuelHyperSync.res.mjs +5 -4
- package/src/sources/FuelHyperSync.resi +3 -1
- package/src/sources/FuelHyperSyncClient.res +7 -10
- package/src/sources/FuelHyperSyncSource.res +18 -45
- package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
- package/src/sources/HyperSync.res +49 -229
- package/src/sources/HyperSync.res.mjs +74 -191
- package/src/sources/HyperSync.resi +11 -35
- package/src/sources/HyperSyncClient.res +9 -79
- package/src/sources/HyperSyncClient.res.mjs +2 -6
- package/src/sources/RequestStat.res +5 -0
- package/src/sources/RequestStat.res.mjs +2 -0
- package/src/sources/RpcSource.res +149 -43
- package/src/sources/RpcSource.res.mjs +104 -41
- package/src/sources/SimulateSource.res +8 -5
- package/src/sources/SimulateSource.res.mjs +6 -6
- package/src/sources/Source.res +89 -16
- package/src/sources/Source.res.mjs +50 -1
- package/src/sources/SourceManager.res +255 -50
- package/src/sources/SourceManager.res.mjs +149 -55
- package/src/sources/SourceManager.resi +2 -6
- package/src/sources/Svm.res +0 -7
- package/src/sources/Svm.res.mjs +0 -8
- package/src/sources/SvmHyperSyncClient.res +21 -16
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
- package/src/sources/SvmHyperSyncSource.res +25 -117
- package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
- package/svm.schema.json +3 -2
package/src/PgStorage.res.mjs
CHANGED
|
@@ -471,9 +471,11 @@ function classifyWriteError(specificError, table, exn) {
|
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
|
|
474
|
+
function makeSetQueryCache() {
|
|
475
|
+
return new WeakMap();
|
|
476
|
+
}
|
|
475
477
|
|
|
476
|
-
async function setOrThrow(sql, items, table, itemSchema, pgSchema, chainIdModeOpt) {
|
|
478
|
+
async function setOrThrow(sql, items, table, itemSchema, pgSchema, setQueryCache, chainIdModeOpt) {
|
|
477
479
|
let chainIdMode = chainIdModeOpt !== undefined ? chainIdModeOpt : "int32";
|
|
478
480
|
if (items.length === 0) {
|
|
479
481
|
return;
|
|
@@ -656,7 +658,7 @@ function executeSet(sql, items, dbFunction) {
|
|
|
656
658
|
}
|
|
657
659
|
}
|
|
658
660
|
|
|
659
|
-
async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, escapeTables) {
|
|
661
|
+
async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, escapeTables) {
|
|
660
662
|
try {
|
|
661
663
|
let chainIdMode = config.chainIdMode;
|
|
662
664
|
let shouldSaveHistory = Config.shouldSaveHistory(config, isInReorgThreshold);
|
|
@@ -670,7 +672,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
670
672
|
if (item.kind !== 0) {
|
|
671
673
|
return;
|
|
672
674
|
}
|
|
673
|
-
let coordinate = ChainId.toString(
|
|
675
|
+
let coordinate = ChainId.toString(item.chainId) + `-` + item.blockNumber.toString() + `-` + item.logIndex.toString();
|
|
674
676
|
if (seenLogCoordinates.has(coordinate)) {
|
|
675
677
|
return;
|
|
676
678
|
} else {
|
|
@@ -687,7 +689,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
687
689
|
}
|
|
688
690
|
let setRawEvents = async sql => {
|
|
689
691
|
try {
|
|
690
|
-
return await executeSet(sql, rawEvents, (sql, items) => setOrThrow(sql, items, InternalTable.RawEvents.table, InternalTable.RawEvents.schema, pgSchema, chainIdMode));
|
|
692
|
+
return await executeSet(sql, rawEvents, (sql, items) => setOrThrow(sql, items, InternalTable.RawEvents.table, InternalTable.RawEvents.schema, pgSchema, setQueryCache, chainIdMode));
|
|
691
693
|
} catch (raw_exn) {
|
|
692
694
|
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
693
695
|
return classifyWriteError(specificError, InternalTable.RawEvents.table, exn);
|
|
@@ -791,14 +793,14 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
791
793
|
}));
|
|
792
794
|
}
|
|
793
795
|
let entityHistory = getEntityHistory(entityConfig);
|
|
794
|
-
promises.push(setOrThrow(sql, batchSetUpdates, entityHistory.table, entityHistory.setChangeSchema, pgSchema, chainIdMode));
|
|
796
|
+
promises.push(setOrThrow(sql, batchSetUpdates, entityHistory.table, entityHistory.setChangeSchema, pgSchema, setQueryCache, chainIdMode));
|
|
795
797
|
}
|
|
796
798
|
}
|
|
797
799
|
if (Utils.$$Array.notEmpty(entitiesToSet)) {
|
|
798
800
|
if (shouldRemoveInvalidUtf8) {
|
|
799
801
|
removeInvalidUtf8InPlace(entitiesToSet);
|
|
800
802
|
}
|
|
801
|
-
promises.push(setOrThrow(sql, entitiesToSet, entityConfig.table, getRowSchema(entityConfig), pgSchema, chainIdMode));
|
|
803
|
+
promises.push(setOrThrow(sql, entitiesToSet, entityConfig.table, getRowSchema(entityConfig), pgSchema, setQueryCache, chainIdMode));
|
|
802
804
|
}
|
|
803
805
|
if (Utils.$$Array.notEmpty(idsToDelete)) {
|
|
804
806
|
promises.push(deleteByIdsOrThrow(sql, pgSchema, idsToDelete, entityConfig.table, Primitive_option.some(scopeChainId)));
|
|
@@ -870,7 +872,7 @@ async function writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, co
|
|
|
870
872
|
if (exn$1.RE_EXN_ID === PgEncodingError) {
|
|
871
873
|
let escapeTables$1 = escapeTables !== undefined ? Primitive_option.valFromOption(escapeTables) : new Set();
|
|
872
874
|
escapeTables$1.add(exn$1.table);
|
|
873
|
-
return await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, Primitive_option.some(escapeTables$1));
|
|
875
|
+
return await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, updatedEntities, sinkPromise, chainMetaData, Primitive_option.some(escapeTables$1));
|
|
874
876
|
}
|
|
875
877
|
throw exn$1;
|
|
876
878
|
}
|
|
@@ -954,6 +956,7 @@ function make(sql, pgHost, pgSchema, pgPort, pgUser, pgDatabase, pgPassword, isH
|
|
|
954
956
|
let cacheDirPath = Path.resolve(".envio", "cache");
|
|
955
957
|
let storageName = "postgres";
|
|
956
958
|
let indexManager = IndexManager.make();
|
|
959
|
+
let setQueryCache = new WeakMap();
|
|
957
960
|
let loadCatalogRows = (sql, indexName) => sql.unsafe(IndexCatalog.makeQuery(pgSchema, indexName)).then(rows => S$RescriptSchema.parseOrThrow(rows, IndexCatalog.rowsSchema));
|
|
958
961
|
let refreshIndexCatalog = async () => {
|
|
959
962
|
let rows = await loadCatalogRows(sql, undefined);
|
|
@@ -1325,7 +1328,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
|
|
|
1325
1328
|
msg: `Committed ` + missing.length.toString() + ` schema indexes and the ready timestamp in ` + formatSeconds(timeRef) + `s.`
|
|
1326
1329
|
});
|
|
1327
1330
|
};
|
|
1328
|
-
let setOrThrow$1 = (items, table, itemSchema) => setOrThrow(sql, items, table, itemSchema, pgSchema,
|
|
1331
|
+
let setOrThrow$1 = (items, table, itemSchema) => setOrThrow(sql, items, table, itemSchema, pgSchema, setQueryCache, chainIdMode);
|
|
1329
1332
|
let setEffectCacheOrThrow = async (table, itemSchema, items, initialize) => {
|
|
1330
1333
|
if (initialize) {
|
|
1331
1334
|
await sql.unsafe(makeCreateTableQuery(table, pgSchema, false, undefined));
|
|
@@ -1487,7 +1490,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::` + addrCh
|
|
|
1487
1490
|
sinkPromise = undefined;
|
|
1488
1491
|
}
|
|
1489
1492
|
let primaryTimerRef = Performance.now();
|
|
1490
|
-
await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, updatedEffectsCache, pgUpdates, sinkPromise, chainMetaData, undefined);
|
|
1493
|
+
await writeBatch(sql, batch, pgSchema, rollback, isInReorgThreshold, config, allEntities, setEffectCacheOrThrow, setQueryCache, updatedEffectsCache, pgUpdates, sinkPromise, chainMetaData, undefined);
|
|
1491
1494
|
return onWrite(storageName, Performance.secondsSince(primaryTimerRef));
|
|
1492
1495
|
};
|
|
1493
1496
|
let close = () => sql.end();
|
|
@@ -1590,7 +1593,7 @@ export {
|
|
|
1590
1593
|
pgErrorMessageSchema,
|
|
1591
1594
|
PgEncodingError,
|
|
1592
1595
|
classifyWriteError,
|
|
1593
|
-
|
|
1596
|
+
makeSetQueryCache,
|
|
1594
1597
|
setOrThrow,
|
|
1595
1598
|
makeSchemaTableNamesQuery,
|
|
1596
1599
|
makeEffectCacheTableNamesQuery,
|
package/src/ReorgDetection.res
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
type blockDataWithTimestamp = {
|
|
2
|
-
blockHash: string,
|
|
3
|
-
blockNumber: int,
|
|
4
|
-
blockTimestamp: int,
|
|
5
|
-
}
|
|
6
|
-
|
|
7
1
|
type blockData = {
|
|
8
|
-
// Block hash is used for actual comparison to test for reorg
|
|
9
2
|
blockHash: string,
|
|
10
3
|
blockNumber: int,
|
|
11
4
|
}
|
|
12
5
|
|
|
13
|
-
external generalizeBlockDataWithTimestamp: blockDataWithTimestamp => blockData = "%identity"
|
|
14
|
-
|
|
15
6
|
type reorgDetected = {
|
|
16
7
|
scannedBlock: blockData,
|
|
17
8
|
receivedBlock: blockData,
|
|
@@ -30,216 +21,3 @@ let reorgDetectedToLogParams = (reorgDetected: reorgDetected, ~shouldRollbackOnR
|
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
type reorgResult = NoReorg | ReorgDetected(reorgDetected)
|
|
33
|
-
|
|
34
|
-
type t = {
|
|
35
|
-
// Whether to rollback on reorg
|
|
36
|
-
// Even if it's disabled, we still track reorgs checkpoints in memory
|
|
37
|
-
// and log when we detect an unhandled reorg
|
|
38
|
-
shouldRollbackOnReorg: bool,
|
|
39
|
-
// Number of blocks behind head, we want to keep track
|
|
40
|
-
// as a threshold for reorgs. If for eg. this is 200,
|
|
41
|
-
// it means we are accounting for reorgs up to 200 blocks
|
|
42
|
-
// behind the head
|
|
43
|
-
maxReorgDepth: int,
|
|
44
|
-
// A hash map of recent blockdata by block number to make comparison checks
|
|
45
|
-
// for reorgs.
|
|
46
|
-
dataByBlockNumber: dict<blockData>,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let make = (
|
|
50
|
-
~chainReorgCheckpoints: array<Internal.reorgCheckpoint>,
|
|
51
|
-
~maxReorgDepth,
|
|
52
|
-
~shouldRollbackOnReorg,
|
|
53
|
-
) => {
|
|
54
|
-
let dataByBlockNumber = Dict.make()
|
|
55
|
-
|
|
56
|
-
chainReorgCheckpoints->Array.forEach(block => {
|
|
57
|
-
dataByBlockNumber->Utils.Dict.setByInt(
|
|
58
|
-
block.blockNumber,
|
|
59
|
-
{
|
|
60
|
-
blockHash: block.blockHash,
|
|
61
|
-
blockNumber: block.blockNumber,
|
|
62
|
-
},
|
|
63
|
-
)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
shouldRollbackOnReorg,
|
|
68
|
-
maxReorgDepth,
|
|
69
|
-
dataByBlockNumber,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let getDataByBlockNumberCopyInThreshold = ({dataByBlockNumber, maxReorgDepth}: t, ~knownHeight) => {
|
|
74
|
-
// Js engine automatically orders numeric object keys
|
|
75
|
-
let ascBlockNumberKeys = dataByBlockNumber->Dict.keysToArray
|
|
76
|
-
let thresholdBlockNumber = knownHeight - maxReorgDepth
|
|
77
|
-
|
|
78
|
-
let copy = Dict.make()
|
|
79
|
-
|
|
80
|
-
for idx in 0 to ascBlockNumberKeys->Array.length - 1 {
|
|
81
|
-
let blockNumberKey = ascBlockNumberKeys->Array.getUnsafe(idx)
|
|
82
|
-
let scannedBlock = dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
|
|
83
|
-
let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber
|
|
84
|
-
if isInReorgThreshold {
|
|
85
|
-
copy->Dict.set(blockNumberKey, scannedBlock)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
copy
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Registers observed (blockNumber, blockHash) pairs from a range fetch, prunes
|
|
93
|
-
* unneeded data, and returns the updated state.
|
|
94
|
-
*
|
|
95
|
-
* Iterates the provided block hashes, skips entries outside the reorg threshold,
|
|
96
|
-
* and compares each one against the previously scanned data. Returns on the first
|
|
97
|
-
* mismatch as `ReorgDetected`.
|
|
98
|
-
*
|
|
99
|
-
* Resets internal state if shouldRollbackOnReorg is false (detect-only mode).
|
|
100
|
-
*/
|
|
101
|
-
let registerReorgGuard = (
|
|
102
|
-
{maxReorgDepth, shouldRollbackOnReorg} as self: t,
|
|
103
|
-
~blockHashes: array<blockData>,
|
|
104
|
-
~knownHeight,
|
|
105
|
-
) => {
|
|
106
|
-
let dataByBlockNumberCopyInThreshold = self->getDataByBlockNumberCopyInThreshold(~knownHeight)
|
|
107
|
-
let thresholdBlockNumber = knownHeight - maxReorgDepth
|
|
108
|
-
|
|
109
|
-
let maybeReorgDetected = ref(None)
|
|
110
|
-
let idx = ref(0)
|
|
111
|
-
while maybeReorgDetected.contents === None && idx.contents < blockHashes->Array.length {
|
|
112
|
-
let receivedBlock = blockHashes->Array.getUnsafe(idx.contents)
|
|
113
|
-
if receivedBlock.blockNumber >= thresholdBlockNumber {
|
|
114
|
-
let key = receivedBlock.blockNumber->Int.toString
|
|
115
|
-
// The working copy contains both previously scanned blocks AND blocks
|
|
116
|
-
// already written by earlier iterations of this same call, so a duplicate
|
|
117
|
-
// block number with a mismatching hash inside `blockHashes` itself is
|
|
118
|
-
// flagged as a reorg.
|
|
119
|
-
switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
120
|
-
| Some(scannedBlock) if scannedBlock.blockHash !== receivedBlock.blockHash =>
|
|
121
|
-
maybeReorgDetected := Some({receivedBlock, scannedBlock})
|
|
122
|
-
| _ => dataByBlockNumberCopyInThreshold->Dict.set(key, receivedBlock)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
idx := idx.contents + 1
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
switch maybeReorgDetected.contents {
|
|
129
|
-
| Some(reorgDetected) => (
|
|
130
|
-
shouldRollbackOnReorg
|
|
131
|
-
? self
|
|
132
|
-
: make(~chainReorgCheckpoints=[], ~maxReorgDepth, ~shouldRollbackOnReorg),
|
|
133
|
-
ReorgDetected(reorgDetected),
|
|
134
|
-
)
|
|
135
|
-
| None => (
|
|
136
|
-
{
|
|
137
|
-
maxReorgDepth,
|
|
138
|
-
dataByBlockNumber: dataByBlockNumberCopyInThreshold,
|
|
139
|
-
shouldRollbackOnReorg,
|
|
140
|
-
},
|
|
141
|
-
NoReorg,
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
Returns the latest block number which matches block number and hashes in the provided array
|
|
148
|
-
If it doesn't exist in the reorg threshold it returns NotFound
|
|
149
|
-
*/
|
|
150
|
-
let getLatestValidScannedBlock = (
|
|
151
|
-
reorgDetection: t,
|
|
152
|
-
~blockNumbersAndHashes: array<blockDataWithTimestamp>,
|
|
153
|
-
) => {
|
|
154
|
-
let verifiedDataByBlockNumber = Dict.make()
|
|
155
|
-
for idx in 0 to blockNumbersAndHashes->Array.length - 1 {
|
|
156
|
-
let blockData = blockNumbersAndHashes->Array.getUnsafe(idx)
|
|
157
|
-
verifiedDataByBlockNumber->Dict.set(blockData.blockNumber->Int.toString, blockData)
|
|
158
|
-
}
|
|
159
|
-
// Js engine automatically orders numeric object keys
|
|
160
|
-
let ascBlockNumberKeys = verifiedDataByBlockNumber->Dict.keysToArray
|
|
161
|
-
|
|
162
|
-
let getPrevScannedBlockNumber = idx =>
|
|
163
|
-
ascBlockNumberKeys
|
|
164
|
-
->Array.get(idx - 1)
|
|
165
|
-
->Option.map(key => {
|
|
166
|
-
(verifiedDataByBlockNumber->Dict.getUnsafe(key)).blockNumber
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
let rec loop = idx => {
|
|
170
|
-
switch ascBlockNumberKeys->Array.get(idx) {
|
|
171
|
-
| Some(blockNumberKey) =>
|
|
172
|
-
switch reorgDetection.dataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
|
|
173
|
-
| Some(scannedBlock)
|
|
174
|
-
if (verifiedDataByBlockNumber->Dict.getUnsafe(blockNumberKey)).blockHash ===
|
|
175
|
-
scannedBlock.blockHash =>
|
|
176
|
-
loop(idx + 1)
|
|
177
|
-
| _ => getPrevScannedBlockNumber(idx)
|
|
178
|
-
}
|
|
179
|
-
| None => getPrevScannedBlockNumber(idx)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
loop(0)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
Return a BlockNumbersAndHashes.t rolled back to where blockData is less
|
|
187
|
-
than the provided blockNumber
|
|
188
|
-
*/
|
|
189
|
-
let rollbackToValidBlockNumber = (
|
|
190
|
-
{dataByBlockNumber, maxReorgDepth, shouldRollbackOnReorg}: t,
|
|
191
|
-
~blockNumber: int,
|
|
192
|
-
) => {
|
|
193
|
-
// Js engine automatically orders numeric object keys
|
|
194
|
-
let ascBlockNumberKeys = dataByBlockNumber->Dict.keysToArray
|
|
195
|
-
|
|
196
|
-
let newDataByBlockNumber = Dict.make()
|
|
197
|
-
|
|
198
|
-
let rec loop = idx => {
|
|
199
|
-
switch ascBlockNumberKeys->Array.get(idx) {
|
|
200
|
-
| Some(blockNumberKey) => {
|
|
201
|
-
let scannedBlock = dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
|
|
202
|
-
let shouldKeep = scannedBlock.blockNumber <= blockNumber
|
|
203
|
-
if shouldKeep {
|
|
204
|
-
newDataByBlockNumber->Dict.set(blockNumberKey, scannedBlock)
|
|
205
|
-
loop(idx + 1)
|
|
206
|
-
} else {
|
|
207
|
-
()
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
| None => ()
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
loop(0)
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
maxReorgDepth,
|
|
217
|
-
dataByBlockNumber: newDataByBlockNumber,
|
|
218
|
-
shouldRollbackOnReorg,
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let getThresholdBlockNumbersBelowBlock = (self: t, ~blockNumber: int, ~knownHeight) => {
|
|
223
|
-
let arr = []
|
|
224
|
-
|
|
225
|
-
// Js engine automatically orders numeric object keys
|
|
226
|
-
let ascBlockNumberKeys = self.dataByBlockNumber->Dict.keysToArray
|
|
227
|
-
let thresholdBlockNumber = knownHeight - self.maxReorgDepth
|
|
228
|
-
|
|
229
|
-
for idx in 0 to ascBlockNumberKeys->Array.length - 1 {
|
|
230
|
-
let blockNumberKey = ascBlockNumberKeys->Array.getUnsafe(idx)
|
|
231
|
-
let scannedBlock = self.dataByBlockNumber->Dict.getUnsafe(blockNumberKey)
|
|
232
|
-
let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber
|
|
233
|
-
if isInReorgThreshold && scannedBlock.blockNumber < blockNumber {
|
|
234
|
-
arr->Array.push(scannedBlock.blockNumber)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
arr
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let getHashByBlockNumber = (reorgDetection: t, ~blockNumber) => {
|
|
241
|
-
switch reorgDetection.dataByBlockNumber->Utils.Dict.dangerouslyGetByIntNonOption(blockNumber) {
|
|
242
|
-
| Some(v) => Null.Value(v.blockHash)
|
|
243
|
-
| None => Null.Null
|
|
244
|
-
}
|
|
245
|
-
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
3
|
|
|
5
4
|
function reorgDetectedToLogParams(reorgDetected, shouldRollbackOnReorg) {
|
|
6
5
|
let scannedBlock = reorgDetected.scannedBlock;
|
|
@@ -14,169 +13,7 @@ function reorgDetectedToLogParams(reorgDetected, shouldRollbackOnReorg) {
|
|
|
14
13
|
};
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
function make(chainReorgCheckpoints, maxReorgDepth, shouldRollbackOnReorg) {
|
|
18
|
-
let dataByBlockNumber = {};
|
|
19
|
-
chainReorgCheckpoints.forEach(block => {
|
|
20
|
-
dataByBlockNumber[block.block_number] = {
|
|
21
|
-
blockHash: block.block_hash,
|
|
22
|
-
blockNumber: block.block_number
|
|
23
|
-
};
|
|
24
|
-
});
|
|
25
|
-
return {
|
|
26
|
-
shouldRollbackOnReorg: shouldRollbackOnReorg,
|
|
27
|
-
maxReorgDepth: maxReorgDepth,
|
|
28
|
-
dataByBlockNumber: dataByBlockNumber
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getDataByBlockNumberCopyInThreshold(param, knownHeight) {
|
|
33
|
-
let dataByBlockNumber = param.dataByBlockNumber;
|
|
34
|
-
let ascBlockNumberKeys = Object.keys(dataByBlockNumber);
|
|
35
|
-
let thresholdBlockNumber = knownHeight - param.maxReorgDepth | 0;
|
|
36
|
-
let copy = {};
|
|
37
|
-
for (let idx = 0, idx_finish = ascBlockNumberKeys.length; idx < idx_finish; ++idx) {
|
|
38
|
-
let blockNumberKey = ascBlockNumberKeys[idx];
|
|
39
|
-
let scannedBlock = dataByBlockNumber[blockNumberKey];
|
|
40
|
-
let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber;
|
|
41
|
-
if (isInReorgThreshold) {
|
|
42
|
-
copy[blockNumberKey] = scannedBlock;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return copy;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function registerReorgGuard(self, blockHashes, knownHeight) {
|
|
49
|
-
let maxReorgDepth = self.maxReorgDepth;
|
|
50
|
-
let shouldRollbackOnReorg = self.shouldRollbackOnReorg;
|
|
51
|
-
let dataByBlockNumberCopyInThreshold = getDataByBlockNumberCopyInThreshold(self, knownHeight);
|
|
52
|
-
let thresholdBlockNumber = knownHeight - maxReorgDepth | 0;
|
|
53
|
-
let maybeReorgDetected;
|
|
54
|
-
let idx = 0;
|
|
55
|
-
while (maybeReorgDetected === undefined && idx < blockHashes.length) {
|
|
56
|
-
let receivedBlock = blockHashes[idx];
|
|
57
|
-
if (receivedBlock.blockNumber >= thresholdBlockNumber) {
|
|
58
|
-
let key = receivedBlock.blockNumber.toString();
|
|
59
|
-
let scannedBlock = dataByBlockNumberCopyInThreshold[key];
|
|
60
|
-
if (scannedBlock !== undefined && scannedBlock.blockHash !== receivedBlock.blockHash) {
|
|
61
|
-
maybeReorgDetected = {
|
|
62
|
-
scannedBlock: scannedBlock,
|
|
63
|
-
receivedBlock: receivedBlock
|
|
64
|
-
};
|
|
65
|
-
} else {
|
|
66
|
-
dataByBlockNumberCopyInThreshold[key] = receivedBlock;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
idx = idx + 1 | 0;
|
|
70
|
-
};
|
|
71
|
-
let reorgDetected = maybeReorgDetected;
|
|
72
|
-
if (reorgDetected !== undefined) {
|
|
73
|
-
return [
|
|
74
|
-
shouldRollbackOnReorg ? self : make([], maxReorgDepth, shouldRollbackOnReorg),
|
|
75
|
-
{
|
|
76
|
-
TAG: "ReorgDetected",
|
|
77
|
-
_0: reorgDetected
|
|
78
|
-
}
|
|
79
|
-
];
|
|
80
|
-
} else {
|
|
81
|
-
return [
|
|
82
|
-
{
|
|
83
|
-
shouldRollbackOnReorg: shouldRollbackOnReorg,
|
|
84
|
-
maxReorgDepth: maxReorgDepth,
|
|
85
|
-
dataByBlockNumber: dataByBlockNumberCopyInThreshold
|
|
86
|
-
},
|
|
87
|
-
"NoReorg"
|
|
88
|
-
];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getLatestValidScannedBlock(reorgDetection, blockNumbersAndHashes) {
|
|
93
|
-
let verifiedDataByBlockNumber = {};
|
|
94
|
-
for (let idx = 0, idx_finish = blockNumbersAndHashes.length; idx < idx_finish; ++idx) {
|
|
95
|
-
let blockData = blockNumbersAndHashes[idx];
|
|
96
|
-
verifiedDataByBlockNumber[blockData.blockNumber.toString()] = blockData;
|
|
97
|
-
}
|
|
98
|
-
let ascBlockNumberKeys = Object.keys(verifiedDataByBlockNumber);
|
|
99
|
-
let getPrevScannedBlockNumber = idx => Stdlib_Option.map(ascBlockNumberKeys[idx - 1 | 0], key => verifiedDataByBlockNumber[key].blockNumber);
|
|
100
|
-
let _idx = 0;
|
|
101
|
-
while (true) {
|
|
102
|
-
let idx$1 = _idx;
|
|
103
|
-
let blockNumberKey = ascBlockNumberKeys[idx$1];
|
|
104
|
-
if (blockNumberKey === undefined) {
|
|
105
|
-
return getPrevScannedBlockNumber(idx$1);
|
|
106
|
-
}
|
|
107
|
-
let scannedBlock = reorgDetection.dataByBlockNumber[blockNumberKey];
|
|
108
|
-
if (scannedBlock === undefined) {
|
|
109
|
-
return getPrevScannedBlockNumber(idx$1);
|
|
110
|
-
}
|
|
111
|
-
if (verifiedDataByBlockNumber[blockNumberKey].blockHash !== scannedBlock.blockHash) {
|
|
112
|
-
return getPrevScannedBlockNumber(idx$1);
|
|
113
|
-
}
|
|
114
|
-
_idx = idx$1 + 1 | 0;
|
|
115
|
-
continue;
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function rollbackToValidBlockNumber(param, blockNumber) {
|
|
120
|
-
let dataByBlockNumber = param.dataByBlockNumber;
|
|
121
|
-
let ascBlockNumberKeys = Object.keys(dataByBlockNumber);
|
|
122
|
-
let newDataByBlockNumber = {};
|
|
123
|
-
let loop = _idx => {
|
|
124
|
-
while (true) {
|
|
125
|
-
let idx = _idx;
|
|
126
|
-
let blockNumberKey = ascBlockNumberKeys[idx];
|
|
127
|
-
if (blockNumberKey === undefined) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
let scannedBlock = dataByBlockNumber[blockNumberKey];
|
|
131
|
-
let shouldKeep = scannedBlock.blockNumber <= blockNumber;
|
|
132
|
-
if (!shouldKeep) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
newDataByBlockNumber[blockNumberKey] = scannedBlock;
|
|
136
|
-
_idx = idx + 1 | 0;
|
|
137
|
-
continue;
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
loop(0);
|
|
141
|
-
return {
|
|
142
|
-
shouldRollbackOnReorg: param.shouldRollbackOnReorg,
|
|
143
|
-
maxReorgDepth: param.maxReorgDepth,
|
|
144
|
-
dataByBlockNumber: newDataByBlockNumber
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function getThresholdBlockNumbersBelowBlock(self, blockNumber, knownHeight) {
|
|
149
|
-
let arr = [];
|
|
150
|
-
let ascBlockNumberKeys = Object.keys(self.dataByBlockNumber);
|
|
151
|
-
let thresholdBlockNumber = knownHeight - self.maxReorgDepth | 0;
|
|
152
|
-
for (let idx = 0, idx_finish = ascBlockNumberKeys.length; idx < idx_finish; ++idx) {
|
|
153
|
-
let blockNumberKey = ascBlockNumberKeys[idx];
|
|
154
|
-
let scannedBlock = self.dataByBlockNumber[blockNumberKey];
|
|
155
|
-
let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber;
|
|
156
|
-
if (isInReorgThreshold && scannedBlock.blockNumber < blockNumber) {
|
|
157
|
-
arr.push(scannedBlock.blockNumber);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return arr;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getHashByBlockNumber(reorgDetection, blockNumber) {
|
|
164
|
-
let v = reorgDetection.dataByBlockNumber[blockNumber];
|
|
165
|
-
if (v !== undefined) {
|
|
166
|
-
return v.blockHash;
|
|
167
|
-
} else {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
16
|
export {
|
|
173
17
|
reorgDetectedToLogParams,
|
|
174
|
-
make,
|
|
175
|
-
getDataByBlockNumberCopyInThreshold,
|
|
176
|
-
registerReorgGuard,
|
|
177
|
-
getLatestValidScannedBlock,
|
|
178
|
-
rollbackToValidBlockNumber,
|
|
179
|
-
getThresholdBlockNumbersBelowBlock,
|
|
180
|
-
getHashByBlockNumber,
|
|
181
18
|
}
|
|
182
19
|
/* No side effect */
|
package/src/Rollback.res
CHANGED
|
@@ -10,27 +10,29 @@ let getLastKnownValidBlock = async (
|
|
|
10
10
|
~reorgBlockNumber: int,
|
|
11
11
|
~isRealtime: bool,
|
|
12
12
|
) => {
|
|
13
|
-
//
|
|
13
|
+
// Before the search, not after: it re-fetches the scanned hashes through the
|
|
14
|
+
// sources, and a source answering from a cache it filled on the orphaned
|
|
15
|
+
// chain would confirm blocks that no longer exist - stopping the rollback
|
|
16
|
+
// short of the real fork.
|
|
17
|
+
chainState->ChainState.sourceManager->SourceManager.onReorg
|
|
18
|
+
|
|
19
|
+
// Don't include the reorg block itself - different source instances
|
|
14
20
|
// may have mismatching hashes at the head, so we always rollback
|
|
15
21
|
// the block where we detected the reorg.
|
|
16
22
|
let scannedBlockNumbers =
|
|
17
|
-
chainState
|
|
18
|
-
->ChainState.reorgDetection
|
|
19
|
-
->ReorgDetection.getThresholdBlockNumbersBelowBlock(
|
|
20
|
-
~blockNumber=reorgBlockNumber,
|
|
21
|
-
~knownHeight=chainState->ChainState.knownHeight,
|
|
22
|
-
)
|
|
23
|
+
chainState->ChainState.getReorgThresholdBlockNumbersBelow(~blockNumber=reorgBlockNumber)
|
|
23
24
|
|
|
24
25
|
switch scannedBlockNumbers {
|
|
25
26
|
| [] => chainState->ChainState.getHighestBlockBelowThreshold
|
|
26
27
|
| _ => {
|
|
27
|
-
let
|
|
28
|
+
let blockStore = await chainState
|
|
28
29
|
->ChainState.sourceManager
|
|
29
30
|
->SourceManager.getBlockHashes(~blockNumbers=scannedBlockNumbers, ~isRealtime)
|
|
30
31
|
|
|
31
|
-
switch chainState
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
switch chainState->ChainState.getLatestValidScannedBlock(
|
|
33
|
+
~blockStore,
|
|
34
|
+
~blockNumbers=scannedBlockNumbers,
|
|
35
|
+
) {
|
|
34
36
|
| Some(blockNumber) => blockNumber
|
|
35
37
|
| None => chainState->ChainState.getHighestBlockBelowThreshold
|
|
36
38
|
}
|
|
@@ -54,15 +56,12 @@ let rec rollback = async (
|
|
|
54
56
|
let chainState = state->IndexerState.getChainState(~chainId)
|
|
55
57
|
|
|
56
58
|
state->IndexerState.enterFindingReorgDepth
|
|
59
|
+
|
|
57
60
|
let rollbackTargetBlockNumber = await chainState->getLastKnownValidBlock(
|
|
58
61
|
~reorgBlockNumber,
|
|
59
62
|
~isRealtime=state->IndexerState.isRealtime,
|
|
60
63
|
)
|
|
61
64
|
|
|
62
|
-
chainState
|
|
63
|
-
->ChainState.sourceManager
|
|
64
|
-
->SourceManager.onReorg(~rollbackTargetBlock=rollbackTargetBlockNumber)
|
|
65
|
-
|
|
66
65
|
state->IndexerState.foundReorgDepth(~chainId, ~rollbackTargetBlockNumber)
|
|
67
66
|
// Rendezvous with the processing loop: whichever of {depth found, loop
|
|
68
67
|
// idle} happens last triggers the rollback; the earlier one finds the
|
package/src/Rollback.res.mjs
CHANGED
|
@@ -12,17 +12,17 @@ import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
|
12
12
|
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
13
13
|
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
14
14
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
15
|
-
import * as ReorgDetection from "./ReorgDetection.res.mjs";
|
|
16
15
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
17
16
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
18
17
|
|
|
19
18
|
async function getLastKnownValidBlock(chainState, reorgBlockNumber, isRealtime) {
|
|
20
|
-
|
|
19
|
+
SourceManager.onReorg(ChainState.sourceManager(chainState));
|
|
20
|
+
let scannedBlockNumbers = ChainState.getReorgThresholdBlockNumbersBelow(chainState, reorgBlockNumber);
|
|
21
21
|
if (scannedBlockNumbers.length === 0) {
|
|
22
22
|
return ChainState.getHighestBlockBelowThreshold(chainState);
|
|
23
23
|
}
|
|
24
|
-
let
|
|
25
|
-
let blockNumber =
|
|
24
|
+
let blockStore = await SourceManager.getBlockHashes(ChainState.sourceManager(chainState), scannedBlockNumbers, isRealtime);
|
|
25
|
+
let blockNumber = ChainState.getLatestValidScannedBlock(chainState, blockStore, scannedBlockNumbers);
|
|
26
26
|
if (blockNumber !== undefined) {
|
|
27
27
|
return blockNumber;
|
|
28
28
|
} else {
|
|
@@ -104,7 +104,6 @@ async function rollback(state, scheduleFetch, scheduleProcessing, scheduleRollba
|
|
|
104
104
|
let chainState = IndexerState.getChainState(state, chainId);
|
|
105
105
|
IndexerState.enterFindingReorgDepth(state);
|
|
106
106
|
let rollbackTargetBlockNumber = await getLastKnownValidBlock(chainState, match.blockNumber, IndexerState.isRealtime(state));
|
|
107
|
-
SourceManager.onReorg(ChainState.sourceManager(chainState), rollbackTargetBlockNumber);
|
|
108
107
|
IndexerState.foundReorgDepth(state, chainId, rollbackTargetBlockNumber);
|
|
109
108
|
return scheduleRollback();
|
|
110
109
|
case "FoundReorgDepth" :
|
package/src/SimulateItems.res
CHANGED
|
@@ -350,7 +350,7 @@ let parse = (
|
|
|
350
350
|
let liveRegistrations =
|
|
351
351
|
HandlerRegister.getSimulateOnEventRegistrations(
|
|
352
352
|
~config,
|
|
353
|
-
~chainId
|
|
353
|
+
~chainId,
|
|
354
354
|
~eventConfig,
|
|
355
355
|
)->Array.filter(reg =>
|
|
356
356
|
(reg.handler->Option.isSome || reg.contractRegister->Option.isSome) &&
|
|
@@ -388,7 +388,7 @@ let parse = (
|
|
|
388
388
|
contractName: eventConfig.contractName,
|
|
389
389
|
eventName: eventConfig.name,
|
|
390
390
|
params,
|
|
391
|
-
chainId
|
|
391
|
+
chainId,
|
|
392
392
|
srcAddress,
|
|
393
393
|
logIndex,
|
|
394
394
|
transaction,
|
package/src/Utils.res
CHANGED
|
@@ -576,6 +576,9 @@ module Set = {
|
|
|
576
576
|
@send
|
|
577
577
|
external intersection: (t<'value>, t<'value>) => t<'value> = "intersection"
|
|
578
578
|
|
|
579
|
+
@send
|
|
580
|
+
external union: (t<'value>, t<'value>) => t<'value> = "union"
|
|
581
|
+
|
|
579
582
|
let immutableAdd: (t<'a>, 'a) => t<'a> = %raw(`(set, value) => {
|
|
580
583
|
return new Set([...set, value])
|
|
581
584
|
}`)
|