envio 3.2.1 → 3.3.0-alpha.1

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.
Files changed (106) hide show
  1. package/package.json +6 -7
  2. package/src/Batch.res +12 -15
  3. package/src/Batch.res.mjs +4 -5
  4. package/src/BatchProcessing.res +199 -0
  5. package/src/BatchProcessing.res.mjs +125 -0
  6. package/src/ChainFetching.res +373 -0
  7. package/src/ChainFetching.res.mjs +206 -0
  8. package/src/ChainMetadata.res +27 -0
  9. package/src/ChainMetadata.res.mjs +27 -0
  10. package/src/ChainState.res +643 -0
  11. package/src/ChainState.res.mjs +476 -0
  12. package/src/ChainState.resi +87 -0
  13. package/src/Config.res +13 -4
  14. package/src/Config.res.mjs +8 -3
  15. package/src/ContractRegisterContext.res +106 -0
  16. package/src/ContractRegisterContext.res.mjs +76 -0
  17. package/src/Core.res +3 -0
  18. package/src/CrossChainState.res +294 -0
  19. package/src/CrossChainState.res.mjs +221 -0
  20. package/src/CrossChainState.resi +39 -0
  21. package/src/Ecosystem.res +58 -0
  22. package/src/Ecosystem.res.mjs +43 -1
  23. package/src/Env.res +0 -2
  24. package/src/Env.res.mjs +0 -3
  25. package/src/EventConfigBuilder.res +2 -2
  26. package/src/EventProcessing.res +60 -46
  27. package/src/EventProcessing.res.mjs +33 -32
  28. package/src/EventUtils.res +0 -4
  29. package/src/EventUtils.res.mjs +0 -4
  30. package/src/ExitOnCaughtUp.res +10 -0
  31. package/src/ExitOnCaughtUp.res.mjs +22 -0
  32. package/src/FetchState.res +205 -73
  33. package/src/FetchState.res.mjs +242 -103
  34. package/src/InMemoryStore.res +36 -451
  35. package/src/InMemoryStore.res.mjs +20 -369
  36. package/src/IndexerLoop.res +43 -0
  37. package/src/IndexerLoop.res.mjs +46 -0
  38. package/src/IndexerState.res +536 -0
  39. package/src/IndexerState.res.mjs +543 -0
  40. package/src/IndexerState.resi +128 -0
  41. package/src/Internal.res +29 -8
  42. package/src/LoadLayer.res +20 -18
  43. package/src/LoadLayer.res.mjs +14 -12
  44. package/src/LoadLayer.resi +6 -3
  45. package/src/Logging.res +11 -44
  46. package/src/Logging.res.mjs +10 -42
  47. package/src/Main.res +54 -79
  48. package/src/Main.res.mjs +44 -56
  49. package/src/PgStorage.res +8 -71
  50. package/src/PgStorage.res.mjs +3 -47
  51. package/src/Prometheus.res +1 -13
  52. package/src/Prometheus.res.mjs +84 -96
  53. package/src/PruneStaleHistory.res +45 -0
  54. package/src/PruneStaleHistory.res.mjs +46 -0
  55. package/src/RawEvent.res +73 -0
  56. package/src/RawEvent.res.mjs +51 -0
  57. package/src/Rollback.res +204 -0
  58. package/src/Rollback.res.mjs +118 -0
  59. package/src/SimulateItems.res +12 -10
  60. package/src/SimulateItems.res.mjs +1 -1
  61. package/src/UserContext.res +26 -114
  62. package/src/UserContext.res.mjs +15 -78
  63. package/src/Writing.res +242 -0
  64. package/src/Writing.res.mjs +215 -0
  65. package/src/db/InternalTable.res +14 -27
  66. package/src/sources/Evm.res +40 -1
  67. package/src/sources/Evm.res.mjs +94 -84
  68. package/src/sources/Fuel.res +40 -1
  69. package/src/sources/Fuel.res.mjs +36 -26
  70. package/src/sources/HyperFuel.res +41 -120
  71. package/src/sources/HyperFuel.res.mjs +42 -80
  72. package/src/sources/HyperFuel.resi +1 -24
  73. package/src/sources/HyperFuelClient.res +16 -297
  74. package/src/sources/HyperFuelClient.res.mjs +5 -4
  75. package/src/sources/HyperFuelSource.res +33 -8
  76. package/src/sources/HyperFuelSource.res.mjs +56 -10
  77. package/src/sources/HyperSyncSource.res +5 -3
  78. package/src/sources/HyperSyncSource.res.mjs +1 -1
  79. package/src/sources/RpcSource.res +2 -2
  80. package/src/sources/RpcSource.res.mjs +1 -1
  81. package/src/sources/SourceManager.res +14 -23
  82. package/src/sources/SourceManager.res.mjs +27 -29
  83. package/src/sources/SourceManager.resi +4 -2
  84. package/src/sources/Svm.res +18 -1
  85. package/src/sources/Svm.res.mjs +30 -22
  86. package/src/sources/SvmHyperSyncSource.res +1 -1
  87. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  88. package/src/tui/Tui.res +38 -36
  89. package/src/tui/Tui.res.mjs +51 -51
  90. package/src/ChainFetcher.res +0 -519
  91. package/src/ChainFetcher.res.mjs +0 -314
  92. package/src/ChainManager.res +0 -358
  93. package/src/ChainManager.res.mjs +0 -291
  94. package/src/Ctx.res +0 -6
  95. package/src/Ctx.res.mjs +0 -2
  96. package/src/GlobalState.res +0 -1056
  97. package/src/GlobalState.res.mjs +0 -1086
  98. package/src/GlobalStateManager.res +0 -57
  99. package/src/GlobalStateManager.res.mjs +0 -68
  100. package/src/GlobalStateManager.resi +0 -7
  101. package/src/Ports.res +0 -5
  102. package/src/Ports.res.mjs +0 -9
  103. package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
  104. package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
  105. package/src/sources/EnvioApiClient.res +0 -15
  106. package/src/sources/EnvioApiClient.res.mjs +0 -24
@@ -571,33 +571,20 @@ GROUP BY "${(#chain_id: field :> string)}";`
571
571
  }
572
572
 
573
573
  module RawEvents = {
574
- type t = {
575
- @as("chain_id") chainId: int,
576
- @as("event_id") eventId: bigint,
577
- @as("event_name") eventName: string,
578
- @as("contract_name") contractName: string,
579
- @as("block_number") blockNumber: int,
580
- @as("log_index") logIndex: int,
581
- @as("src_address") srcAddress: Address.t,
582
- @as("block_hash") blockHash: string,
583
- @as("block_timestamp") blockTimestamp: int,
584
- @as("block_fields") blockFields: JSON.t,
585
- @as("transaction_fields") transactionFields: JSON.t,
586
- params: JSON.t,
587
- }
588
-
589
- let schema = S.schema(s => {
590
- chainId: s.matches(S.int),
591
- eventId: s.matches(S.bigint),
592
- eventName: s.matches(S.string),
593
- contractName: s.matches(S.string),
594
- blockNumber: s.matches(S.int),
595
- logIndex: s.matches(S.int),
596
- srcAddress: s.matches(Address.schema),
597
- blockHash: s.matches(S.string),
598
- blockTimestamp: s.matches(S.int),
599
- blockFields: s.matches(S.json(~validate=false)),
600
- transactionFields: s.matches(S.json(~validate=false)),
574
+ type t = Internal.rawEvent
575
+
576
+ let schema = S.schema((s): t => {
577
+ chain_id: s.matches(S.int),
578
+ event_id: s.matches(S.bigint),
579
+ event_name: s.matches(S.string),
580
+ contract_name: s.matches(S.string),
581
+ block_number: s.matches(S.int),
582
+ log_index: s.matches(S.int),
583
+ src_address: s.matches(Address.schema),
584
+ block_hash: s.matches(S.string),
585
+ block_timestamp: s.matches(S.int),
586
+ block_fields: s.matches(S.json(~validate=false)),
587
+ transaction_fields: s.matches(S.json(~validate=false)),
601
588
  params: s.matches(S.json(~validate=false)),
602
589
  })
603
590
 
@@ -1,10 +1,25 @@
1
+ // EVM's concrete item payload. Erased to `Internal.eventPayload` on the item
2
+ // and recovered here via `toPayload`.
3
+ type payload = {
4
+ contractName: string,
5
+ eventName: string,
6
+ params: Internal.eventParams,
7
+ chainId: int,
8
+ srcAddress: Address.t,
9
+ logIndex: int,
10
+ transaction: Internal.eventTransaction,
11
+ block: Internal.eventBlock,
12
+ }
13
+ external fromPayload: payload => Internal.eventPayload = "%identity"
14
+ external toPayload: Internal.eventPayload => payload = "%identity"
15
+
1
16
  let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
2
17
  delete fields.hash
3
18
  delete fields.number
4
19
  delete fields.timestamp
5
20
  }`)
6
21
 
7
- let ecosystem: Ecosystem.t = {
22
+ let make = (~logger: Pino.t): Ecosystem.t => {
8
23
  name: Evm,
9
24
  blockFields: [
10
25
  "number",
@@ -90,4 +105,28 @@ let ecosystem: Ecosystem.t = {
90
105
  onEventBlockFilterSchema: S.object(s =>
91
106
  s.field("block", S.option(S.object(s2 => s2.field("number", S.unknown))))
92
107
  ),
108
+ logger,
109
+ toEvent: eventItem => eventItem.payload->Internal.payloadToEvent,
110
+ toEventLogger: eventItem =>
111
+ Logging.createChildFrom(
112
+ ~logger,
113
+ ~params={
114
+ "contract": eventItem.eventConfig.contractName,
115
+ "event": eventItem.eventConfig.name,
116
+ "chainId": eventItem.chain->ChainMap.Chain.toChainId,
117
+ "block": eventItem.blockNumber,
118
+ "logIndex": eventItem.logIndex,
119
+ "address": (eventItem.payload->toPayload).srcAddress,
120
+ },
121
+ ),
122
+ toRawEvent: eventItem => {
123
+ let payload = eventItem.payload->toPayload
124
+ eventItem->RawEvent.make(
125
+ ~block=payload.block,
126
+ ~transaction=payload.transaction,
127
+ ~params=payload.params,
128
+ ~srcAddress=payload.srcAddress,
129
+ ~cleanUpRawEventFieldsInPlace,
130
+ )
131
+ },
93
132
  }
@@ -1,5 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Logging from "../Logging.res.mjs";
4
+ import * as RawEvent from "../RawEvent.res.mjs";
3
5
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
4
6
 
5
7
  let cleanUpRawEventFieldsInPlace = (fields => {
@@ -8,91 +10,99 @@ let cleanUpRawEventFieldsInPlace = (fields => {
8
10
  delete fields.timestamp
9
11
  });
10
12
 
11
- let ecosystem_blockFields = [
12
- "number",
13
- "timestamp",
14
- "hash",
15
- "parentHash",
16
- "nonce",
17
- "sha3Uncles",
18
- "logsBloom",
19
- "transactionsRoot",
20
- "stateRoot",
21
- "receiptsRoot",
22
- "miner",
23
- "difficulty",
24
- "totalDifficulty",
25
- "extraData",
26
- "size",
27
- "gasLimit",
28
- "gasUsed",
29
- "uncles",
30
- "baseFeePerGas",
31
- "blobGasUsed",
32
- "excessBlobGas",
33
- "parentBeaconBlockRoot",
34
- "withdrawalsRoot",
35
- "l1BlockNumber",
36
- "sendCount",
37
- "sendRoot",
38
- "mixHash"
39
- ];
40
-
41
- let ecosystem_transactionFields = [
42
- "transactionIndex",
43
- "hash",
44
- "from",
45
- "to",
46
- "gas",
47
- "gasPrice",
48
- "maxPriorityFeePerGas",
49
- "maxFeePerGas",
50
- "cumulativeGasUsed",
51
- "effectiveGasPrice",
52
- "gasUsed",
53
- "input",
54
- "nonce",
55
- "value",
56
- "v",
57
- "r",
58
- "s",
59
- "contractAddress",
60
- "logsBloom",
61
- "root",
62
- "status",
63
- "yParity",
64
- "chainId",
65
- "maxFeePerBlobGas",
66
- "blobVersionedHashes",
67
- "type",
68
- "l1Fee",
69
- "l1GasPrice",
70
- "l1GasUsed",
71
- "l1FeeScalar",
72
- "gasUsedForL1",
73
- "accessList",
74
- "authorizationList"
75
- ];
76
-
77
- let ecosystem_onBlockFilterSchema = S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown)))));
78
-
79
- let ecosystem_onEventBlockFilterSchema = S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown)))));
80
-
81
- let ecosystem = {
82
- name: "evm",
83
- blockFields: ecosystem_blockFields,
84
- transactionFields: ecosystem_transactionFields,
85
- blockNumberName: "number",
86
- blockTimestampName: "timestamp",
87
- blockHashName: "hash",
88
- cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
89
- onBlockMethodName: "onBlock",
90
- onBlockFilterSchema: ecosystem_onBlockFilterSchema,
91
- onEventBlockFilterSchema: ecosystem_onEventBlockFilterSchema
92
- };
13
+ function make(logger) {
14
+ return {
15
+ name: "evm",
16
+ blockFields: [
17
+ "number",
18
+ "timestamp",
19
+ "hash",
20
+ "parentHash",
21
+ "nonce",
22
+ "sha3Uncles",
23
+ "logsBloom",
24
+ "transactionsRoot",
25
+ "stateRoot",
26
+ "receiptsRoot",
27
+ "miner",
28
+ "difficulty",
29
+ "totalDifficulty",
30
+ "extraData",
31
+ "size",
32
+ "gasLimit",
33
+ "gasUsed",
34
+ "uncles",
35
+ "baseFeePerGas",
36
+ "blobGasUsed",
37
+ "excessBlobGas",
38
+ "parentBeaconBlockRoot",
39
+ "withdrawalsRoot",
40
+ "l1BlockNumber",
41
+ "sendCount",
42
+ "sendRoot",
43
+ "mixHash"
44
+ ],
45
+ transactionFields: [
46
+ "transactionIndex",
47
+ "hash",
48
+ "from",
49
+ "to",
50
+ "gas",
51
+ "gasPrice",
52
+ "maxPriorityFeePerGas",
53
+ "maxFeePerGas",
54
+ "cumulativeGasUsed",
55
+ "effectiveGasPrice",
56
+ "gasUsed",
57
+ "input",
58
+ "nonce",
59
+ "value",
60
+ "v",
61
+ "r",
62
+ "s",
63
+ "contractAddress",
64
+ "logsBloom",
65
+ "root",
66
+ "status",
67
+ "yParity",
68
+ "chainId",
69
+ "maxFeePerBlobGas",
70
+ "blobVersionedHashes",
71
+ "type",
72
+ "l1Fee",
73
+ "l1GasPrice",
74
+ "l1GasUsed",
75
+ "l1FeeScalar",
76
+ "gasUsedForL1",
77
+ "accessList",
78
+ "authorizationList"
79
+ ],
80
+ blockNumberName: "number",
81
+ blockTimestampName: "timestamp",
82
+ blockHashName: "hash",
83
+ cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
84
+ onBlockMethodName: "onBlock",
85
+ onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown))))),
86
+ onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("number", S$RescriptSchema.unknown))))),
87
+ logger: logger,
88
+ toEvent: eventItem => eventItem.payload,
89
+ toEventLogger: eventItem => Logging.createChildFrom(logger, {
90
+ contract: eventItem.eventConfig.contractName,
91
+ event: eventItem.eventConfig.name,
92
+ chainId: eventItem.chain,
93
+ block: eventItem.blockNumber,
94
+ logIndex: eventItem.logIndex,
95
+ address: eventItem.payload.srcAddress
96
+ }),
97
+ toRawEvent: eventItem => {
98
+ let payload = eventItem.payload;
99
+ return RawEvent.make(eventItem, payload.block, payload.transaction, payload.params, payload.srcAddress, cleanUpRawEventFieldsInPlace);
100
+ }
101
+ };
102
+ }
93
103
 
94
104
  export {
95
105
  cleanUpRawEventFieldsInPlace,
96
- ecosystem,
106
+ make,
97
107
  }
98
- /* ecosystem Not a pure module */
108
+ /* Logging Not a pure module */
@@ -1,10 +1,25 @@
1
+ // Fuel's concrete item payload. Erased to `Internal.eventPayload` on the item
2
+ // and recovered here via `toPayload`.
3
+ type payload = {
4
+ contractName: string,
5
+ eventName: string,
6
+ params: Internal.eventParams,
7
+ chainId: int,
8
+ srcAddress: Address.t,
9
+ logIndex: int,
10
+ transaction: Internal.eventTransaction,
11
+ block: Internal.eventBlock,
12
+ }
13
+ external fromPayload: payload => Internal.eventPayload = "%identity"
14
+ external toPayload: Internal.eventPayload => payload = "%identity"
15
+
1
16
  let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
2
17
  delete fields.id
3
18
  delete fields.height
4
19
  delete fields.time
5
20
  }`)
6
21
 
7
- let ecosystem: Ecosystem.t = {
22
+ let make = (~logger: Pino.t): Ecosystem.t => {
8
23
  name: Fuel,
9
24
  blockFields: ["id", "height", "time"],
10
25
  transactionFields: ["id"],
@@ -25,4 +40,28 @@ let ecosystem: Ecosystem.t = {
25
40
  onEventBlockFilterSchema: S.object(s =>
26
41
  s.field("block", S.option(S.object(s2 => s2.field("height", S.unknown))))
27
42
  ),
43
+ logger,
44
+ toEvent: eventItem => eventItem.payload->Internal.payloadToEvent,
45
+ toEventLogger: eventItem =>
46
+ Logging.createChildFrom(
47
+ ~logger,
48
+ ~params={
49
+ "contract": eventItem.eventConfig.contractName,
50
+ "event": eventItem.eventConfig.name,
51
+ "chainId": eventItem.chain->ChainMap.Chain.toChainId,
52
+ "block": eventItem.blockNumber,
53
+ "logIndex": eventItem.logIndex,
54
+ "address": (eventItem.payload->toPayload).srcAddress,
55
+ },
56
+ ),
57
+ toRawEvent: eventItem => {
58
+ let payload = eventItem.payload->toPayload
59
+ eventItem->RawEvent.make(
60
+ ~block=payload.block,
61
+ ~transaction=payload.transaction,
62
+ ~params=payload.params,
63
+ ~srcAddress=payload.srcAddress,
64
+ ~cleanUpRawEventFieldsInPlace,
65
+ )
66
+ },
28
67
  }
@@ -1,5 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Logging from "../Logging.res.mjs";
4
+ import * as RawEvent from "../RawEvent.res.mjs";
3
5
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
4
6
 
5
7
  let cleanUpRawEventFieldsInPlace = (fields => {
@@ -8,33 +10,41 @@ let cleanUpRawEventFieldsInPlace = (fields => {
8
10
  delete fields.time
9
11
  });
10
12
 
11
- let ecosystem_blockFields = [
12
- "id",
13
- "height",
14
- "time"
15
- ];
16
-
17
- let ecosystem_transactionFields = ["id"];
18
-
19
- let ecosystem_onBlockFilterSchema = S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown)))));
20
-
21
- let ecosystem_onEventBlockFilterSchema = S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown)))));
22
-
23
- let ecosystem = {
24
- name: "fuel",
25
- blockFields: ecosystem_blockFields,
26
- transactionFields: ecosystem_transactionFields,
27
- blockNumberName: "height",
28
- blockTimestampName: "time",
29
- blockHashName: "id",
30
- cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
31
- onBlockMethodName: "onBlock",
32
- onBlockFilterSchema: ecosystem_onBlockFilterSchema,
33
- onEventBlockFilterSchema: ecosystem_onEventBlockFilterSchema
34
- };
13
+ function make(logger) {
14
+ return {
15
+ name: "fuel",
16
+ blockFields: [
17
+ "id",
18
+ "height",
19
+ "time"
20
+ ],
21
+ transactionFields: ["id"],
22
+ blockNumberName: "height",
23
+ blockTimestampName: "time",
24
+ blockHashName: "id",
25
+ cleanUpRawEventFieldsInPlace: cleanUpRawEventFieldsInPlace,
26
+ onBlockMethodName: "onBlock",
27
+ onBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown))))),
28
+ onEventBlockFilterSchema: S$RescriptSchema.object(s => s.f("block", S$RescriptSchema.option(S$RescriptSchema.object(s2 => s2.f("height", S$RescriptSchema.unknown))))),
29
+ logger: logger,
30
+ toEvent: eventItem => eventItem.payload,
31
+ toEventLogger: eventItem => Logging.createChildFrom(logger, {
32
+ contract: eventItem.eventConfig.contractName,
33
+ event: eventItem.eventConfig.name,
34
+ chainId: eventItem.chain,
35
+ block: eventItem.blockNumber,
36
+ logIndex: eventItem.logIndex,
37
+ address: eventItem.payload.srcAddress
38
+ }),
39
+ toRawEvent: eventItem => {
40
+ let payload = eventItem.payload;
41
+ return RawEvent.make(eventItem, payload.block, payload.transaction, payload.params, payload.srcAddress, cleanUpRawEventFieldsInPlace);
42
+ }
43
+ };
44
+ }
35
45
 
36
46
  export {
37
47
  cleanUpRawEventFieldsInPlace,
38
- ecosystem,
48
+ make,
39
49
  }
40
- /* ecosystem Not a pure module */
50
+ /* Logging Not a pure module */
@@ -1,21 +1,3 @@
1
- //Manage clients in cache so we don't need to reinstantiate each time
2
- //Ideally client should be passed in as a param to the functions but
3
- //we are still sharing the same signature with eth archive query builder
4
-
5
- module CachedClients = {
6
- let cache: dict<HyperFuelClient.t> = Dict.make()
7
-
8
- let getClient = url => {
9
- switch cache->Utils.Dict.dangerouslyGetNonOption(url) {
10
- | Some(client) => client
11
- | None =>
12
- let newClient = HyperFuelClient.make({url: url})
13
- cache->Dict.set(url, newClient)
14
- newClient
15
- }
16
- }
17
- }
18
-
19
1
  type hyperSyncPage<'item> = {
20
2
  items: array<'item>,
21
3
  nextBlock: int,
@@ -36,29 +18,8 @@ type item = {
36
18
  block: block,
37
19
  }
38
20
 
39
- type blockNumberAndHash = {
40
- blockNumber: int,
41
- hash: string,
42
- }
43
-
44
21
  type logsQueryPage = hyperSyncPage<item>
45
22
 
46
- type missingParams = {
47
- queryName: string,
48
- missingParams: array<string>,
49
- }
50
- type queryError = UnexpectedMissingParams(missingParams)
51
-
52
- let queryErrorToMsq = (e: queryError): string => {
53
- switch e {
54
- | UnexpectedMissingParams({queryName, missingParams}) =>
55
- `${queryName} query failed due to unexpected missing params on response:
56
- ${missingParams->Array.joinUnsafe(", ")}`
57
- }
58
- }
59
-
60
- type queryResponse<'a> = result<'a, queryError>
61
-
62
23
  module GetLogs = {
63
24
  type error =
64
25
  | UnexpectedMissingParams({missingParams: array<string>})
@@ -66,6 +27,31 @@ module GetLogs = {
66
27
 
67
28
  exception Error(error)
68
29
 
30
+ // Rust encodes structured failures as a JSON payload in the napi error's
31
+ // message: `{"kind":"MissingFields","fields":["receipt.txId", ...]}`.
32
+ // JSON.parse + shape check is the recovery protocol — no string-grepping
33
+ // on anyhow's Debug format.
34
+ let extractMissingParams = (exn: exn): option<array<string>> => {
35
+ let message = switch exn {
36
+ | JsExn(jsExn) => jsExn->JsExn.message
37
+ | _ => None
38
+ }
39
+ switch message {
40
+ | None => None
41
+ | Some(msg) =>
42
+ switch msg->JSON.parseOrThrow->JSON.Decode.object {
43
+ | exception _ => None
44
+ | None => None
45
+ | Some(obj) =>
46
+ switch (obj->Dict.get("kind"), obj->Dict.get("fields")) {
47
+ | (Some(String("MissingFields")), Some(Array(fields))) =>
48
+ Some(fields->Array.filterMap(JSON.Decode.string))
49
+ | _ => None
50
+ }
51
+ }
52
+ }
53
+ }
54
+
69
55
  let makeRequestBody = (
70
56
  ~fromBlock,
71
57
  ~toBlockInclusive,
@@ -121,9 +107,7 @@ module GetLogs = {
121
107
  let {receipts, blocks} = response_data
122
108
 
123
109
  let blocksDict = Dict.make()
124
- blocks
125
- ->(Utils.magic: option<'a> => 'a)
126
- ->Array.forEach(block => {
110
+ blocks->Array.forEach(block => {
127
111
  blocksDict->Dict.set(block.height->(Utils.magic: int => string), block)
128
112
  })
129
113
 
@@ -166,93 +150,30 @@ module GetLogs = {
166
150
  page
167
151
  }
168
152
 
169
- let query = async (~serverUrl, ~fromBlock, ~toBlock, ~recieptsSelection): logsQueryPage => {
153
+ let query = async (
154
+ ~client: HyperFuelClient.t,
155
+ ~fromBlock,
156
+ ~toBlock,
157
+ ~recieptsSelection,
158
+ ): logsQueryPage => {
170
159
  let query: HyperFuelClient.QueryTypes.query = makeRequestBody(
171
160
  ~fromBlock,
172
161
  ~toBlockInclusive=toBlock,
173
162
  ~recieptsSelection,
174
163
  )
175
164
 
176
- let hyperFuelClient = CachedClients.getClient(serverUrl)
177
-
178
- let res = await hyperFuelClient->HyperFuelClient.getSelectedData(query)
165
+ let res = switch await client->HyperFuelClient.getSelectedData(query) {
166
+ | res => res
167
+ | exception exn =>
168
+ switch exn->extractMissingParams {
169
+ | Some(missingParams) => throw(Error(UnexpectedMissingParams({missingParams: missingParams})))
170
+ | None => throw(exn)
171
+ }
172
+ }
179
173
  if res.nextBlock <= fromBlock {
180
- // Might happen when /height response was from another instance of HyperSync
174
+ // Might happen when /height response was from another instance of HyperFuel
181
175
  throw(Error(WrongInstance))
182
176
  }
183
177
  res->convertResponse
184
178
  }
185
179
  }
186
-
187
- module BlockData = {
188
- let convertResponse = (res: HyperFuelClient.queryResponseTyped): option<
189
- ReorgDetection.blockDataWithTimestamp,
190
- > => {
191
- res.data.blocks->Option.flatMap(blocks => {
192
- blocks
193
- ->Array.get(0)
194
- ->Option.map(block => {
195
- switch block {
196
- | {height: blockNumber, time: timestamp, id: blockHash} =>
197
- (
198
- {
199
- blockTimestamp: timestamp,
200
- blockNumber,
201
- blockHash,
202
- }: ReorgDetection.blockDataWithTimestamp
203
- )
204
- }
205
- })
206
- })
207
- }
208
-
209
- let rec queryBlockData = async (~serverUrl, ~blockNumber, ~logger): option<
210
- ReorgDetection.blockDataWithTimestamp,
211
- > => {
212
- let query: HyperFuelClient.QueryTypes.query = {
213
- fromBlock: blockNumber,
214
- toBlockExclusive: blockNumber + 1,
215
- // FIXME: Theoretically it should work without the outputs filter, but it doesn't for some reason
216
- outputs: [%raw(`{}`)],
217
- // FIXME: Had to add inputs {} as well, since it failed on block 1211599 during wildcard Call indexing
218
- inputs: [%raw(`{}`)],
219
- fieldSelection: {
220
- block: [Height, Id, Time],
221
- },
222
- includeAllBlocks: true,
223
- }
224
-
225
- let hyperFuelClient = CachedClients.getClient(serverUrl)
226
-
227
- let logger = Logging.createChildFrom(
228
- ~logger,
229
- ~params={"logType": "hypersync get blockhash query", "blockNumber": blockNumber},
230
- )
231
-
232
- let executeQuery = () => hyperFuelClient->HyperFuelClient.getSelectedData(query)
233
-
234
- let res = await executeQuery->Time.retryAsyncWithExponentialBackOff(~logger)
235
-
236
- // If the block is not found, retry the query. This can occur since replicas of hypersync might not hack caught up yet
237
- if res.nextBlock <= blockNumber {
238
- let logger = Logging.createChild(~params={"url": serverUrl})
239
- let delayMilliseconds = 100
240
- logger->Logging.childInfo(
241
- `Block #${blockNumber->Int.toString} not found in HyperFuel. HyperFuel has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${delayMilliseconds->Int.toString}ms.`,
242
- )
243
- await Time.resolvePromiseAfterDelay(~delayMilliseconds)
244
- await queryBlockData(~serverUrl, ~blockNumber, ~logger)
245
- } else {
246
- res->convertResponse
247
- }
248
- }
249
- }
250
-
251
- let queryBlockData = BlockData.queryBlockData
252
-
253
- let heightRoute = Rest.route(() => {
254
- path: "/height",
255
- method: Get,
256
- input: _ => (),
257
- responses: [s => s.field("height", S.int)],
258
- })