From da34e3569e95357d9469209d926cb645f0750bfa Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 23 Nov 2023 21:26:26 -0500 Subject: [PATCH] Account for provider config weight when kicking off a request in FallbackProvider (#4298). --- src.ts/_tests/test-providers-fallback.ts | 66 ++++++++++++++++++++++++ src.ts/providers/provider-fallback.ts | 10 ++-- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src.ts/_tests/test-providers-fallback.ts b/src.ts/_tests/test-providers-fallback.ts index 4b2716505..66627740a 100644 --- a/src.ts/_tests/test-providers-fallback.ts +++ b/src.ts/_tests/test-providers-fallback.ts @@ -4,6 +4,7 @@ import { isError, makeError, AbstractProvider, FallbackProvider, Network, + ZeroAddress } from "../index.js"; 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>> { + const inflights: Array> = [ [ ] ]; + + 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 +}); diff --git a/src.ts/providers/provider-fallback.ts b/src.ts/providers/provider-fallback.ts index 7f8fc6eef..f5a00f7cf 100644 --- a/src.ts/providers/provider-fallback.ts +++ b/src.ts/providers/provider-fallback.ts @@ -684,7 +684,7 @@ export class FallbackProvider extends AbstractProvider { // Add any new runners, because a staller timed out or a result // or error response came in. for (let i = 0; i < newRunners; i++) { - this.#addRunner(running, req) + this.#addRunner(running, req); } // All providers have returned, and we have no result @@ -759,8 +759,12 @@ export class FallbackProvider extends AbstractProvider { // Bootstrap enough runners to meet quorum const running: Set = new Set(); - for (let i = 0; i < this.quorum; i++) { - this.#addRunner(running, req); + let inflightQuorum = 0; + 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);