Skip to content

Commit 46f114c

Browse files
authored
Merge pull request #247 from lightninglabs/uint64-strings
refactor: use strings for GRPC uint64 fields
2 parents b5d4e17 + 1871f6b commit 46f114c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4019
-3948
lines changed

app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@emotion/react": "11.4.0",
2121
"@emotion/styled": "11.3.0",
2222
"@improbable-eng/grpc-web": "0.14.0",
23-
"big.js": "5.2.2",
23+
"big.js": "6.1.1",
2424
"bootstrap": "4.5.0",
2525
"copy-to-clipboard": "3.3.1",
2626
"d3": "6.3.1",
@@ -58,7 +58,7 @@
5858
"@testing-library/jest-dom": "5.11.5",
5959
"@testing-library/react": "11.1.1",
6060
"@testing-library/user-event": "12.2.0",
61-
"@types/big.js": "4.0.5",
61+
"@types/big.js": "6.1.1",
6262
"@types/d3": "6.2.0",
6363
"@types/debug": "4.1.5",
6464
"@types/file-saver": "2.0.1",

app/scripts/build-protos.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ const protoSources = async () => {
3636
throw new Error(`go.mod did not match pattern ${POOL_VERSION_PATTERN}`);
3737
}
3838

39-
console.log(`Found lnd version ${lndVersion[1]} and loop version ${loopVersion[1]}.`);
39+
console.log(
40+
`Found:\n LND ${lndVersion[1]}\n Loop ${loopVersion[1]}\n Pool ${poolVersion[1]}`,
41+
);
4042
return {
4143
lnd: `lightningnetwork/lnd/${lndVersion[1]}/lnrpc/rpc.proto`,
4244
loop: `lightninglabs/loop/${loopVersion[1]}/looprpc/client.proto`,
@@ -63,7 +65,7 @@ const download = async () => {
6365
for ([name, urlPath] of Object.entries(await protoSources())) {
6466
const url = `https://raw.githubusercontent.com/${urlPath}`;
6567
const filePath = join(appPath, '..', 'proto', `${name}.proto`);
66-
mkdirSync(dirname(filePath), {recursive: true});
68+
mkdirSync(dirname(filePath), { recursive: true });
6769
console.log(`${url}`);
6870
console.log(` -> ${filePath}`);
6971
const content = await new Promise((resolve, reject) => {
@@ -78,6 +80,26 @@ const download = async () => {
7880
}
7981
};
8082

83+
/**
84+
* Adds "[jstype = JS_STRING]" to uint64 fields to indicate that they should be
85+
* represented as strings to avoid Number overflow issues
86+
*/
87+
const sanitize = async () => {
88+
const filePaths = Object.keys(filePatches).map(name =>
89+
join(appPath, '..', 'proto', `${name}.proto`),
90+
);
91+
for (path of filePaths) {
92+
let content = (await fs.readFile(path)).toString();
93+
content = content.replace(/^\s*(repeated)? u?int64 ((?!jstype).)*$/gm, match => {
94+
// add the jstype descriptor
95+
return /^.*];$/.test(match)
96+
? match.replace(/\s*];$/, `, jstype = JS_STRING];`)
97+
: match.replace(/;$/, ` [jstype = JS_STRING];`);
98+
});
99+
await fs.writeFile(path, content);
100+
}
101+
};
102+
81103
/**
82104
* Executes the `protoc` compiler to convert *.proto files into TS & JS code
83105
*/
@@ -117,11 +139,6 @@ const patch = async () => {
117139
console.log('\nPatching generated JS files');
118140

119141
for (const filename of Object.keys(filePatches)) {
120-
const patch = [
121-
'/* eslint-disable */',
122-
`var proto = { ${filePatches[filename]} };`,
123-
'',
124-
].join('\n');
125142
const path = join(
126143
appPath,
127144
'src',
@@ -132,7 +149,15 @@ const patch = async () => {
132149

133150
console.log(` - ${path}`);
134151
let content = await fs.readFile(path);
152+
153+
// apply the webpack patch
154+
const patch = [
155+
'/* eslint-disable */',
156+
`var proto = { ${filePatches[filename]} };`,
157+
'',
158+
].join('\n');
135159
content = `${patch}\n${content}`;
160+
136161
await fs.writeFile(path, content);
137162
}
138163
};
@@ -143,6 +168,7 @@ const patch = async () => {
143168
const main = async () => {
144169
try {
145170
await download();
171+
await sanitize();
146172
await generate();
147173
await patch();
148174
} catch (error) {

app/src/__stories__/ProcessingSwaps.stories.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect } from 'react';
22
import { observable } from 'mobx';
33
import * as LOOP from 'types/generated/loop_pb';
4+
import Big from 'big.js';
45
import { loopListSwaps } from 'util/tests/sampleData';
56
import { useStore } from 'store';
67
import { Swap } from 'store/models';
@@ -47,7 +48,7 @@ const mockSwap = (type: number, state: number, id?: string) => {
4748
swap.id = `${id || ''}${swap.id}`;
4849
swap.type = type;
4950
swap.state = state;
50-
swap.lastUpdateTime = Date.now() * 1000 * 1000;
51+
swap.lastUpdateTime = Big(Date.now() * 1000 * 1000);
5152
return swap;
5253
};
5354
// create a list of swaps to use for stories
@@ -84,7 +85,7 @@ export const LoopInProgress = () => {
8485
await delay(2000);
8586
swap.state = SUCCESS;
8687
await delay(2000);
87-
swap.initiationTime = 0;
88+
swap.initiationTime = Big(0);
8889
};
8990

9091
startTransitions();
@@ -106,7 +107,7 @@ export const LoopOutProgress = () => {
106107
await delay(2000);
107108
swap.state = SUCCESS;
108109
await delay(2000);
109-
swap.initiationTime = 0;
110+
swap.initiationTime = Big(0);
110111
};
111112

112113
startTransitions();

app/src/__tests__/components/loop/LoopPage.spec.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { runInAction } from 'mobx';
33
import { SwapStatus } from 'types/generated/loop_pb';
44
import { grpc } from '@improbable-eng/grpc-web';
55
import { fireEvent, waitFor } from '@testing-library/react';
6+
import Big from 'big.js';
67
import { saveAs } from 'file-saver';
78
import { formatSats } from 'util/formatters';
89
import { renderWithProviders } from 'util/tests';
@@ -70,9 +71,9 @@ describe('LoopPage component', () => {
7071
const { findByText } = render();
7172
// convert from numeric timestamp to string (1586390353623905000 -> '4/15/2020')
7273
const formatDate = (s: SwapStatus.AsObject) =>
73-
new Date(s.initiationTime / 1000 / 1000).toLocaleDateString();
74+
new Date(+Big(s.initiationTime).div(1000).div(1000)).toLocaleDateString();
7475
const [swap1, swap2] = loopListSwaps.swapsList.sort(
75-
(a, b) => b.initiationTime - a.initiationTime,
76+
(a, b) => +Big(b.initiationTime).sub(a.initiationTime),
7677
);
7778

7879
expect(await findByText(formatDate(swap1))).toBeInTheDocument();

app/src/__tests__/components/loop/ProcessingSwaps.spec.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { runInAction } from 'mobx';
33
import * as LOOP from 'types/generated/loop_pb';
44
import { fireEvent } from '@testing-library/react';
5+
import Big from 'big.js';
56
import { renderWithProviders } from 'util/tests';
67
import { loopListSwaps } from 'util/tests/sampleData';
78
import { createStore, Store } from 'store';
@@ -27,7 +28,7 @@ describe('ProcessingSwaps component', () => {
2728
swap.id = `${id || ''}${swap.id}`;
2829
swap.type = type;
2930
swap.state = state;
30-
swap.lastUpdateTime = Date.now() * 1000 * 1000;
31+
swap.lastUpdateTime = Big(Date.now() * 1000 * 1000);
3132
runInAction(() => {
3233
store.swapStore.swaps.set(swap.id, swap);
3334
});

app/src/__tests__/components/pool/AccountSection.spec.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ describe('AccountSection', () => {
117117
});
118118

119119
expect(store.accountStore.activeTraderKey).toBe(hex(poolInitAccount.traderKey));
120-
expect(store.fundNewAccountView.amount).toBe(0);
120+
expect(+store.fundNewAccountView.amount).toBe(0);
121121
expect(store.fundNewAccountView.confTarget).toBe(DEFAULT_CONF_TARGET);
122122
expect(store.fundNewAccountView.expireBlocks).toBe(DEFAULT_EXPIRE_BLOCKS);
123123
});
@@ -206,7 +206,7 @@ describe('AccountSection', () => {
206206
expect(getByText('Account')).toBeInTheDocument();
207207
});
208208

209-
expect(+store.accountStore.activeAccount.totalBalance).toBe(
209+
expect(store.accountStore.activeAccount.totalBalance.toString()).toBe(
210210
poolDepositAccount.account.value,
211211
);
212212
expect(store.fundAccountView.amount).toBe(0);
@@ -322,7 +322,7 @@ describe('AccountSection', () => {
322322
});
323323

324324
expect(req!.traderKey).toBe(b64(store.accountStore.activeAccount.traderKey));
325-
expect(req!.outputWithFee?.feeRateSatPerKw).toBe(2500);
325+
expect(req!.outputWithFee?.feeRateSatPerKw).toBe('2500');
326326
expect(req!.outputWithFee?.address).toBe('abc123');
327327
});
328328

@@ -375,7 +375,7 @@ describe('AccountSection', () => {
375375
});
376376

377377
expect(req!.accountKey).toBe(b64(store.accountStore.activeAccount.traderKey));
378-
expect(req!.feeRateSatPerKw).toBe(31250);
378+
expect(req!.feeRateSatPerKw).toBe('31250');
379379
expect(req!.relativeExpiry).toBe(2016);
380380
});
381381
});

app/src/__tests__/components/pool/BatchStats.spec.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { runInAction } from 'mobx';
33
import { act, waitFor } from '@testing-library/react';
4+
import Big from 'big.js';
45
import { formatSats } from 'util/formatters';
56
import { renderWithProviders } from 'util/tests';
67
import { createStore, Store } from 'store';
@@ -25,7 +26,7 @@ describe('BatchStats', () => {
2526
jest.useFakeTimers();
2627
runInAction(() => {
2728
const nowSecs = Math.ceil(Date.now() / 1000);
28-
store.batchStore.nextBatchTimestamp = nowSecs + 90;
29+
store.batchStore.nextBatchTimestamp = Big(nowSecs + 90);
2930
});
3031
const { getByText } = render();
3132
expect(getByText('Next Batch')).toBeInTheDocument();

app/src/__tests__/components/pool/OrderFormSection.spec.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ describe('OrderFormSection', () => {
7171
});
7272

7373
fireEvent.click(getByText('Place Bid Order'));
74-
expect(bid!.details.amt).toBe(1000000);
74+
expect(bid!.details.amt).toBe('1000000');
7575
expect(bid!.details.rateFixed).toBe(4960);
7676
expect(bid!.details.minUnitsMatch).toBe(1);
7777
expect(bid!.leaseDurationBlocks).toBe(2016);
7878
expect(bid!.minNodeTier).toBe(1);
79-
expect(bid!.details.maxBatchFeeRateSatPerKw).toBe(253);
79+
expect(bid!.details.maxBatchFeeRateSatPerKw).toBe('253');
8080
});
8181

8282
it('should submit an ask order', async () => {
@@ -95,11 +95,11 @@ describe('OrderFormSection', () => {
9595
});
9696

9797
fireEvent.click(getByText('Place Ask Order'));
98-
expect(ask!.details.amt).toBe(1000000);
98+
expect(ask!.details.amt).toBe('1000000');
9999
expect(ask!.details.rateFixed).toBe(4960);
100100
expect(ask!.details.minUnitsMatch).toBe(1);
101101
expect(ask!.leaseDurationBlocks).toBe(2016);
102-
expect(ask!.details.maxBatchFeeRateSatPerKw).toBe(253);
102+
expect(ask!.details.maxBatchFeeRateSatPerKw).toBe('253');
103103
});
104104

105105
it('should submit an order with a different lease duration', async () => {
@@ -119,12 +119,12 @@ describe('OrderFormSection', () => {
119119
});
120120

121121
fireEvent.click(getByText('Place Bid Order'));
122-
expect(bid!.details.amt).toBe(1000000);
122+
expect(bid!.details.amt).toBe('1000000');
123123
expect(bid!.details.rateFixed).toBe(2480);
124124
expect(bid!.details.minUnitsMatch).toBe(1);
125125
expect(bid!.leaseDurationBlocks).toBe(4032);
126126
expect(bid!.minNodeTier).toBe(1);
127-
expect(bid!.details.maxBatchFeeRateSatPerKw).toBe(253);
127+
expect(bid!.details.maxBatchFeeRateSatPerKw).toBe('253');
128128
});
129129

130130
it('should reset the form after placing an order', async () => {

app/src/__tests__/store/accountStore.spec.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ describe('AccountStore', () => {
4646
});
4747

4848
it('should return sorted accounts', async () => {
49-
const a = new Account(rootStore, { ...poolInitAccount, value: 300 });
50-
const b = new Account(rootStore, { ...poolInitAccount, value: 100 });
49+
const a = new Account(rootStore, { ...poolInitAccount, value: '300' });
50+
const b = new Account(rootStore, { ...poolInitAccount, value: '100' });
5151
const c = new Account(rootStore, {
5252
...poolInitAccount,
5353
expirationHeight: 5000,
@@ -76,8 +76,8 @@ describe('AccountStore', () => {
7676
});
7777

7878
it('should excluded closed accounts in sorted accounts', async () => {
79-
const a = new Account(rootStore, { ...poolInitAccount, value: 300 });
80-
const b = new Account(rootStore, { ...poolInitAccount, value: 100 });
79+
const a = new Account(rootStore, { ...poolInitAccount, value: '300' });
80+
const b = new Account(rootStore, { ...poolInitAccount, value: '100' });
8181
const c = new Account(rootStore, {
8282
...poolInitAccount,
8383
expirationHeight: 5000,
@@ -158,7 +158,7 @@ describe('AccountStore', () => {
158158

159159
it('should create a new Account', async () => {
160160
expect(store.accounts.size).toEqual(0);
161-
await store.createAccount(3000000, 4032);
161+
await store.createAccount(Big(3000000), 4032);
162162
expect(store.accounts.size).toEqual(1);
163163
expect(store.activeAccount).toBeDefined();
164164
});
@@ -168,7 +168,7 @@ describe('AccountStore', () => {
168168
throw new Error('test-err');
169169
});
170170
expect(rootStore.appView.alerts.size).toBe(0);
171-
await store.createAccount(3000000, 4032);
171+
await store.createAccount(Big(3000000), 4032);
172172
await waitFor(() => {
173173
expect(rootStore.appView.alerts.size).toBe(1);
174174
expect(values(rootStore.appView.alerts)[0].message).toBe('test-err');
@@ -216,7 +216,9 @@ describe('AccountStore', () => {
216216
it('should deposit funds into an account', async () => {
217217
await store.fetchAccounts();
218218
const txid = await store.deposit(1);
219-
expect(+store.activeAccount.totalBalance).toBe(poolDepositAccount.account?.value);
219+
expect(store.activeAccount.totalBalance.toString()).toBe(
220+
poolDepositAccount.account?.value,
221+
);
220222
expect(txid).toEqual(poolDepositAccount.depositTxid);
221223
});
222224

@@ -236,7 +238,9 @@ describe('AccountStore', () => {
236238
it('should withdraw funds from an account', async () => {
237239
await store.fetchAccounts();
238240
const txid = await store.withdraw(1);
239-
expect(+store.activeAccount.totalBalance).toBe(poolWithdrawAccount.account?.value);
241+
expect(store.activeAccount.totalBalance.toString()).toBe(
242+
poolWithdrawAccount.account?.value,
243+
);
240244
expect(txid).toEqual(poolWithdrawAccount.withdrawTxid);
241245
});
242246

app/src/__tests__/store/buildSwapView.spec.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ describe('BuildSwapView', () => {
111111
});
112112

113113
it('should ensure amount is greater than the min terms', async () => {
114-
store.setAmount(Big(loopInTerms.minSwapAmount - 100));
114+
store.setAmount(Big(loopInTerms.minSwapAmount).sub(100));
115115
await store.getTerms();
116-
expect(+store.amountForSelected).toBe(loopInTerms.minSwapAmount);
116+
expect(store.amountForSelected.toString()).toBe(loopInTerms.minSwapAmount);
117117
});
118118

119119
it('should ensure amount is less than the max terms', async () => {
120120
store.setAmount(Big(loopInTerms.maxSwapAmount + 100));
121121
await store.getTerms();
122-
expect(+store.amountForSelected).toBe(loopInTerms.maxSwapAmount);
122+
expect(store.amountForSelected.toString()).toBe(loopInTerms.maxSwapAmount);
123123
});
124124

125125
it('should validate the conf target', async () => {
@@ -293,7 +293,7 @@ describe('BuildSwapView', () => {
293293
store.setDirection(SwapDirection.OUT);
294294
store.setAmount(Big(600));
295295

296-
let deadline = 0;
296+
let deadline = '';
297297
// mock the grpc unary function in order to capture the supplied deadline
298298
// passed in with the API request
299299
injectIntoGrpcUnary((desc, props) => {
@@ -303,7 +303,7 @@ describe('BuildSwapView', () => {
303303
// run a loop on mainnet and verify the deadline
304304
rootStore.nodeStore.network = 'mainnet';
305305
store.requestSwap();
306-
await waitFor(() => expect(deadline).toBeGreaterThan(0));
306+
await waitFor(() => expect(+deadline).toBeGreaterThan(0));
307307

308308
// inject again for the next swap
309309
injectIntoGrpcUnary((desc, props) => {
@@ -313,7 +313,7 @@ describe('BuildSwapView', () => {
313313
// run a loop on regtest and verify the deadline
314314
rootStore.nodeStore.network = 'regtest';
315315
store.requestSwap();
316-
await waitFor(() => expect(deadline).toEqual(0));
316+
await waitFor(() => expect(+deadline).toEqual(0));
317317
});
318318

319319
it('should handle errors when performing a loop', async () => {
@@ -370,7 +370,12 @@ describe('BuildSwapView', () => {
370370
describe('min/max swap limits', () => {
371371
const addChannel = (capacity: number, localBalance: number) => {
372372
const remoteBalance = capacity - localBalance;
373-
const lndChan = { ...lndChannel, capacity, localBalance, remoteBalance };
373+
const lndChan = {
374+
...lndChannel,
375+
capacity: `${capacity}`,
376+
localBalance: `${localBalance}`,
377+
remoteBalance: `${remoteBalance}`,
378+
};
374379
const channel = Channel.create(rootStore, lndChan);
375380
channel.chanId = `${channel.chanId}${rootStore.channelStore.channels.size}`;
376381
channel.remotePubkey = `${channel.remotePubkey}${rootStore.channelStore.channels.size}`;

0 commit comments

Comments
 (0)