691 lines
34 KiB
JavaScript
691 lines
34 KiB
JavaScript
"use strict";
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
};
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
function step(op) {
|
|
if (f) throw new TypeError("Generator is already executing.");
|
|
while (_) try {
|
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
switch (op[0]) {
|
|
case 0: case 1: t = op; break;
|
|
case 4: _.label++; return { value: op[1], done: false };
|
|
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
default:
|
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
if (t[2]) _.ops.pop();
|
|
_.trys.pop(); continue;
|
|
}
|
|
op = body.call(thisArg, _);
|
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
}
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var abstract_provider_1 = require("@ethersproject/abstract-provider");
|
|
var bignumber_1 = require("@ethersproject/bignumber");
|
|
var bytes_1 = require("@ethersproject/bytes");
|
|
var properties_1 = require("@ethersproject/properties");
|
|
var random_1 = require("@ethersproject/random");
|
|
var web_1 = require("@ethersproject/web");
|
|
var base_provider_1 = require("./base-provider");
|
|
var logger_1 = require("@ethersproject/logger");
|
|
var _version_1 = require("./_version");
|
|
var logger = new logger_1.Logger(_version_1.version);
|
|
function now() { return (new Date()).getTime(); }
|
|
// Returns to network as long as all agree, or null if any is null.
|
|
// Throws an error if any two networks do not match.
|
|
function checkNetworks(networks) {
|
|
var result = null;
|
|
for (var i = 0; i < networks.length; i++) {
|
|
var network = networks[i];
|
|
// Null! We do not know our network; bail.
|
|
if (network == null) {
|
|
return null;
|
|
}
|
|
if (result) {
|
|
// Make sure the network matches the previous networks
|
|
if (!(result.name === network.name && result.chainId === network.chainId &&
|
|
((result.ensAddress === network.ensAddress) || (result.ensAddress == null && network.ensAddress == null)))) {
|
|
logger.throwArgumentError("provider mismatch", "networks", networks);
|
|
}
|
|
}
|
|
else {
|
|
result = network;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function median(values, maxDelta) {
|
|
values = values.slice().sort();
|
|
var middle = Math.floor(values.length / 2);
|
|
// Odd length; take the middle
|
|
if (values.length % 2) {
|
|
return values[middle];
|
|
}
|
|
// Even length; take the average of the two middle
|
|
var a = values[middle - 1], b = values[middle];
|
|
if (maxDelta != null && Math.abs(a - b) > maxDelta) {
|
|
return null;
|
|
}
|
|
return (a + b) / 2;
|
|
}
|
|
function serialize(value) {
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
else if (typeof (value) === "number" || typeof (value) === "boolean") {
|
|
return JSON.stringify(value);
|
|
}
|
|
else if (typeof (value) === "string") {
|
|
return value;
|
|
}
|
|
else if (bignumber_1.BigNumber.isBigNumber(value)) {
|
|
return value.toString();
|
|
}
|
|
else if (Array.isArray(value)) {
|
|
return JSON.stringify(value.map(function (i) { return serialize(i); }));
|
|
}
|
|
else if (typeof (value) === "object") {
|
|
var keys = Object.keys(value);
|
|
keys.sort();
|
|
return "{" + keys.map(function (key) {
|
|
var v = value[key];
|
|
if (typeof (v) === "function") {
|
|
v = "[function]";
|
|
}
|
|
else {
|
|
v = serialize(v);
|
|
}
|
|
return JSON.stringify(key) + ":" + v;
|
|
}).join(",") + "}";
|
|
}
|
|
throw new Error("unknown value type: " + typeof (value));
|
|
}
|
|
// Next request ID to use for emitting debug info
|
|
var nextRid = 1;
|
|
;
|
|
function stall(duration) {
|
|
var cancel = null;
|
|
var timer = null;
|
|
var promise = (new Promise(function (resolve) {
|
|
cancel = function () {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
resolve();
|
|
};
|
|
timer = setTimeout(cancel, duration);
|
|
}));
|
|
var wait = function (func) {
|
|
promise = promise.then(func);
|
|
return promise;
|
|
};
|
|
function getPromise() {
|
|
return promise;
|
|
}
|
|
return { cancel: cancel, getPromise: getPromise, wait: wait };
|
|
}
|
|
var ForwardErrors = [
|
|
logger_1.Logger.errors.CALL_EXCEPTION,
|
|
logger_1.Logger.errors.INSUFFICIENT_FUNDS,
|
|
logger_1.Logger.errors.NONCE_EXPIRED,
|
|
logger_1.Logger.errors.REPLACEMENT_UNDERPRICED,
|
|
logger_1.Logger.errors.UNPREDICTABLE_GAS_LIMIT
|
|
];
|
|
var ForwardProperties = [
|
|
"address",
|
|
"args",
|
|
"errorArgs",
|
|
"errorSignature",
|
|
"method",
|
|
"transaction",
|
|
];
|
|
;
|
|
function exposeDebugConfig(config, now) {
|
|
var result = {
|
|
weight: config.weight
|
|
};
|
|
Object.defineProperty(result, "provider", { get: function () { return config.provider; } });
|
|
if (config.start) {
|
|
result.start = config.start;
|
|
}
|
|
if (now) {
|
|
result.duration = (now - config.start);
|
|
}
|
|
if (config.done) {
|
|
if (config.error) {
|
|
result.error = config.error;
|
|
}
|
|
else {
|
|
result.result = config.result || null;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function normalizedTally(normalize, quorum) {
|
|
return function (configs) {
|
|
// Count the votes for each result
|
|
var tally = {};
|
|
configs.forEach(function (c) {
|
|
var value = normalize(c.result);
|
|
if (!tally[value]) {
|
|
tally[value] = { count: 0, result: c.result };
|
|
}
|
|
tally[value].count++;
|
|
});
|
|
// Check for a quorum on any given result
|
|
var keys = Object.keys(tally);
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var check = tally[keys[i]];
|
|
if (check.count >= quorum) {
|
|
return check.result;
|
|
}
|
|
}
|
|
// No quroum
|
|
return undefined;
|
|
};
|
|
}
|
|
function getProcessFunc(provider, method, params) {
|
|
var normalize = serialize;
|
|
switch (method) {
|
|
case "getBlockNumber":
|
|
// Return the median value, unless there is (median + 1) is also
|
|
// present, in which case that is probably true and the median
|
|
// is going to be stale soon. In the event of a malicious node,
|
|
// the lie will be true soon enough.
|
|
return function (configs) {
|
|
var values = configs.map(function (c) { return c.result; });
|
|
// Get the median block number
|
|
var blockNumber = median(configs.map(function (c) { return c.result; }), 2);
|
|
if (blockNumber == null) {
|
|
return undefined;
|
|
}
|
|
blockNumber = Math.ceil(blockNumber);
|
|
// If the next block height is present, its prolly safe to use
|
|
if (values.indexOf(blockNumber + 1) >= 0) {
|
|
blockNumber++;
|
|
}
|
|
// Don't ever roll back the blockNumber
|
|
if (blockNumber >= provider._highestBlockNumber) {
|
|
provider._highestBlockNumber = blockNumber;
|
|
}
|
|
return provider._highestBlockNumber;
|
|
};
|
|
case "getGasPrice":
|
|
// Return the middle (round index up) value, similar to median
|
|
// but do not average even entries and choose the higher.
|
|
// Malicious actors must compromise 50% of the nodes to lie.
|
|
return function (configs) {
|
|
var values = configs.map(function (c) { return c.result; });
|
|
values.sort();
|
|
return values[Math.floor(values.length / 2)];
|
|
};
|
|
case "getEtherPrice":
|
|
// Returns the median price. Malicious actors must compromise at
|
|
// least 50% of the nodes to lie (in a meaningful way).
|
|
return function (configs) {
|
|
return median(configs.map(function (c) { return c.result; }));
|
|
};
|
|
// No additional normalizing required; serialize is enough
|
|
case "getBalance":
|
|
case "getTransactionCount":
|
|
case "getCode":
|
|
case "getStorageAt":
|
|
case "call":
|
|
case "estimateGas":
|
|
case "getLogs":
|
|
break;
|
|
// We drop the confirmations from transactions as it is approximate
|
|
case "getTransaction":
|
|
case "getTransactionReceipt":
|
|
normalize = function (tx) {
|
|
if (tx == null) {
|
|
return null;
|
|
}
|
|
tx = properties_1.shallowCopy(tx);
|
|
tx.confirmations = -1;
|
|
return serialize(tx);
|
|
};
|
|
break;
|
|
// We drop the confirmations from transactions as it is approximate
|
|
case "getBlock":
|
|
// We drop the confirmations from transactions as it is approximate
|
|
if (params.includeTransactions) {
|
|
normalize = function (block) {
|
|
if (block == null) {
|
|
return null;
|
|
}
|
|
block = properties_1.shallowCopy(block);
|
|
block.transactions = block.transactions.map(function (tx) {
|
|
tx = properties_1.shallowCopy(tx);
|
|
tx.confirmations = -1;
|
|
return tx;
|
|
});
|
|
return serialize(block);
|
|
};
|
|
}
|
|
else {
|
|
normalize = function (block) {
|
|
if (block == null) {
|
|
return null;
|
|
}
|
|
return serialize(block);
|
|
};
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error("unknown method: " + method);
|
|
}
|
|
// Return the result if and only if the expected quorum is
|
|
// satisfied and agreed upon for the final result.
|
|
return normalizedTally(normalize, provider.quorum);
|
|
}
|
|
// If we are doing a blockTag query, we need to make sure the backend is
|
|
// caught up to the FallbackProvider, before sending a request to it.
|
|
function waitForSync(config, blockNumber) {
|
|
return __awaiter(this, void 0, void 0, function () {
|
|
var provider;
|
|
return __generator(this, function (_a) {
|
|
provider = (config.provider);
|
|
if ((provider.blockNumber != null && provider.blockNumber >= blockNumber) || blockNumber === -1) {
|
|
return [2 /*return*/, provider];
|
|
}
|
|
return [2 /*return*/, web_1.poll(function () {
|
|
return new Promise(function (resolve, reject) {
|
|
setTimeout(function () {
|
|
// We are synced
|
|
if (provider.blockNumber >= blockNumber) {
|
|
return resolve(provider);
|
|
}
|
|
// We're done; just quit
|
|
if (config.cancelled) {
|
|
return resolve(null);
|
|
}
|
|
// Try again, next block
|
|
return resolve(undefined);
|
|
}, 0);
|
|
});
|
|
}, { oncePoll: provider })];
|
|
});
|
|
});
|
|
}
|
|
function getRunner(config, currentBlockNumber, method, params) {
|
|
return __awaiter(this, void 0, void 0, function () {
|
|
var provider, _a, filter;
|
|
return __generator(this, function (_b) {
|
|
switch (_b.label) {
|
|
case 0:
|
|
provider = config.provider;
|
|
_a = method;
|
|
switch (_a) {
|
|
case "getBlockNumber": return [3 /*break*/, 1];
|
|
case "getGasPrice": return [3 /*break*/, 1];
|
|
case "getEtherPrice": return [3 /*break*/, 2];
|
|
case "getBalance": return [3 /*break*/, 3];
|
|
case "getTransactionCount": return [3 /*break*/, 3];
|
|
case "getCode": return [3 /*break*/, 3];
|
|
case "getStorageAt": return [3 /*break*/, 6];
|
|
case "getBlock": return [3 /*break*/, 9];
|
|
case "call": return [3 /*break*/, 12];
|
|
case "estimateGas": return [3 /*break*/, 12];
|
|
case "getTransaction": return [3 /*break*/, 15];
|
|
case "getTransactionReceipt": return [3 /*break*/, 15];
|
|
case "getLogs": return [3 /*break*/, 16];
|
|
}
|
|
return [3 /*break*/, 19];
|
|
case 1: return [2 /*return*/, provider[method]()];
|
|
case 2:
|
|
if (provider.getEtherPrice) {
|
|
return [2 /*return*/, provider.getEtherPrice()];
|
|
}
|
|
return [3 /*break*/, 19];
|
|
case 3:
|
|
if (!(params.blockTag && bytes_1.isHexString(params.blockTag))) return [3 /*break*/, 5];
|
|
return [4 /*yield*/, waitForSync(config, currentBlockNumber)];
|
|
case 4:
|
|
provider = _b.sent();
|
|
_b.label = 5;
|
|
case 5: return [2 /*return*/, provider[method](params.address, params.blockTag || "latest")];
|
|
case 6:
|
|
if (!(params.blockTag && bytes_1.isHexString(params.blockTag))) return [3 /*break*/, 8];
|
|
return [4 /*yield*/, waitForSync(config, currentBlockNumber)];
|
|
case 7:
|
|
provider = _b.sent();
|
|
_b.label = 8;
|
|
case 8: return [2 /*return*/, provider.getStorageAt(params.address, params.position, params.blockTag || "latest")];
|
|
case 9:
|
|
if (!(params.blockTag && bytes_1.isHexString(params.blockTag))) return [3 /*break*/, 11];
|
|
return [4 /*yield*/, waitForSync(config, currentBlockNumber)];
|
|
case 10:
|
|
provider = _b.sent();
|
|
_b.label = 11;
|
|
case 11: return [2 /*return*/, provider[(params.includeTransactions ? "getBlockWithTransactions" : "getBlock")](params.blockTag || params.blockHash)];
|
|
case 12:
|
|
if (!(params.blockTag && bytes_1.isHexString(params.blockTag))) return [3 /*break*/, 14];
|
|
return [4 /*yield*/, waitForSync(config, currentBlockNumber)];
|
|
case 13:
|
|
provider = _b.sent();
|
|
_b.label = 14;
|
|
case 14: return [2 /*return*/, provider[method](params.transaction)];
|
|
case 15: return [2 /*return*/, provider[method](params.transactionHash)];
|
|
case 16:
|
|
filter = params.filter;
|
|
if (!((filter.fromBlock && bytes_1.isHexString(filter.fromBlock)) || (filter.toBlock && bytes_1.isHexString(filter.toBlock)))) return [3 /*break*/, 18];
|
|
return [4 /*yield*/, waitForSync(config, currentBlockNumber)];
|
|
case 17:
|
|
provider = _b.sent();
|
|
_b.label = 18;
|
|
case 18: return [2 /*return*/, provider.getLogs(filter)];
|
|
case 19: return [2 /*return*/, logger.throwError("unknown method error", logger_1.Logger.errors.UNKNOWN_ERROR, {
|
|
method: method,
|
|
params: params
|
|
})];
|
|
}
|
|
});
|
|
});
|
|
}
|
|
var FallbackProvider = /** @class */ (function (_super) {
|
|
__extends(FallbackProvider, _super);
|
|
function FallbackProvider(providers, quorum) {
|
|
var _newTarget = this.constructor;
|
|
var _this = this;
|
|
logger.checkNew(_newTarget, FallbackProvider);
|
|
if (providers.length === 0) {
|
|
logger.throwArgumentError("missing providers", "providers", providers);
|
|
}
|
|
var providerConfigs = providers.map(function (configOrProvider, index) {
|
|
if (abstract_provider_1.Provider.isProvider(configOrProvider)) {
|
|
return Object.freeze({ provider: configOrProvider, weight: 1, stallTimeout: 750, priority: 1 });
|
|
}
|
|
var config = properties_1.shallowCopy(configOrProvider);
|
|
if (config.priority == null) {
|
|
config.priority = 1;
|
|
}
|
|
if (config.stallTimeout == null) {
|
|
config.stallTimeout = 750;
|
|
}
|
|
if (config.weight == null) {
|
|
config.weight = 1;
|
|
}
|
|
var weight = config.weight;
|
|
if (weight % 1 || weight > 512 || weight < 1) {
|
|
logger.throwArgumentError("invalid weight; must be integer in [1, 512]", "providers[" + index + "].weight", weight);
|
|
}
|
|
return Object.freeze(config);
|
|
});
|
|
var total = providerConfigs.reduce(function (accum, c) { return (accum + c.weight); }, 0);
|
|
if (quorum == null) {
|
|
quorum = total / 2;
|
|
}
|
|
else if (quorum > total) {
|
|
logger.throwArgumentError("quorum will always fail; larger than total weight", "quorum", quorum);
|
|
}
|
|
// Are all providers' networks are known
|
|
var networkOrReady = checkNetworks(providerConfigs.map(function (c) { return (c.provider).network; }));
|
|
// Not all networks are known; we must stall
|
|
if (networkOrReady == null) {
|
|
networkOrReady = new Promise(function (resolve, reject) {
|
|
setTimeout(function () {
|
|
_this.detectNetwork().then(resolve, reject);
|
|
}, 0);
|
|
});
|
|
}
|
|
_this = _super.call(this, networkOrReady) || this;
|
|
// Preserve a copy, so we do not get mutated
|
|
properties_1.defineReadOnly(_this, "providerConfigs", Object.freeze(providerConfigs));
|
|
properties_1.defineReadOnly(_this, "quorum", quorum);
|
|
_this._highestBlockNumber = -1;
|
|
return _this;
|
|
}
|
|
FallbackProvider.prototype.detectNetwork = function () {
|
|
return __awaiter(this, void 0, void 0, function () {
|
|
var networks;
|
|
return __generator(this, function (_a) {
|
|
switch (_a.label) {
|
|
case 0: return [4 /*yield*/, Promise.all(this.providerConfigs.map(function (c) { return c.provider.getNetwork(); }))];
|
|
case 1:
|
|
networks = _a.sent();
|
|
return [2 /*return*/, checkNetworks(networks)];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
FallbackProvider.prototype.perform = function (method, params) {
|
|
return __awaiter(this, void 0, void 0, function () {
|
|
var results, i_1, result, processFunc, configs, currentBlockNumber, i, first, _loop_1, this_1, state_1;
|
|
var _this = this;
|
|
return __generator(this, function (_a) {
|
|
switch (_a.label) {
|
|
case 0:
|
|
if (!(method === "sendTransaction")) return [3 /*break*/, 2];
|
|
return [4 /*yield*/, Promise.all(this.providerConfigs.map(function (c) {
|
|
return c.provider.sendTransaction(params.signedTransaction).then(function (result) {
|
|
return result.hash;
|
|
}, function (error) {
|
|
return error;
|
|
});
|
|
}))];
|
|
case 1:
|
|
results = _a.sent();
|
|
// Any success is good enough (other errors are likely "already seen" errors
|
|
for (i_1 = 0; i_1 < results.length; i_1++) {
|
|
result = results[i_1];
|
|
if (typeof (result) === "string") {
|
|
return [2 /*return*/, result];
|
|
}
|
|
}
|
|
// They were all an error; pick the first error
|
|
throw results[0];
|
|
case 2:
|
|
if (!(this._highestBlockNumber === -1 && method !== "getBlockNumber")) return [3 /*break*/, 4];
|
|
return [4 /*yield*/, this.getBlockNumber()];
|
|
case 3:
|
|
_a.sent();
|
|
_a.label = 4;
|
|
case 4:
|
|
processFunc = getProcessFunc(this, method, params);
|
|
configs = random_1.shuffled(this.providerConfigs.map(properties_1.shallowCopy));
|
|
configs.sort(function (a, b) { return (a.priority - b.priority); });
|
|
currentBlockNumber = this._highestBlockNumber;
|
|
i = 0;
|
|
first = true;
|
|
_loop_1 = function () {
|
|
var t0, inflightWeight, _loop_2, waiting, results, result, errors;
|
|
return __generator(this, function (_a) {
|
|
switch (_a.label) {
|
|
case 0:
|
|
t0 = now();
|
|
inflightWeight = configs.filter(function (c) { return (c.runner && ((t0 - c.start) < c.stallTimeout)); })
|
|
.reduce(function (accum, c) { return (accum + c.weight); }, 0);
|
|
_loop_2 = function () {
|
|
var config = configs[i++];
|
|
var rid = nextRid++;
|
|
config.start = now();
|
|
config.staller = stall(config.stallTimeout);
|
|
config.staller.wait(function () { config.staller = null; });
|
|
config.runner = getRunner(config, currentBlockNumber, method, params).then(function (result) {
|
|
config.done = true;
|
|
config.result = result;
|
|
if (_this.listenerCount("debug")) {
|
|
_this.emit("debug", {
|
|
action: "request",
|
|
rid: rid,
|
|
backend: exposeDebugConfig(config, now()),
|
|
request: { method: method, params: properties_1.deepCopy(params) },
|
|
provider: _this
|
|
});
|
|
}
|
|
}, function (error) {
|
|
config.done = true;
|
|
config.error = error;
|
|
if (_this.listenerCount("debug")) {
|
|
_this.emit("debug", {
|
|
action: "request",
|
|
rid: rid,
|
|
backend: exposeDebugConfig(config, now()),
|
|
request: { method: method, params: properties_1.deepCopy(params) },
|
|
provider: _this
|
|
});
|
|
}
|
|
});
|
|
if (this_1.listenerCount("debug")) {
|
|
this_1.emit("debug", {
|
|
action: "request",
|
|
rid: rid,
|
|
backend: exposeDebugConfig(config, null),
|
|
request: { method: method, params: properties_1.deepCopy(params) },
|
|
provider: this_1
|
|
});
|
|
}
|
|
inflightWeight += config.weight;
|
|
};
|
|
// Start running enough to meet quorum
|
|
while (inflightWeight < this_1.quorum && i < configs.length) {
|
|
_loop_2();
|
|
}
|
|
waiting = [];
|
|
configs.forEach(function (c) {
|
|
if (c.done || !c.runner) {
|
|
return;
|
|
}
|
|
waiting.push(c.runner);
|
|
if (c.staller) {
|
|
waiting.push(c.staller.getPromise());
|
|
}
|
|
});
|
|
if (!waiting.length) return [3 /*break*/, 2];
|
|
return [4 /*yield*/, Promise.race(waiting)];
|
|
case 1:
|
|
_a.sent();
|
|
_a.label = 2;
|
|
case 2:
|
|
results = configs.filter(function (c) { return (c.done && c.error == null); });
|
|
if (!(results.length >= this_1.quorum)) return [3 /*break*/, 5];
|
|
result = processFunc(results);
|
|
if (result !== undefined) {
|
|
// Shut down any stallers
|
|
configs.forEach(function (c) {
|
|
if (c.staller) {
|
|
c.staller.cancel();
|
|
}
|
|
c.cancelled = true;
|
|
});
|
|
return [2 /*return*/, { value: result }];
|
|
}
|
|
if (!!first) return [3 /*break*/, 4];
|
|
return [4 /*yield*/, stall(100).getPromise()];
|
|
case 3:
|
|
_a.sent();
|
|
_a.label = 4;
|
|
case 4:
|
|
first = false;
|
|
_a.label = 5;
|
|
case 5:
|
|
errors = configs.reduce(function (accum, c) {
|
|
if (!c.done || c.error == null) {
|
|
return accum;
|
|
}
|
|
var code = (c.error).code;
|
|
if (ForwardErrors.indexOf(code) >= 0) {
|
|
if (!accum[code]) {
|
|
accum[code] = { error: c.error, weight: 0 };
|
|
}
|
|
accum[code].weight += c.weight;
|
|
}
|
|
return accum;
|
|
}, ({}));
|
|
Object.keys(errors).forEach(function (errorCode) {
|
|
var tally = errors[errorCode];
|
|
if (tally.weight < _this.quorum) {
|
|
return;
|
|
}
|
|
// Shut down any stallers
|
|
configs.forEach(function (c) {
|
|
if (c.staller) {
|
|
c.staller.cancel();
|
|
}
|
|
c.cancelled = true;
|
|
});
|
|
var e = (tally.error);
|
|
var props = {};
|
|
ForwardProperties.forEach(function (name) {
|
|
if (e[name] == null) {
|
|
return;
|
|
}
|
|
props[name] = e[name];
|
|
});
|
|
logger.throwError(e.reason || e.message, errorCode, props);
|
|
});
|
|
// All configs have run to completion; we will never get more data
|
|
if (configs.filter(function (c) { return !c.done; }).length === 0) {
|
|
return [2 /*return*/, "break"];
|
|
}
|
|
return [2 /*return*/];
|
|
}
|
|
});
|
|
};
|
|
this_1 = this;
|
|
_a.label = 5;
|
|
case 5:
|
|
if (!true) return [3 /*break*/, 7];
|
|
return [5 /*yield**/, _loop_1()];
|
|
case 6:
|
|
state_1 = _a.sent();
|
|
if (typeof state_1 === "object")
|
|
return [2 /*return*/, state_1.value];
|
|
if (state_1 === "break")
|
|
return [3 /*break*/, 7];
|
|
return [3 /*break*/, 5];
|
|
case 7:
|
|
// Shut down any stallers; shouldn't be any
|
|
configs.forEach(function (c) {
|
|
if (c.staller) {
|
|
c.staller.cancel();
|
|
}
|
|
c.cancelled = true;
|
|
});
|
|
return [2 /*return*/, logger.throwError("failed to meet quorum", logger_1.Logger.errors.SERVER_ERROR, {
|
|
method: method,
|
|
params: params,
|
|
//results: configs.map((c) => c.result),
|
|
//errors: configs.map((c) => c.error),
|
|
results: configs.map(function (c) { return exposeDebugConfig(c); }),
|
|
provider: this
|
|
})];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
return FallbackProvider;
|
|
}(base_provider_1.BaseProvider));
|
|
exports.FallbackProvider = FallbackProvider;
|
|
//# sourceMappingURL=fallback-provider.js.map
|