Skip to content

Commit

Permalink
Dirty hack to prevent provider from freezing in case of all RPCs are …
Browse files Browse the repository at this point in the history
…unavailable
  • Loading branch information
nikitaeverywhere committed Aug 23, 2018
1 parent 27e22a7 commit b4d9f2b
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 5 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Reliable Web3 provider which features:
+ Connects to multiple RPC APIs and switches between them when some are unavailable.
+ Allows to specify private keys for signing transactions.

Primarily made for server-side usage (as it depends on [heavy library](https://github.com/MetaMask/provider-engine)).
Primarily made for server-side usage (as it depends on [a heavy library...](https://github.com/MetaMask/provider-engine)).

Installation
------------
Expand Down
20 changes: 17 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ function ZeusProvider (options = {}) {
this.options = Object.assign({
// onRpcProviderChange: ({ from, to, error, response }) => { ... },
rpcApis: ["https://mainnet.infura.io"],
privateKeys: []
privateKeys: [],
_zeus: this // <SUPERDIRTYHACK>injects networkUnreachable property to Zeus Provider</SUPERDIRTYHACK>
}, options);

/**
Expand Down Expand Up @@ -65,8 +66,21 @@ ZeusProvider.prototype.send = function () {
return this.engine.send.apply(this.engine, arguments);
};

ZeusProvider.prototype.sendAsync = function () {
this.engine.sendAsync.apply(this.engine, arguments);
ZeusProvider.prototype.sendAsync = function (...args) {
// <SUPERDIRTYHACK> (described in RpcBalancerSubprovider)
if (typeof args[1] === "function") {
let fired = false;
const realFun = args[1];
const t = setInterval(() => this.networkUnreachable && hackedFun(new Error("All RPCs are unreachable, cannot complete request")), 100);
const hackedFun = args[1] = function (...args) {
if (fired) return;
fired = true;
clearInterval(t);
realFun.apply(this, args);
};
}
// </SUPERDIRTYHACK>
this.engine.sendAsync.apply(this.engine, args);
};

ZeusProvider.prototype.terminate = function () {
Expand Down
15 changes: 14 additions & 1 deletion src/providers/RpcBalancerSubprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = class RpcFetchBalancerSubprovider extends SubProvider {
}

handleRequest (payload, next, end) {
// console.log("Handling", payload);
let attempts = 1;
let previousRpcApi;
// In order not to mess up with callbacks, RPC API switch is performed only if the previousRpcApi === current
Expand All @@ -70,15 +71,27 @@ module.exports = class RpcFetchBalancerSubprovider extends SubProvider {
const success = this.switchToNextRpcApi(previousRpcApi, err, res);
attempts++;
if (success && attempts > (this.options.rpcRetries || 3) * this.rpcApis.length) {
// <HACK> <INFO DESC="A super DIRTY HACK which prevents provider engine from hanging if all nodes are unavailable"/>
if (this.options._zeus && (payload.method === "eth_getBlockByNumber" || payload.method === "eth_blockNumber")) {
this.options._zeus.networkUnreachable = true;
}
// </HACK>
return end(err, res);
}
return setTimeout(request, this.options.rpcRetryTimeout || 25);
}
// <HACK> <INFO DESC="A super DIRTY HACK which prevents provider engine from hanging if all nodes are unavailable"/>
if (this.options._zeus && (payload.method === "eth_getBlockByNumber" || payload.method === "eth_blockNumber")) {
this.options._zeus.networkUnreachable = false;
}
// </HACK>
end(err, res);
}
const request = () => {
previousRpcApi = this.currentRpcApi;
return this.getCurrentRpcApi().handleRequest(payload, next, handle);
return this.getCurrentRpcApi().handleRequest(payload, next, function () {
return handle.apply(this, arguments);
});
};
request();
}
Expand Down
27 changes: 27 additions & 0 deletions test/rpcSwitching.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,33 @@ describe("RPC switching tests", function() {

});

it("Exit if all block number retrieval attempts fail", async function () {

const callbacks = [];
const rpcs = [
"http://localhost:1355",
"http://localhost:1399"
];
mockRpc(rpcs[0], 502, 50, null);
mockRpc(rpcs[1], 502, 50, null);
const web3 = new Web3(new ZeusProvider({
rpcRetries: 2,
rpcApis: rpcs,
onRpcProviderChange: (res) => callbacks.push(res)
}));

try {
await web3.eth.getBlockNumber();
assert.fail("Must throw an error");
} catch (e) {
assert.ok(true);
}

// assert.equal(callbacks.length > 4, true);
web3.currentProvider.terminate();

});

});

});

0 comments on commit b4d9f2b

Please sign in to comment.