Skip to content

Commit 037aa41

Browse files
authored
Merge pull request #337 from lightninglabs/pool-order-version
pool: determine order type based on Pool version
2 parents 4fb4666 + ba13b29 commit 037aa41

File tree

13 files changed

+653
-93
lines changed

13 files changed

+653
-93
lines changed

app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"react-toastify": "6.0.6",
4848
"react-virtualized": "9.21.2",
4949
"reactour": "1.18.0",
50+
"semver": "^7.3.5",
5051
"styled-components": "5.1.1"
5152
},
5253
"devDependencies": {
@@ -73,6 +74,7 @@
7374
"@types/react-router": "5.1.8",
7475
"@types/react-virtualized": "9.21.10",
7576
"@types/reactour": "1.17.1",
77+
"@types/semver": "^7.3.9",
7678
"@typescript-eslint/eslint-plugin": "5.17.0",
7779
"@typescript-eslint/parser": "5.17.0",
7880
"cross-env": "7.0.2",

app/scripts/build-protos.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,28 @@ const protoSources = async () => {
5252
const filePatches = {
5353
lnd: {
5454
patch: 'lnrpc: {}',
55-
folder: 'proto'
55+
folder: 'proto',
5656
},
5757
loop: {
5858
patch: 'looprpc: {}',
59-
folder: 'proto'
59+
folder: 'proto',
6060
},
6161
common: {
6262
patch: 'looprpc: {}',
63-
folder: 'proto'
63+
folder: 'proto',
6464
},
6565
trader: {
6666
patch: 'poolrpc: {}',
67-
folder: 'proto'
67+
folder: 'proto',
6868
},
6969
'auctioneerrpc/auctioneer': {
7070
patch: 'poolrpc: {}',
71-
folder: 'proto'
71+
folder: 'proto',
7272
},
7373
'lit-sessions': {
7474
patch: 'litrpc: {}',
75-
folder: 'litrpc'
76-
}
75+
folder: 'litrpc',
76+
},
7777
};
7878

7979
/**
@@ -132,9 +132,9 @@ const generate = async () => {
132132
'.bin',
133133
platform() === 'win32' ? 'protoc-gen-ts.cmd' : 'protoc-gen-ts',
134134
);
135-
const files = Object
136-
.keys(filePatches)
137-
.map(name => `../${filePatches[name].folder}/${name}.proto`);
135+
const files = Object.keys(filePatches).map(
136+
name => `../${filePatches[name].folder}/${name}.proto`,
137+
);
138138
const protocCmd = [
139139
'protoc',
140140
`-I../proto`,
@@ -185,13 +185,35 @@ const patch = async () => {
185185
}
186186
};
187187

188+
/**
189+
* *Temporary* patch to workaround a grpc-web issue with deserializing the GetInfoResponse.market_info field.
190+
* This patch will just comment it out in the proto file, which will omit the field in the JS/TS serialization
191+
* code that is generated by the protoc-gen-ts plugin.
192+
* See: https://github.com/lightninglabs/lightning-terminal/pull/337
193+
*/
194+
const patchPool = async () => {
195+
console.log('\nPatching Pool proto file');
196+
const path = join(appPath, '..', 'proto', 'trader.proto');
197+
198+
console.log(` - ${path}`);
199+
let content = await fs.readFile(path);
200+
201+
// comment out the `market_info` field
202+
const line = 'map<uint32, MarketInfo> market_info = 15;';
203+
const patch = '// [workaround, see patchPool() in build-protos.js] ';
204+
content = content.toString().replace(line, patch + line);
205+
206+
await fs.writeFile(path, content);
207+
};
208+
188209
/**
189210
* An async wrapper with error handling around the two funcs above
190211
*/
191212
const main = async () => {
192213
try {
193214
await download();
194215
await sanitize();
216+
await patchPool();
195217
await generate();
196218
await patch();
197219
} catch (error) {

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
/* eslint-disable @typescript-eslint/no-non-null-assertion */
22
import React from 'react';
33
import * as POOL from 'types/generated/trader_pb';
4+
import { grpc } from '@improbable-eng/grpc-web';
45
import { fireEvent, waitFor } from '@testing-library/react';
5-
import { injectIntoGrpcUnary, renderWithProviders } from 'util/tests';
6+
import { injectIntoGrpcUnary, renderWithProviders, sampleGrpcResponse } from 'util/tests';
67
import { createStore, Store } from 'store';
78
import OrderFormSection from 'components/pool/OrderFormSection';
89

10+
const grpcMock = grpc as jest.Mocked<typeof grpc>;
11+
912
describe('OrderFormSection', () => {
1013
let store: Store;
1114

@@ -64,14 +67,21 @@ describe('OrderFormSection', () => {
6467
changeInput('Max Batch Fee Rate', '1');
6568
await changeSelect('Min Node Tier', 'T0 - All Nodes');
6669

67-
let bid: Required<POOL.Bid.AsObject>;
70+
// handle the GetInfo call
71+
grpcMock.unary.mockImplementationOnce((desc, opts) => {
72+
opts.onEnd(sampleGrpcResponse(desc));
73+
return undefined as any;
74+
});
75+
let bid: Required<POOL.Bid.AsObject> = {} as any;
6876
// capture the rate that is sent to the API
6977
injectIntoGrpcUnary((_, props) => {
7078
bid = (props.request.toObject() as any).bid;
7179
});
7280

7381
fireEvent.click(getByText('Place Bid Order'));
74-
expect(bid!.details.amt).toBe('1000000');
82+
await waitFor(() => {
83+
expect(bid!.details.amt).toBe('1000000');
84+
});
7585
expect(bid!.details.rateFixed).toBe(4960);
7686
expect(bid!.details.minUnitsMatch).toBe(1);
7787
expect(bid!.leaseDurationBlocks).toBe(2016);
@@ -88,14 +98,21 @@ describe('OrderFormSection', () => {
8898
changeInput('Minimum Channel Size', '100000');
8999
changeInput('Max Batch Fee Rate', '1');
90100

101+
// handle the GetInfo call
102+
grpcMock.unary.mockImplementationOnce((desc, opts) => {
103+
opts.onEnd(sampleGrpcResponse(desc));
104+
return undefined as any;
105+
});
91106
let ask: Required<POOL.Ask.AsObject>;
92107
// capture the rate that is sent to the API
93108
injectIntoGrpcUnary((_, props) => {
94109
ask = (props.request.toObject() as any).ask;
95110
});
96111

97112
fireEvent.click(getByText('Place Ask Order'));
98-
expect(ask!.details.amt).toBe('1000000');
113+
await waitFor(() => {
114+
expect(ask!.details.amt).toBe('1000000');
115+
});
99116
expect(ask!.details.rateFixed).toBe(4960);
100117
expect(ask!.details.minUnitsMatch).toBe(1);
101118
expect(ask!.leaseDurationBlocks).toBe(2016);
@@ -112,14 +129,21 @@ describe('OrderFormSection', () => {
112129
await changeSelect('Channel Duration', '1 month (open)');
113130
await changeSelect('Min Node Tier', 'T0 - All Nodes');
114131

132+
// handle the GetInfo call
133+
grpcMock.unary.mockImplementationOnce((desc, opts) => {
134+
opts.onEnd(sampleGrpcResponse(desc));
135+
return undefined as any;
136+
});
115137
let bid: Required<POOL.Bid.AsObject>;
116138
// capture the rate that is sent to the API
117139
injectIntoGrpcUnary((_, props) => {
118140
bid = (props.request.toObject() as any).bid;
119141
});
120142

121143
fireEvent.click(getByText('Place Bid Order'));
122-
expect(bid!.details.amt).toBe('1000000');
144+
await waitFor(() => {
145+
expect(bid!.details.amt).toBe('1000000');
146+
});
123147
expect(bid!.details.rateFixed).toBe(2480);
124148
expect(bid!.details.minUnitsMatch).toBe(1);
125149
expect(bid!.leaseDurationBlocks).toBe(4032);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { grpc } from '@improbable-eng/grpc-web';
55
import { waitFor } from '@testing-library/react';
66
import Big from 'big.js';
77
import { hex } from 'util/strings';
8+
import { sampleGrpcResponse } from 'util/tests';
89
import { poolInvalidOrder, poolListOrders, poolSubmitOrder } from 'util/tests/sampleData';
910
import { createStore, OrderStore, Store } from 'store';
1011
import { Order } from 'store/models';
@@ -101,6 +102,12 @@ describe('OrderStore', () => {
101102

102103
it('should handle invalid orders', async () => {
103104
await rootStore.accountStore.fetchAccounts();
105+
// handle the GetInfo call
106+
grpcMock.unary.mockImplementationOnce((desc, opts) => {
107+
opts.onEnd(sampleGrpcResponse(desc));
108+
return undefined as any;
109+
});
110+
// mock the SubmitOrder response
104111
grpcMock.unary.mockImplementationOnce((desc, opts) => {
105112
if (desc.methodName === 'SubmitOrder') {
106113
const res = {

app/src/api/pool.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as POOL from 'types/generated/trader_pb';
33
import { Trader } from 'types/generated/trader_pb_service';
44
import Big from 'big.js';
55
import { Buffer } from 'buffer';
6+
import { coerce, satisfies } from 'semver';
67
import { b64 } from 'util/strings';
78
import { OrderType, Tier } from 'store/models/order';
89
import BaseApi from './base';
@@ -20,9 +21,17 @@ export const ONE_UNIT = 100000;
2021
// The minimum batch fee rate in sats/kw
2122
export const MIN_FEE_RATE_KW = 253;
2223

23-
// The latest order version. This should be updated along with pool CLI
24-
// see: https://github.com/lightninglabs/pool/blob/master/order/interface.go#L35
25-
export const ORDER_VERSION = 4;
24+
// The default order version to use if the Pool version cannot be detected
25+
export const DEFAULT_ORDER_VERSION = 2;
26+
27+
// maps from the Pool version semver condition to a compatible order version
28+
// see: https://github.com/lightninglabs/pool/blob/master/order/interfaces.go#L42
29+
export const ORDER_VERSION_COMPAT: Record<string, number> = {
30+
'>=0.5.2-alpha': 5,
31+
'>=0.5.0-alpha': 4,
32+
'>=0.4.0-alpha': 2,
33+
'>=0.2.7-alpha': 1,
34+
};
2635

2736
const POOL_INITIATOR = 'lit-ui';
2837

@@ -41,6 +50,15 @@ class PoolApi extends BaseApi<PoolEvents> {
4150
this._grpc = grpc;
4251
}
4352

53+
/**
54+
* call the pool `GetInfo` RPC and return the response
55+
*/
56+
async getInfo(): Promise<POOL.GetInfoResponse.AsObject> {
57+
const req = new POOL.GetInfoRequest();
58+
const res = await this._grpc.request(Trader.GetInfo, req, this._meta);
59+
return res.toObject();
60+
}
61+
4462
/**
4563
* call the pool `QuoteAccount` RPC and return the response
4664
*/
@@ -201,6 +219,9 @@ class PoolApi extends BaseApi<PoolEvents> {
201219
throw new Error(`The rate is too low. it must equate to at least 1 sat per block`);
202220
}
203221

222+
const info = await this.getInfo();
223+
const orderVersion = this.getOrderVersion(info.version);
224+
204225
const req = new POOL.SubmitOrderRequest();
205226
req.setInitiator(POOL_INITIATOR);
206227

@@ -215,15 +236,15 @@ class PoolApi extends BaseApi<PoolEvents> {
215236
case OrderType.Bid:
216237
const bid = new POOL.Bid();
217238
bid.setLeaseDurationBlocks(duration);
218-
bid.setVersion(ORDER_VERSION);
239+
bid.setVersion(orderVersion);
219240
if (minNodeTier !== undefined) bid.setMinNodeTier(minNodeTier);
220241
bid.setDetails(order);
221242
req.setBid(bid);
222243
break;
223244
case OrderType.Ask:
224245
const ask = new POOL.Ask();
225246
ask.setLeaseDurationBlocks(duration);
226-
ask.setVersion(ORDER_VERSION);
247+
ask.setVersion(orderVersion);
227248
ask.setDetails(order);
228249
req.setAsk(ask);
229250
break;
@@ -358,6 +379,26 @@ class PoolApi extends BaseApi<PoolEvents> {
358379
calcPctRate(fixedRate: Big, duration: Big) {
359380
return +fixedRate.mul(duration).div(FEE_RATE_TOTAL_PARTS);
360381
}
382+
383+
/**
384+
* Determines which order version to use based on the provided version string
385+
* @param version the version of pool
386+
* (ex: 0.5.4-alpha commit=v0.5.4-alpha.0.20220114202858-525fe156d240)
387+
*/
388+
getOrderVersion(version: string): number {
389+
// try to convert the pool version string to a SemVer object (ex: 0.5.4)
390+
const verNum = coerce(version);
391+
if (!verNum) return DEFAULT_ORDER_VERSION;
392+
393+
// find the first key which the pool version satisfies
394+
const matchedKey = Object.keys(ORDER_VERSION_COMPAT).find(condition =>
395+
satisfies(verNum, condition),
396+
);
397+
if (!matchedKey) return DEFAULT_ORDER_VERSION;
398+
399+
// return the order version that was matched
400+
return ORDER_VERSION_COMPAT[matchedKey];
401+
}
361402
}
362403

363404
export default PoolApi;

app/src/types/generated/lnd_pb.d.ts

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)