envio 3.9.0 → 3.10.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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +35 -3
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +52 -44
- package/src/Config.res.mjs +51 -17
- package/src/Core.res +4 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/svm.schema.json +1 -75
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "../Utils.res.mjs";
|
|
4
|
+
import * as Performance from "../bindings/Performance.res.mjs";
|
|
5
|
+
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
9
|
+
|
|
10
|
+
function retryDelayFor(count) {
|
|
11
|
+
if (count <= 0) {
|
|
12
|
+
return 0;
|
|
13
|
+
} else {
|
|
14
|
+
return Utils.expBackoff(250, count - 1 | 0, 60000);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function truncateDetail(detail) {
|
|
19
|
+
if (detail.length > 200) {
|
|
20
|
+
return detail.slice(0, 200) + "...";
|
|
21
|
+
} else {
|
|
22
|
+
return detail;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function subscribe(staleTimeout, onHeight, onStatus, connect) {
|
|
27
|
+
let closeConnectionRef = {
|
|
28
|
+
contents: undefined
|
|
29
|
+
};
|
|
30
|
+
let failureCount = {
|
|
31
|
+
contents: 0
|
|
32
|
+
};
|
|
33
|
+
let generation = {
|
|
34
|
+
contents: 0
|
|
35
|
+
};
|
|
36
|
+
let timeoutId = {
|
|
37
|
+
contents: undefined
|
|
38
|
+
};
|
|
39
|
+
let unreadableDetail = {
|
|
40
|
+
contents: undefined
|
|
41
|
+
};
|
|
42
|
+
let liveAt = {
|
|
43
|
+
contents: undefined
|
|
44
|
+
};
|
|
45
|
+
let minProvenMillis = staleTimeout / 4 | 0;
|
|
46
|
+
let closeSafely = close => {
|
|
47
|
+
try {
|
|
48
|
+
return close();
|
|
49
|
+
} catch (exn) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
let closeConnection = () => {
|
|
54
|
+
let close = closeConnectionRef.contents;
|
|
55
|
+
if (close !== undefined) {
|
|
56
|
+
closeSafely(close);
|
|
57
|
+
}
|
|
58
|
+
closeConnectionRef.contents = undefined;
|
|
59
|
+
};
|
|
60
|
+
let armStaleTimeout = () => {
|
|
61
|
+
Utils.clearTimeoutRef(timeoutId);
|
|
62
|
+
timeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
63
|
+
timeoutId.contents = undefined;
|
|
64
|
+
let detail = unreadableDetail.contents;
|
|
65
|
+
if (detail !== undefined) {
|
|
66
|
+
return fail("Unreadable", detail);
|
|
67
|
+
} else {
|
|
68
|
+
return fail("Stale", undefined);
|
|
69
|
+
}
|
|
70
|
+
}, staleTimeout));
|
|
71
|
+
};
|
|
72
|
+
let fail = (reason, detail) => {
|
|
73
|
+
generation.contents = generation.contents + 1 | 0;
|
|
74
|
+
Utils.clearTimeoutRef(timeoutId);
|
|
75
|
+
closeConnection();
|
|
76
|
+
let since = liveAt.contents;
|
|
77
|
+
let servedMillis = since !== undefined ? Performance.now() - since | 0 : 0;
|
|
78
|
+
let provenMillis = Primitive_int.max(retryDelayFor(failureCount.contents), minProvenMillis);
|
|
79
|
+
failureCount.contents = servedMillis >= provenMillis ? 1 : failureCount.contents + 1 | 0;
|
|
80
|
+
let retryMillis = Utils.jitter(retryDelayFor(failureCount.contents));
|
|
81
|
+
let reason$1;
|
|
82
|
+
reason$1 = typeof reason !== "object" && reason === "Closed" && servedMillis >= provenMillis ? "Rotated" : reason;
|
|
83
|
+
timeoutId.contents = Primitive_option.some(setTimeout(() => {
|
|
84
|
+
timeoutId.contents = undefined;
|
|
85
|
+
start();
|
|
86
|
+
}, retryMillis));
|
|
87
|
+
onStatus({
|
|
88
|
+
TAG: "Down",
|
|
89
|
+
reason: reason$1,
|
|
90
|
+
detail: Stdlib_Option.map(detail, truncateDetail)
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
let start = () => {
|
|
94
|
+
generation.contents = generation.contents + 1 | 0;
|
|
95
|
+
unreadableDetail.contents = undefined;
|
|
96
|
+
liveAt.contents = undefined;
|
|
97
|
+
let connectionGeneration = generation.contents;
|
|
98
|
+
armStaleTimeout();
|
|
99
|
+
let goLive = () => {
|
|
100
|
+
armStaleTimeout();
|
|
101
|
+
if (Stdlib_Option.isNone(liveAt.contents)) {
|
|
102
|
+
liveAt.contents = Performance.now();
|
|
103
|
+
return onStatus("Live");
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
let driver_onConnected = () => {
|
|
107
|
+
if (generation.contents === connectionGeneration) {
|
|
108
|
+
return goLive();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
let driver_onKeepAlive = () => {
|
|
112
|
+
if (generation.contents === connectionGeneration && Stdlib_Option.isNone(unreadableDetail.contents)) {
|
|
113
|
+
return armStaleTimeout();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
let driver_onHeight = height => {
|
|
117
|
+
if (generation.contents === connectionGeneration) {
|
|
118
|
+
unreadableDetail.contents = undefined;
|
|
119
|
+
goLive();
|
|
120
|
+
return onHeight(height);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
let driver_onUnreadable = detail => {
|
|
124
|
+
if (generation.contents === connectionGeneration) {
|
|
125
|
+
unreadableDetail.contents = detail;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
let driver_onFailure = (reason, detail) => {
|
|
130
|
+
if (generation.contents === connectionGeneration) {
|
|
131
|
+
return fail(reason, detail);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
let driver = {
|
|
135
|
+
onConnected: driver_onConnected,
|
|
136
|
+
onKeepAlive: driver_onKeepAlive,
|
|
137
|
+
onHeight: driver_onHeight,
|
|
138
|
+
onUnreadable: driver_onUnreadable,
|
|
139
|
+
onFailure: driver_onFailure
|
|
140
|
+
};
|
|
141
|
+
let connected;
|
|
142
|
+
try {
|
|
143
|
+
connected = {
|
|
144
|
+
TAG: "Ok",
|
|
145
|
+
_0: connect(driver)
|
|
146
|
+
};
|
|
147
|
+
} catch (raw_exn) {
|
|
148
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
149
|
+
connected = {
|
|
150
|
+
TAG: "Error",
|
|
151
|
+
_0: exn
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (connected.TAG !== "Ok") {
|
|
155
|
+
if (generation.contents === connectionGeneration) {
|
|
156
|
+
return fail("ConnectFailed", Utils.exnMessage(connected._0));
|
|
157
|
+
} else {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
let closeCurrentConnection = connected._0;
|
|
162
|
+
if (generation.contents === connectionGeneration) {
|
|
163
|
+
closeConnectionRef.contents = closeCurrentConnection;
|
|
164
|
+
return;
|
|
165
|
+
} else {
|
|
166
|
+
return closeSafely(closeCurrentConnection);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
start();
|
|
170
|
+
return () => {
|
|
171
|
+
generation.contents = generation.contents + 1 | 0;
|
|
172
|
+
Utils.clearTimeoutRef(timeoutId);
|
|
173
|
+
closeConnection();
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let baseRetryMillis = 250;
|
|
178
|
+
|
|
179
|
+
let maxRetryMillis = 60000;
|
|
180
|
+
|
|
181
|
+
let provenStaleFraction = 4;
|
|
182
|
+
|
|
183
|
+
let maxDetailLength = 200;
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
baseRetryMillis,
|
|
187
|
+
maxRetryMillis,
|
|
188
|
+
provenStaleFraction,
|
|
189
|
+
maxDetailLength,
|
|
190
|
+
retryDelayFor,
|
|
191
|
+
truncateDetail,
|
|
192
|
+
subscribe,
|
|
193
|
+
}
|
|
194
|
+
/* Utils Not a pure module */
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Short enough that a chain at the head is not held up behind it, long enough
|
|
2
|
+
// that a wait polling through a whole block interval is not thousands of
|
|
3
|
+
// requests.
|
|
4
|
+
let pollingInterval = 400
|
|
5
|
+
|
|
1
6
|
// A napi error carries nothing but a message, so the native clients signal the
|
|
2
7
|
// recoverable conditions SourceManager knows how to retry as a `PREFIX:<int>`
|
|
3
8
|
// marker. Keep in sync with `request_stats.rs`.
|
|
@@ -6,6 +6,10 @@ type logsQueryPage = {
|
|
|
6
6
|
blockStore: BlockStore.t,
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// How often a HyperSync source is asked for the height while nothing is pushing
|
|
10
|
+
// one. Every ecosystem's client talks to the same service, so they wait alike.
|
|
11
|
+
let pollingInterval: int
|
|
12
|
+
|
|
9
13
|
// Map a native client's `PREFIX:<int>` failure marker onto the exception
|
|
10
14
|
// SourceManager retries on (`RateLimited` / `SourceBehindHead`), falling back to
|
|
11
15
|
// the original cause.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The server pings every 5s, so 15s is a safe margin before treating the
|
|
2
|
+
// connection as dead.
|
|
3
|
+
let staleTimeout = 15_000
|
|
4
|
+
|
|
5
|
+
let failure = (error: EventSource.errorEvent): (
|
|
6
|
+
Source.heightSubscriptionDownReason,
|
|
7
|
+
option<string>,
|
|
8
|
+
) =>
|
|
9
|
+
switch (error.code, error.message) {
|
|
10
|
+
| (Some(code), message) => (Http(code), message)
|
|
11
|
+
| (None, Some(message)) => (TransportError, Some(message))
|
|
12
|
+
// The stream ended without an HTTP error, which is how a load balancer
|
|
13
|
+
// rotating connections shows up.
|
|
14
|
+
| (None, None) => (Closed, None)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let subscribe = (~hyperSyncUrl, ~apiToken, ~onHeight, ~onStatus) =>
|
|
18
|
+
HeightStream.subscribe(~staleTimeout, ~onHeight, ~onStatus, ~connect=driver => {
|
|
19
|
+
let userAgent = `hyperindex/${Utils.EnvioPackage.value.version}`
|
|
20
|
+
let es = EventSource.create(
|
|
21
|
+
~url=`${hyperSyncUrl}/height/sse`,
|
|
22
|
+
~options={
|
|
23
|
+
fetch: (url, ~args) => {
|
|
24
|
+
let headers =
|
|
25
|
+
args.headers
|
|
26
|
+
->Option.getOr(Dict.make())
|
|
27
|
+
->Utils.Dict.merge(
|
|
28
|
+
Dict.fromArray([("Authorization", `Bearer ${apiToken}`), ("User-Agent", userAgent)]),
|
|
29
|
+
)
|
|
30
|
+
EventSource.Fetch.fetch(url, ~args={...args, headers: headers})
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
es->EventSource.onopen(_ => driver.onConnected())
|
|
36
|
+
es->EventSource.onerror(error => {
|
|
37
|
+
let (reason, detail) = error->failure
|
|
38
|
+
driver.onFailure(~reason, ~detail?)
|
|
39
|
+
})
|
|
40
|
+
es->EventSource.addEventListener("ping", _ => driver.onKeepAlive())
|
|
41
|
+
es->EventSource.addEventListener("height", event =>
|
|
42
|
+
switch event.data->Int.fromString {
|
|
43
|
+
| Some(height) => driver.onHeight(height)
|
|
44
|
+
| None => driver.onUnreadable(~detail=event.data)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
() => es->EventSource.close
|
|
49
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Utils from "../Utils.res.mjs";
|
|
4
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
5
|
+
import * as Eventsource from "eventsource";
|
|
6
|
+
import * as HeightStream from "./HeightStream.res.mjs";
|
|
7
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
|
|
9
|
+
function failure(error) {
|
|
10
|
+
let match = error.code;
|
|
11
|
+
let match$1 = error.message;
|
|
12
|
+
if (match !== undefined) {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
TAG: "Http",
|
|
16
|
+
_0: match
|
|
17
|
+
},
|
|
18
|
+
match$1
|
|
19
|
+
];
|
|
20
|
+
} else if (match$1 !== undefined) {
|
|
21
|
+
return [
|
|
22
|
+
"TransportError",
|
|
23
|
+
match$1
|
|
24
|
+
];
|
|
25
|
+
} else {
|
|
26
|
+
return [
|
|
27
|
+
"Closed",
|
|
28
|
+
undefined
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function subscribe(hyperSyncUrl, apiToken, onHeight, onStatus) {
|
|
34
|
+
return HeightStream.subscribe(15000, onHeight, onStatus, driver => {
|
|
35
|
+
let userAgent = `hyperindex/` + Utils.EnvioPackage.value.version;
|
|
36
|
+
let es = new Eventsource.EventSource(hyperSyncUrl + `/height/sse`, {
|
|
37
|
+
fetch: (url, args) => {
|
|
38
|
+
let headers = Utils.Dict.merge(Stdlib_Option.getOr(args.headers, {}), Object.fromEntries([
|
|
39
|
+
[
|
|
40
|
+
"Authorization",
|
|
41
|
+
`Bearer ` + apiToken
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"User-Agent",
|
|
45
|
+
userAgent
|
|
46
|
+
]
|
|
47
|
+
]));
|
|
48
|
+
let newrecord = {...args};
|
|
49
|
+
return fetch(url, (newrecord.headers = headers, newrecord));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
es.onopen = () => driver.onConnected();
|
|
53
|
+
es.onerror = error => {
|
|
54
|
+
let match = failure(error);
|
|
55
|
+
driver.onFailure(match[0], match[1]);
|
|
56
|
+
};
|
|
57
|
+
es.addEventListener("ping", param => driver.onKeepAlive());
|
|
58
|
+
es.addEventListener("height", event => {
|
|
59
|
+
let height = Stdlib_Int.fromString(event.data, undefined);
|
|
60
|
+
if (height !== undefined) {
|
|
61
|
+
return driver.onHeight(height);
|
|
62
|
+
} else {
|
|
63
|
+
return driver.onUnreadable(event.data);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return () => {
|
|
67
|
+
es.close();
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let staleTimeout = 15000;
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
staleTimeout,
|
|
76
|
+
failure,
|
|
77
|
+
subscribe,
|
|
78
|
+
}
|
|
79
|
+
/* Utils Not a pure module */
|
|
@@ -1129,7 +1129,7 @@ let make = (
|
|
|
1129
1129
|
|
|
1130
1130
|
let createHeightSubscription =
|
|
1131
1131
|
ws->Option.map(wsUrl =>
|
|
1132
|
-
(~onHeight) =>
|
|
1132
|
+
(~onHeight, ~onStatus) => EvmRpcWs.subscribe(~wsUrl, ~onHeight, ~onStatus)
|
|
1133
1133
|
)
|
|
1134
1134
|
|
|
1135
1135
|
{
|
|
@@ -8,6 +8,7 @@ import * as Source from "./Source.res.mjs";
|
|
|
8
8
|
import * as Address from "../Address.res.mjs";
|
|
9
9
|
import * as ChainId from "../ChainId.res.mjs";
|
|
10
10
|
import * as Logging from "../Logging.res.mjs";
|
|
11
|
+
import * as EvmRpcWs from "./EvmRpcWs.res.mjs";
|
|
11
12
|
import * as Internal from "../Internal.res.mjs";
|
|
12
13
|
import * as BlockStore from "./BlockStore.res.mjs";
|
|
13
14
|
import * as LazyLoader from "../LazyLoader.res.mjs";
|
|
@@ -24,7 +25,6 @@ import * as HyperSyncClient from "./HyperSyncClient.res.mjs";
|
|
|
24
25
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
25
26
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
26
27
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
27
|
-
import * as RpcWebSocketHeightStream from "./RpcWebSocketHeightStream.res.mjs";
|
|
28
28
|
|
|
29
29
|
let TransactionDataNotFound = /* @__PURE__ */Primitive_exceptions.create("RpcSource.TransactionDataNotFound");
|
|
30
30
|
|
|
@@ -1069,7 +1069,7 @@ function make(param) {
|
|
|
1069
1069
|
},
|
|
1070
1070
|
requestStats: drainRequestStats()
|
|
1071
1071
|
}));
|
|
1072
|
-
let createHeightSubscription = Stdlib_Option.map(param.ws, wsUrl => (onHeight =>
|
|
1072
|
+
let createHeightSubscription = Stdlib_Option.map(param.ws, wsUrl => ((onHeight, onStatus) => EvmRpcWs.subscribe(wsUrl, onHeight, onStatus)));
|
|
1073
1073
|
return {
|
|
1074
1074
|
name: name,
|
|
1075
1075
|
sourceFor: param.sourceFor,
|
package/src/sources/Source.res
CHANGED
|
@@ -130,6 +130,56 @@ exception GetItemsError(getItemsError)
|
|
|
130
130
|
|
|
131
131
|
type sourceFor = Sync | Fallback | Realtime
|
|
132
132
|
|
|
133
|
+
// Why a height subscription's connection ended. Closed, so a consumer decides
|
|
134
|
+
// what a failure means by matching on it rather than by recognising a string,
|
|
135
|
+
// and so every transport describes an outage in the same words.
|
|
136
|
+
type heightSubscriptionDownReason =
|
|
137
|
+
// Ended cleanly after serving long enough to have been worth making: a load
|
|
138
|
+
// balancer moving a connection, and routine.
|
|
139
|
+
| Rotated
|
|
140
|
+
// Ended cleanly without having served: a server dropping connections it
|
|
141
|
+
// ought to be keeping.
|
|
142
|
+
| Closed
|
|
143
|
+
// Delivered nothing at all for a whole staleness window.
|
|
144
|
+
| Stale
|
|
145
|
+
// Sent frames the transport could not read. Alone among these it never heals
|
|
146
|
+
// on its own, which is why consumers tell an operator about it.
|
|
147
|
+
| Unreadable
|
|
148
|
+
// No connection was established to begin with.
|
|
149
|
+
| ConnectFailed
|
|
150
|
+
// The provider refused the subscription.
|
|
151
|
+
| SubscribeRejected
|
|
152
|
+
// The socket itself reported an error.
|
|
153
|
+
| TransportError
|
|
154
|
+
// Refused with an HTTP status, which is the whole of what an SSE stream is
|
|
155
|
+
// told about a refusal.
|
|
156
|
+
| Http(int)
|
|
157
|
+
// The consumer gave the source up. Not an outage, but the connection is gone
|
|
158
|
+
// and a count that omitted it would leave the source looking connected.
|
|
159
|
+
| Unsubscribed
|
|
160
|
+
|
|
161
|
+
// Bounded, so it is safe as a metric label. Whatever the provider said about
|
|
162
|
+
// one failure belongs in `detail` and in a log line, never here.
|
|
163
|
+
let downReasonLabel = reason =>
|
|
164
|
+
switch reason {
|
|
165
|
+
| Rotated => "rotated"
|
|
166
|
+
| Closed => "closed"
|
|
167
|
+
| Stale => "stale"
|
|
168
|
+
| Unreadable => "unreadable"
|
|
169
|
+
| ConnectFailed => "connect-failed"
|
|
170
|
+
| SubscribeRejected => "subscribe-rejected"
|
|
171
|
+
| TransportError => "error"
|
|
172
|
+
| Http(code) => `http-${code->Int.toString}`
|
|
173
|
+
| Unsubscribed => "unsubscribed"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Connection state of a height subscription, reported by every transport.
|
|
177
|
+
// `Down` repeats on each failed retry while the stream stays broken, and `Live`
|
|
178
|
+
// fires on every (re)connect, so consumers must treat both as idempotent.
|
|
179
|
+
// `detail` is whatever the provider said for this one failure — an error
|
|
180
|
+
// message, or the frame nobody could read.
|
|
181
|
+
type heightSubscriptionStatus = Live | Down({reason: heightSubscriptionDownReason, detail?: string})
|
|
182
|
+
|
|
133
183
|
type t = {
|
|
134
184
|
name: string,
|
|
135
185
|
sourceFor: sourceFor,
|
|
@@ -160,7 +210,10 @@ type t = {
|
|
|
160
210
|
~retry: int,
|
|
161
211
|
~logger: Pino.t,
|
|
162
212
|
) => promise<blockRangeFetchResponse>,
|
|
163
|
-
createHeightSubscription?: (
|
|
213
|
+
createHeightSubscription?: (
|
|
214
|
+
~onHeight: int => unit,
|
|
215
|
+
~onStatus: heightSubscriptionStatus => unit,
|
|
216
|
+
) => unit => unit,
|
|
164
217
|
// Invoked when a reorg or internally inconsistent response means local state
|
|
165
218
|
// may point at an orphaned chain (e.g. the RPC block cache): drop all of it.
|
|
166
219
|
// Deliberately takes no rollback target — the deepest reorged block isn't
|
|
@@ -50,6 +50,30 @@ let RateLimited = /* @__PURE__ */Primitive_exceptions.create("Source.RateLimited
|
|
|
50
50
|
|
|
51
51
|
let GetItemsError = /* @__PURE__ */Primitive_exceptions.create("Source.GetItemsError");
|
|
52
52
|
|
|
53
|
+
function downReasonLabel(reason) {
|
|
54
|
+
if (typeof reason === "object") {
|
|
55
|
+
return `http-` + reason._0.toString();
|
|
56
|
+
}
|
|
57
|
+
switch (reason) {
|
|
58
|
+
case "Rotated" :
|
|
59
|
+
return "rotated";
|
|
60
|
+
case "Closed" :
|
|
61
|
+
return "closed";
|
|
62
|
+
case "Stale" :
|
|
63
|
+
return "stale";
|
|
64
|
+
case "Unreadable" :
|
|
65
|
+
return "unreadable";
|
|
66
|
+
case "ConnectFailed" :
|
|
67
|
+
return "connect-failed";
|
|
68
|
+
case "SubscribeRejected" :
|
|
69
|
+
return "subscribe-rejected";
|
|
70
|
+
case "TransportError" :
|
|
71
|
+
return "error";
|
|
72
|
+
case "Unsubscribed" :
|
|
73
|
+
return "unsubscribed";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
export {
|
|
54
78
|
nativeFailurePrefix,
|
|
55
79
|
nativeFailurePayloadSchema,
|
|
@@ -58,5 +82,6 @@ export {
|
|
|
58
82
|
SourceBehindHead,
|
|
59
83
|
RateLimited,
|
|
60
84
|
GetItemsError,
|
|
85
|
+
downReasonLabel,
|
|
61
86
|
}
|
|
62
87
|
/* nativeFailurePayloadSchema Not a pure module */
|