envio 2.31.0-alpha.0 → 2.31.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/Batch.res +400 -28
- package/src/Batch.res.js +286 -24
- package/src/EventRegister.res +9 -3
- package/src/EventRegister.res.js +6 -3
- package/src/EventRegister.resi +4 -1
- package/src/FetchState.res +116 -155
- package/src/FetchState.res.js +116 -106
- package/src/Internal.res +49 -0
- package/src/InternalConfig.res +1 -1
- package/src/Persistence.res +16 -1
- package/src/Persistence.res.js +1 -1
- package/src/PgStorage.res +49 -61
- package/src/PgStorage.res.js +44 -37
- package/src/Prometheus.res +7 -1
- package/src/Prometheus.res.js +8 -1
- package/src/ReorgDetection.res +222 -235
- package/src/ReorgDetection.res.js +34 -28
- package/src/SafeCheckpointTracking.res +132 -0
- package/src/SafeCheckpointTracking.res.js +95 -0
- package/src/Utils.res +64 -21
- package/src/Utils.res.js +61 -30
- package/src/db/EntityHistory.res +172 -294
- package/src/db/EntityHistory.res.js +98 -218
- package/src/db/InternalTable.gen.ts +13 -13
- package/src/db/InternalTable.res +286 -77
- package/src/db/InternalTable.res.js +160 -79
- package/src/db/Table.res +1 -0
- package/src/db/Table.res.js +1 -1
- package/src/sources/EventRouter.res +1 -1
- package/src/sources/Source.res +1 -1
package/src/ReorgDetection.res
CHANGED
|
@@ -40,188 +40,168 @@ type reorgResult = NoReorg | ReorgDetected(reorgDetected)
|
|
|
40
40
|
type validBlockError = NotFound | AlreadyReorgedHashes
|
|
41
41
|
type validBlockResult = result<blockDataWithTimestamp, validBlockError>
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
~shouldRollbackOnReorg: bool,
|
|
63
|
-
) => (t, reorgResult)
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
Returns the latest block data which matches block number and hashes in the provided array
|
|
67
|
-
If it doesn't exist in the reorg threshold it returns None or the latest scanned block outside of the reorg threshold
|
|
68
|
-
*/
|
|
69
|
-
let getLatestValidScannedBlock: (
|
|
70
|
-
t,
|
|
71
|
-
~blockNumbersAndHashes: array<blockDataWithTimestamp>,
|
|
72
|
-
~currentBlockHeight: int,
|
|
73
|
-
~skipReorgDuplicationCheck: bool=?,
|
|
74
|
-
) => validBlockResult
|
|
75
|
-
|
|
76
|
-
let getThresholdBlockNumbers: (t, ~currentBlockHeight: int) => array<int>
|
|
77
|
-
|
|
78
|
-
let rollbackToValidBlockNumber: (t, ~blockNumber: int) => t
|
|
79
|
-
} = {
|
|
80
|
-
type t = {
|
|
81
|
-
// Number of blocks behind head, we want to keep track
|
|
82
|
-
// as a threshold for reorgs. If for eg. this is 200,
|
|
83
|
-
// it means we are accounting for reorgs up to 200 blocks
|
|
84
|
-
// behind the head
|
|
85
|
-
confirmedBlockThreshold: int,
|
|
86
|
-
// A hash map of recent blockdata by block number to make comparison checks
|
|
87
|
-
// for reorgs.
|
|
88
|
-
dataByBlockNumber: dict<blockData>,
|
|
89
|
-
// The latest block which detected a reorg
|
|
90
|
-
// and should never be valid.
|
|
91
|
-
// We keep track of this to avoid responses
|
|
92
|
-
// with the stale data from other data-source instances.
|
|
93
|
-
detectedReorgBlock: option<blockData>,
|
|
94
|
-
}
|
|
43
|
+
type t = {
|
|
44
|
+
// Whether to rollback on reorg
|
|
45
|
+
// Even if it's disabled, we still track reorgs checkpoints in memory
|
|
46
|
+
// and log when we detect an unhandled reorg
|
|
47
|
+
shouldRollbackOnReorg: bool,
|
|
48
|
+
// Number of blocks behind head, we want to keep track
|
|
49
|
+
// as a threshold for reorgs. If for eg. this is 200,
|
|
50
|
+
// it means we are accounting for reorgs up to 200 blocks
|
|
51
|
+
// behind the head
|
|
52
|
+
maxReorgDepth: int,
|
|
53
|
+
// A hash map of recent blockdata by block number to make comparison checks
|
|
54
|
+
// for reorgs.
|
|
55
|
+
dataByBlockNumber: dict<blockData>,
|
|
56
|
+
// The latest block which detected a reorg
|
|
57
|
+
// and should never be valid.
|
|
58
|
+
// We keep track of this to avoid responses
|
|
59
|
+
// with the stale data from other data-source instances.
|
|
60
|
+
detectedReorgBlock: option<blockData>,
|
|
61
|
+
}
|
|
95
62
|
|
|
96
|
-
|
|
97
|
-
|
|
63
|
+
let make = (
|
|
64
|
+
~chainReorgCheckpoints: array<Internal.reorgCheckpoint>,
|
|
65
|
+
~maxReorgDepth,
|
|
66
|
+
~shouldRollbackOnReorg,
|
|
67
|
+
~detectedReorgBlock=?,
|
|
68
|
+
) => {
|
|
69
|
+
let dataByBlockNumber = Js.Dict.empty()
|
|
70
|
+
|
|
71
|
+
chainReorgCheckpoints->Belt.Array.forEach(block => {
|
|
72
|
+
dataByBlockNumber->Utils.Dict.setByInt(
|
|
73
|
+
block.blockNumber,
|
|
74
|
+
{
|
|
75
|
+
blockHash: block.blockHash,
|
|
76
|
+
blockNumber: block.blockNumber,
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
})
|
|
98
80
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
81
|
+
{
|
|
82
|
+
shouldRollbackOnReorg,
|
|
83
|
+
maxReorgDepth,
|
|
84
|
+
dataByBlockNumber,
|
|
85
|
+
detectedReorgBlock,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
102
88
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
let getDataByBlockNumberCopyInThreshold = (
|
|
90
|
+
{dataByBlockNumber, maxReorgDepth}: t,
|
|
91
|
+
~currentBlockHeight,
|
|
92
|
+
) => {
|
|
93
|
+
// Js engine automatically orders numeric object keys
|
|
94
|
+
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
95
|
+
let thresholdBlockNumber = currentBlockHeight - maxReorgDepth
|
|
96
|
+
|
|
97
|
+
let copy = Js.Dict.empty()
|
|
98
|
+
|
|
99
|
+
for idx in 0 to ascBlockNumberKeys->Array.length - 1 {
|
|
100
|
+
let blockNumberKey = ascBlockNumberKeys->Js.Array2.unsafe_get(idx)
|
|
101
|
+
let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
|
|
102
|
+
let isInReorgThreshold = scannedBlock.blockNumber >= thresholdBlockNumber
|
|
103
|
+
if isInReorgThreshold {
|
|
104
|
+
copy->Js.Dict.set(blockNumberKey, scannedBlock)
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
|
-
//Instantiates empty LastBlockHashes
|
|
110
|
-
let empty = (~confirmedBlockThreshold) => {
|
|
111
|
-
confirmedBlockThreshold,
|
|
112
|
-
dataByBlockNumber: Js.Dict.empty(),
|
|
113
|
-
detectedReorgBlock: None,
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let getDataByBlockNumberCopyInThreshold = (
|
|
117
|
-
{dataByBlockNumber, confirmedBlockThreshold}: t,
|
|
118
|
-
~currentBlockHeight,
|
|
119
|
-
) => {
|
|
120
|
-
// Js engine automatically orders numeric object keys
|
|
121
|
-
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
122
|
-
let thresholdBlockNumber = currentBlockHeight - confirmedBlockThreshold
|
|
123
107
|
|
|
124
|
-
|
|
108
|
+
copy
|
|
109
|
+
}
|
|
125
110
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
111
|
+
/** Registers a new reorg guard, prunes unneeded data, and returns the updated state.
|
|
112
|
+
* Resets internal state if shouldRollbackOnReorg is false (detect-only mode)
|
|
113
|
+
*/
|
|
114
|
+
let registerReorgGuard = (
|
|
115
|
+
{maxReorgDepth, shouldRollbackOnReorg} as self: t,
|
|
116
|
+
~reorgGuard: reorgGuard,
|
|
117
|
+
~currentBlockHeight,
|
|
118
|
+
) => {
|
|
119
|
+
let dataByBlockNumberCopyInThreshold =
|
|
120
|
+
self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
121
|
+
|
|
122
|
+
let {rangeLastBlock, prevRangeLastBlock} = reorgGuard
|
|
123
|
+
|
|
124
|
+
let maybeReorgDetected = switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
|
|
125
|
+
rangeLastBlock.blockNumber->Int.toString,
|
|
126
|
+
) {
|
|
127
|
+
| Some(scannedBlock) if scannedBlock.blockHash !== rangeLastBlock.blockHash =>
|
|
128
|
+
Some({
|
|
129
|
+
receivedBlock: rangeLastBlock,
|
|
130
|
+
scannedBlock,
|
|
131
|
+
})
|
|
132
|
+
| _ =>
|
|
133
|
+
switch prevRangeLastBlock {
|
|
134
|
+
//If parentHash is None, then it's the genesis block (no reorg)
|
|
135
|
+
//Need to check that parentHash matches because of the dynamic contracts
|
|
136
|
+
| None => None
|
|
137
|
+
| Some(prevRangeLastBlock) =>
|
|
138
|
+
switch dataByBlockNumberCopyInThreshold->Utils.Dict.dangerouslyGetNonOption(
|
|
139
|
+
prevRangeLastBlock.blockNumber->Int.toString,
|
|
140
|
+
) {
|
|
141
|
+
| Some(scannedBlock) if scannedBlock.blockHash !== prevRangeLastBlock.blockHash =>
|
|
142
|
+
Some({
|
|
143
|
+
receivedBlock: prevRangeLastBlock,
|
|
144
|
+
scannedBlock,
|
|
145
|
+
})
|
|
146
|
+
| _ => None
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
|
-
|
|
135
|
-
copy
|
|
136
149
|
}
|
|
137
150
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Some({
|
|
154
|
-
receivedBlock: rangeLastBlock,
|
|
155
|
-
scannedBlock,
|
|
156
|
-
})
|
|
157
|
-
| _ =>
|
|
151
|
+
switch maybeReorgDetected {
|
|
152
|
+
| Some(reorgDetected) => (
|
|
153
|
+
shouldRollbackOnReorg
|
|
154
|
+
? {
|
|
155
|
+
...self,
|
|
156
|
+
detectedReorgBlock: Some(reorgDetected.scannedBlock),
|
|
157
|
+
}
|
|
158
|
+
: make(~chainReorgCheckpoints=[], ~maxReorgDepth, ~shouldRollbackOnReorg),
|
|
159
|
+
ReorgDetected(reorgDetected),
|
|
160
|
+
)
|
|
161
|
+
| None => {
|
|
162
|
+
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
163
|
+
rangeLastBlock.blockNumber->Int.toString,
|
|
164
|
+
rangeLastBlock,
|
|
165
|
+
)
|
|
158
166
|
switch prevRangeLastBlock {
|
|
159
|
-
|
|
160
|
-
//Need to check that parentHash matches because of the dynamic contracts
|
|
161
|
-
| None => None
|
|
167
|
+
| None => ()
|
|
162
168
|
| Some(prevRangeLastBlock) =>
|
|
163
|
-
|
|
169
|
+
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
164
170
|
prevRangeLastBlock.blockNumber->Int.toString,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Some({
|
|
168
|
-
receivedBlock: prevRangeLastBlock,
|
|
169
|
-
scannedBlock,
|
|
170
|
-
})
|
|
171
|
-
| _ => None
|
|
172
|
-
}
|
|
171
|
+
prevRangeLastBlock,
|
|
172
|
+
)
|
|
173
173
|
}
|
|
174
|
-
}
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
ReorgDetected(reorgDetected),
|
|
175
|
+
(
|
|
176
|
+
{
|
|
177
|
+
maxReorgDepth,
|
|
178
|
+
dataByBlockNumber: dataByBlockNumberCopyInThreshold,
|
|
179
|
+
detectedReorgBlock: None,
|
|
180
|
+
shouldRollbackOnReorg,
|
|
181
|
+
},
|
|
182
|
+
NoReorg,
|
|
185
183
|
)
|
|
186
|
-
| None => {
|
|
187
|
-
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
188
|
-
rangeLastBlock.blockNumber->Int.toString,
|
|
189
|
-
rangeLastBlock,
|
|
190
|
-
)
|
|
191
|
-
switch prevRangeLastBlock {
|
|
192
|
-
| None => ()
|
|
193
|
-
| Some(prevRangeLastBlock) =>
|
|
194
|
-
dataByBlockNumberCopyInThreshold->Js.Dict.set(
|
|
195
|
-
prevRangeLastBlock.blockNumber->Int.toString,
|
|
196
|
-
prevRangeLastBlock,
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
(
|
|
201
|
-
{
|
|
202
|
-
confirmedBlockThreshold,
|
|
203
|
-
dataByBlockNumber: dataByBlockNumberCopyInThreshold,
|
|
204
|
-
detectedReorgBlock: None,
|
|
205
|
-
},
|
|
206
|
-
NoReorg,
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
184
|
}
|
|
210
185
|
}
|
|
186
|
+
}
|
|
211
187
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
188
|
+
/**
|
|
189
|
+
Returns the latest block data which matches block number and hashes in the provided array
|
|
190
|
+
If it doesn't exist in the reorg threshold it returns None or the latest scanned block outside of the reorg threshold
|
|
191
|
+
*/
|
|
192
|
+
let getLatestValidScannedBlock = (
|
|
193
|
+
self: t,
|
|
194
|
+
~blockNumbersAndHashes: array<blockDataWithTimestamp>,
|
|
195
|
+
~currentBlockHeight,
|
|
196
|
+
~skipReorgDuplicationCheck=false,
|
|
197
|
+
) => {
|
|
198
|
+
let verifiedDataByBlockNumber = Js.Dict.empty()
|
|
199
|
+
for idx in 0 to blockNumbersAndHashes->Array.length - 1 {
|
|
200
|
+
let blockData = blockNumbersAndHashes->Array.getUnsafe(idx)
|
|
201
|
+
verifiedDataByBlockNumber->Js.Dict.set(blockData.blockNumber->Int.toString, blockData)
|
|
202
|
+
}
|
|
223
203
|
|
|
224
|
-
|
|
204
|
+
/*
|
|
225
205
|
Let's say we indexed block X with hash A.
|
|
226
206
|
The next query we got the block X with hash B.
|
|
227
207
|
We assume that the hash A is reorged since we received it earlier than B.
|
|
@@ -234,98 +214,105 @@ module LastBlockScannedHashes: {
|
|
|
234
214
|
we can skip the reorg duplication check if we're sure that the block hashes query
|
|
235
215
|
is not coming from a different instance. (let's say we tried several times)
|
|
236
216
|
*/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| None => false
|
|
246
|
-
}
|
|
217
|
+
let isAlreadyReorgedResponse = skipReorgDuplicationCheck
|
|
218
|
+
? false
|
|
219
|
+
: switch self.detectedReorgBlock {
|
|
220
|
+
| Some(detectedReorgBlock) =>
|
|
221
|
+
switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(
|
|
222
|
+
detectedReorgBlock.blockNumber->Int.toString,
|
|
223
|
+
) {
|
|
224
|
+
| Some(verifiedBlockData) => verifiedBlockData.blockHash === detectedReorgBlock.blockHash
|
|
247
225
|
| None => false
|
|
248
226
|
}
|
|
249
|
-
|
|
250
|
-
if isAlreadyReorgedResponse {
|
|
251
|
-
Error(AlreadyReorgedHashes)
|
|
252
|
-
} else {
|
|
253
|
-
let dataByBlockNumber = self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
254
|
-
// Js engine automatically orders numeric object keys
|
|
255
|
-
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
256
|
-
|
|
257
|
-
let getPrevScannedBlock = idx =>
|
|
258
|
-
switch ascBlockNumberKeys
|
|
259
|
-
->Belt.Array.get(idx - 1)
|
|
260
|
-
->Option.flatMap(key => {
|
|
261
|
-
// We should already validate that the block number is verified at the point
|
|
262
|
-
verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(key)
|
|
263
|
-
}) {
|
|
264
|
-
| Some(data) => Ok(data)
|
|
265
|
-
| None => Error(NotFound)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
let rec loop = idx => {
|
|
269
|
-
switch ascBlockNumberKeys->Belt.Array.get(idx) {
|
|
270
|
-
| Some(blockNumberKey) =>
|
|
271
|
-
let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
|
|
272
|
-
switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
|
|
273
|
-
| None =>
|
|
274
|
-
Js.Exn.raiseError(
|
|
275
|
-
`Unexpected case. Couldn't find verified hash for block number ${blockNumberKey}`,
|
|
276
|
-
)
|
|
277
|
-
| Some(verifiedBlockData) if verifiedBlockData.blockHash === scannedBlock.blockHash =>
|
|
278
|
-
loop(idx + 1)
|
|
279
|
-
| Some(_) => getPrevScannedBlock(idx)
|
|
280
|
-
}
|
|
281
|
-
| None => getPrevScannedBlock(idx)
|
|
282
|
-
}
|
|
227
|
+
| None => false
|
|
283
228
|
}
|
|
284
|
-
loop(0)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
229
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
let rollbackToValidBlockNumber = (
|
|
293
|
-
{dataByBlockNumber, confirmedBlockThreshold}: t,
|
|
294
|
-
~blockNumber: int,
|
|
295
|
-
) => {
|
|
230
|
+
if isAlreadyReorgedResponse {
|
|
231
|
+
Error(AlreadyReorgedHashes)
|
|
232
|
+
} else {
|
|
233
|
+
let dataByBlockNumber = self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
296
234
|
// Js engine automatically orders numeric object keys
|
|
297
235
|
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
298
236
|
|
|
299
|
-
let
|
|
237
|
+
let getPrevScannedBlock = idx =>
|
|
238
|
+
switch ascBlockNumberKeys
|
|
239
|
+
->Belt.Array.get(idx - 1)
|
|
240
|
+
->Option.flatMap(key => {
|
|
241
|
+
// We should already validate that the block number is verified at the point
|
|
242
|
+
verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(key)
|
|
243
|
+
}) {
|
|
244
|
+
| Some(data) => Ok(data)
|
|
245
|
+
| None => Error(NotFound)
|
|
246
|
+
}
|
|
300
247
|
|
|
301
248
|
let rec loop = idx => {
|
|
302
249
|
switch ascBlockNumberKeys->Belt.Array.get(idx) {
|
|
303
|
-
| Some(blockNumberKey) =>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
250
|
+
| Some(blockNumberKey) =>
|
|
251
|
+
let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
|
|
252
|
+
switch verifiedDataByBlockNumber->Utils.Dict.dangerouslyGetNonOption(blockNumberKey) {
|
|
253
|
+
| None =>
|
|
254
|
+
Js.Exn.raiseError(
|
|
255
|
+
`Unexpected case. Couldn't find verified hash for block number ${blockNumberKey}`,
|
|
256
|
+
)
|
|
257
|
+
| Some(verifiedBlockData) if verifiedBlockData.blockHash === scannedBlock.blockHash =>
|
|
258
|
+
loop(idx + 1)
|
|
259
|
+
| Some(_) => getPrevScannedBlock(idx)
|
|
312
260
|
}
|
|
313
|
-
| None => ()
|
|
261
|
+
| None => getPrevScannedBlock(idx)
|
|
314
262
|
}
|
|
315
263
|
}
|
|
316
264
|
loop(0)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
317
267
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
268
|
+
/**
|
|
269
|
+
Return a BlockNumbersAndHashes.t rolled back to where blockData is less
|
|
270
|
+
than the provided blockNumber
|
|
271
|
+
*/
|
|
272
|
+
let rollbackToValidBlockNumber = (
|
|
273
|
+
{dataByBlockNumber, maxReorgDepth, shouldRollbackOnReorg}: t,
|
|
274
|
+
~blockNumber: int,
|
|
275
|
+
) => {
|
|
276
|
+
// Js engine automatically orders numeric object keys
|
|
277
|
+
let ascBlockNumberKeys = dataByBlockNumber->Js.Dict.keys
|
|
278
|
+
|
|
279
|
+
let newDataByBlockNumber = Js.Dict.empty()
|
|
280
|
+
|
|
281
|
+
let rec loop = idx => {
|
|
282
|
+
switch ascBlockNumberKeys->Belt.Array.get(idx) {
|
|
283
|
+
| Some(blockNumberKey) => {
|
|
284
|
+
let scannedBlock = dataByBlockNumber->Js.Dict.unsafeGet(blockNumberKey)
|
|
285
|
+
let shouldKeep = scannedBlock.blockNumber <= blockNumber
|
|
286
|
+
if shouldKeep {
|
|
287
|
+
newDataByBlockNumber->Js.Dict.set(blockNumberKey, scannedBlock)
|
|
288
|
+
loop(idx + 1)
|
|
289
|
+
} else {
|
|
290
|
+
()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
| None => ()
|
|
322
294
|
}
|
|
323
295
|
}
|
|
296
|
+
loop(0)
|
|
297
|
+
|
|
298
|
+
{
|
|
299
|
+
maxReorgDepth,
|
|
300
|
+
dataByBlockNumber: newDataByBlockNumber,
|
|
301
|
+
detectedReorgBlock: None,
|
|
302
|
+
shouldRollbackOnReorg,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
324
305
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
306
|
+
let getThresholdBlockNumbers = (self: t, ~currentBlockHeight) => {
|
|
307
|
+
let dataByBlockNumberCopyInThreshold =
|
|
308
|
+
self->getDataByBlockNumberCopyInThreshold(~currentBlockHeight)
|
|
309
|
+
|
|
310
|
+
dataByBlockNumberCopyInThreshold->Js.Dict.values->Js.Array2.map(v => v.blockNumber)
|
|
311
|
+
}
|
|
328
312
|
|
|
329
|
-
|
|
313
|
+
let getHashByBlockNumber = (reorgDetection: t, ~blockNumber) => {
|
|
314
|
+
switch reorgDetection.dataByBlockNumber->Utils.Dict.dangerouslyGetByIntNonOption(blockNumber) {
|
|
315
|
+
| Some(v) => Js.Null.Value(v.blockHash)
|
|
316
|
+
| None => Js.Null.Null
|
|
330
317
|
}
|
|
331
318
|
}
|
|
@@ -18,30 +18,26 @@ function reorgDetectedToLogParams(reorgDetected, shouldRollbackOnReorg) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
21
|
+
function make(chainReorgCheckpoints, maxReorgDepth, shouldRollbackOnReorg, detectedReorgBlock) {
|
|
22
22
|
var dataByBlockNumber = {};
|
|
23
|
-
Belt_Array.forEach(
|
|
24
|
-
dataByBlockNumber[block.
|
|
23
|
+
Belt_Array.forEach(chainReorgCheckpoints, (function (block) {
|
|
24
|
+
dataByBlockNumber[block.block_number] = {
|
|
25
|
+
blockHash: block.block_hash,
|
|
26
|
+
blockNumber: block.block_number
|
|
27
|
+
};
|
|
25
28
|
}));
|
|
26
29
|
return {
|
|
27
|
-
|
|
30
|
+
shouldRollbackOnReorg: shouldRollbackOnReorg,
|
|
31
|
+
maxReorgDepth: maxReorgDepth,
|
|
28
32
|
dataByBlockNumber: dataByBlockNumber,
|
|
29
33
|
detectedReorgBlock: detectedReorgBlock
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
function empty(confirmedBlockThreshold) {
|
|
34
|
-
return {
|
|
35
|
-
confirmedBlockThreshold: confirmedBlockThreshold,
|
|
36
|
-
dataByBlockNumber: {},
|
|
37
|
-
detectedReorgBlock: undefined
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
37
|
function getDataByBlockNumberCopyInThreshold(param, currentBlockHeight) {
|
|
42
38
|
var dataByBlockNumber = param.dataByBlockNumber;
|
|
43
39
|
var ascBlockNumberKeys = Object.keys(dataByBlockNumber);
|
|
44
|
-
var thresholdBlockNumber = currentBlockHeight - param.
|
|
40
|
+
var thresholdBlockNumber = currentBlockHeight - param.maxReorgDepth | 0;
|
|
45
41
|
var copy = {};
|
|
46
42
|
for(var idx = 0 ,idx_finish = ascBlockNumberKeys.length; idx < idx_finish; ++idx){
|
|
47
43
|
var blockNumberKey = ascBlockNumberKeys[idx];
|
|
@@ -55,8 +51,9 @@ function getDataByBlockNumberCopyInThreshold(param, currentBlockHeight) {
|
|
|
55
51
|
return copy;
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
function registerReorgGuard(self, reorgGuard, currentBlockHeight
|
|
59
|
-
var
|
|
54
|
+
function registerReorgGuard(self, reorgGuard, currentBlockHeight) {
|
|
55
|
+
var maxReorgDepth = self.maxReorgDepth;
|
|
56
|
+
var shouldRollbackOnReorg = self.shouldRollbackOnReorg;
|
|
60
57
|
var dataByBlockNumberCopyInThreshold = getDataByBlockNumberCopyInThreshold(self, currentBlockHeight);
|
|
61
58
|
var prevRangeLastBlock = reorgGuard.prevRangeLastBlock;
|
|
62
59
|
var rangeLastBlock = reorgGuard.rangeLastBlock;
|
|
@@ -85,10 +82,11 @@ function registerReorgGuard(self, reorgGuard, currentBlockHeight, shouldRollback
|
|
|
85
82
|
if (maybeReorgDetected !== undefined) {
|
|
86
83
|
return [
|
|
87
84
|
shouldRollbackOnReorg ? ({
|
|
88
|
-
|
|
85
|
+
shouldRollbackOnReorg: self.shouldRollbackOnReorg,
|
|
86
|
+
maxReorgDepth: self.maxReorgDepth,
|
|
89
87
|
dataByBlockNumber: self.dataByBlockNumber,
|
|
90
88
|
detectedReorgBlock: maybeReorgDetected.scannedBlock
|
|
91
|
-
}) :
|
|
89
|
+
}) : make([], maxReorgDepth, shouldRollbackOnReorg, undefined),
|
|
92
90
|
{
|
|
93
91
|
TAG: "ReorgDetected",
|
|
94
92
|
_0: maybeReorgDetected
|
|
@@ -101,7 +99,8 @@ function registerReorgGuard(self, reorgGuard, currentBlockHeight, shouldRollback
|
|
|
101
99
|
}
|
|
102
100
|
return [
|
|
103
101
|
{
|
|
104
|
-
|
|
102
|
+
shouldRollbackOnReorg: shouldRollbackOnReorg,
|
|
103
|
+
maxReorgDepth: maxReorgDepth,
|
|
105
104
|
dataByBlockNumber: dataByBlockNumberCopyInThreshold,
|
|
106
105
|
detectedReorgBlock: undefined
|
|
107
106
|
},
|
|
@@ -196,7 +195,8 @@ function rollbackToValidBlockNumber(param, blockNumber) {
|
|
|
196
195
|
};
|
|
197
196
|
loop(0);
|
|
198
197
|
return {
|
|
199
|
-
|
|
198
|
+
shouldRollbackOnReorg: param.shouldRollbackOnReorg,
|
|
199
|
+
maxReorgDepth: param.maxReorgDepth,
|
|
200
200
|
dataByBlockNumber: newDataByBlockNumber,
|
|
201
201
|
detectedReorgBlock: undefined
|
|
202
202
|
};
|
|
@@ -209,15 +209,21 @@ function getThresholdBlockNumbers(self, currentBlockHeight) {
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
212
|
+
function getHashByBlockNumber(reorgDetection, blockNumber) {
|
|
213
|
+
var v = reorgDetection.dataByBlockNumber[blockNumber];
|
|
214
|
+
if (v !== undefined) {
|
|
215
|
+
return v.blockHash;
|
|
216
|
+
} else {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
220
|
|
|
221
221
|
exports.reorgDetectedToLogParams = reorgDetectedToLogParams;
|
|
222
|
-
exports.
|
|
222
|
+
exports.make = make;
|
|
223
|
+
exports.getDataByBlockNumberCopyInThreshold = getDataByBlockNumberCopyInThreshold;
|
|
224
|
+
exports.registerReorgGuard = registerReorgGuard;
|
|
225
|
+
exports.getLatestValidScannedBlock = getLatestValidScannedBlock;
|
|
226
|
+
exports.rollbackToValidBlockNumber = rollbackToValidBlockNumber;
|
|
227
|
+
exports.getThresholdBlockNumbers = getThresholdBlockNumbers;
|
|
228
|
+
exports.getHashByBlockNumber = getHashByBlockNumber;
|
|
223
229
|
/* No side effect */
|