envio 3.11.0 → 3.12.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 +3 -0
- package/package.json +6 -6
- package/src/Batch.res +111 -78
- package/src/Batch.res.mjs +53 -46
- package/src/BatchProcessing.res +1 -11
- package/src/BatchProcessing.res.mjs +1 -4
- package/src/ChainState.res +72 -19
- package/src/ChainState.res.mjs +50 -12
- package/src/ChainState.resi +5 -0
- package/src/Config.res +58 -37
- package/src/Config.res.mjs +63 -53
- package/src/CrossChainState.res +49 -52
- package/src/CrossChainState.res.mjs +25 -23
- package/src/CrossChainState.resi +6 -13
- package/src/EffectState.res +13 -4
- package/src/EffectState.resi +7 -1
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +6 -6
- package/src/FetchState.res +14 -0
- package/src/FetchState.res.mjs +14 -0
- package/src/FinalizeBackfill.res +16 -12
- package/src/FinalizeBackfill.res.mjs +3 -3
- package/src/HistoryPolicy.res +46 -0
- package/src/HistoryPolicy.res.mjs +50 -0
- package/src/InMemoryStore.res +30 -32
- package/src/InMemoryStore.res.mjs +33 -28
- package/src/IndexerState.res +66 -45
- package/src/IndexerState.res.mjs +57 -29
- package/src/IndexerState.resi +8 -13
- package/src/Internal.res +4 -2
- package/src/LoadLayer.res +11 -4
- package/src/LoadLayer.res.mjs +8 -5
- package/src/Main.res +25 -24
- package/src/Main.res.mjs +24 -25
- package/src/Metrics.res +49 -1
- package/src/Metrics.res.mjs +26 -3
- package/src/Persistence.res +55 -16
- package/src/Persistence.res.mjs +18 -3
- package/src/PgStorage.res +194 -118
- package/src/PgStorage.res.mjs +132 -72
- package/src/PruneStaleHistory.res +9 -15
- package/src/PruneStaleHistory.res.mjs +11 -20
- package/src/Rollback.res +9 -14
- package/src/Rollback.res.mjs +5 -3
- package/src/Sink.res +7 -5
- package/src/Sink.res.mjs +4 -4
- package/src/Staging.res +298 -0
- package/src/Staging.res.mjs +257 -0
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +7 -6
- package/src/UserContext.res +7 -4
- package/src/UserContext.res.mjs +5 -2
- package/src/Utils.res +9 -0
- package/src/Utils.res.mjs +9 -0
- package/src/Writing.res +34 -20
- package/src/Writing.res.mjs +15 -20
- package/src/bindings/ClickHouse.res +41 -34
- package/src/bindings/ClickHouse.res.mjs +36 -30
- package/src/bindings/ClickHouseSink.res +45 -186
- package/src/bindings/ClickHouseSink.res.mjs +21 -174
- package/src/db/CheckpointSequence.res +109 -0
- package/src/db/CheckpointSequence.res.mjs +99 -0
- package/src/db/EntityHistory.res +6 -5
- package/src/db/EntityHistory.res.mjs +5 -5
- package/src/db/Frontier.res +86 -0
- package/src/db/Frontier.res.mjs +126 -0
- package/src/db/InternalTable.res +145 -27
- package/src/db/InternalTable.res.mjs +115 -29
- package/src/db/RollbackFloors.res +34 -41
- package/src/db/RollbackFloors.res.mjs +27 -71
- package/src/sources/BlockStore.res +3 -0
- package/src/sources/EvmHyperSyncSource.res +3 -1
- package/src/sources/EvmHyperSyncSource.res.mjs +2 -1
- package/src/sources/HyperSync.res +2 -0
- package/src/sources/HyperSync.res.mjs +1 -0
- package/src/sources/HyperSync.resi +1 -0
- package/src/sources/HyperSyncClient.res +3 -0
- package/src/sources/RequestStat.res +8 -1
- package/src/sources/Source.res +4 -3
- package/src/sources/SourceManager.res +30 -10
- package/src/sources/SourceManager.res.mjs +24 -7
- package/src/sources/SourceManager.resi +2 -0
- package/src/tui/Tui.res +56 -143
- package/src/tui/Tui.res.mjs +70 -166
- package/src/tui/components/TuiData.res +61 -16
- package/src/tui/components/TuiData.res.mjs +53 -15
- package/svm.schema.json +0 -7
- package/src/db/CheckpointBounds.res +0 -53
- package/src/db/CheckpointBounds.res.mjs +0 -44
package/src/Config.res.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js
|
|
|
21
21
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
22
22
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
23
23
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
24
|
+
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
24
25
|
import * as EventConfigBuilder from "./EventConfigBuilder.res.mjs";
|
|
25
26
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
26
27
|
|
|
@@ -436,6 +437,7 @@ let publicConfigSchema = S$RescriptSchema.schema(s => ({
|
|
|
436
437
|
description: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
437
438
|
handlers: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
438
439
|
isDev: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
440
|
+
isolatedChains: s.m(S$RescriptSchema.option(S$RescriptSchema.array(ChainId.schema))),
|
|
439
441
|
fullBatchSize: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
|
|
440
442
|
rollbackOnReorg: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
441
443
|
saveFullHistory: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
|
|
@@ -460,6 +462,33 @@ function contractMappingOf(chainConfigs) {
|
|
|
460
462
|
return ContractMapping.make(names);
|
|
461
463
|
}
|
|
462
464
|
|
|
465
|
+
function getChain(config, chainId) {
|
|
466
|
+
if (ChainMap.has(config.chainMap, chainId)) {
|
|
467
|
+
return chainId;
|
|
468
|
+
} else {
|
|
469
|
+
return Stdlib_JsError.throwWithMessage("No chain with id " + ChainId.toString(chainId) + " found in config.yaml");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function isolate(config, chainIds) {
|
|
474
|
+
let shared = config.userEntities.filter(entityConfig => entityConfig.crossChain);
|
|
475
|
+
if (shared.length !== 0) {
|
|
476
|
+
Stdlib_JsError.throwWithMessage(`Only a schema whose entities are all per-chain can be split across processes. Shared across chains: ` + shared.map(entityConfig => entityConfig.name).join(", ") + `.`);
|
|
477
|
+
}
|
|
478
|
+
chainIds.forEach(chainId => {
|
|
479
|
+
getChain(config, chainId);
|
|
480
|
+
});
|
|
481
|
+
let chains = ChainMap.values(config.chainMap).filter(chain => chainIds.includes(chain.id));
|
|
482
|
+
let newrecord = {...config};
|
|
483
|
+
newrecord.isolated = true;
|
|
484
|
+
newrecord.defaultChain = chains[0];
|
|
485
|
+
newrecord.chainMap = ChainMap.fromArrayUnsafe(chains.map(chain => [
|
|
486
|
+
chain.id,
|
|
487
|
+
chain
|
|
488
|
+
]));
|
|
489
|
+
return newrecord;
|
|
490
|
+
}
|
|
491
|
+
|
|
463
492
|
function fromPublic(publicConfigJson) {
|
|
464
493
|
let publicConfig;
|
|
465
494
|
try {
|
|
@@ -755,38 +784,50 @@ function fromPublic(publicConfigJson) {
|
|
|
755
784
|
name: Utils.$$String.capitalize(param[0]),
|
|
756
785
|
handler: param[1].handler
|
|
757
786
|
})) : [];
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
787
|
+
let config_name = publicConfig.name;
|
|
788
|
+
let config_description = publicConfig.description;
|
|
789
|
+
let config_handlers = Stdlib_Option.getOr(publicConfig.handlers, "src/handlers");
|
|
790
|
+
let config_shouldRollbackOnReorg = Stdlib_Option.getOr(publicConfig.rollbackOnReorg, true);
|
|
791
|
+
let config_shouldSaveFullHistory = Stdlib_Option.getOr(publicConfig.saveFullHistory, false);
|
|
792
|
+
let config_chainIdMode = Stdlib_Option.getOr(publicConfig.chainIdMode, "int32");
|
|
793
|
+
let config_contractMapping = contractMappingOf(chains);
|
|
794
|
+
let config_defaultChain = chains[0];
|
|
795
|
+
let config_enableRawEvents = Stdlib_Option.getOr(publicConfig.rawEvents, false);
|
|
796
|
+
let config_batchSize = Stdlib_Option.getOr(publicConfig.fullBatchSize, 5000);
|
|
797
|
+
let config_isDev = Stdlib_Option.getOr(publicConfig.isDev, false);
|
|
798
|
+
let config_checkpointSequence = CheckpointSequence.fromEntities(userEntities);
|
|
799
|
+
let config = {
|
|
800
|
+
name: config_name,
|
|
801
|
+
description: config_description,
|
|
802
|
+
handlers: config_handlers,
|
|
762
803
|
contractHandlers: contractHandlers,
|
|
763
|
-
shouldRollbackOnReorg:
|
|
764
|
-
shouldSaveFullHistory:
|
|
804
|
+
shouldRollbackOnReorg: config_shouldRollbackOnReorg,
|
|
805
|
+
shouldSaveFullHistory: config_shouldSaveFullHistory,
|
|
765
806
|
defaultCrossChain: defaultCrossChain,
|
|
766
807
|
storage: globalStorage,
|
|
767
|
-
chainIdMode:
|
|
808
|
+
chainIdMode: config_chainIdMode,
|
|
768
809
|
chainMap: chainMap,
|
|
769
|
-
contractMapping:
|
|
770
|
-
defaultChain:
|
|
810
|
+
contractMapping: config_contractMapping,
|
|
811
|
+
defaultChain: config_defaultChain,
|
|
771
812
|
ecosystem: ecosystem,
|
|
772
|
-
enableRawEvents:
|
|
813
|
+
enableRawEvents: config_enableRawEvents,
|
|
773
814
|
maxAddrInPartition: Env.maxAddrInPartition,
|
|
774
815
|
clientFilterAddressThreshold: Env.clientFilterAddressThreshold,
|
|
775
|
-
batchSize:
|
|
816
|
+
batchSize: config_batchSize,
|
|
776
817
|
reorgThresholdReadyTolerance: 100,
|
|
777
818
|
lowercaseAddresses: lowercaseAddresses,
|
|
778
|
-
isDev:
|
|
819
|
+
isDev: config_isDev,
|
|
820
|
+
isolated: false,
|
|
779
821
|
userEntitiesByName: userEntitiesByName,
|
|
780
822
|
userEntities: userEntities,
|
|
781
|
-
allEnums: allEnums
|
|
823
|
+
allEnums: allEnums,
|
|
824
|
+
checkpointSequence: config_checkpointSequence
|
|
782
825
|
};
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (ChainMap.keys(config.chainMap).length > 1) {
|
|
787
|
-
return config.userEntities.every(entityConfig => !entityConfig.crossChain);
|
|
826
|
+
let chainIds = publicConfig.isolatedChains;
|
|
827
|
+
if (chainIds !== undefined) {
|
|
828
|
+
return isolate(config, chainIds);
|
|
788
829
|
} else {
|
|
789
|
-
return
|
|
830
|
+
return config;
|
|
790
831
|
}
|
|
791
832
|
}
|
|
792
833
|
|
|
@@ -853,36 +894,6 @@ function getEventConfig(config, contractName, eventName, chainId) {
|
|
|
853
894
|
});
|
|
854
895
|
}
|
|
855
896
|
|
|
856
|
-
function shouldSaveHistory(config, isInReorgThreshold, chainId) {
|
|
857
|
-
if (config.shouldSaveFullHistory) {
|
|
858
|
-
return true;
|
|
859
|
-
} else if (config.shouldRollbackOnReorg && isInReorgThreshold) {
|
|
860
|
-
if (chainId !== undefined && isIsolatedMultichain(config)) {
|
|
861
|
-
return ChainMap.get(config.chainMap, Primitive_option.valFromOption(chainId)).maxReorgDepth > 0;
|
|
862
|
-
} else {
|
|
863
|
-
return true;
|
|
864
|
-
}
|
|
865
|
-
} else {
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
function shouldPruneHistory(config, isInReorgThreshold) {
|
|
871
|
-
if (!config.shouldSaveFullHistory && config.shouldRollbackOnReorg) {
|
|
872
|
-
return isInReorgThreshold;
|
|
873
|
-
} else {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
function getChain(config, chainId) {
|
|
879
|
-
if (ChainMap.has(config.chainMap, chainId)) {
|
|
880
|
-
return chainId;
|
|
881
|
-
} else {
|
|
882
|
-
return Stdlib_JsError.throwWithMessage("No chain with id " + ChainId.toString(chainId) + " found in config.yaml");
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
897
|
let primedJson = {
|
|
887
898
|
contents: undefined
|
|
888
899
|
};
|
|
@@ -931,6 +942,7 @@ function stripSensitiveData(json) {
|
|
|
931
942
|
};
|
|
932
943
|
if (typeof cloned === "object" && cloned !== null && !Array.isArray(cloned)) {
|
|
933
944
|
Utils.Dict.deleteInPlace(cloned, "isDev");
|
|
945
|
+
Utils.Dict.deleteInPlace(cloned, "isolatedChains");
|
|
934
946
|
stripChains(cloned["evm"]);
|
|
935
947
|
stripChains(cloned["fuel"]);
|
|
936
948
|
stripChains(cloned["svm"]);
|
|
@@ -1159,14 +1171,12 @@ export {
|
|
|
1159
1171
|
publicConfigStorageSchema,
|
|
1160
1172
|
publicConfigSchema,
|
|
1161
1173
|
contractMappingOf,
|
|
1174
|
+
getChain,
|
|
1175
|
+
isolate,
|
|
1162
1176
|
fromPublic,
|
|
1163
|
-
isIsolatedMultichain,
|
|
1164
1177
|
normalizeUserAddress,
|
|
1165
1178
|
normalizeSimulateAddress,
|
|
1166
1179
|
getEventConfig,
|
|
1167
|
-
shouldSaveHistory,
|
|
1168
|
-
shouldPruneHistory,
|
|
1169
|
-
getChain,
|
|
1170
1180
|
prime,
|
|
1171
1181
|
getPublicConfigJson,
|
|
1172
1182
|
stripSensitiveData,
|
package/src/CrossChainState.res
CHANGED
|
@@ -8,13 +8,13 @@ type t = {
|
|
|
8
8
|
// Chain ids in a stable order, so the cross-chain loops iterate the chains
|
|
9
9
|
// without allocating a values array on every tick.
|
|
10
10
|
chainIds: array<ChainId.t>,
|
|
11
|
-
// True once every chain has caught up to head/endBlock
|
|
11
|
+
// True once every chain this process drives has caught up to head/endBlock and
|
|
12
|
+
// the indexes they owe are committed. Monotonic during a run.
|
|
12
13
|
mutable isRealtime: bool,
|
|
13
|
-
// True once
|
|
14
|
-
// but before the deferred schema indexes and `ready_at` are committed. The
|
|
15
|
-
//
|
|
14
|
+
// True once those chains have caught up and there's nothing left to process,
|
|
15
|
+
// but before the deferred schema indexes and `ready_at` are committed. The gap
|
|
16
|
+
// between this and `isRealtime` is the FinalizingIndexes phase.
|
|
16
17
|
mutable isCaughtUp: bool,
|
|
17
|
-
mutable isInReorgThreshold: bool,
|
|
18
18
|
// Indexer-wide fetch buffer pool (item count), shared across all chains.
|
|
19
19
|
targetBufferSize: int,
|
|
20
20
|
}
|
|
@@ -26,18 +26,12 @@ let calculateTargetBufferSize = () =>
|
|
|
26
26
|
| None => 100_000
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
let make = (
|
|
30
|
-
~chainStates,
|
|
31
|
-
~isInReorgThreshold,
|
|
32
|
-
~isRealtime,
|
|
33
|
-
~targetBufferSize=calculateTargetBufferSize(),
|
|
34
|
-
): t => {
|
|
29
|
+
let make = (~chainStates, ~isRealtime, ~targetBufferSize=calculateTargetBufferSize()): t => {
|
|
35
30
|
{
|
|
36
31
|
chainStates,
|
|
37
32
|
chainIds: chainStates->Dict.valuesToArray->Array.map(cs => (cs->ChainState.chainConfig).id),
|
|
38
33
|
isRealtime,
|
|
39
34
|
isCaughtUp: isRealtime,
|
|
40
|
-
isInReorgThreshold,
|
|
41
35
|
targetBufferSize,
|
|
42
36
|
}
|
|
43
37
|
}
|
|
@@ -51,10 +45,21 @@ let getChainState = (crossChainState: t, chainId) =>
|
|
|
51
45
|
|
|
52
46
|
let chainStates = (crossChainState: t) => crossChainState.chainStates
|
|
53
47
|
let isRealtime = (crossChainState: t) => crossChainState.isRealtime
|
|
48
|
+
let chainIds = (crossChainState: t) => crossChainState.chainIds
|
|
54
49
|
let isCaughtUp = (crossChainState: t) => crossChainState.isCaughtUp
|
|
55
|
-
|
|
50
|
+
// Chains enter the threshold together. It is the run-wide reading; what a
|
|
51
|
+
// write keeps is decided per chain.
|
|
52
|
+
let isInReorgThreshold = (crossChainState: t) => {
|
|
53
|
+
let chainStates = crossChainState.chainStates->Dict.valuesToArray
|
|
54
|
+
chainStates->Utils.Array.notEmpty && chainStates->Array.every(ChainState.isInReorgThreshold)
|
|
55
|
+
}
|
|
56
56
|
let targetBufferSize = (crossChainState: t) => crossChainState.targetBufferSize
|
|
57
57
|
|
|
58
|
+
// Whether each chain's writes still need history, keyed by chain id — what the
|
|
59
|
+
// history policy is built from.
|
|
60
|
+
let shouldSaveHistory = (crossChainState: t) =>
|
|
61
|
+
crossChainState.chainStates->Utils.Dict.mapValues(ChainState.shouldSaveHistory)
|
|
62
|
+
|
|
58
63
|
// Ready-to-process items across every chain — the live draw against
|
|
59
64
|
// targetBufferSize, which is a budget of processable events (items stuck behind
|
|
60
65
|
// a gap don't count toward the goal of keeping ~targetBufferSize ready).
|
|
@@ -74,14 +79,20 @@ let nextItemIsNone = (crossChainState: t): bool =>
|
|
|
74
79
|
|
|
75
80
|
// Each chain's safe checkpoint: the last one a reorg on that chain can no longer
|
|
76
81
|
// reach, or None while it has nothing safe yet. A chain that can't be rolled
|
|
77
|
-
// back (maxReorgDepth = 0) tracks none, and everything
|
|
78
|
-
|
|
82
|
+
// back (maxReorgDepth = 0) tracks none, and everything committed is safe from
|
|
83
|
+
// it: under one shared sequence that is the run's highest id, not the chain's
|
|
84
|
+
// own — an idle chain's would hold every other chain's prune back.
|
|
85
|
+
let getSafeCheckpointIdByChain = (
|
|
86
|
+
crossChainState: t,
|
|
87
|
+
~sequence: CheckpointSequence.t,
|
|
88
|
+
~committedFrontier,
|
|
89
|
+
) =>
|
|
79
90
|
crossChainState.chainIds->Array.map(chainId => {
|
|
80
91
|
let cs = crossChainState->getChainState(chainId)
|
|
81
92
|
(
|
|
82
93
|
chainId,
|
|
83
94
|
switch cs->ChainState.safeCheckpointTracking {
|
|
84
|
-
| None => Some(
|
|
95
|
+
| None => Some(sequence->CheckpointSequence.position(committedFrontier, ~chainId))
|
|
85
96
|
| Some(tracking) =>
|
|
86
97
|
tracking->SafeCheckpointTracking.getSafeCheckpointId(
|
|
87
98
|
~sourceBlockNumber=cs->ChainState.knownHeight,
|
|
@@ -94,18 +105,14 @@ let getSafeCheckpointIdByChain = (crossChainState: t, ~committedCheckpointId) =>
|
|
|
94
105
|
|
|
95
106
|
let createBatch = (
|
|
96
107
|
crossChainState: t,
|
|
97
|
-
~
|
|
108
|
+
~config: Config.t,
|
|
109
|
+
~frontier,
|
|
98
110
|
~batchSizeTarget: int,
|
|
99
|
-
~isRollback: bool,
|
|
100
111
|
): Batch.t => {
|
|
101
112
|
Batch.make(
|
|
102
|
-
~
|
|
103
|
-
~
|
|
104
|
-
|
|
105
|
-
// This is needed to currectly overwrite old state
|
|
106
|
-
// in an append-only ClickHouse insert.
|
|
107
|
-
isRollback ? 1n : 0n,
|
|
108
|
-
),
|
|
113
|
+
~sequence=config.checkpointSequence,
|
|
114
|
+
~history=config->HistoryPolicy.decide(~shouldSaveHistory=crossChainState->shouldSaveHistory),
|
|
115
|
+
~frontier,
|
|
109
116
|
~chainsBeforeBatch=crossChainState.chainStates->Utils.Dict.mapValues(
|
|
110
117
|
ChainState.toChainBeforeBatch,
|
|
111
118
|
),
|
|
@@ -123,8 +130,6 @@ let enterReorgThreshold = (crossChainState: t) => {
|
|
|
123
130
|
->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
|
|
124
131
|
->ChainState.enterReorgThreshold
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
crossChainState.isInReorgThreshold = true
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
// Commit each progressed chain's batch progress, then record whether the whole
|
|
@@ -146,17 +151,6 @@ let applyBatchProgress = (crossChainState: t, ~batch: Batch.t, ~blockTimestampNa
|
|
|
146
151
|
|
|
147
152
|
crossChainState.isCaughtUp =
|
|
148
153
|
crossChainState.isCaughtUp || (crossChainState->nextItemIsNone && everyChainCaughtUp.contents)
|
|
149
|
-
|
|
150
|
-
// A run resumed with every chain already stamped ready needs no finalize —
|
|
151
|
-
// the indexes were committed together with those stamps.
|
|
152
|
-
let allChainsReady = ref(true)
|
|
153
|
-
for i in 0 to chainIds->Array.length - 1 {
|
|
154
|
-
if !(crossChainState->getChainState(chainIds->Array.getUnsafe(i))->ChainState.isReady) {
|
|
155
|
-
allChainsReady := false
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady.contents
|
|
160
154
|
}
|
|
161
155
|
|
|
162
156
|
// Every chain has buffered up to its head (or endblock) with nothing
|
|
@@ -191,29 +185,25 @@ let markCaughtUpIfSettled = (crossChainState: t) =>
|
|
|
191
185
|
// may have moved on — at which point no chain looks at head any more and the
|
|
192
186
|
// indexes it still owes would wait out another whole backfill. Deciding here,
|
|
193
187
|
// before any source request, keeps that debt tied to the progress that was
|
|
194
|
-
// actually committed. Skipped
|
|
195
|
-
//
|
|
188
|
+
// actually committed. Skipped when the run resumed realtime, which is exactly
|
|
189
|
+
// the case where the indexes are already committed and nothing is owed.
|
|
196
190
|
let markCaughtUpOnResume = (crossChainState: t) => {
|
|
197
191
|
let everyChainCaughtUp = ref(crossChainState.chainIds->Array.length > 0)
|
|
198
|
-
let everyChainReady = ref(true)
|
|
199
192
|
for i in 0 to crossChainState.chainIds->Array.length - 1 {
|
|
200
193
|
let cs = crossChainState->getChainState(crossChainState.chainIds->Array.getUnsafe(i))
|
|
201
194
|
if !(cs->ChainState.isDurablyCaughtUp) {
|
|
202
195
|
everyChainCaughtUp := false
|
|
203
196
|
}
|
|
204
|
-
if !(cs->ChainState.isReady) {
|
|
205
|
-
everyChainReady := false
|
|
206
|
-
}
|
|
207
197
|
}
|
|
208
198
|
|
|
209
|
-
if everyChainCaughtUp.contents && !
|
|
199
|
+
if everyChainCaughtUp.contents && !crossChainState.isRealtime && crossChainState->nextItemIsNone {
|
|
210
200
|
crossChainState.isCaughtUp = true
|
|
211
201
|
}
|
|
212
202
|
}
|
|
213
203
|
|
|
214
|
-
// Concludes the FinalizingIndexes phase: stamps every chain
|
|
215
|
-
// already committed alongside
|
|
216
|
-
// indexer to realtime.
|
|
204
|
+
// Concludes the FinalizingIndexes phase: stamps every chain this process drives
|
|
205
|
+
// with the `ready_at` already committed alongside their deferred schema indexes,
|
|
206
|
+
// and switches the indexer to realtime.
|
|
217
207
|
let markReady = (crossChainState: t, ~readyAt) => {
|
|
218
208
|
for i in 0 to crossChainState.chainIds->Array.length - 1 {
|
|
219
209
|
crossChainState
|
|
@@ -267,6 +257,10 @@ let idleOrWaitAction = (cs: ChainState.t) =>
|
|
|
267
257
|
? FetchState.NothingToQuery
|
|
268
258
|
: FetchState.WaitingForNewBlock
|
|
269
259
|
|
|
260
|
+
// How far past the alignment anchor's frontier progress a follower may fetch,
|
|
261
|
+
// as a fraction of its own alignable range.
|
|
262
|
+
let alignmentMargin = 0.2
|
|
263
|
+
|
|
270
264
|
// Dispatch a fetch tick across the whole indexer from one shared pool of
|
|
271
265
|
// ~targetBufferSize ready events, as a waterfall: visit chains furthest-behind
|
|
272
266
|
// first, hand each the budget remaining at that point (plus its own
|
|
@@ -350,11 +344,14 @@ let checkAndFetch = async (
|
|
|
350
344
|
(isCold ? Pervasives.min(remaining.contents, coldChainBudget) : remaining.contents) +.
|
|
351
345
|
cs->ChainState.pendingBudget
|
|
352
346
|
let maxTargetBlock = switch alignment {
|
|
353
|
-
//
|
|
347
|
+
// 20% margin past the anchor's line: chains whose progress tracks the
|
|
354
348
|
// anchor closely would otherwise flap in and out of the clamp on every
|
|
355
|
-
// small frontier move, stalling their pipeline every other tick.
|
|
349
|
+
// small frontier move, stalling their pipeline every other tick. The
|
|
350
|
+
// margin is also the headroom a follower keeps buffered while the anchor
|
|
351
|
+
// is mid-fetch, so it has to outlast a slow anchor response, not just
|
|
352
|
+
// absorb frontier jitter.
|
|
356
353
|
| Some((anchorChainId, progress)) if anchorChainId !== chainId =>
|
|
357
|
-
Some(cs->ChainState.blockAtProgress(~progress=progress +.
|
|
354
|
+
Some(cs->ChainState.blockAtProgress(~progress=progress +. alignmentMargin))
|
|
358
355
|
| _ => None
|
|
359
356
|
}
|
|
360
357
|
switch cs->ChainState.getNextQuery(~chainTargetItems, ~maxTargetBlock?) {
|
|
@@ -404,7 +401,7 @@ let checkAndFetch = async (
|
|
|
404
401
|
switch actionByChain->ChainId.Dict.dangerouslyGetNonOption(chainId) {
|
|
405
402
|
| Some(NothingToQuery)
|
|
406
403
|
| None => ()
|
|
407
|
-
| Some(action) => promises->Array.push(dispatchChain(~chainId
|
|
404
|
+
| Some(action) => promises->Array.push(dispatchChain(~chainId, ~action))
|
|
408
405
|
}
|
|
409
406
|
}
|
|
410
407
|
let _ = await promises->Promise.all
|
|
@@ -6,8 +6,10 @@ import * as Utils from "./Utils.res.mjs";
|
|
|
6
6
|
import * as Logging from "./Logging.res.mjs";
|
|
7
7
|
import * as ChainState from "./ChainState.res.mjs";
|
|
8
8
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
9
|
+
import * as HistoryPolicy from "./HistoryPolicy.res.mjs";
|
|
9
10
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
10
11
|
import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
|
|
12
|
+
import * as CheckpointSequence from "./db/CheckpointSequence.res.mjs";
|
|
11
13
|
import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
|
|
12
14
|
|
|
13
15
|
function calculateTargetBufferSize() {
|
|
@@ -18,7 +20,7 @@ function calculateTargetBufferSize() {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
function make(chainStates,
|
|
23
|
+
function make(chainStates, isRealtime, targetBufferSizeOpt) {
|
|
22
24
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : (
|
|
23
25
|
Env.targetBufferSize !== undefined ? Env.targetBufferSize : 100000
|
|
24
26
|
);
|
|
@@ -27,7 +29,6 @@ function make(chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt)
|
|
|
27
29
|
chainIds: Object.values(chainStates).map(cs => ChainState.chainConfig(cs).id),
|
|
28
30
|
isRealtime: isRealtime,
|
|
29
31
|
isCaughtUp: isRealtime,
|
|
30
|
-
isInReorgThreshold: isInReorgThreshold,
|
|
31
32
|
targetBufferSize: targetBufferSize
|
|
32
33
|
};
|
|
33
34
|
}
|
|
@@ -40,18 +41,31 @@ function isRealtime(crossChainState) {
|
|
|
40
41
|
return crossChainState.isRealtime;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function chainIds(crossChainState) {
|
|
45
|
+
return crossChainState.chainIds;
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
function isCaughtUp(crossChainState) {
|
|
44
49
|
return crossChainState.isCaughtUp;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
function isInReorgThreshold(crossChainState) {
|
|
48
|
-
|
|
53
|
+
let chainStates = Object.values(crossChainState.chainStates);
|
|
54
|
+
if (Utils.$$Array.notEmpty(chainStates)) {
|
|
55
|
+
return chainStates.every(ChainState.isInReorgThreshold);
|
|
56
|
+
} else {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
function targetBufferSize(crossChainState) {
|
|
52
62
|
return crossChainState.targetBufferSize;
|
|
53
63
|
}
|
|
54
64
|
|
|
65
|
+
function shouldSaveHistory(crossChainState) {
|
|
66
|
+
return Utils.Dict.mapValues(crossChainState.chainStates, ChainState.shouldSaveHistory);
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
function totalReadyCount(crossChainState) {
|
|
56
70
|
let total = 0;
|
|
57
71
|
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
@@ -65,21 +79,19 @@ function nextItemIsNone(crossChainState) {
|
|
|
65
79
|
return !Object.values(crossChainState.chainStates).some(ChainState.hasReadyItem);
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
function getSafeCheckpointIdByChain(crossChainState,
|
|
82
|
+
function getSafeCheckpointIdByChain(crossChainState, sequence, committedFrontier) {
|
|
69
83
|
return crossChainState.chainIds.map(chainId => {
|
|
70
84
|
let cs = crossChainState.chainStates[chainId];
|
|
71
85
|
let tracking = ChainState.safeCheckpointTracking(cs);
|
|
72
86
|
return [
|
|
73
87
|
chainId,
|
|
74
|
-
tracking !== undefined ? SafeCheckpointTracking.getSafeCheckpointId(tracking, ChainState.knownHeight(cs)) :
|
|
88
|
+
tracking !== undefined ? SafeCheckpointTracking.getSafeCheckpointId(tracking, ChainState.knownHeight(cs)) : CheckpointSequence.position(sequence, committedFrontier, chainId)
|
|
75
89
|
];
|
|
76
90
|
});
|
|
77
91
|
}
|
|
78
92
|
|
|
79
|
-
function createBatch(crossChainState,
|
|
80
|
-
return Batch.make(
|
|
81
|
-
isRollback ? 1n : 0n
|
|
82
|
-
), Utils.Dict.mapValues(crossChainState.chainStates, ChainState.toChainBeforeBatch), batchSizeTarget, crossChainState.isInReorgThreshold);
|
|
93
|
+
function createBatch(crossChainState, config, frontier, batchSizeTarget) {
|
|
94
|
+
return Batch.make(config.checkpointSequence, frontier, Utils.Dict.mapValues(crossChainState.chainStates, ChainState.toChainBeforeBatch), batchSizeTarget, HistoryPolicy.decide(config, shouldSaveHistory(crossChainState)));
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
function enterReorgThreshold(crossChainState) {
|
|
@@ -87,7 +99,6 @@ function enterReorgThreshold(crossChainState) {
|
|
|
87
99
|
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
88
100
|
ChainState.enterReorgThreshold(crossChainState.chainStates[crossChainState.chainIds[i]]);
|
|
89
101
|
}
|
|
90
|
-
crossChainState.isInReorgThreshold = true;
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
function applyBatchProgress(crossChainState, batch, blockTimestampName) {
|
|
@@ -101,13 +112,6 @@ function applyBatchProgress(crossChainState, batch, blockTimestampName) {
|
|
|
101
112
|
}
|
|
102
113
|
}
|
|
103
114
|
crossChainState.isCaughtUp = crossChainState.isCaughtUp || nextItemIsNone(crossChainState) && everyChainCaughtUp;
|
|
104
|
-
let allChainsReady = true;
|
|
105
|
-
for (let i$1 = 0, i_finish$1 = chainIds.length; i$1 < i_finish$1; ++i$1) {
|
|
106
|
-
if (!ChainState.isReady(crossChainState.chainStates[chainIds[i$1]])) {
|
|
107
|
-
allChainsReady = false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
crossChainState.isRealtime = crossChainState.isRealtime || allChainsReady;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
function isSettledAtHead(crossChainState) {
|
|
@@ -129,17 +133,13 @@ function markCaughtUpIfSettled(crossChainState) {
|
|
|
129
133
|
|
|
130
134
|
function markCaughtUpOnResume(crossChainState) {
|
|
131
135
|
let everyChainCaughtUp = crossChainState.chainIds.length !== 0;
|
|
132
|
-
let everyChainReady = true;
|
|
133
136
|
for (let i = 0, i_finish = crossChainState.chainIds.length; i < i_finish; ++i) {
|
|
134
137
|
let cs = crossChainState.chainStates[crossChainState.chainIds[i]];
|
|
135
138
|
if (!ChainState.isDurablyCaughtUp(cs)) {
|
|
136
139
|
everyChainCaughtUp = false;
|
|
137
140
|
}
|
|
138
|
-
if (!ChainState.isReady(cs)) {
|
|
139
|
-
everyChainReady = false;
|
|
140
|
-
}
|
|
141
141
|
}
|
|
142
|
-
if (everyChainCaughtUp && !
|
|
142
|
+
if (everyChainCaughtUp && !crossChainState.isRealtime && nextItemIsNone(crossChainState)) {
|
|
143
143
|
crossChainState.isCaughtUp = true;
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
@@ -199,7 +199,7 @@ async function checkAndFetch(crossChainState, dispatchChain) {
|
|
|
199
199
|
let chainTargetItems = (
|
|
200
200
|
isCold ? Primitive_float.min(remaining.contents, minimumAdmissionBudget) : remaining.contents
|
|
201
201
|
) + ChainState.pendingBudget(cs);
|
|
202
|
-
let maxTargetBlock = alignment !== undefined && alignment[0] !== chainId ? ChainState.blockAtProgress(cs, alignment[1] + 0.
|
|
202
|
+
let maxTargetBlock = alignment !== undefined && alignment[0] !== chainId ? ChainState.blockAtProgress(cs, alignment[1] + 0.2) : undefined;
|
|
203
203
|
let action = ChainState.getNextQuery(cs, chainTargetItems, maxTargetBlock);
|
|
204
204
|
if (typeof action !== "object") {
|
|
205
205
|
if (action === "WaitingForNewBlock") {
|
|
@@ -249,9 +249,11 @@ export {
|
|
|
249
249
|
make,
|
|
250
250
|
chainStates,
|
|
251
251
|
isRealtime,
|
|
252
|
+
chainIds,
|
|
252
253
|
isCaughtUp,
|
|
253
254
|
isInReorgThreshold,
|
|
254
255
|
targetBufferSize,
|
|
256
|
+
shouldSaveHistory,
|
|
255
257
|
nextItemIsNone,
|
|
256
258
|
getSafeCheckpointIdByChain,
|
|
257
259
|
createBatch,
|
package/src/CrossChainState.resi
CHANGED
|
@@ -5,34 +5,27 @@ type t
|
|
|
5
5
|
|
|
6
6
|
let calculateTargetBufferSize: unit => int
|
|
7
7
|
|
|
8
|
-
let make: (
|
|
9
|
-
~chainStates: dict<ChainState.t>,
|
|
10
|
-
~isInReorgThreshold: bool,
|
|
11
|
-
~isRealtime: bool,
|
|
12
|
-
~targetBufferSize: int=?,
|
|
13
|
-
) => t
|
|
8
|
+
let make: (~chainStates: dict<ChainState.t>, ~isRealtime: bool, ~targetBufferSize: int=?) => t
|
|
14
9
|
|
|
15
10
|
// Accessors.
|
|
16
11
|
let chainStates: t => dict<ChainState.t>
|
|
17
12
|
let isRealtime: t => bool
|
|
13
|
+
let chainIds: t => array<ChainId.t>
|
|
18
14
|
let isCaughtUp: t => bool
|
|
19
15
|
let isInReorgThreshold: t => bool
|
|
20
16
|
let targetBufferSize: t => int
|
|
17
|
+
let shouldSaveHistory: t => dict<bool>
|
|
21
18
|
|
|
22
19
|
// Derived (pure).
|
|
23
20
|
let nextItemIsNone: t => bool
|
|
24
21
|
let getSafeCheckpointIdByChain: (
|
|
25
22
|
t,
|
|
26
|
-
~
|
|
23
|
+
~sequence: CheckpointSequence.t,
|
|
24
|
+
~committedFrontier: Frontier.t,
|
|
27
25
|
) => array<(ChainId.t, option<Internal.checkpointId>)>
|
|
28
26
|
|
|
29
27
|
// Cross-chain transitions.
|
|
30
|
-
let createBatch: (
|
|
31
|
-
t,
|
|
32
|
-
~processedCheckpointId: Internal.checkpointId,
|
|
33
|
-
~batchSizeTarget: int,
|
|
34
|
-
~isRollback: bool,
|
|
35
|
-
) => Batch.t
|
|
28
|
+
let createBatch: (t, ~config: Config.t, ~frontier: Frontier.t, ~batchSizeTarget: int) => Batch.t
|
|
36
29
|
let enterReorgThreshold: t => unit
|
|
37
30
|
let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
|
|
38
31
|
let markCaughtUpIfSettled: t => unit
|
package/src/EffectState.res
CHANGED
|
@@ -36,15 +36,24 @@ type effectStats = {
|
|
|
36
36
|
mutable hasCache: bool,
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// A handler output, stamped with the checkpoint that produced it on the chain
|
|
40
|
+
// whose handler ran it. A cross-chain effect's entries come from several
|
|
41
|
+
// chains, and under per-chain sequences an id only compares against its own
|
|
42
|
+
// chain's committed one.
|
|
43
|
+
type cacheEntry = {
|
|
44
|
+
output: Internal.effectOutput,
|
|
45
|
+
chainId: ChainId.t,
|
|
46
|
+
checkpointId: Internal.checkpointId,
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
type effectCacheInMemTable = {
|
|
40
50
|
// Cache keys whose handler output is persisted on the next write. Drained
|
|
41
51
|
// each write; eviction is driven by the per-entry checkpointId instead.
|
|
42
52
|
mutable idsToStore: array<string>,
|
|
43
53
|
mutable invalidationsCount: int,
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
mutable dict: dict<Change.t<Internal.effectOutput>>,
|
|
54
|
+
// Committed entries are dropped once persisted or re-derivable, mirroring
|
|
55
|
+
// entity changes; a db read is stamped loadedFromDbCheckpointId.
|
|
56
|
+
mutable dict: dict<cacheEntry>,
|
|
48
57
|
mutable changesCount: float,
|
|
49
58
|
effect: Internal.effect,
|
|
50
59
|
// The scope this cache is for and its resolved db table (the address). The
|
package/src/EffectState.resi
CHANGED
|
@@ -11,10 +11,16 @@ type effectRateLimitState = {
|
|
|
11
11
|
// toMetrics.
|
|
12
12
|
type effectStats
|
|
13
13
|
|
|
14
|
+
type cacheEntry = {
|
|
15
|
+
output: Internal.effectOutput,
|
|
16
|
+
chainId: ChainId.t,
|
|
17
|
+
checkpointId: Internal.checkpointId,
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
type effectCacheInMemTable = {
|
|
15
21
|
mutable idsToStore: array<string>,
|
|
16
22
|
mutable invalidationsCount: int,
|
|
17
|
-
mutable dict: dict<
|
|
23
|
+
mutable dict: dict<cacheEntry>,
|
|
18
24
|
mutable changesCount: float,
|
|
19
25
|
effect: Internal.effect,
|
|
20
26
|
scope: Internal.chainScope,
|
package/src/EventProcessing.res
CHANGED
|
@@ -157,9 +157,9 @@ let preloadBatchOrThrow = async (
|
|
|
157
157
|
|
|
158
158
|
for checkpointIdx in 0 to batch.checkpointIds->Array.length - 1 {
|
|
159
159
|
let checkpointId = batch.checkpointIds->Array.getUnsafe(checkpointIdx)
|
|
160
|
-
let
|
|
160
|
+
let checkpointItemsCount = batch.checkpointItemsCount->Array.getUnsafe(checkpointIdx)
|
|
161
161
|
|
|
162
|
-
for idx in 0 to
|
|
162
|
+
for idx in 0 to checkpointItemsCount - 1 {
|
|
163
163
|
let item = batch.items->Array.getUnsafe(itemIdx.contents + idx)
|
|
164
164
|
switch item {
|
|
165
165
|
| Event(_) =>
|
|
@@ -233,7 +233,7 @@ let preloadBatchOrThrow = async (
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
itemIdx := itemIdx.contents +
|
|
236
|
+
itemIdx := itemIdx.contents + checkpointItemsCount
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
let _ = await Promise.all(promises)
|
|
@@ -251,9 +251,9 @@ let runBatchHandlersOrThrow = async (
|
|
|
251
251
|
|
|
252
252
|
for checkpointIdx in 0 to batch.checkpointIds->Array.length - 1 {
|
|
253
253
|
let checkpointId = batch.checkpointIds->Array.getUnsafe(checkpointIdx)
|
|
254
|
-
let
|
|
254
|
+
let checkpointItemsCount = batch.checkpointItemsCount->Array.getUnsafe(checkpointIdx)
|
|
255
255
|
|
|
256
|
-
for idx in 0 to
|
|
256
|
+
for idx in 0 to checkpointItemsCount - 1 {
|
|
257
257
|
let item = batch.items->Array.getUnsafe(itemIdx.contents + idx)
|
|
258
258
|
|
|
259
259
|
await runHandlerOrThrow(
|
|
@@ -266,7 +266,7 @@ let runBatchHandlersOrThrow = async (
|
|
|
266
266
|
~chains,
|
|
267
267
|
)
|
|
268
268
|
}
|
|
269
|
-
itemIdx := itemIdx.contents +
|
|
269
|
+
itemIdx := itemIdx.contents + checkpointItemsCount
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|