Account for provider config weight when kicking off a request in FallbackProvider (#4298).

This commit is contained in:
Richard Moore 2023-11-23 21:26:26 -05:00
parent e2485b8ef9
commit da34e3569e
2 changed files with 73 additions and 3 deletions

@ -4,6 +4,7 @@ import {
isError, makeError, isError, makeError,
AbstractProvider, FallbackProvider, Network, AbstractProvider, FallbackProvider, Network,
ZeroAddress
} from "../index.js"; } from "../index.js";
import type { import type {
@ -93,3 +94,68 @@ describe("Test Fallback broadcast", function() {
}); });
}); });
}); });
describe("Test Inflight Quorum", function() {
// Fires the %%actions%% as providers which will delay before returning,
// and returns an array of arrays, where each sub-array indicates which
// providers were inflight at once.
async function test(actions: Array<{ delay: number, stallTimeout: number, priority: number, weight: number }>, quorum: number): Promise<Array<Array<number>>> {
const inflights: Array<Array<number>> = [ [ ] ];
const configs = actions.map(({ delay, stallTimeout, priority, weight }, index) => ({
provider: new MockProvider(async (r) => {
if (r.method === "getBlockNumber") { return 1; }
if (r.method === "getBalance") {
// Add this as inflight
let last = inflights.pop();
if (last == null) { throw new Error("no elements"); }
inflights.push(last);
last = last.slice();
last.push(index);
inflights.push(last);
// Do the thing
await stall(delay);
// Remove as inflight
last = inflights.pop();
if (last == null) { throw new Error("no elements"); }
inflights.push(last);
last = last.filter((v) => (v !== index));
inflights.push(last);
return 0;
}
console.log(r);
throw new Error(`unhandled method: ${ r.method }`);
}),
stallTimeout, priority, weight
}));
const provider = new FallbackProvider(configs, network, {
cacheTimeout: -1, pollingInterval: 100,
quorum
});
await provider.getBalance(ZeroAddress);
return inflights;
}
// See: #4298
it("applies weights against inflight requests", async function() {
this.timeout(2000);
const inflights = await test([
{ delay: 50, stallTimeout: 1000, priority: 1, weight: 2 },
{ delay: 50, stallTimeout: 1000, priority: 1, weight: 2 },
], 2);
// Make sure there is never more than 1 inflight provider at once
for (const running of inflights) {
assert.ok(running.length <= 1, `too many inflight requests: ${ JSON.stringify(inflights) }`);
}
});
// @TODO: add lots more tests, checking on priority, weight and stall
// configurations
});

@ -684,7 +684,7 @@ export class FallbackProvider extends AbstractProvider {
// Add any new runners, because a staller timed out or a result // Add any new runners, because a staller timed out or a result
// or error response came in. // or error response came in.
for (let i = 0; i < newRunners; i++) { for (let i = 0; i < newRunners; i++) {
this.#addRunner(running, req) this.#addRunner(running, req);
} }
// All providers have returned, and we have no result // All providers have returned, and we have no result
@ -759,8 +759,12 @@ export class FallbackProvider extends AbstractProvider {
// Bootstrap enough runners to meet quorum // Bootstrap enough runners to meet quorum
const running: Set<RunnerState> = new Set(); const running: Set<RunnerState> = new Set();
for (let i = 0; i < this.quorum; i++) { let inflightQuorum = 0;
this.#addRunner(running, req); while (true) {
const runner = this.#addRunner(running, req);
if (runner == null) { break; }
inflightQuorum += runner.config.weight;
if (inflightQuorum >= this.quorum) { break; }
} }
const result = await this.#waitForQuorum(running, req); const result = await this.#waitForQuorum(running, req);