@qevm/providers 1.0.2 → 1.0.3
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 +3 -9
- package/lib/_version.d.ts +1 -1
- package/lib/_version.js +1 -1
- package/lib/base-provider.d.ts +2 -2
- package/lib/base-provider.d.ts.map +1 -1
- package/lib/base-provider.js +300 -145
- package/lib/base-provider.js.map +1 -1
- package/lib/browser-ipc-provider.d.ts.map +1 -1
- package/lib/browser-ipc-provider.js.map +1 -1
- package/lib/browser-net.d.ts.map +1 -1
- package/lib/browser-net.js.map +1 -1
- package/lib/browser-ws.d.ts.map +1 -1
- package/lib/browser-ws.js +2 -2
- package/lib/browser-ws.js.map +1 -1
- package/lib/fallback-provider.d.ts +1 -1
- package/lib/fallback-provider.d.ts.map +1 -1
- package/lib/fallback-provider.js +86 -55
- package/lib/fallback-provider.js.map +1 -1
- package/lib/formatter.d.ts.map +1 -1
- package/lib/formatter.js +35 -29
- package/lib/formatter.js.map +1 -1
- package/lib/index.d.ts +3 -10
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -28
- package/lib/index.js.map +1 -1
- package/lib/ipc-provider.d.ts +1 -1
- package/lib/ipc-provider.d.ts.map +1 -1
- package/lib/ipc-provider.js +6 -4
- package/lib/ipc-provider.js.map +1 -1
- package/lib/json-rpc-batch-provider.d.ts.map +1 -1
- package/lib/json-rpc-batch-provider.js +6 -6
- package/lib/json-rpc-batch-provider.js.map +1 -1
- package/lib/json-rpc-provider.d.ts +2 -2
- package/lib/json-rpc-provider.d.ts.map +1 -1
- package/lib/json-rpc-provider.js +167 -69
- package/lib/json-rpc-provider.js.map +1 -1
- package/lib/url-json-rpc-provider.d.ts +1 -1
- package/lib/url-json-rpc-provider.d.ts.map +1 -1
- package/lib/url-json-rpc-provider.js +4 -4
- package/lib/url-json-rpc-provider.js.map +1 -1
- package/lib/web3-provider.d.ts +1 -2
- package/lib/web3-provider.d.ts.map +1 -1
- package/lib/web3-provider.js +11 -14
- package/lib/web3-provider.js.map +1 -1
- package/lib/websocket-provider.d.ts +1 -1
- package/lib/websocket-provider.d.ts.map +1 -1
- package/lib/websocket-provider.js +21 -17
- package/lib/websocket-provider.js.map +1 -1
- package/lib/ws.d.ts.map +1 -1
- package/package.json +61 -58
- package/src.ts/_version.ts +1 -1
- package/src.ts/base-provider.ts +1418 -645
- package/src.ts/browser-ipc-provider.ts +1 -3
- package/src.ts/browser-net.ts +1 -1
- package/src.ts/browser-ws.ts +14 -8
- package/src.ts/fallback-provider.ts +375 -198
- package/src.ts/formatter.ts +161 -81
- package/src.ts/index.ts +54 -68
- package/src.ts/ipc-provider.ts +8 -7
- package/src.ts/json-rpc-batch-provider.ts +48 -44
- package/src.ts/json-rpc-provider.ts +594 -245
- package/src.ts/url-json-rpc-provider.ts +34 -15
- package/src.ts/web3-provider.ts +87 -54
- package/src.ts/websocket-provider.ts +124 -66
- package/src.ts/ws.ts +1 -1
- package/lib/alchemy-provider.d.ts +0 -17
- package/lib/alchemy-provider.d.ts.map +0 -1
- package/lib/alchemy-provider.js +0 -88
- package/lib/alchemy-provider.js.map +0 -1
- package/lib/ankr-provider.d.ts +0 -10
- package/lib/ankr-provider.d.ts.map +0 -1
- package/lib/ankr-provider.js +0 -59
- package/lib/ankr-provider.js.map +0 -1
- package/lib/cloudflare-provider.d.ts +0 -8
- package/lib/cloudflare-provider.d.ts.map +0 -1
- package/lib/cloudflare-provider.js +0 -37
- package/lib/cloudflare-provider.js.map +0 -1
- package/lib/etherscan-provider.d.ts +0 -18
- package/lib/etherscan-provider.d.ts.map +0 -1
- package/lib/etherscan-provider.js +0 -408
- package/lib/etherscan-provider.js.map +0 -1
- package/lib/infura-provider.d.ts +0 -21
- package/lib/infura-provider.d.ts.map +0 -1
- package/lib/infura-provider.js +0 -117
- package/lib/infura-provider.js.map +0 -1
- package/lib/nodesmith-provider.d.ts +0 -7
- package/lib/nodesmith-provider.d.ts.map +0 -1
- package/lib/nodesmith-provider.js +0 -44
- package/lib/nodesmith-provider.js.map +0 -1
- package/lib/pocket-provider.d.ts +0 -12
- package/lib/pocket-provider.d.ts.map +0 -1
- package/lib/pocket-provider.js +0 -78
- package/lib/pocket-provider.js.map +0 -1
- package/src.ts/alchemy-provider.ts +0 -101
- package/src.ts/ankr-provider.ts +0 -68
- package/src.ts/cloudflare-provider.ts +0 -42
- package/src.ts/etherscan-provider.ts +0 -454
- package/src.ts/infura-provider.ts +0 -143
- package/src.ts/nodesmith-provider.ts +0 -50
- package/src.ts/pocket-provider.ts +0 -93
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Block,
|
|
5
|
+
BlockWithTransactions,
|
|
6
|
+
Provider,
|
|
7
|
+
} from "@qevm/abstract-provider";
|
|
4
8
|
import { BigNumber } from "@qevm/bignumber";
|
|
5
9
|
import { isHexString } from "@qevm/bytes";
|
|
6
|
-
import { Network } from "@
|
|
7
|
-
import { deepCopy, defineReadOnly, shallowCopy } from "@
|
|
10
|
+
import { Network } from "@qevm/networks";
|
|
11
|
+
import { deepCopy, defineReadOnly, shallowCopy } from "@qevm/properties";
|
|
8
12
|
import { shuffled } from "@qevm/random";
|
|
9
13
|
import { poll } from "@qevm/web";
|
|
10
14
|
|
|
11
15
|
import { BaseProvider } from "./base-provider";
|
|
12
16
|
import { isCommunityResource } from "./formatter";
|
|
13
17
|
|
|
14
|
-
import { Logger } from "@
|
|
18
|
+
import { Logger } from "@qevm/logger";
|
|
15
19
|
import { version } from "./_version";
|
|
16
20
|
const logger = new Logger(version);
|
|
17
21
|
|
|
18
|
-
function now() {
|
|
22
|
+
function now() {
|
|
23
|
+
return new Date().getTime();
|
|
24
|
+
}
|
|
19
25
|
|
|
20
26
|
// Returns to network as long as all agree, or null if any is null.
|
|
21
27
|
// Throws an error if any two networks do not match.
|
|
@@ -26,15 +32,27 @@ function checkNetworks(networks: Array<Network>): Network {
|
|
|
26
32
|
const network = networks[i];
|
|
27
33
|
|
|
28
34
|
// Null! We do not know our network; bail.
|
|
29
|
-
if (network == null) {
|
|
35
|
+
if (network == null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
30
38
|
|
|
31
39
|
if (result) {
|
|
32
40
|
// Make sure the network matches the previous networks
|
|
33
|
-
if (
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
if (
|
|
42
|
+
!(
|
|
43
|
+
result.name === network.name &&
|
|
44
|
+
result.chainId === network.chainId &&
|
|
45
|
+
(result.ensAddress === network.ensAddress ||
|
|
46
|
+
(result.ensAddress == null &&
|
|
47
|
+
network.ensAddress == null))
|
|
48
|
+
)
|
|
49
|
+
) {
|
|
50
|
+
logger.throwArgumentError(
|
|
51
|
+
"provider mismatch",
|
|
52
|
+
"networks",
|
|
53
|
+
networks,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
38
56
|
} else {
|
|
39
57
|
result = network;
|
|
40
58
|
}
|
|
@@ -53,7 +71,8 @@ function median(values: Array<number>, maxDelta?: number): number {
|
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
// Even length; take the average of the two middle
|
|
56
|
-
const a = values[middle - 1],
|
|
74
|
+
const a = values[middle - 1],
|
|
75
|
+
b = values[middle];
|
|
57
76
|
|
|
58
77
|
if (maxDelta != null && Math.abs(a - b) > maxDelta) {
|
|
59
78
|
return null;
|
|
@@ -65,35 +84,40 @@ function median(values: Array<number>, maxDelta?: number): number {
|
|
|
65
84
|
function serialize(value: any): string {
|
|
66
85
|
if (value === null) {
|
|
67
86
|
return "null";
|
|
68
|
-
} else if (typeof
|
|
87
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
69
88
|
return JSON.stringify(value);
|
|
70
|
-
} else if (typeof
|
|
89
|
+
} else if (typeof value === "string") {
|
|
71
90
|
return value;
|
|
72
91
|
} else if (BigNumber.isBigNumber(value)) {
|
|
73
92
|
return value.toString();
|
|
74
93
|
} else if (Array.isArray(value)) {
|
|
75
94
|
return JSON.stringify(value.map((i) => serialize(i)));
|
|
76
|
-
} else if (typeof
|
|
95
|
+
} else if (typeof value === "object") {
|
|
77
96
|
const keys = Object.keys(value);
|
|
78
97
|
keys.sort();
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
return (
|
|
99
|
+
"{" +
|
|
100
|
+
keys
|
|
101
|
+
.map((key) => {
|
|
102
|
+
let v = value[key];
|
|
103
|
+
if (typeof v === "function") {
|
|
104
|
+
v = "[function]";
|
|
105
|
+
} else {
|
|
106
|
+
v = serialize(v);
|
|
107
|
+
}
|
|
108
|
+
return JSON.stringify(key) + ":" + v;
|
|
109
|
+
})
|
|
110
|
+
.join(",") +
|
|
111
|
+
"}"
|
|
112
|
+
);
|
|
88
113
|
}
|
|
89
114
|
|
|
90
|
-
throw new Error("unknown value type: " + typeof
|
|
115
|
+
throw new Error("unknown value type: " + typeof value);
|
|
91
116
|
}
|
|
92
117
|
|
|
93
118
|
// Next request ID to use for emitting debug info
|
|
94
119
|
let nextRid = 1;
|
|
95
120
|
|
|
96
|
-
|
|
97
121
|
export interface FallbackProviderConfig {
|
|
98
122
|
// The Provider
|
|
99
123
|
provider: Provider;
|
|
@@ -112,35 +136,35 @@ export interface FallbackProviderConfig {
|
|
|
112
136
|
// provider may be more reliable or trustworthy than others, but usually
|
|
113
137
|
// this should be left as the default
|
|
114
138
|
weight?: number;
|
|
115
|
-
}
|
|
139
|
+
}
|
|
116
140
|
|
|
117
141
|
// A Staller is used to provide a delay to give a Provider a chance to response
|
|
118
142
|
// before asking the next Provider to try.
|
|
119
143
|
type Staller = {
|
|
120
|
-
wait: (func: () => void) => Promise<void
|
|
121
|
-
getPromise: () => Promise<void
|
|
122
|
-
cancel: () => void
|
|
144
|
+
wait: (func: () => void) => Promise<void>;
|
|
145
|
+
getPromise: () => Promise<void>;
|
|
146
|
+
cancel: () => void;
|
|
123
147
|
};
|
|
124
148
|
|
|
125
149
|
function stall(duration: number): Staller {
|
|
126
150
|
let cancel: () => void = null;
|
|
127
151
|
|
|
128
152
|
let timer: NodeJS.Timeout = null;
|
|
129
|
-
let promise = <Promise<void>>
|
|
130
|
-
cancel = function() {
|
|
153
|
+
let promise = <Promise<void>>new Promise((resolve) => {
|
|
154
|
+
cancel = function () {
|
|
131
155
|
if (timer) {
|
|
132
156
|
clearTimeout(timer);
|
|
133
157
|
timer = null;
|
|
134
158
|
}
|
|
135
159
|
resolve();
|
|
136
|
-
}
|
|
160
|
+
};
|
|
137
161
|
timer = setTimeout(cancel, duration);
|
|
138
|
-
})
|
|
162
|
+
});
|
|
139
163
|
|
|
140
164
|
const wait = (func: () => void) => {
|
|
141
165
|
promise = promise.then(func);
|
|
142
166
|
return promise;
|
|
143
|
-
}
|
|
167
|
+
};
|
|
144
168
|
|
|
145
169
|
function getPromise(): Promise<void> {
|
|
146
170
|
return promise;
|
|
@@ -154,7 +178,7 @@ const ForwardErrors = [
|
|
|
154
178
|
Logger.errors.INSUFFICIENT_FUNDS,
|
|
155
179
|
Logger.errors.NONCE_EXPIRED,
|
|
156
180
|
Logger.errors.REPLACEMENT_UNDERPRICED,
|
|
157
|
-
Logger.errors.UNPREDICTABLE_GAS_LIMIT
|
|
181
|
+
Logger.errors.UNPREDICTABLE_GAS_LIMIT,
|
|
158
182
|
];
|
|
159
183
|
|
|
160
184
|
const ForwardProperties = [
|
|
@@ -166,7 +190,6 @@ const ForwardProperties = [
|
|
|
166
190
|
"transaction",
|
|
167
191
|
];
|
|
168
192
|
|
|
169
|
-
|
|
170
193
|
// @TODO: Make this an object with staller and cancel built-in
|
|
171
194
|
interface RunningConfig extends FallbackProviderConfig {
|
|
172
195
|
start?: number;
|
|
@@ -176,15 +199,19 @@ interface RunningConfig extends FallbackProviderConfig {
|
|
|
176
199
|
staller?: Staller;
|
|
177
200
|
result?: any;
|
|
178
201
|
error?: Error;
|
|
179
|
-
}
|
|
202
|
+
}
|
|
180
203
|
|
|
181
204
|
function exposeDebugConfig(config: RunningConfig, now?: number): any {
|
|
182
205
|
const result: any = {
|
|
183
|
-
weight: config.weight
|
|
206
|
+
weight: config.weight,
|
|
184
207
|
};
|
|
185
208
|
Object.defineProperty(result, "provider", { get: () => config.provider });
|
|
186
|
-
if (config.start) {
|
|
187
|
-
|
|
209
|
+
if (config.start) {
|
|
210
|
+
result.start = config.start;
|
|
211
|
+
}
|
|
212
|
+
if (now) {
|
|
213
|
+
result.duration = now - config.start;
|
|
214
|
+
}
|
|
188
215
|
if (config.done) {
|
|
189
216
|
if (config.error) {
|
|
190
217
|
result.error = config.error;
|
|
@@ -195,14 +222,18 @@ function exposeDebugConfig(config: RunningConfig, now?: number): any {
|
|
|
195
222
|
return result;
|
|
196
223
|
}
|
|
197
224
|
|
|
198
|
-
function normalizedTally(
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
function normalizedTally(
|
|
226
|
+
normalize: (value: any) => string,
|
|
227
|
+
quorum: number,
|
|
228
|
+
): (configs: Array<RunningConfig>) => any {
|
|
229
|
+
return function (configs: Array<RunningConfig>): any {
|
|
201
230
|
// Count the votes for each result
|
|
202
|
-
const tally: { [
|
|
231
|
+
const tally: { [key: string]: { count: number; result: any } } = {};
|
|
203
232
|
configs.forEach((c) => {
|
|
204
233
|
const value = normalize(c.result);
|
|
205
|
-
if (!tally[value]) {
|
|
234
|
+
if (!tally[value]) {
|
|
235
|
+
tally[value] = { count: 0, result: c.result };
|
|
236
|
+
}
|
|
206
237
|
tally[value].count++;
|
|
207
238
|
});
|
|
208
239
|
|
|
@@ -217,10 +248,13 @@ function normalizedTally(normalize: (value: any) => string, quorum: number): (co
|
|
|
217
248
|
|
|
218
249
|
// No quroum
|
|
219
250
|
return undefined;
|
|
220
|
-
}
|
|
251
|
+
};
|
|
221
252
|
}
|
|
222
|
-
function getProcessFunc(
|
|
223
|
-
|
|
253
|
+
function getProcessFunc(
|
|
254
|
+
provider: FallbackProvider,
|
|
255
|
+
method: string,
|
|
256
|
+
params: { [key: string]: any },
|
|
257
|
+
): (configs: Array<RunningConfig>) => any {
|
|
224
258
|
let normalize = serialize;
|
|
225
259
|
|
|
226
260
|
switch (method) {
|
|
@@ -229,17 +263,24 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [
|
|
|
229
263
|
// present, in which case that is probably true and the median
|
|
230
264
|
// is going to be stale soon. In the event of a malicious node,
|
|
231
265
|
// the lie will be true soon enough.
|
|
232
|
-
return function(configs: Array<RunningConfig>): number {
|
|
266
|
+
return function (configs: Array<RunningConfig>): number {
|
|
233
267
|
const values = configs.map((c) => c.result);
|
|
234
268
|
|
|
235
269
|
// Get the median block number
|
|
236
|
-
let blockNumber = median(
|
|
237
|
-
|
|
270
|
+
let blockNumber = median(
|
|
271
|
+
configs.map((c) => c.result),
|
|
272
|
+
2,
|
|
273
|
+
);
|
|
274
|
+
if (blockNumber == null) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
238
277
|
|
|
239
278
|
blockNumber = Math.ceil(blockNumber);
|
|
240
279
|
|
|
241
280
|
// If the next block height is present, its prolly safe to use
|
|
242
|
-
if (values.indexOf(blockNumber + 1) >= 0) {
|
|
281
|
+
if (values.indexOf(blockNumber + 1) >= 0) {
|
|
282
|
+
blockNumber++;
|
|
283
|
+
}
|
|
243
284
|
|
|
244
285
|
// Don't ever roll back the blockNumber
|
|
245
286
|
if (blockNumber >= provider._highestBlockNumber) {
|
|
@@ -253,18 +294,18 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [
|
|
|
253
294
|
// Return the middle (round index up) value, similar to median
|
|
254
295
|
// but do not average even entries and choose the higher.
|
|
255
296
|
// Malicious actors must compromise 50% of the nodes to lie.
|
|
256
|
-
return function(configs: Array<RunningConfig>): BigNumber {
|
|
297
|
+
return function (configs: Array<RunningConfig>): BigNumber {
|
|
257
298
|
const values = configs.map((c) => c.result);
|
|
258
299
|
values.sort();
|
|
259
300
|
return values[Math.floor(values.length / 2)];
|
|
260
|
-
}
|
|
301
|
+
};
|
|
261
302
|
|
|
262
303
|
case "getEtherPrice":
|
|
263
304
|
// Returns the median price. Malicious actors must compromise at
|
|
264
305
|
// least 50% of the nodes to lie (in a meaningful way).
|
|
265
|
-
return function(configs: Array<RunningConfig>): number {
|
|
306
|
+
return function (configs: Array<RunningConfig>): number {
|
|
266
307
|
return median(configs.map((c) => c.result));
|
|
267
|
-
}
|
|
308
|
+
};
|
|
268
309
|
|
|
269
310
|
// No additional normalizing required; serialize is enough
|
|
270
311
|
case "getBalance":
|
|
@@ -279,21 +320,25 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [
|
|
|
279
320
|
// We drop the confirmations from transactions as it is approximate
|
|
280
321
|
case "getTransaction":
|
|
281
322
|
case "getTransactionReceipt":
|
|
282
|
-
normalize = function(tx: any): string {
|
|
283
|
-
if (tx == null) {
|
|
323
|
+
normalize = function (tx: any): string {
|
|
324
|
+
if (tx == null) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
284
327
|
|
|
285
328
|
tx = shallowCopy(tx);
|
|
286
329
|
tx.confirmations = -1;
|
|
287
330
|
return serialize(tx);
|
|
288
|
-
}
|
|
331
|
+
};
|
|
289
332
|
break;
|
|
290
333
|
|
|
291
334
|
// We drop the confirmations from transactions as it is approximate
|
|
292
335
|
case "getBlock":
|
|
293
336
|
// We drop the confirmations from transactions as it is approximate
|
|
294
337
|
if (params.includeTransactions) {
|
|
295
|
-
normalize = function(block: BlockWithTransactions): string {
|
|
296
|
-
if (block == null) {
|
|
338
|
+
normalize = function (block: BlockWithTransactions): string {
|
|
339
|
+
if (block == null) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
297
342
|
|
|
298
343
|
block = shallowCopy(block);
|
|
299
344
|
block.transactions = block.transactions.map((tx) => {
|
|
@@ -304,10 +349,12 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [
|
|
|
304
349
|
return serialize(block);
|
|
305
350
|
};
|
|
306
351
|
} else {
|
|
307
|
-
normalize = function(block: Block): string {
|
|
308
|
-
if (block == null) {
|
|
352
|
+
normalize = function (block: Block): string {
|
|
353
|
+
if (block == null) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
309
356
|
return serialize(block);
|
|
310
|
-
}
|
|
357
|
+
};
|
|
311
358
|
}
|
|
312
359
|
break;
|
|
313
360
|
|
|
@@ -318,36 +365,52 @@ function getProcessFunc(provider: FallbackProvider, method: string, params: { [
|
|
|
318
365
|
// Return the result if and only if the expected quorum is
|
|
319
366
|
// satisfied and agreed upon for the final result.
|
|
320
367
|
return normalizedTally(normalize, provider.quorum);
|
|
321
|
-
|
|
322
368
|
}
|
|
323
369
|
|
|
324
370
|
// If we are doing a blockTag query, we need to make sure the backend is
|
|
325
371
|
// caught up to the FallbackProvider, before sending a request to it.
|
|
326
|
-
async function waitForSync(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
372
|
+
async function waitForSync(
|
|
373
|
+
config: RunningConfig,
|
|
374
|
+
blockNumber: number,
|
|
375
|
+
): Promise<BaseProvider> {
|
|
376
|
+
const provider = <BaseProvider>config.provider;
|
|
377
|
+
|
|
378
|
+
if (
|
|
379
|
+
(provider.blockNumber != null && provider.blockNumber >= blockNumber) ||
|
|
380
|
+
blockNumber === -1
|
|
381
|
+
) {
|
|
330
382
|
return provider;
|
|
331
383
|
}
|
|
332
384
|
|
|
333
|
-
return poll(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
385
|
+
return poll(
|
|
386
|
+
() => {
|
|
387
|
+
return new Promise((resolve, reject) => {
|
|
388
|
+
setTimeout(function () {
|
|
389
|
+
// We are synced
|
|
390
|
+
if (provider.blockNumber >= blockNumber) {
|
|
391
|
+
return resolve(provider);
|
|
392
|
+
}
|
|
339
393
|
|
|
340
|
-
|
|
341
|
-
|
|
394
|
+
// We're done; just quit
|
|
395
|
+
if (config.cancelled) {
|
|
396
|
+
return resolve(null);
|
|
397
|
+
}
|
|
342
398
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
399
|
+
// Try again, next block
|
|
400
|
+
return resolve(undefined);
|
|
401
|
+
}, 0);
|
|
402
|
+
});
|
|
403
|
+
},
|
|
404
|
+
{ oncePoll: provider },
|
|
405
|
+
);
|
|
348
406
|
}
|
|
349
407
|
|
|
350
|
-
async function getRunner(
|
|
408
|
+
async function getRunner(
|
|
409
|
+
config: RunningConfig,
|
|
410
|
+
currentBlockNumber: number,
|
|
411
|
+
method: string,
|
|
412
|
+
params: { [key: string]: any },
|
|
413
|
+
): Promise<any> {
|
|
351
414
|
let provider = config.provider;
|
|
352
415
|
|
|
353
416
|
switch (method) {
|
|
@@ -363,23 +426,34 @@ async function getRunner(config: RunningConfig, currentBlockNumber: number, meth
|
|
|
363
426
|
case "getTransactionCount":
|
|
364
427
|
case "getCode":
|
|
365
428
|
if (params.blockTag && isHexString(params.blockTag)) {
|
|
366
|
-
provider = await waitForSync(config, currentBlockNumber)
|
|
429
|
+
provider = await waitForSync(config, currentBlockNumber);
|
|
367
430
|
}
|
|
368
|
-
return provider[method](
|
|
431
|
+
return provider[method](
|
|
432
|
+
params.address,
|
|
433
|
+
params.blockTag || "latest",
|
|
434
|
+
);
|
|
369
435
|
case "getStorageAt":
|
|
370
436
|
if (params.blockTag && isHexString(params.blockTag)) {
|
|
371
|
-
provider = await waitForSync(config, currentBlockNumber)
|
|
437
|
+
provider = await waitForSync(config, currentBlockNumber);
|
|
372
438
|
}
|
|
373
|
-
return provider.getStorageAt(
|
|
439
|
+
return provider.getStorageAt(
|
|
440
|
+
params.address,
|
|
441
|
+
params.position,
|
|
442
|
+
params.blockTag || "latest",
|
|
443
|
+
);
|
|
374
444
|
case "getBlock":
|
|
375
445
|
if (params.blockTag && isHexString(params.blockTag)) {
|
|
376
|
-
provider = await waitForSync(config, currentBlockNumber)
|
|
446
|
+
provider = await waitForSync(config, currentBlockNumber);
|
|
377
447
|
}
|
|
378
|
-
return provider[
|
|
448
|
+
return provider[
|
|
449
|
+
params.includeTransactions
|
|
450
|
+
? "getBlockWithTransactions"
|
|
451
|
+
: "getBlock"
|
|
452
|
+
](params.blockTag || params.blockHash);
|
|
379
453
|
case "call":
|
|
380
454
|
case "estimateGas":
|
|
381
455
|
if (params.blockTag && isHexString(params.blockTag)) {
|
|
382
|
-
provider = await waitForSync(config, currentBlockNumber)
|
|
456
|
+
provider = await waitForSync(config, currentBlockNumber);
|
|
383
457
|
}
|
|
384
458
|
if (method === "call" && params.blockTag) {
|
|
385
459
|
return provider[method](params.transaction, params.blockTag);
|
|
@@ -390,17 +464,24 @@ async function getRunner(config: RunningConfig, currentBlockNumber: number, meth
|
|
|
390
464
|
return provider[method](params.transactionHash);
|
|
391
465
|
case "getLogs": {
|
|
392
466
|
let filter = params.filter;
|
|
393
|
-
if (
|
|
394
|
-
|
|
467
|
+
if (
|
|
468
|
+
(filter.fromBlock && isHexString(filter.fromBlock)) ||
|
|
469
|
+
(filter.toBlock && isHexString(filter.toBlock))
|
|
470
|
+
) {
|
|
471
|
+
provider = await waitForSync(config, currentBlockNumber);
|
|
395
472
|
}
|
|
396
473
|
return provider.getLogs(filter);
|
|
397
474
|
}
|
|
398
475
|
}
|
|
399
476
|
|
|
400
|
-
return logger.throwError(
|
|
401
|
-
method
|
|
402
|
-
|
|
403
|
-
|
|
477
|
+
return logger.throwError(
|
|
478
|
+
"unknown method error",
|
|
479
|
+
Logger.errors.UNKNOWN_ERROR,
|
|
480
|
+
{
|
|
481
|
+
method: method,
|
|
482
|
+
params: params,
|
|
483
|
+
},
|
|
484
|
+
);
|
|
404
485
|
}
|
|
405
486
|
|
|
406
487
|
export class FallbackProvider extends BaseProvider {
|
|
@@ -412,44 +493,77 @@ export class FallbackProvider extends BaseProvider {
|
|
|
412
493
|
// sample of backends
|
|
413
494
|
_highestBlockNumber: number;
|
|
414
495
|
|
|
415
|
-
constructor(
|
|
496
|
+
constructor(
|
|
497
|
+
providers: Array<Provider | FallbackProviderConfig>,
|
|
498
|
+
quorum?: number,
|
|
499
|
+
) {
|
|
416
500
|
if (providers.length === 0) {
|
|
417
|
-
logger.throwArgumentError(
|
|
501
|
+
logger.throwArgumentError(
|
|
502
|
+
"missing providers",
|
|
503
|
+
"providers",
|
|
504
|
+
providers,
|
|
505
|
+
);
|
|
418
506
|
}
|
|
419
507
|
|
|
420
|
-
const providerConfigs: Array<FallbackProviderConfig> = providers.map(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
508
|
+
const providerConfigs: Array<FallbackProviderConfig> = providers.map(
|
|
509
|
+
(configOrProvider, index) => {
|
|
510
|
+
if (Provider.isProvider(configOrProvider)) {
|
|
511
|
+
const stallTimeout = isCommunityResource(configOrProvider)
|
|
512
|
+
? 2000
|
|
513
|
+
: 750;
|
|
514
|
+
const priority = 1;
|
|
515
|
+
return Object.freeze({
|
|
516
|
+
provider: configOrProvider,
|
|
517
|
+
weight: 1,
|
|
518
|
+
stallTimeout,
|
|
519
|
+
priority,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
426
522
|
|
|
427
|
-
|
|
523
|
+
const config: FallbackProviderConfig =
|
|
524
|
+
shallowCopy(configOrProvider);
|
|
428
525
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
526
|
+
if (config.priority == null) {
|
|
527
|
+
config.priority = 1;
|
|
528
|
+
}
|
|
529
|
+
if (config.stallTimeout == null) {
|
|
530
|
+
config.stallTimeout = isCommunityResource(configOrProvider)
|
|
531
|
+
? 2000
|
|
532
|
+
: 750;
|
|
533
|
+
}
|
|
534
|
+
if (config.weight == null) {
|
|
535
|
+
config.weight = 1;
|
|
536
|
+
}
|
|
434
537
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
538
|
+
const weight = config.weight;
|
|
539
|
+
if (weight % 1 || weight > 512 || weight < 1) {
|
|
540
|
+
logger.throwArgumentError(
|
|
541
|
+
"invalid weight; must be integer in [1, 512]",
|
|
542
|
+
`providers[${index}].weight`,
|
|
543
|
+
weight,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
439
546
|
|
|
440
|
-
|
|
441
|
-
|
|
547
|
+
return Object.freeze(config);
|
|
548
|
+
},
|
|
549
|
+
);
|
|
442
550
|
|
|
443
|
-
const total = providerConfigs.reduce((accum, c) =>
|
|
551
|
+
const total = providerConfigs.reduce((accum, c) => accum + c.weight, 0);
|
|
444
552
|
|
|
445
553
|
if (quorum == null) {
|
|
446
554
|
quorum = total / 2;
|
|
447
555
|
} else if (quorum > total) {
|
|
448
|
-
logger.throwArgumentError(
|
|
556
|
+
logger.throwArgumentError(
|
|
557
|
+
"quorum will always fail; larger than total weight",
|
|
558
|
+
"quorum",
|
|
559
|
+
quorum,
|
|
560
|
+
);
|
|
449
561
|
}
|
|
450
562
|
|
|
451
563
|
// Are all providers' networks are known
|
|
452
|
-
let networkOrReady: Network | Promise<Network> = checkNetworks(
|
|
564
|
+
let networkOrReady: Network | Promise<Network> = checkNetworks(
|
|
565
|
+
providerConfigs.map((c) => (<any>c.provider).network),
|
|
566
|
+
);
|
|
453
567
|
|
|
454
568
|
// Not all networks are known; we must stall
|
|
455
569
|
if (networkOrReady == null) {
|
|
@@ -470,25 +584,39 @@ export class FallbackProvider extends BaseProvider {
|
|
|
470
584
|
}
|
|
471
585
|
|
|
472
586
|
async detectNetwork(): Promise<Network> {
|
|
473
|
-
const networks = await Promise.all(
|
|
587
|
+
const networks = await Promise.all(
|
|
588
|
+
this.providerConfigs.map((c) => c.provider.getNetwork()),
|
|
589
|
+
);
|
|
474
590
|
return checkNetworks(networks);
|
|
475
591
|
}
|
|
476
592
|
|
|
477
|
-
async perform(
|
|
593
|
+
async perform(
|
|
594
|
+
method: string,
|
|
595
|
+
params: { [name: string]: any },
|
|
596
|
+
): Promise<any> {
|
|
478
597
|
// Sending transactions is special; always broadcast it to all backends
|
|
479
598
|
if (method === "sendTransaction") {
|
|
480
|
-
const results: Array<string | Error> = await Promise.all(
|
|
481
|
-
|
|
482
|
-
return
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
599
|
+
const results: Array<string | Error> = await Promise.all(
|
|
600
|
+
this.providerConfigs.map((c) => {
|
|
601
|
+
return c.provider
|
|
602
|
+
.sendTransaction(params.signedTransaction)
|
|
603
|
+
.then(
|
|
604
|
+
(result) => {
|
|
605
|
+
return result.hash;
|
|
606
|
+
},
|
|
607
|
+
(error) => {
|
|
608
|
+
return error;
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
}),
|
|
612
|
+
);
|
|
487
613
|
|
|
488
614
|
// Any success is good enough (other errors are likely "already seen" errors
|
|
489
615
|
for (let i = 0; i < results.length; i++) {
|
|
490
616
|
const result = results[i];
|
|
491
|
-
if (typeof
|
|
617
|
+
if (typeof result === "string") {
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
492
620
|
}
|
|
493
621
|
|
|
494
622
|
// They were all an error; pick the first error
|
|
@@ -505,8 +633,10 @@ export class FallbackProvider extends BaseProvider {
|
|
|
505
633
|
|
|
506
634
|
// Shuffle the providers and then sort them by their priority; we
|
|
507
635
|
// shallowCopy them since we will store the result in them too
|
|
508
|
-
const configs: Array<RunningConfig> = shuffled(
|
|
509
|
-
|
|
636
|
+
const configs: Array<RunningConfig> = shuffled(
|
|
637
|
+
this.providerConfigs.map(shallowCopy),
|
|
638
|
+
);
|
|
639
|
+
configs.sort((a, b) => a.priority - b.priority);
|
|
510
640
|
|
|
511
641
|
const currentBlockNumber = this._highestBlockNumber;
|
|
512
642
|
|
|
@@ -516,8 +646,9 @@ export class FallbackProvider extends BaseProvider {
|
|
|
516
646
|
const t0 = now();
|
|
517
647
|
|
|
518
648
|
// Compute the inflight weight (exclude anything past)
|
|
519
|
-
let inflightWeight = configs
|
|
520
|
-
|
|
649
|
+
let inflightWeight = configs
|
|
650
|
+
.filter((c) => c.runner && t0 - c.start < c.stallTimeout)
|
|
651
|
+
.reduce((accum, c) => accum + c.weight, 0);
|
|
521
652
|
|
|
522
653
|
// Start running enough to meet quorum
|
|
523
654
|
while (inflightWeight < this.quorum && i < configs.length) {
|
|
@@ -527,44 +658,59 @@ export class FallbackProvider extends BaseProvider {
|
|
|
527
658
|
|
|
528
659
|
config.start = now();
|
|
529
660
|
config.staller = stall(config.stallTimeout);
|
|
530
|
-
config.staller.wait(() => {
|
|
531
|
-
|
|
532
|
-
config.runner = getRunner(config, currentBlockNumber, method, params).then((result) => {
|
|
533
|
-
config.done = true;
|
|
534
|
-
config.result = result;
|
|
535
|
-
|
|
536
|
-
if (this.listenerCount("debug")) {
|
|
537
|
-
this.emit("debug", {
|
|
538
|
-
action: "request",
|
|
539
|
-
rid: rid,
|
|
540
|
-
backend: exposeDebugConfig(config, now()),
|
|
541
|
-
request: { method: method, params: deepCopy(params) },
|
|
542
|
-
provider: this
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
}, (error) => {
|
|
547
|
-
config.done = true;
|
|
548
|
-
config.error = error;
|
|
549
|
-
|
|
550
|
-
if (this.listenerCount("debug")) {
|
|
551
|
-
this.emit("debug", {
|
|
552
|
-
action: "request",
|
|
553
|
-
rid: rid,
|
|
554
|
-
backend: exposeDebugConfig(config, now()),
|
|
555
|
-
request: { method: method, params: deepCopy(params) },
|
|
556
|
-
provider: this
|
|
557
|
-
});
|
|
558
|
-
}
|
|
661
|
+
config.staller.wait(() => {
|
|
662
|
+
config.staller = null;
|
|
559
663
|
});
|
|
560
664
|
|
|
665
|
+
config.runner = getRunner(
|
|
666
|
+
config,
|
|
667
|
+
currentBlockNumber,
|
|
668
|
+
method,
|
|
669
|
+
params,
|
|
670
|
+
).then(
|
|
671
|
+
(result) => {
|
|
672
|
+
config.done = true;
|
|
673
|
+
config.result = result;
|
|
674
|
+
|
|
675
|
+
if (this.listenerCount("debug")) {
|
|
676
|
+
this.emit("debug", {
|
|
677
|
+
action: "request",
|
|
678
|
+
rid: rid,
|
|
679
|
+
backend: exposeDebugConfig(config, now()),
|
|
680
|
+
request: {
|
|
681
|
+
method: method,
|
|
682
|
+
params: deepCopy(params),
|
|
683
|
+
},
|
|
684
|
+
provider: this,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
(error) => {
|
|
689
|
+
config.done = true;
|
|
690
|
+
config.error = error;
|
|
691
|
+
|
|
692
|
+
if (this.listenerCount("debug")) {
|
|
693
|
+
this.emit("debug", {
|
|
694
|
+
action: "request",
|
|
695
|
+
rid: rid,
|
|
696
|
+
backend: exposeDebugConfig(config, now()),
|
|
697
|
+
request: {
|
|
698
|
+
method: method,
|
|
699
|
+
params: deepCopy(params),
|
|
700
|
+
},
|
|
701
|
+
provider: this,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
);
|
|
706
|
+
|
|
561
707
|
if (this.listenerCount("debug")) {
|
|
562
708
|
this.emit("debug", {
|
|
563
709
|
action: "request",
|
|
564
710
|
rid: rid,
|
|
565
711
|
backend: exposeDebugConfig(config, null),
|
|
566
712
|
request: { method: method, params: deepCopy(params) },
|
|
567
|
-
provider: this
|
|
713
|
+
provider: this,
|
|
568
714
|
});
|
|
569
715
|
}
|
|
570
716
|
|
|
@@ -572,60 +718,83 @@ export class FallbackProvider extends BaseProvider {
|
|
|
572
718
|
}
|
|
573
719
|
|
|
574
720
|
// Wait for anything meaningful to finish or stall out
|
|
575
|
-
const waiting: Array<Promise<any>> = [
|
|
721
|
+
const waiting: Array<Promise<any>> = [];
|
|
576
722
|
configs.forEach((c) => {
|
|
577
|
-
if (c.done || !c.runner) {
|
|
723
|
+
if (c.done || !c.runner) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
578
726
|
waiting.push(c.runner);
|
|
579
|
-
if (c.staller) {
|
|
727
|
+
if (c.staller) {
|
|
728
|
+
waiting.push(c.staller.getPromise());
|
|
729
|
+
}
|
|
580
730
|
});
|
|
581
731
|
|
|
582
|
-
if (waiting.length) {
|
|
732
|
+
if (waiting.length) {
|
|
733
|
+
await Promise.race(waiting);
|
|
734
|
+
}
|
|
583
735
|
|
|
584
736
|
// Check the quorum and process the results; the process function
|
|
585
737
|
// may additionally decide the quorum is not met
|
|
586
|
-
const results = configs.filter((c) =>
|
|
738
|
+
const results = configs.filter((c) => c.done && c.error == null);
|
|
587
739
|
if (results.length >= this.quorum) {
|
|
588
740
|
const result = processFunc(results);
|
|
589
741
|
if (result !== undefined) {
|
|
590
742
|
// Shut down any stallers
|
|
591
|
-
configs.forEach(c => {
|
|
592
|
-
if (c.staller) {
|
|
743
|
+
configs.forEach((c) => {
|
|
744
|
+
if (c.staller) {
|
|
745
|
+
c.staller.cancel();
|
|
746
|
+
}
|
|
593
747
|
c.cancelled = true;
|
|
594
748
|
});
|
|
595
749
|
return result;
|
|
596
750
|
}
|
|
597
|
-
if (!first) {
|
|
751
|
+
if (!first) {
|
|
752
|
+
await stall(100).getPromise();
|
|
753
|
+
}
|
|
598
754
|
first = false;
|
|
599
755
|
}
|
|
600
756
|
|
|
601
757
|
// No result, check for errors that should be forwarded
|
|
602
|
-
const errors = configs.reduce(
|
|
603
|
-
|
|
758
|
+
const errors = configs.reduce(
|
|
759
|
+
(accum, c) => {
|
|
760
|
+
if (!c.done || c.error == null) {
|
|
761
|
+
return accum;
|
|
762
|
+
}
|
|
604
763
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
764
|
+
const code = (<any>c.error).code;
|
|
765
|
+
if (ForwardErrors.indexOf(code) >= 0) {
|
|
766
|
+
if (!accum[code]) {
|
|
767
|
+
accum[code] = { error: c.error, weight: 0 };
|
|
768
|
+
}
|
|
769
|
+
accum[code].weight += c.weight;
|
|
770
|
+
}
|
|
610
771
|
|
|
611
|
-
|
|
612
|
-
|
|
772
|
+
return accum;
|
|
773
|
+
},
|
|
774
|
+
<{ [code: string]: { error: Error; weight: number } }>{},
|
|
775
|
+
);
|
|
613
776
|
|
|
614
777
|
Object.keys(errors).forEach((errorCode: string) => {
|
|
615
778
|
const tally = errors[errorCode];
|
|
616
|
-
if (tally.weight < this.quorum) {
|
|
779
|
+
if (tally.weight < this.quorum) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
617
782
|
|
|
618
783
|
// Shut down any stallers
|
|
619
|
-
configs.forEach(c => {
|
|
620
|
-
if (c.staller) {
|
|
784
|
+
configs.forEach((c) => {
|
|
785
|
+
if (c.staller) {
|
|
786
|
+
c.staller.cancel();
|
|
787
|
+
}
|
|
621
788
|
c.cancelled = true;
|
|
622
789
|
});
|
|
623
790
|
|
|
624
|
-
const e = <any>
|
|
791
|
+
const e = <any>tally.error;
|
|
625
792
|
|
|
626
|
-
const props: { [
|
|
793
|
+
const props: { [name: string]: any } = {};
|
|
627
794
|
ForwardProperties.forEach((name) => {
|
|
628
|
-
if (e[name] == null) {
|
|
795
|
+
if (e[name] == null) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
629
798
|
props[name] = e[name];
|
|
630
799
|
});
|
|
631
800
|
|
|
@@ -633,22 +802,30 @@ export class FallbackProvider extends BaseProvider {
|
|
|
633
802
|
});
|
|
634
803
|
|
|
635
804
|
// All configs have run to completion; we will never get more data
|
|
636
|
-
if (configs.filter((c) => !c.done).length === 0) {
|
|
805
|
+
if (configs.filter((c) => !c.done).length === 0) {
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
637
808
|
}
|
|
638
809
|
|
|
639
810
|
// Shut down any stallers; shouldn't be any
|
|
640
|
-
configs.forEach(c => {
|
|
641
|
-
if (c.staller) {
|
|
811
|
+
configs.forEach((c) => {
|
|
812
|
+
if (c.staller) {
|
|
813
|
+
c.staller.cancel();
|
|
814
|
+
}
|
|
642
815
|
c.cancelled = true;
|
|
643
816
|
});
|
|
644
817
|
|
|
645
|
-
return logger.throwError(
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
818
|
+
return logger.throwError(
|
|
819
|
+
"failed to meet quorum",
|
|
820
|
+
Logger.errors.SERVER_ERROR,
|
|
821
|
+
{
|
|
822
|
+
method: method,
|
|
823
|
+
params: params,
|
|
824
|
+
//results: configs.map((c) => c.result),
|
|
825
|
+
//errors: configs.map((c) => c.error),
|
|
826
|
+
results: configs.map((c) => exposeDebugConfig(c)),
|
|
827
|
+
provider: this,
|
|
828
|
+
},
|
|
829
|
+
);
|
|
653
830
|
}
|
|
654
831
|
}
|