envio 3.15.0 → 3.16.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/README.md +40 -34
- package/evm.schema.json +11 -2
- package/fuel.schema.json +10 -1
- package/index.d.ts +6 -0
- package/licenses/README.md +1 -1
- package/package.json +8 -8
- package/src/Batch.res.mjs +5 -5
- package/src/BatchProcessing.res +1 -0
- package/src/BatchProcessing.res.mjs +1 -0
- package/src/ChainState.res +48 -7
- package/src/ChainState.res.mjs +27 -11
- package/src/Clock.res +57 -0
- package/src/Clock.res.mjs +72 -0
- package/src/Config.res +10 -3
- package/src/Config.res.mjs +4 -2
- package/src/Core.res +4 -40
- package/src/EffectState.res +10 -2
- package/src/EffectState.res.mjs +8 -4
- package/src/EffectState.resi +3 -1
- package/src/EntityId.res +2 -7
- package/src/EntityId.res.mjs +1 -8
- package/src/EntityTables.res +4 -1
- package/src/EntityTables.res.mjs +1 -1
- package/src/FetchState.res +157 -69
- package/src/FetchState.res.mjs +144 -49
- package/src/FinalizeBackfill.res +2 -4
- package/src/FinalizeBackfill.res.mjs +3 -4
- package/src/InMemoryTable.res +17 -16
- package/src/InMemoryTable.res.mjs +14 -15
- package/src/IndexerState.res +28 -4
- package/src/IndexerState.res.mjs +19 -6
- package/src/IndexerState.resi +4 -0
- package/src/Internal.res +0 -49
- package/src/Internal.res.mjs +1 -80
- package/src/LoadLayer.res +14 -8
- package/src/LoadLayer.res.mjs +8 -6
- package/src/Main.res.mjs +1 -1
- package/src/Persistence.res +3 -23
- package/src/Persistence.res.mjs +3 -3
- package/src/Rollback.res +0 -1
- package/src/Rollback.res.mjs +1 -1
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +1 -1
- package/src/StagedRows.res +106 -0
- package/src/StagedRows.res.mjs +102 -0
- package/src/Staging.res +32 -14
- package/src/Staging.res.mjs +17 -20
- package/src/Storage.res +277 -948
- package/src/Storage.res.mjs +215 -707
- package/src/Supervisor.res +75 -56
- package/src/Supervisor.res.mjs +55 -26
- package/src/TestIndexer.res +22 -27
- package/src/TestIndexer.res.mjs +20 -17
- package/src/Throttler.res +15 -8
- package/src/Throttler.res.mjs +9 -6
- package/src/Throttler.resi +1 -1
- package/src/Utils.res +0 -6
- package/src/Utils.res.mjs +0 -5
- package/src/Writing.res +0 -1
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +0 -322
- package/src/bindings/ClickHouse.res.mjs +0 -338
- package/src/bindings/StorageClient.res +107 -174
- package/src/bindings/StorageClient.res.mjs +19 -8
- package/src/bindings/Vitest.res +0 -26
- package/src/bindings/Vitest.res.mjs +0 -3
- package/src/db/Table.res +24 -150
- package/src/db/Table.res.mjs +38 -272
- package/src/sources/ChainSources.res +2 -0
- package/src/sources/ChainSources.res.mjs +2 -2
- package/src/sources/EvmChain.res +3 -0
- package/src/sources/EvmChain.res.mjs +4 -2
- package/src/sources/EvmHyperSyncSource.res +2 -2
- package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
- package/src/sources/EvmRpcClient.res +13 -3
- package/src/sources/EvmRpcClient.res.mjs +4 -3
- package/src/sources/EvmRpcWs.res +4 -3
- package/src/sources/EvmRpcWs.res.mjs +2 -2
- package/src/sources/HeightFeed.res +31 -10
- package/src/sources/HeightFeed.res.mjs +10 -6
- package/src/sources/HeightStream.res +32 -17
- package/src/sources/HeightStream.res.mjs +10 -10
- package/src/sources/HyperSyncSSE.res +4 -6
- package/src/sources/HyperSyncSSE.res.mjs +2 -2
- package/src/sources/RpcSource.res +10 -1
- package/src/sources/RpcSource.res.mjs +4 -3
- package/src/sources/Source.res +3 -0
- package/src/sources/SourceManager.res +84 -77
- package/src/sources/SourceManager.res.mjs +17 -20
- package/src/sources/SourceManager.resi +3 -4
- package/src/sources/StartBlockResolver.res +1 -0
- package/src/sources/StartBlockResolver.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +2 -2
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/svm.schema.json +11 -2
- package/src/PgWriting.res +0 -36
- package/src/PgWriting.res.mjs +0 -34
- package/src/db/IndexDefinition.res +0 -47
- package/src/db/IndexDefinition.res.mjs +0 -67
|
@@ -85,7 +85,7 @@ type t = {
|
|
|
85
85
|
// Should take into consideration partitions fetching for previous states (before rollback)
|
|
86
86
|
mutable fetchingPartitionsCount: int,
|
|
87
87
|
recoveryTimeout: float,
|
|
88
|
-
|
|
88
|
+
clock: Clock.t,
|
|
89
89
|
// Why a source was last disabled for good. A disabled source is never
|
|
90
90
|
// retried, so by the time the manager runs out of sources this is the reason
|
|
91
91
|
// it did — and the only thing that tells a caller what to change.
|
|
@@ -93,11 +93,10 @@ type t = {
|
|
|
93
93
|
mutable hasRealtime: bool,
|
|
94
94
|
mutable committedRateLimitTimeMs: float,
|
|
95
95
|
mutable rateLimitWaiters: int,
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// (TUI) can compute elapsed time with their own Date.now() reads.
|
|
96
|
+
// When the current rate-limit window started on `clock`, or None if not
|
|
97
|
+
// currently waiting.
|
|
99
98
|
mutable activeRateLimitStartMs: option<float>,
|
|
100
|
-
//
|
|
99
|
+
// Timestamp on `clock` by which the server expects the longest current
|
|
101
100
|
// wait to clear. Tracks the latest reset across concurrent waiters so
|
|
102
101
|
// the displayed countdown reflects when the indexer will actually retry.
|
|
103
102
|
mutable activeRateLimitResetAtMs: option<float>,
|
|
@@ -206,20 +205,20 @@ let inFlightCount = sourceManager => sourceManager.fetchingPartitionsCount
|
|
|
206
205
|
let getRateLimitTimeMs = sourceManager =>
|
|
207
206
|
sourceManager.committedRateLimitTimeMs +.
|
|
208
207
|
switch sourceManager.activeRateLimitStartMs {
|
|
209
|
-
| Some(startMs) =>
|
|
208
|
+
| Some(startMs) => sourceManager.clock.now() -. startMs
|
|
210
209
|
| None => 0.0
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
let getRateLimitResetInMs = sourceManager =>
|
|
214
213
|
switch sourceManager.activeRateLimitResetAtMs {
|
|
215
214
|
| Some(resetAt) =>
|
|
216
|
-
let remaining = resetAt -.
|
|
215
|
+
let remaining = resetAt -. sourceManager.clock.now()
|
|
217
216
|
remaining > 0.0 ? Some(remaining) : None
|
|
218
217
|
| None => None
|
|
219
218
|
}
|
|
220
219
|
|
|
221
220
|
let startRateLimitTimeout = (sourceManager, ~resetMs) => {
|
|
222
|
-
let now =
|
|
221
|
+
let now = sourceManager.clock.now()
|
|
223
222
|
if sourceManager.rateLimitWaiters === 0 {
|
|
224
223
|
sourceManager.activeRateLimitStartMs = Some(now)
|
|
225
224
|
}
|
|
@@ -237,7 +236,7 @@ let stopRateLimitTimeout = sourceManager => {
|
|
|
237
236
|
switch sourceManager.activeRateLimitStartMs {
|
|
238
237
|
| Some(startMs) =>
|
|
239
238
|
sourceManager.committedRateLimitTimeMs =
|
|
240
|
-
sourceManager.committedRateLimitTimeMs +.
|
|
239
|
+
sourceManager.committedRateLimitTimeMs +. sourceManager.clock.now() -. startMs
|
|
241
240
|
sourceManager.activeRateLimitStartMs = None
|
|
242
241
|
| None => ()
|
|
243
242
|
}
|
|
@@ -259,16 +258,8 @@ let waitForRateLimitReset = async (sourceManager: t, ~resetMs, ~retry, ~logger)
|
|
|
259
258
|
->Int.toString}s. For higher limits upgrade your plan at https://envio.dev/app/api-tokens.`,
|
|
260
259
|
~params={"retry": retry, "waitMs": waitMs},
|
|
261
260
|
)
|
|
262
|
-
let waitedAt = Date.now()
|
|
263
261
|
sourceManager->startRateLimitTimeout(~resetMs=waitMs)
|
|
264
|
-
await
|
|
265
|
-
// Timers run on the event loop's cached clock, so one can fire with Date.now()
|
|
266
|
-
// a millisecond short of the wait it was set for, and the wait is counted by
|
|
267
|
-
// Date.now(). Temporary until main takes the same fix.
|
|
268
|
-
let short = waitedAt +. waitMs->Int.toFloat -. Date.now()
|
|
269
|
-
if short > 0. {
|
|
270
|
-
await Utils.delay(short->Math.ceil->Float.toInt)
|
|
271
|
-
}
|
|
262
|
+
await sourceManager.clock->Clock.delay(~label="rate-limit reset", waitMs)
|
|
272
263
|
sourceManager->stopRateLimitTimeout
|
|
273
264
|
}
|
|
274
265
|
|
|
@@ -354,11 +345,12 @@ type timing = {
|
|
|
354
345
|
reducedPollingInterval?: int,
|
|
355
346
|
recoveryTimeout?: float,
|
|
356
347
|
getHeightRetryInterval?: (~retry: int) => int,
|
|
357
|
-
retryBackoffScale?: float,
|
|
358
348
|
heightPollTimeout?: int,
|
|
349
|
+
clock?: Clock.t,
|
|
359
350
|
}
|
|
360
351
|
|
|
361
352
|
let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing: timing={}) => {
|
|
353
|
+
let clock = timing.clock->Option.getOr(Clock.real)
|
|
362
354
|
let newBlockStallTimeout = timing.newBlockStallTimeout->Option.getOr(60_000)
|
|
363
355
|
let newBlockStallTimeoutRealtime = timing.newBlockStallTimeoutRealtime->Option.getOr(20_000)
|
|
364
356
|
let stalledPollingInterval = timing.stalledPollingInterval->Option.getOr(5_000)
|
|
@@ -392,6 +384,7 @@ let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing:
|
|
|
392
384
|
~recordRequestStats=stats => requestStats->recordStatsInto(stats),
|
|
393
385
|
~getHeightRetryInterval,
|
|
394
386
|
~pollTimeoutMillis=timing.heightPollTimeout->Option.getOr(60_000),
|
|
387
|
+
~clock,
|
|
395
388
|
),
|
|
396
389
|
disabled: false,
|
|
397
390
|
lastFailedAt: None,
|
|
@@ -408,7 +401,7 @@ let make = (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime, ~timing:
|
|
|
408
401
|
stalledPollingInterval,
|
|
409
402
|
reducedPollingInterval,
|
|
410
403
|
recoveryTimeout,
|
|
411
|
-
|
|
404
|
+
clock,
|
|
412
405
|
statusStart: Performance.now(),
|
|
413
406
|
status: Idle,
|
|
414
407
|
idleSeconds: 0.,
|
|
@@ -517,8 +510,8 @@ let compareByOldestFailure = (a: sourceState, b: sourceState) =>
|
|
|
517
510
|
}
|
|
518
511
|
|
|
519
512
|
// Priority: working primaries > working secondaries > all primaries.
|
|
520
|
-
let getNextSources = (sourceManager, ~isRealtime, ~excludedSources=?) => {
|
|
521
|
-
let now =
|
|
513
|
+
let getNextSources = (sourceManager: t, ~isRealtime, ~excludedSources=?) => {
|
|
514
|
+
let now = sourceManager.clock.now()
|
|
522
515
|
let workingPrimarySources = []
|
|
523
516
|
let allPrimarySources = []
|
|
524
517
|
let workingSecondarySources = []
|
|
@@ -589,10 +582,8 @@ let retryBackoffMillis = retry =>
|
|
|
589
582
|
// 0, these must never busy-loop when there is no other source to move to.
|
|
590
583
|
let minRecoverableBackoffMillis = 50
|
|
591
584
|
|
|
592
|
-
// The logged backoff stays the unscaled one, so a scaled-down test still reads
|
|
593
|
-
// the schedule an operator would see.
|
|
594
585
|
let sleepBackoff = (sourceManager: t, millis) =>
|
|
595
|
-
|
|
586
|
+
sourceManager.clock->Clock.delay(~label="retry backoff", millis)
|
|
596
587
|
|
|
597
588
|
// Back off before retrying the same request, failing the source over first when
|
|
598
589
|
// another one can actually take over. Marking `lastFailedAt` only demotes this
|
|
@@ -614,7 +605,7 @@ let backoffBeforeRetry = async (
|
|
|
614
605
|
let switchedToWorkingSource = if (
|
|
615
606
|
retry >= failoverAfterRetries && (retry - failoverAfterRetries)->mod(2) === 0
|
|
616
607
|
) {
|
|
617
|
-
let now =
|
|
608
|
+
let now = sourceManager.clock.now()
|
|
618
609
|
sourceState.lastFailedAt = Some(now)
|
|
619
610
|
switch sourceManager->getNextSource(~isRealtime, ~excludedSources?) {
|
|
620
611
|
| Some(next) =>
|
|
@@ -851,8 +842,8 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
|
|
|
851
842
|
let cleanup = () => {
|
|
852
843
|
watched->Array.forEach(subscription => subscription.unsubscribe())
|
|
853
844
|
watched->Utils.Array.clearInPlace
|
|
854
|
-
|
|
855
|
-
|
|
845
|
+
sourceManager.clock->Clock.clearRef(pokeTimeoutId)
|
|
846
|
+
sourceManager.clock->Clock.clearRef(stallTimeoutId)
|
|
856
847
|
}
|
|
857
848
|
|
|
858
849
|
let settled = ref(false)
|
|
@@ -922,32 +913,44 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
|
|
|
922
913
|
// Re-armed because distrusting a stream is a one-shot — the next height it
|
|
923
914
|
// delivers takes it back at its word — and the silence that earned it can
|
|
924
915
|
// come straight back.
|
|
925
|
-
let rec armPokeTimeout = () =>
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
916
|
+
let rec armPokeTimeout = () =>
|
|
917
|
+
pokeTimeoutId :=
|
|
918
|
+
Some(
|
|
919
|
+
sourceManager.clock.setTimeout(
|
|
920
|
+
~label="stall poke",
|
|
921
|
+
() => {
|
|
922
|
+
pokeTimeoutId := None
|
|
923
|
+
if !settled.contents {
|
|
924
|
+
watched->Array.forEach(subscription => subscription.distrustStream())
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Re-checked: distrusting can answer the wait, and a timer armed
|
|
928
|
+
// after the cleanup that follows is one nothing can ever clear.
|
|
929
|
+
if !settled.contents {
|
|
930
|
+
armPokeTimeout()
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
sourceManager.clock->Clock.jitter(stallTimeout),
|
|
934
|
+
),
|
|
935
|
+
)
|
|
937
936
|
|
|
938
937
|
// Punctual, unlike the poke above: newBlockStallTimeout is a promise to the
|
|
939
938
|
// operator about when they hear about a quiet chain, and spreading that
|
|
940
939
|
// would report it early. Fires once — the warning is worth saying once per
|
|
941
940
|
// wait, and a fallback stays recruited.
|
|
942
|
-
let armStallTimeout = () =>
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
941
|
+
let armStallTimeout = () =>
|
|
942
|
+
stallTimeoutId :=
|
|
943
|
+
Some(
|
|
944
|
+
sourceManager.clock.setTimeout(
|
|
945
|
+
~label="stall timeout",
|
|
946
|
+
() => {
|
|
947
|
+
stallTimeoutId := None
|
|
948
|
+
if !settled.contents {
|
|
949
|
+
stalled := true
|
|
950
|
+
|
|
951
|
+
// Build fallback: non-disabled sources not in mainSources with a valid role, even with recent lastFailedAt
|
|
952
|
+
let fallbackSources = []
|
|
953
|
+
sourcesState->Array.forEach(sourceState => {
|
|
951
954
|
if (
|
|
952
955
|
!sourceState.disabled &&
|
|
953
956
|
!(mainSources->Array.includes(sourceState)) &&
|
|
@@ -959,37 +962,41 @@ let waitForNewBlock = (sourceManager: t, ~knownHeight, ~isRealtime, ~reducedPoll
|
|
|
959
962
|
) {
|
|
960
963
|
fallbackSources->Array.push(sourceState)
|
|
961
964
|
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
switch fallbackSources {
|
|
968
|
+
| [] =>
|
|
969
|
+
logger->Logger.warn(
|
|
970
|
+
`No new blocks detected within ${(stallTimeout / 1000)
|
|
971
|
+
->Int.toString}s. Polling will continue at a reduced rate. For better reliability, refer to our RPC fallback guide: https://docs.envio.dev/docs/HyperIndex/rpc-sync`,
|
|
972
|
+
)
|
|
973
|
+
| _ =>
|
|
974
|
+
logger->Logger.warn(
|
|
975
|
+
`No new blocks detected within ${(stallTimeout / 1000)
|
|
976
|
+
->Int.toString}s. Continuing polling with secondary RPC sources from the configuration.`,
|
|
977
|
+
)
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Recruited to poll, not to stream: see `watch`.
|
|
981
|
+
fallbackSources->Array.forEach(sourceState =>
|
|
982
|
+
sourceState->watch(~withStream=false)
|
|
970
983
|
)
|
|
971
|
-
| _ =>
|
|
972
|
-
logger->Logger.warn(
|
|
973
|
-
`No new blocks detected within ${(stallTimeout / 1000)
|
|
974
|
-
->Int.toString}s. Continuing polling with secondary RPC sources from the configuration.`,
|
|
975
|
-
)
|
|
976
|
-
}
|
|
977
984
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
if !settled.contents {
|
|
989
|
-
watched->Array.forEach(subscription => subscription.distrustStream())
|
|
985
|
+
// A fallback recruited here can still be holding a stream from an
|
|
986
|
+
// earlier wait that made it a primary, and a live stream is not
|
|
987
|
+
// polled behind. It has to inherit the verdict the primaries
|
|
988
|
+
// already earned — this wait has heard nothing for a whole window
|
|
989
|
+
// — or it would sit silent behind that stream until the next
|
|
990
|
+
// spread poke. Distrusting a waiter that already does so is a
|
|
991
|
+
// no-op, so this reaches the new ones only.
|
|
992
|
+
if !settled.contents {
|
|
993
|
+
watched->Array.forEach(subscription => subscription.distrustStream())
|
|
994
|
+
}
|
|
990
995
|
}
|
|
991
|
-
}
|
|
992
|
-
|
|
996
|
+
},
|
|
997
|
+
stallTimeout,
|
|
998
|
+
),
|
|
999
|
+
)
|
|
993
1000
|
|
|
994
1001
|
armPokeTimeout()
|
|
995
1002
|
armStallTimeout()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Clock from "../Clock.res.mjs";
|
|
3
4
|
import * as Utils from "../Utils.res.mjs";
|
|
4
5
|
import * as Logger from "../Logger.res.mjs";
|
|
5
6
|
import * as Source from "./Source.res.mjs";
|
|
@@ -153,7 +154,7 @@ function inFlightCount(sourceManager) {
|
|
|
153
154
|
function getRateLimitTimeMs(sourceManager) {
|
|
154
155
|
let startMs = sourceManager.activeRateLimitStartMs;
|
|
155
156
|
return sourceManager.committedRateLimitTimeMs + (
|
|
156
|
-
startMs !== undefined ?
|
|
157
|
+
startMs !== undefined ? sourceManager.clock.now() - startMs : 0.0
|
|
157
158
|
);
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -162,14 +163,14 @@ function getRateLimitResetInMs(sourceManager) {
|
|
|
162
163
|
if (resetAt === undefined) {
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
|
-
let remaining = resetAt -
|
|
166
|
+
let remaining = resetAt - sourceManager.clock.now();
|
|
166
167
|
if (remaining > 0.0) {
|
|
167
168
|
return remaining;
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
function startRateLimitTimeout(sourceManager, resetMs) {
|
|
172
|
-
let now =
|
|
173
|
+
let now = sourceManager.clock.now();
|
|
173
174
|
if (sourceManager.rateLimitWaiters === 0) {
|
|
174
175
|
sourceManager.activeRateLimitStartMs = now;
|
|
175
176
|
}
|
|
@@ -186,20 +187,15 @@ async function waitForRateLimitReset(sourceManager, resetMs, retry, logger) {
|
|
|
186
187
|
retry: retry,
|
|
187
188
|
waitMs: waitMs
|
|
188
189
|
});
|
|
189
|
-
let waitedAt = Date.now();
|
|
190
190
|
startRateLimitTimeout(sourceManager, waitMs);
|
|
191
|
-
await
|
|
192
|
-
let short = waitedAt + waitMs - Date.now();
|
|
193
|
-
if (short > 0) {
|
|
194
|
-
await Utils.delay(Math.ceil(short) | 0);
|
|
195
|
-
}
|
|
191
|
+
await Clock.delay(sourceManager.clock, "rate-limit reset", waitMs);
|
|
196
192
|
sourceManager.rateLimitWaiters = sourceManager.rateLimitWaiters - 1 | 0;
|
|
197
193
|
if (sourceManager.rateLimitWaiters !== 0) {
|
|
198
194
|
return;
|
|
199
195
|
}
|
|
200
196
|
let startMs = sourceManager.activeRateLimitStartMs;
|
|
201
197
|
if (startMs !== undefined) {
|
|
202
|
-
sourceManager.committedRateLimitTimeMs = sourceManager.committedRateLimitTimeMs +
|
|
198
|
+
sourceManager.committedRateLimitTimeMs = sourceManager.committedRateLimitTimeMs + sourceManager.clock.now() - startMs;
|
|
203
199
|
sourceManager.activeRateLimitStartMs = undefined;
|
|
204
200
|
}
|
|
205
201
|
sourceManager.activeRateLimitResetAtMs = undefined;
|
|
@@ -278,6 +274,7 @@ function makeGetHeightRetryInterval(initialRetryInterval, backoffMultiplicative,
|
|
|
278
274
|
|
|
279
275
|
function make(sources, logger, isRealtime, timingOpt) {
|
|
280
276
|
let timing = timingOpt !== undefined ? timingOpt : ({});
|
|
277
|
+
let clock = Stdlib_Option.getOr(timing.clock, Clock.real);
|
|
281
278
|
let newBlockStallTimeout = Stdlib_Option.getOr(timing.newBlockStallTimeout, 60000);
|
|
282
279
|
let newBlockStallTimeoutRealtime = Stdlib_Option.getOr(timing.newBlockStallTimeoutRealtime, 20000);
|
|
283
280
|
let stalledPollingInterval = Stdlib_Option.getOr(timing.stalledPollingInterval, 5000);
|
|
@@ -293,7 +290,7 @@ function make(sources, logger, isRealtime, timingOpt) {
|
|
|
293
290
|
let requestStats = {};
|
|
294
291
|
return {
|
|
295
292
|
source: source,
|
|
296
|
-
feed: HeightFeed.make(source, logger, stats => recordStatsInto(requestStats, stats), getHeightRetryInterval$1, Stdlib_Option.getOr(timing.heightPollTimeout, 60000)),
|
|
293
|
+
feed: HeightFeed.make(source, logger, stats => recordStatsInto(requestStats, stats), getHeightRetryInterval$1, Stdlib_Option.getOr(timing.heightPollTimeout, 60000), clock),
|
|
297
294
|
disabled: false,
|
|
298
295
|
lastFailedAt: undefined,
|
|
299
296
|
requestStats: requestStats
|
|
@@ -314,7 +311,7 @@ function make(sources, logger, isRealtime, timingOpt) {
|
|
|
314
311
|
waitingLogged: false,
|
|
315
312
|
fetchingPartitionsCount: 0,
|
|
316
313
|
recoveryTimeout: recoveryTimeout,
|
|
317
|
-
|
|
314
|
+
clock: clock,
|
|
318
315
|
disableReason: undefined,
|
|
319
316
|
hasRealtime: hasRealtime,
|
|
320
317
|
committedRateLimitTimeMs: 0.0,
|
|
@@ -422,7 +419,7 @@ function compareByOldestFailure(a, b) {
|
|
|
422
419
|
}
|
|
423
420
|
|
|
424
421
|
function getNextSources(sourceManager, isRealtime, excludedSources) {
|
|
425
|
-
let now =
|
|
422
|
+
let now = sourceManager.clock.now();
|
|
426
423
|
let workingPrimarySources = [];
|
|
427
424
|
let allPrimarySources = [];
|
|
428
425
|
let workingSecondarySources = [];
|
|
@@ -475,7 +472,7 @@ function getNextSource(sourceManager, isRealtime, excludedSources) {
|
|
|
475
472
|
}
|
|
476
473
|
|
|
477
474
|
function sleepBackoff(sourceManager, millis) {
|
|
478
|
-
return
|
|
475
|
+
return Clock.delay(sourceManager.clock, "retry backoff", millis);
|
|
479
476
|
}
|
|
480
477
|
|
|
481
478
|
async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime, backoffMillis, minBackoffMillisOpt, failoverAfterRetriesOpt, excludedSources) {
|
|
@@ -483,7 +480,7 @@ async function backoffBeforeRetry(sourceManager, sourceState, retry, isRealtime,
|
|
|
483
480
|
let failoverAfterRetries = failoverAfterRetriesOpt !== undefined ? failoverAfterRetriesOpt : 2;
|
|
484
481
|
let switchedToWorkingSource;
|
|
485
482
|
if (retry >= failoverAfterRetries && (retry - failoverAfterRetries | 0) % 2 === 0) {
|
|
486
|
-
let now =
|
|
483
|
+
let now = sourceManager.clock.now();
|
|
487
484
|
sourceState.lastFailedAt = now;
|
|
488
485
|
let next = getNextSource(sourceManager, isRealtime, excludedSources);
|
|
489
486
|
if (next !== undefined) {
|
|
@@ -599,8 +596,8 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
|
|
|
599
596
|
let cleanup = () => {
|
|
600
597
|
watched.forEach(subscription => subscription.unsubscribe());
|
|
601
598
|
Utils.$$Array.clearInPlace(watched);
|
|
602
|
-
|
|
603
|
-
|
|
599
|
+
Clock.clearRef(sourceManager.clock, pokeTimeoutId);
|
|
600
|
+
Clock.clearRef(sourceManager.clock, stallTimeoutId);
|
|
604
601
|
};
|
|
605
602
|
let settled = {
|
|
606
603
|
contents: false
|
|
@@ -648,7 +645,7 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
|
|
|
648
645
|
return;
|
|
649
646
|
}
|
|
650
647
|
let armPokeTimeout = () => {
|
|
651
|
-
pokeTimeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
648
|
+
pokeTimeoutId.contents = Primitive_option.some(sourceManager.clock.setTimeout("stall poke", () => {
|
|
652
649
|
pokeTimeoutId.contents = undefined;
|
|
653
650
|
if (!settled.contents) {
|
|
654
651
|
watched.forEach(subscription => subscription.distrustStream());
|
|
@@ -656,10 +653,10 @@ function waitForNewBlock(sourceManager, knownHeight, isRealtime, reducedPolling)
|
|
|
656
653
|
if (!settled.contents) {
|
|
657
654
|
return armPokeTimeout();
|
|
658
655
|
}
|
|
659
|
-
},
|
|
656
|
+
}, Clock.jitter(sourceManager.clock, stallTimeout)));
|
|
660
657
|
};
|
|
661
658
|
let armStallTimeout = () => {
|
|
662
|
-
stallTimeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
659
|
+
stallTimeoutId.contents = Primitive_option.some(sourceManager.clock.setTimeout("stall timeout", () => {
|
|
663
660
|
stallTimeoutId.contents = undefined;
|
|
664
661
|
if (settled.contents) {
|
|
665
662
|
return;
|
|
@@ -17,11 +17,10 @@ type timing = {
|
|
|
17
17
|
reducedPollingInterval?: int,
|
|
18
18
|
recoveryTimeout?: float,
|
|
19
19
|
getHeightRetryInterval?: (~retry: int) => int,
|
|
20
|
-
// Multiplies the sleep before retrying an items query or a block-hash
|
|
21
|
-
// lookup, but not the backoff it logs. A rate limit's wait is the server's
|
|
22
|
-
// to set and isn't scaled.
|
|
23
|
-
retryBackoffScale?: float,
|
|
24
20
|
heightPollTimeout?: int,
|
|
21
|
+
// Every timer the manager and its height feeds arm, and every time they
|
|
22
|
+
// read. A test passes one that only moves when the test advances it.
|
|
23
|
+
clock?: Clock.t,
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
let make: (~sources: array<Source.t>, ~logger: Logger.t, ~isRealtime: bool, ~timing: timing=?) => t
|
|
@@ -25,6 +25,7 @@ let makeProbeSources = (chainConfig: Config.chain, ~logger, ~lowercaseAddresses)
|
|
|
25
25
|
~blockStore=BlockStore.make(~ecosystem=chainConfig.ecosystem),
|
|
26
26
|
~transactionStore=TransactionStore.make(~ecosystem=chainConfig.ecosystem),
|
|
27
27
|
~logger,
|
|
28
|
+
~maxAddressesPerRequest=Env.maxAddrInPartition,
|
|
28
29
|
)
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Env from "../Env.res.mjs";
|
|
3
4
|
import * as Utils from "../Utils.res.mjs";
|
|
4
5
|
import * as Logger from "../Logger.res.mjs";
|
|
5
6
|
import * as ChainId from "../ChainId.res.mjs";
|
|
@@ -14,7 +15,7 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
|
|
|
14
15
|
|
|
15
16
|
function makeProbeSources(chainConfig, logger, lowercaseAddresses) {
|
|
16
17
|
let addressStore = AddressStore.make(chainConfig.ecosystem, []);
|
|
17
|
-
return ChainSources.make(chainConfig, [], undefined, addressStore, lowercaseAddresses, BlockStore.make(chainConfig.ecosystem), TransactionStore.make(chainConfig.ecosystem), logger);
|
|
18
|
+
return ChainSources.make(chainConfig, [], undefined, addressStore, lowercaseAddresses, BlockStore.make(chainConfig.ecosystem), TransactionStore.make(chainConfig.ecosystem), logger, Env.maxAddrInPartition);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function candidateSources(sources) {
|
|
@@ -132,4 +133,4 @@ export {
|
|
|
132
133
|
resolveHeadOrThrow,
|
|
133
134
|
resolveAllOrThrow,
|
|
134
135
|
}
|
|
135
|
-
/*
|
|
136
|
+
/* Env Not a pure module */
|
|
@@ -277,7 +277,7 @@ let make = (
|
|
|
277
277
|
{height, requestStats: [{method: "getHeight", seconds}]}
|
|
278
278
|
},
|
|
279
279
|
getItemsOrThrow,
|
|
280
|
-
createHeightSubscription: (~onHeight, ~onStatus) =>
|
|
281
|
-
HyperSyncSSE.subscribe(~hyperSyncUrl=endpointUrl, ~apiToken, ~onHeight, ~onStatus),
|
|
280
|
+
createHeightSubscription: (~clock, ~onHeight, ~onStatus) =>
|
|
281
|
+
HyperSyncSSE.subscribe(~clock, ~hyperSyncUrl=endpointUrl, ~apiToken, ~onHeight, ~onStatus),
|
|
282
282
|
}
|
|
283
283
|
}
|
|
@@ -205,7 +205,7 @@ function make(param) {
|
|
|
205
205
|
};
|
|
206
206
|
},
|
|
207
207
|
getItemsOrThrow: getItemsOrThrow,
|
|
208
|
-
createHeightSubscription: (onHeight, onStatus) => HyperSyncSSE.subscribe(endpointUrl, apiToken, onHeight, onStatus)
|
|
208
|
+
createHeightSubscription: (clock, onHeight, onStatus) => HyperSyncSSE.subscribe(clock, endpointUrl, apiToken, onHeight, onStatus)
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
|
package/svm.schema.json
CHANGED
|
@@ -219,6 +219,15 @@
|
|
|
219
219
|
"format": "uint32",
|
|
220
220
|
"minimum": 0
|
|
221
221
|
},
|
|
222
|
+
"block_throttle": {
|
|
223
|
+
"description": "Once caught up, wait for this many new slots before fetching them. Delays new instructions by up to as many slots.",
|
|
224
|
+
"type": [
|
|
225
|
+
"integer",
|
|
226
|
+
"null"
|
|
227
|
+
],
|
|
228
|
+
"format": "uint32",
|
|
229
|
+
"minimum": 0
|
|
230
|
+
},
|
|
222
231
|
"hypersync_config": {
|
|
223
232
|
"description": "HyperSync Config for fetching historical instructions on this chain. Optional for the `solana` and `solana-devnet` chain ids, which default to their public HyperSync endpoints; required for any other chain id.",
|
|
224
233
|
"anyOf": [
|
|
@@ -294,7 +303,7 @@
|
|
|
294
303
|
"type": "object",
|
|
295
304
|
"properties": {
|
|
296
305
|
"name": {
|
|
297
|
-
"description": "A unique project-wide name for this program (used in generated
|
|
306
|
+
"description": "A unique project-wide name for this program (used in the generated types).",
|
|
298
307
|
"type": "string"
|
|
299
308
|
},
|
|
300
309
|
"program_id": {
|
|
@@ -348,7 +357,7 @@
|
|
|
348
357
|
"type": "object",
|
|
349
358
|
"properties": {
|
|
350
359
|
"name": {
|
|
351
|
-
"description": "Name of the instruction in the
|
|
360
|
+
"description": "Name of the instruction in the generated types. Should be unique per program.",
|
|
352
361
|
"type": "string"
|
|
353
362
|
},
|
|
354
363
|
"discriminator": {
|
package/src/PgWriting.res
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
let columns = (fields: array<Table.field>, ~kinds: array<Staging.kind>): array<Staging.column> =>
|
|
2
|
-
fields->Array.mapWithIndex((field, index) => {
|
|
3
|
-
Staging.name: field->Table.getPgDbFieldName,
|
|
4
|
-
kind: kinds->Array.getUnsafe(index),
|
|
5
|
-
isNullable: field.isNullable,
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
// Lays a batch into the arena, column by column. The values are the ones the
|
|
9
|
-
// table's own schema produced — a bigint as its digits, a date as its text, a
|
|
10
|
-
// boolean as the number the statement's cast expects — so nothing here has to
|
|
11
|
-
// know what a column means, only where its values go.
|
|
12
|
-
//
|
|
13
|
-
// The rows of a column are written in order because a variable-width slot's row
|
|
14
|
-
// starts where the row before it ended.
|
|
15
|
-
let stage = (
|
|
16
|
-
arena,
|
|
17
|
-
~table,
|
|
18
|
-
~columns: array<Staging.column>,
|
|
19
|
-
~values: array<array<unknown>>,
|
|
20
|
-
~rows,
|
|
21
|
-
) => {
|
|
22
|
-
let stage = arena->Staging.begin(~table, ~rows, ~columns)
|
|
23
|
-
try {
|
|
24
|
-
for column in 0 to columns->Array.length - 1 {
|
|
25
|
-
let values = values->Array.getUnsafe(column)
|
|
26
|
-
for row in 0 to rows - 1 {
|
|
27
|
-
stage->Staging.writeValue(~column, ~row, values->Array.getUnsafe(row))
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
stage->Staging.commit
|
|
31
|
-
} catch {
|
|
32
|
-
| exn =>
|
|
33
|
-
stage->Staging.abort
|
|
34
|
-
throw(exn)
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/PgWriting.res.mjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Table from "./db/Table.res.mjs";
|
|
4
|
-
import * as Staging from "./Staging.res.mjs";
|
|
5
|
-
|
|
6
|
-
function columns(fields, kinds) {
|
|
7
|
-
return fields.map((field, index) => ({
|
|
8
|
-
name: Table.getPgDbFieldName(field),
|
|
9
|
-
kind: kinds[index],
|
|
10
|
-
isNullable: field.isNullable
|
|
11
|
-
}));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function stage(arena, table, columns, values, rows) {
|
|
15
|
-
let stage$1 = Staging.begin(arena, table, rows, columns);
|
|
16
|
-
try {
|
|
17
|
-
for (let column = 0, column_finish = columns.length; column < column_finish; ++column) {
|
|
18
|
-
let values$1 = values[column];
|
|
19
|
-
for (let row = 0; row < rows; ++row) {
|
|
20
|
-
Staging.writeValue(stage$1, column, row, values$1[row]);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return Staging.commit(stage$1);
|
|
24
|
-
} catch (exn) {
|
|
25
|
-
Staging.abort(stage$1);
|
|
26
|
-
throw exn;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
columns,
|
|
32
|
-
stage,
|
|
33
|
-
}
|
|
34
|
-
/* Table Not a pure module */
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// What the indexer wants an index to be: a table, its ordered key columns with
|
|
2
|
-
// their directions, and an access method. The addon names it, matches it
|
|
3
|
-
// against what PostgreSQL holds and builds it.
|
|
4
|
-
|
|
5
|
-
type column = {
|
|
6
|
-
name: string,
|
|
7
|
-
direction: Table.indexFieldDirection,
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type t = {
|
|
11
|
-
tableName: string,
|
|
12
|
-
columns: array<column>,
|
|
13
|
-
method: string,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let make = (~tableName, ~columns) => {tableName, columns, method: "btree"}
|
|
17
|
-
|
|
18
|
-
let single = (~tableName, ~column) =>
|
|
19
|
-
make(~tableName, ~columns=[{name: column, direction: Table.Asc}])
|
|
20
|
-
|
|
21
|
-
let fromIndexFields = (~tableName, ~indexFields: array<Table.compositeIndexField>) =>
|
|
22
|
-
make(
|
|
23
|
-
~tableName,
|
|
24
|
-
~columns=indexFields->Array.map(({fieldName, direction}) => {name: fieldName, direction}),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
let toInput = ({tableName, columns, method}: t): Core.pgIndexInput => {
|
|
28
|
-
tableName,
|
|
29
|
-
columns: columns->Array.map(({name, direction}): Core.pgIndexColumnInput => {
|
|
30
|
-
name,
|
|
31
|
-
direction: switch direction {
|
|
32
|
-
| Table.Asc => "Asc"
|
|
33
|
-
| Desc => "Desc"
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
method,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let describe = (definition: t) =>
|
|
40
|
-
`${definition.tableName}(${definition.columns
|
|
41
|
-
->Array.map(({name, direction}) =>
|
|
42
|
-
switch direction {
|
|
43
|
-
| Asc => name
|
|
44
|
-
| Desc => `${name} DESC`
|
|
45
|
-
}
|
|
46
|
-
)
|
|
47
|
-
->Array.joinUnsafe(", ")}) using ${definition.method}`
|