Skip to content

Commit f6da6d6

Browse files
committed
Merge branch 'fix/request-node-logging' of https://github.com/RequestNetwork/requestNetwork into fix/request-node-logging
2 parents cda06df + 435fb26 commit f6da6d6

File tree

6 files changed

+118
-59
lines changed

6 files changed

+118
-59
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ Test all the packages in the monorepo.
8383
yarn run test
8484
```
8585

86+
Test a specific package by replacing `@requestnetwork/request-client.js` with the desired package name:
87+
88+
```bash
89+
yarn workspace @requestnetwork/request-client.js test
90+
```
91+
8692
## License
8793

8894
[MIT](https://github.com/RequestNetwork/requestNetwork/blob/master/LICENSE)

packages/request-client.js/src/http-config-defaults.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ const config: ClientTypes.IHttpDataAccessConfig = {
66
httpRequestRetryDelay: 100,
77
httpRequestExponentialBackoffDelay: 0,
88
httpRequestMaxExponentialBackoffDelay: 30000,
9-
getConfirmationMaxRetry: 30,
10-
getConfirmationRetryDelay: 1000,
11-
getConfirmationExponentialBackoffDelay: 0,
12-
getConfirmationMaxExponentialBackoffDelay: 30000,
9+
10+
// Exponential backoff starting at 1s, doubling after each retry, up to a maximum of 64s and max 7 retries with an initial 3s defer delay, yielding a total of 8 calls and total timeout of 130s
11+
getConfirmationMaxRetry: 7,
12+
getConfirmationRetryDelay: 0,
13+
getConfirmationExponentialBackoffDelay: 1000,
14+
getConfirmationMaxExponentialBackoffDelay: 64000,
1315
getConfirmationDeferDelay: 3000,
1416
};
1517

packages/request-client.js/src/http-data-access.ts

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ export default class HttpDataAccess implements DataAccessTypes.IDataAccess {
9898
{ channelId, topics, transactionData },
9999
);
100100

101-
const transactionHash: string = normalizeKeccak256Hash(transactionData).value;
102-
103101
// Create the return result with EventEmitter
104102
const result: DataAccessTypes.IReturnPersistTransaction = Object.assign(
105103
new EventEmitter() as DataAccessTypes.PersistTransactionEmitter,
@@ -109,33 +107,15 @@ export default class HttpDataAccess implements DataAccessTypes.IDataAccess {
109107
// Try to get the confirmation
110108
new Promise((r) => setTimeout(r, this.httpConfig.getConfirmationDeferDelay))
111109
.then(async () => {
112-
const confirmedData =
113-
await this.fetchAndRetry<DataAccessTypes.IReturnPersistTransactionRaw>(
114-
'/getConfirmedTransaction',
115-
{
116-
transactionHash,
117-
},
118-
{
119-
maxRetries: this.httpConfig.getConfirmationMaxRetry,
120-
retryDelay: this.httpConfig.getConfirmationRetryDelay,
121-
exponentialBackoffDelay: this.httpConfig.getConfirmationExponentialBackoffDelay,
122-
maxExponentialBackoffDelay: this.httpConfig.getConfirmationMaxExponentialBackoffDelay,
123-
},
124-
);
110+
const confirmedData = await this.getConfirmedTransaction(transactionData);
125111
// when found, emit the event 'confirmed'
126112
result.emit('confirmed', confirmedData);
127113
})
128114
.catch((e) => {
129115
let error: Error = e;
130-
if (e.status === 404) {
116+
if (e && 'status' in e && e.status === 404) {
131117
error = new Error(
132-
`Transaction confirmation not received. Try polling
133-
getTransactionsByChannelId() until the transaction is confirmed.
134-
deferDelay: ${this.httpConfig.getConfirmationDeferDelay}ms,
135-
maxRetries: ${this.httpConfig.getConfirmationMaxRetry},
136-
retryDelay: ${this.httpConfig.getConfirmationRetryDelay}ms,
137-
exponentialBackoffDelay: ${this.httpConfig.getConfirmationExponentialBackoffDelay}ms,
138-
maxExponentialBackoffDelay: ${this.httpConfig.getConfirmationMaxExponentialBackoffDelay}ms`,
118+
`Timeout while confirming the Request was persisted. It is likely that the Request will be confirmed eventually. Catch this error and use getConfirmedTransaction() to continue polling for confirmation. Adjusting the httpConfig settings on the RequestNetwork object to avoid future timeouts. Avoid calling persistTransaction() again to prevent creating a duplicate Request.`,
139119
);
140120
}
141121
result.emit('error', error);
@@ -144,6 +124,29 @@ export default class HttpDataAccess implements DataAccessTypes.IDataAccess {
144124
return result;
145125
}
146126

127+
/**
128+
* Gets a transaction from the node through HTTP.
129+
* @param transactionData The transaction data
130+
*/
131+
public async getConfirmedTransaction(
132+
transactionData: DataAccessTypes.ITransaction,
133+
): Promise<DataAccessTypes.IReturnPersistTransaction> {
134+
const transactionHash: string = normalizeKeccak256Hash(transactionData).value;
135+
136+
return await this.fetchAndRetry(
137+
'/getConfirmedTransaction',
138+
{
139+
transactionHash,
140+
},
141+
{
142+
maxRetries: this.httpConfig.getConfirmationMaxRetry,
143+
retryDelay: this.httpConfig.getConfirmationRetryDelay,
144+
exponentialBackoffDelay: this.httpConfig.getConfirmationExponentialBackoffDelay,
145+
maxExponentialBackoffDelay: this.httpConfig.getConfirmationMaxExponentialBackoffDelay,
146+
},
147+
);
148+
}
149+
147150
/**
148151
* Gets the transactions for a channel from the node through HTTP.
149152
*

packages/request-client.js/test/http-data-access.test.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,18 @@ describe('HttpDataAccess', () => {
2929
getConfirmationMaxRetry: 0,
3030
},
3131
});
32-
await expect(
33-
new Promise((resolve, reject) =>
34-
httpDataAccess.persistTransaction({}, '', []).then((returnPersistTransaction) => {
35-
returnPersistTransaction.on('confirmed', resolve);
36-
returnPersistTransaction.on('error', reject);
37-
}),
38-
),
39-
).rejects.toThrow(
40-
new Error(`Transaction confirmation not received. Try polling
41-
getTransactionsByChannelId() until the transaction is confirmed.
42-
deferDelay: 0ms,
43-
maxRetries: 0,
44-
retryDelay: 1000ms,
45-
exponentialBackoffDelay: 0ms,
46-
maxExponentialBackoffDelay: 30000ms`),
47-
);
32+
const returnPersistTransaction = await httpDataAccess.persistTransaction({}, '', []);
33+
await Promise.race([
34+
new Promise<void>((resolve) => {
35+
returnPersistTransaction.on('error', (e: any) => {
36+
expect(e.message).toBe(
37+
'Timeout while confirming the Request was persisted. It is likely that the Request will be confirmed eventually. Catch this error and use getConfirmedTransaction() to continue polling for confirmation. Adjusting the httpConfig settings on the RequestNetwork object to avoid future timeouts. Avoid calling persistTransaction() again to prevent creating a duplicate Request.',
38+
);
39+
resolve();
40+
});
41+
}),
42+
new Promise((_, reject) => setTimeout(() => reject(new Error('Test timed out')), 5000)),
43+
]);
4844
});
4945
});
5046
});

packages/utils/src/retry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const retry = <TParams extends unknown[], TReturn>(
6161
setTimeout(
6262
resolve,
6363
retryDelay +
64-
Math.min(maxExponentialBackoffDelay, exponentialBackoffDelay * 2 ** retry),
64+
Math.min(maxExponentialBackoffDelay, (exponentialBackoffDelay / 2) * 2 ** retry),
6565
),
6666
);
6767

packages/utils/test/retry.test.ts

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,38 +131,90 @@ describe('Retry', () => {
131131
throw new Error(`threw`);
132132
});
133133

134-
retry(throwFn, {
135-
exponentialBackoffDelay: 1000,
136-
maxExponentialBackoffDelay: 7000,
134+
const retryPromise = retry(throwFn, {
135+
retryDelay: 0,
136+
// Exponential backoff starting at 1s, doubling after each retry, up to a maximum of 64s and max 7 retries, yielding a total of 8 call snad total timeout of 127s
137+
maxRetries: 7,
138+
exponentialBackoffDelay: 1000, // 1s
139+
maxExponentialBackoffDelay: 64000, // 64s
137140
})();
138141

139-
// Should call immediately
142+
// Should call immediately (1 total calls, 0ms total elapsed)
140143
expect(throwFn).toHaveBeenCalledTimes(1);
141144

142-
// Exponential backoff should only call a second time after 2000ms
143-
jest.advanceTimersByTime(1100);
145+
expect(Date.now()).toBe(0);
146+
147+
// 1st retry after 1s (2 total calls, 1000ms total elapsed)
148+
jest.advanceTimersByTime(999);
144149
await Promise.resolve();
145150
expect(throwFn).toHaveBeenCalledTimes(1);
146-
jest.advanceTimersByTime(1100);
151+
jest.advanceTimersByTime(1);
147152
await Promise.resolve();
148153
expect(throwFn).toHaveBeenCalledTimes(2);
154+
expect(Date.now()).toBe(1000);
155+
156+
// 2nd retry after 3s (3 total calls, 3000ms total elapsed)
157+
jest.advanceTimersByTime(1999);
158+
await Promise.resolve();
159+
expect(throwFn).toHaveBeenCalledTimes(2);
160+
jest.advanceTimersByTime(1);
161+
await Promise.resolve();
162+
expect(throwFn).toHaveBeenCalledTimes(3);
163+
expect(Date.now()).toBe(3000);
149164

150-
// Exponential backoff should call a third time after 4100ms
151-
jest.advanceTimersByTime(4100);
165+
// 3rd retry after 4s (4 total calls, 7000ms total elapsed)
166+
jest.advanceTimersByTime(3999);
152167
await Promise.resolve();
153168
expect(throwFn).toHaveBeenCalledTimes(3);
169+
jest.advanceTimersByTime(1);
170+
await Promise.resolve();
171+
expect(throwFn).toHaveBeenCalledTimes(4);
172+
expect(Date.now()).toBe(7000);
154173

155-
// Exponential backoff should call a fourth time after 7100ms
156-
// since maxExponentialBackoffDelay (7000) < 8000
157-
jest.advanceTimersByTime(7100);
174+
// 4th retry after 8s (5 total calls, 15000ms total elapsed)
175+
jest.advanceTimersByTime(7999);
158176
await Promise.resolve();
159177
expect(throwFn).toHaveBeenCalledTimes(4);
178+
jest.advanceTimersByTime(1);
179+
await Promise.resolve();
180+
expect(throwFn).toHaveBeenCalledTimes(5);
181+
expect(Date.now()).toBe(15000);
160182

161-
// Exponential backoff should call a fifth time after 7100ms
162-
// since maxExponentialBackoffDelay (7000) < 8000
163-
jest.advanceTimersByTime(7100);
183+
// 5th retry after 16s (6 total calls, 31000ms total elapsed)
184+
jest.advanceTimersByTime(15999);
164185
await Promise.resolve();
165186
expect(throwFn).toHaveBeenCalledTimes(5);
187+
jest.advanceTimersByTime(1);
188+
await Promise.resolve();
189+
expect(throwFn).toHaveBeenCalledTimes(6);
190+
expect(Date.now()).toBe(31000);
191+
192+
// 6th retry after 32s (7 total calls, 63000ms total elapsed)
193+
jest.advanceTimersByTime(31999);
194+
await Promise.resolve();
195+
expect(throwFn).toHaveBeenCalledTimes(6);
196+
jest.advanceTimersByTime(1);
197+
await Promise.resolve();
198+
expect(throwFn).toHaveBeenCalledTimes(7);
199+
expect(Date.now()).toBe(63000);
200+
201+
// 7th retry after 64s (8 total calls, 127000ms total elapsed)
202+
jest.advanceTimersByTime(63999);
203+
await Promise.resolve();
204+
expect(throwFn).toHaveBeenCalledTimes(7);
205+
jest.advanceTimersByTime(1);
206+
await Promise.resolve();
207+
expect(throwFn).toHaveBeenCalledTimes(8);
208+
expect(Date.now()).toBe(127000);
209+
210+
// Reject and throw after the last retry
211+
await expect(retryPromise).rejects.toThrow('threw');
212+
213+
// No further retries
214+
jest.advanceTimersByTime(1000000000);
215+
await Promise.resolve();
216+
expect(throwFn).toHaveBeenCalledTimes(8);
217+
expect(Date.now()).toBe(1000127000);
166218

167219
jest.useRealTimers();
168220
});

0 commit comments

Comments
 (0)