Skip to content

Commit f7af5cd

Browse files
address comments, update tests, run prettier
1 parent 28ca615 commit f7af5cd

File tree

3 files changed

+204
-191
lines changed

3 files changed

+204
-191
lines changed

sdk/src/network-client.ts

Lines changed: 82 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,16 @@ class AleoNetworkClient {
9696

9797
/**
9898
* Set a header in the `AleoNetworkClient`s header map
99-
*
99+
*
100100
* @param {string} headerName The name of the header to set
101101
* @param {string} value The header value
102-
*
102+
*
103103
* @example
104104
* import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
105-
*
105+
*
106106
* // Create a networkClient
107107
* const networkClient = new AleoNetworkClient();
108-
*
108+
*
109109
* // Set the value of the `Accept-Language` header to `en-US`
110110
* networkClient.setHeader('Accept-Language', 'en-US');
111111
*/
@@ -115,20 +115,20 @@ class AleoNetworkClient {
115115

116116
/**
117117
* Remove a header from the `AleoNetworkClient`s header map
118-
*
118+
*
119119
* @param {string} headerName The name of the header to be removed
120-
*
120+
*
121121
* @example
122122
* import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
123-
*
123+
*
124124
* // Create a networkClient
125125
* const networkClient = new AleoNetworkClient();
126-
*
126+
*
127127
* // Remove the default `X-Aleo-SDK-Version` header
128128
* networkClient.removeHeader('X-Aleo-SDK-Version');
129129
*/
130130
removeHeader(headerName: string) {
131-
delete this.headers[headerName]
131+
delete this.headers[headerName];
132132
}
133133

134134
/**
@@ -137,12 +137,12 @@ class AleoNetworkClient {
137137
* @param url The URL to fetch data from.
138138
*/
139139
async fetchData<Type>(url = "/"): Promise<Type> {
140-
try {
141-
const raw = await retryWithBackoff(() => this.fetchRaw(url));
142-
return parseJSON(raw);
143-
} catch (error) {
144-
throw new Error(`Error fetching data: ${error}`);
145-
}
140+
try {
141+
const raw = await this.fetchRaw(url);
142+
return parseJSON(raw);
143+
} catch (error) {
144+
throw new Error(`Error fetching data: ${error}`);
145+
}
146146
}
147147

148148
/**
@@ -154,23 +154,17 @@ class AleoNetworkClient {
154154
* @param url
155155
*/
156156
async fetchRaw(url = "/"): Promise<string> {
157-
try {
158-
return await retryWithBackoff(async () => {
159-
const response = await get(this.host + url, {
160-
headers: this.headers,
157+
try {
158+
return await retryWithBackoff(async () => {
159+
const response = await get(this.host + url, {
160+
headers: this.headers,
161+
});
162+
return await response.text();
161163
});
162-
return await response.text();
163-
}, {
164-
shouldRetry: (err) => {
165-
const msg = err?.message?.toLowerCase?.() || "";
166-
return msg.includes("network") || msg.includes("timeout");
167-
}
168-
});
169-
} catch (error) {
170-
throw new Error(`Error fetching data: ${error}`);
171-
}
172-
}
173-
164+
} catch (error) {
165+
throw new Error(`Error fetching data: ${error}`);
166+
}
167+
}
174168

175169
/**
176170
* Wrapper around the POST helper to allow mocking in tests. Not meant for use in production.
@@ -179,9 +173,9 @@ class AleoNetworkClient {
179173
* @param options The RequestInit options for the POST request.
180174
* @returns The Response object from the POST request.
181175
*/
182-
private async sendPost(url: string, options: RequestInit) {
183-
return post(url, options);
184-
}
176+
private async _sendPost(url: string, options: RequestInit) {
177+
return post(url, options);
178+
}
185179

186180
/**
187181
* Attempt to find records in the Aleo blockchain.
@@ -384,27 +378,28 @@ class AleoNetworkClient {
384378
}
385379

386380
if (unspent) {
387-
// Otherwise record the nonce that has been found
388-
const serialNumber = recordPlaintext.serialNumberString(
389-
resolvedPrivateKey,
390-
"credits.aleo",
391-
"credits",
392-
);
393-
// Attempt to see if the serial number is spent
394-
try {
395-
await retryWithBackoff(() => this.getTransitionId(serialNumber), {
396-
retryOnStatus: [500, 502, 503, 504],
397-
shouldRetry: (err) => {
398-
const msg = err?.message?.toLowerCase?.() || "";
399-
return msg.includes("timeout") || msg.includes("503") || msg.includes("network");
400-
}
401-
});
402-
// If it succeeds, it means the record was spent → skip it
403-
continue;
404-
} catch (error) {
405-
console.log("Found unspent record!");
406-
}
407-
}
381+
// Otherwise record the nonce that has been found
382+
const serialNumber =
383+
recordPlaintext.serialNumberString(
384+
resolvedPrivateKey,
385+
"credits.aleo",
386+
"credits",
387+
);
388+
// Attempt to see if the serial number is spent
389+
try {
390+
await retryWithBackoff(
391+
() =>
392+
this.getTransitionId(
393+
serialNumber,
394+
),
395+
);
396+
continue;
397+
} catch (error) {
398+
console.log(
399+
"Found unspent record!",
400+
);
401+
}
402+
}
408403

409404
// Add the record to the list of records if the user did not specify amounts.
410405
if (!amounts) {
@@ -1406,13 +1401,14 @@ class AleoNetworkClient {
14061401
? transaction.toString()
14071402
: transaction;
14081403
try {
1409-
const response = await retryWithBackoff(() =>
1410-
this.sendPost(this.host + "/transaction/broadcast", {
1411-
body: transaction_string,
1412-
headers: Object.assign({}, this.headers, {
1413-
"Content-Type": "application/json",
1414-
}),
1415-
}));
1404+
const response = await retryWithBackoff(() =>
1405+
this._sendPost(this.host + "/transaction/broadcast", {
1406+
body: transaction_string,
1407+
headers: Object.assign({}, this.headers, {
1408+
"Content-Type": "application/json",
1409+
}),
1410+
}),
1411+
);
14161412

14171413
try {
14181414
const text = await response.text();
@@ -1436,30 +1432,30 @@ class AleoNetworkClient {
14361432
* @returns {Promise<string>} The solution id of the submitted solution or the resulting error.
14371433
*/
14381434
async submitSolution(solution: string): Promise<string> {
1439-
try {
1440-
const response = await retryWithBackoff(() =>
1441-
post(this.host + "/solution/broadcast", {
1442-
body: solution,
1443-
headers: Object.assign({}, this.headers, {
1444-
"Content-Type": "application/json",
1445-
}),
1446-
}),
1447-
);
1448-
1449-
try {
1450-
const text = await response.text();
1451-
return parseJSON(text);
1452-
} catch (error: any) {
1453-
throw new Error(
1454-
`Error posting solution. Aleo network response: ${error.message}`,
1455-
);
1456-
}
1457-
} catch (error: any) {
1458-
throw new Error(
1459-
`Error posting solution: No response received: ${error.message}`,
1460-
);
1461-
}
1462-
}
1435+
try {
1436+
const response = await retryWithBackoff(() =>
1437+
post(this.host + "/solution/broadcast", {
1438+
body: solution,
1439+
headers: Object.assign({}, this.headers, {
1440+
"Content-Type": "application/json",
1441+
}),
1442+
}),
1443+
);
1444+
1445+
try {
1446+
const text = await response.text();
1447+
return parseJSON(text);
1448+
} catch (error: any) {
1449+
throw new Error(
1450+
`Error posting solution. Aleo network response: ${error.message}`,
1451+
);
1452+
}
1453+
} catch (error: any) {
1454+
throw new Error(
1455+
`Error posting solution: No response received: ${error.message}`,
1456+
);
1457+
}
1458+
}
14631459
/**
14641460
* Await a submitted transaction to be confirmed or rejected on the Aleo network.
14651461
*

sdk/src/utils.ts

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export function logAndThrow(message: string): never {
33
throw new Error(message);
44
}
55

6-
76
export function parseJSON(json: string): any {
87
function revive(key: string, value: any, context: any) {
98
if (Number.isInteger(value)) {
@@ -16,7 +15,6 @@ export function parseJSON(json: string): any {
1615
return JSON.parse(json, revive as any);
1716
}
1817

19-
2018
export async function get(url: URL | string, options?: RequestInit) {
2119
const response = await fetch(url, options);
2220

@@ -27,7 +25,6 @@ export async function get(url: URL | string, options?: RequestInit) {
2725
return response;
2826
}
2927

30-
3128
export async function post(url: URL | string, options: RequestInit) {
3229
options.method = "POST";
3330

@@ -43,48 +40,52 @@ export async function post(url: URL | string, options: RequestInit) {
4340
type RetryOptions = {
4441
maxAttempts?: number;
4542
baseDelay?: number;
43+
jitter?: number;
4644
retryOnStatus?: number[]; // e.g. [500, 502, 503]
4745
shouldRetry?: (err: any) => boolean;
48-
};
49-
50-
export async function retryWithBackoff<T>(
46+
};
47+
48+
export async function retryWithBackoff<T>(
5149
fn: () => Promise<T>,
5250
{
53-
maxAttempts = 5,
54-
baseDelay = 100,
55-
retryOnStatus = [],
56-
shouldRetry,
57-
}: RetryOptions = {}
58-
): Promise<T> {
51+
maxAttempts = 5,
52+
baseDelay = 100,
53+
jitter,
54+
retryOnStatus = [],
55+
shouldRetry,
56+
}: RetryOptions = {},
57+
): Promise<T> {
5958
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
60-
try {
61-
return await fn();
62-
} catch (err: any) {
63-
const isLast = attempt === maxAttempts;
64-
const error = err as Error & { code?: string; status?: number };
65-
66-
let retryable = false;
67-
68-
if (typeof error.status === "number") {
69-
if (error.status >= 500) {
70-
retryable = true;
71-
} else if (error.status >= 400 && shouldRetry) {
72-
retryable = shouldRetry(error);
73-
}
74-
} else if (shouldRetry) {
75-
retryable = shouldRetry(error);
59+
try {
60+
return await fn();
61+
} catch (err: any) {
62+
const isLast = attempt === maxAttempts;
63+
const error = err as Error & { code?: string; status?: number };
64+
65+
let retryable = false;
66+
67+
if (typeof error.status === "number") {
68+
if (error.status >= 500) {
69+
retryable = true;
70+
} else if (error.status >= 400 && shouldRetry) {
71+
retryable = shouldRetry(error);
72+
}
73+
} else if (shouldRetry) {
74+
retryable = shouldRetry(error);
75+
}
76+
77+
if (!retryable || isLast) throw error;
78+
79+
const jitterAmount = jitter ?? baseDelay;
80+
const actualJitter = Math.floor(Math.random() * jitterAmount);
81+
const delay = baseDelay * 2 ** (attempt - 1) + actualJitter;
82+
console.warn(
83+
`Retry ${attempt}/${maxAttempts} failed. Retrying in ${delay}ms...`,
84+
);
85+
86+
await new Promise((res) => setTimeout(res, delay));
7687
}
77-
78-
if (!retryable || isLast) throw error;
79-
80-
const jitter = Math.floor(Math.random() * baseDelay);
81-
const delay = baseDelay * 2 ** (attempt - 1) + jitter;
82-
console.warn(`Retry ${attempt}/${maxAttempts} failed. Retrying in ${delay}ms...`);
83-
84-
await new Promise((res) => setTimeout(res, delay));
85-
}
8688
}
87-
89+
8890
throw new Error("retryWithBackoff: unreachable");
89-
}
90-
91+
}

0 commit comments

Comments
 (0)