Skip to content

Commit 995e0bd

Browse files
authored
fix: retry Filecoin.StateMinerInfo requests (#96)
* deps: add `retry` from Deno stdlib * fix: retry `Filecoin.StateMinerInfo` requests --------- Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
1 parent 0082af5 commit 995e0bd

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

deps.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
export { encodeHex } from 'https://deno.land/std@0.203.0/encoding/hex.ts'
88
export { decodeBase64 } from 'https://deno.land/std@0.203.0/encoding/base64.ts'
99
export { decode as decodeVarint } from 'https://deno.land/x/varint@v2.0.0/varint.ts'
10+
export { retry } from 'https://deno.land/std@0.203.0/async/retry.ts';
11+
1012

1113
// Deno Bundle does not support npm dependencies, we have to load them via CDN
1214
export { CarBlockIterator } from 'https://cdn.skypack.dev/@ipld/car@5.3.2/?dts'

lib/miner-info.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1+
import { retry } from '../vendor/deno-deps.js'
12
import { RPC_URL, RPC_AUTH } from './constants.js'
23

34
/**
45
* @param {string} minerId A miner actor id, e.g. `f0142637`
6+
* @param {object} options
7+
* @param {number} [options.maxAttempts]
58
* @returns {Promise<string>} Miner's PeerId, e.g. `12D3KooWMsPmAA65yHAHgbxgh7CPkEctJHZMeM3rAvoW8CZKxtpG`
69
*/
7-
export async function getMinerPeerId (minerId) {
10+
export async function getMinerPeerId (minerId, { maxAttempts = 5 } = {}) {
811
try {
9-
const res = await rpc('Filecoin.StateMinerInfo', minerId, null)
12+
const res = await retry(() => rpc('Filecoin.StateMinerInfo', minerId, null), {
13+
// The maximum amount of attempts until failure.
14+
maxAttempts,
15+
// The initial and minimum amount of milliseconds between attempts.
16+
minTimeout: 5_000,
17+
// How much to backoff after each retry.
18+
multiplier: 1.5
19+
})
1020
return res.PeerId
1121
} catch (err) {
22+
if (err.name === 'RetryError' && err.cause) {
23+
// eslint-disable-next-line no-ex-assign
24+
err = err.cause
25+
}
1226
err.message = `Cannot obtain miner info for ${minerId}: ${err.message}`
1327
throw err
1428
}

test/miner-info.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test('get peer id of a known miner', async () => {
1111

1212
test('get peer id of a miner that does not exist', async () => {
1313
try {
14-
const result = await getMinerPeerId('f010')
14+
const result = await getMinerPeerId('f010', { maxAttempts: 1 })
1515
throw new AssertionError(
1616
`Expected "getMinerPeerId()" to fail, but it resolved with "${result}" instead.`
1717
)

vendor/deno-deps.js

+52
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,57 @@ function decode(buf, offset = 0) {
6262
}
6363
throw new RangeError("malformed or overflow varint");
6464
}
65+
class AssertionError extends Error {
66+
name = "AssertionError";
67+
constructor(message){
68+
super(message);
69+
}
70+
}
71+
function assert(expr, msg = "") {
72+
if (!expr) {
73+
throw new AssertionError(msg);
74+
}
75+
}
76+
class RetryError extends Error {
77+
constructor(cause, attempts){
78+
super(`Retrying exceeded the maxAttempts (${attempts}).`);
79+
this.name = "RetryError";
80+
this.cause = cause;
81+
}
82+
}
83+
const defaultRetryOptions = {
84+
multiplier: 2,
85+
maxTimeout: 60000,
86+
maxAttempts: 5,
87+
minTimeout: 1000,
88+
jitter: 1
89+
};
90+
async function retry(fn, opts) {
91+
const options = {
92+
...defaultRetryOptions,
93+
...opts
94+
};
95+
assert(options.maxTimeout >= 0, "maxTimeout is less than 0");
96+
assert(options.minTimeout <= options.maxTimeout, "minTimeout is greater than maxTimeout");
97+
assert(options.jitter <= 1, "jitter is greater than 1");
98+
let attempt = 0;
99+
while(true){
100+
try {
101+
return await fn();
102+
} catch (error) {
103+
if (attempt + 1 >= options.maxAttempts) {
104+
throw new RetryError(error, options.maxAttempts);
105+
}
106+
const timeout = _exponentialBackoffWithJitter(options.maxTimeout, options.minTimeout, attempt, options.multiplier, options.jitter);
107+
await new Promise((r)=>setTimeout(r, timeout));
108+
}
109+
attempt++;
110+
}
111+
}
112+
function _exponentialBackoffWithJitter(cap, base, attempt, multiplier, jitter) {
113+
const exp = Math.min(cap, base * multiplier ** attempt);
114+
return (1 - jitter * Math.random()) * exp;
115+
}
65116
const typeofs = [
66117
"string",
67118
"number",
@@ -11692,6 +11743,7 @@ var $t = o4.FastestNodeClient, Vt = o4.HttpCachingChain, qt = o4.HttpChain, zt =
1169211743
export { encodeHex as encodeHex };
1169311744
export { decodeBase64 as decodeBase64 };
1169411745
export { decode as decodeVarint };
11746+
export { retry as retry };
1169511747
export { CarBlockIterator as CarBlockIterator };
1169611748
export { UnsupportedHashError as UnsupportedHashError, HashMismatchError as HashMismatchError, validateBlock as validateBlock };
1169711749
export { ne1 as fetchBeaconByTime, zt as HttpChainClient, Vt as HttpCachingChain };

0 commit comments

Comments
 (0)