Skip to content

Commit

Permalink
refactor(lnd): use SendPayment instead of SendToRoute
Browse files Browse the repository at this point in the history
  • Loading branch information
Karl Ranna committed Jun 11, 2019
1 parent e63ef01 commit a8c50ca
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 95 deletions.
138 changes: 69 additions & 69 deletions lib/lndclient/LndClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,77 @@ class LndClient extends SwapClient {
return this.unaryCall<lndrpc.GetInfoRequest, lndrpc.GetInfoResponse>('getInfo', new lndrpc.GetInfoRequest());
}

public sendSmallestAmount = async (rHash: string, destination: string) => {
const sendRequest = new lndrpc.SendRequest();
sendRequest.setAmt(1);
sendRequest.setDestString(destination);
sendRequest.setPaymentHashString(rHash);
sendRequest.setFinalCltvDelta(this.cltvDelta);
public sendSmallestAmount = async (rHash: string, destination: string): Promise<string> => {
const request = this.buildSendRequest({
rHash,
destination,
amount: 1,
cltvDelta: this.cltvDelta, // TODO: check cltvDelta
});
const preImage = await this.executeSendRequest(request);
return preImage;
}

public sendPayment = async (deal: SwapDeal): Promise<string> => {
assert(deal.state === SwapState.Active);
let request: lndrpc.SendRequest;
if (deal.role === SwapRole.Taker) {
// we are the taker paying the maker
if (!deal.destination) {
assert.fail('swap deal as taker must have a destination');
}
if (!deal.makerCltvDelta) {
assert.fail('swap deal as taker must have a makerCltvDelta');
}
request = this.buildSendRequest({
rHash: deal.rHash,
destination: deal.destination!,
amount: deal.makerAmount,
cltvDelta: deal.makerCltvDelta!, // TODO: check cltvDelta
});
} else {
// we are the maker paying the taker
if (!deal.takerPubKey) {
assert.fail('swap deal as maker must have a takerPubKey');
}
if (!deal.takerCltvDelta) {
assert.fail('swap deal as maker must have a takerCltvDelta');
}
request = this.buildSendRequest({
rHash: deal.rHash,
destination: deal.takerPubKey!,
amount: deal.takerAmount,
cltvDelta: deal.takerCltvDelta!, // TODO: check cltvDelta
});
}
const preImage = await this.executeSendRequest(request);
return preImage;
}

/**
* Builds a lndrpc.SendRequest
*/
private buildSendRequest = (
{ rHash, destination, amount, cltvDelta }:
{ rHash: string, destination: string, amount: number, cltvDelta: number },
): lndrpc.SendRequest => {
const request = new lndrpc.SendRequest();
request.setPaymentHashString(rHash);
request.setDestString(destination);
request.setAmt(amount);
request.setFinalCltvDelta(cltvDelta);
return request;
}

/**
* Executes the provided lndrpc.SendRequest
*/
private executeSendRequest = async (
request: lndrpc.SendRequest,
): Promise<string> => {
let sendPaymentResponse: lndrpc.SendResponse;
try {
sendPaymentResponse = await this.sendPaymentSync(sendRequest);
sendPaymentResponse = await this.sendPaymentSync(request);
} catch (err) {
this.logger.error('got exception from sendPaymentSync', err.message);
throw err;
Expand All @@ -227,61 +289,6 @@ class LndClient extends SwapClient {
return base64ToHex(sendPaymentResponse.getPaymentPreimage_asB64());
}

public sendPayment = async (deal: SwapDeal): Promise<string> => {
assert(deal.state === SwapState.Active);

if (deal.makerToTakerRoutes && deal.role === SwapRole.Maker) {
const request = new lndrpc.SendToRouteRequest();
request.setRoutesList(deal.makerToTakerRoutes as lndrpc.Route[]);
request.setPaymentHashString(deal.rHash);

try {
const sendToRouteResponse = await this.sendToRouteSync(request);
const sendPaymentError = sendToRouteResponse.getPaymentError();
if (sendPaymentError) {
this.logger.error(`sendToRouteSync failed with payment error: ${sendPaymentError}`);
throw new Error(sendPaymentError);
}

return base64ToHex(sendToRouteResponse.getPaymentPreimage_asB64());
} catch (err) {
this.logger.error(`got exception from sendToRouteSync: ${JSON.stringify(request.toObject())}`, err);
throw err;
}
} else if (deal.destination) {
const request = new lndrpc.SendRequest();
request.setDestString(deal.destination);
request.setPaymentHashString(deal.rHash);

if (deal.role === SwapRole.Taker) {
// we are the taker paying the maker
request.setFinalCltvDelta(deal.makerCltvDelta!);
request.setAmt(deal.makerAmount);
} else {
// we are the maker paying the taker
request.setFinalCltvDelta(deal.takerCltvDelta);
request.setAmt(deal.takerAmount);
}

try {
const sendPaymentResponse = await this.sendPaymentSync(request);
const sendPaymentError = sendPaymentResponse.getPaymentError();
if (sendPaymentError) {
this.logger.error(`sendPaymentSync failed with payment error: ${sendPaymentError}`);
throw new Error(sendPaymentError);
}

return base64ToHex(sendPaymentResponse.getPaymentPreimage_asB64());
} catch (err) {
this.logger.error(`got exception from sendPaymentSync: ${JSON.stringify(request.toObject())}`, err);
throw err;
}
} else {
assert.fail('swap deal must have a route or destination to send payment');
return '';
}
}

/**
* Sends a payment through the Lightning Network.
*/
Expand Down Expand Up @@ -380,13 +387,6 @@ class LndClient extends SwapClient {
return this.unaryCall<lndrpc.QueryRoutesRequest, lndrpc.QueryRoutesResponse>('queryRoutes', request);
}

/**
* Sends amount to destination using pre-defined routes.
*/
private sendToRouteSync = (request: lndrpc.SendToRouteRequest): Promise<lndrpc.SendResponse> => {
return this.unaryCall<lndrpc.SendToRouteRequest, lndrpc.SendResponse>('sendToRouteSync', request);
}

public addInvoice = async (rHash: string, amount: number) => {
const addHoldInvoiceRequest = new lndinvoices.AddHoldInvoiceRequest();
addHoldInvoiceRequest.setHash(hexToUint8Array(rHash));
Expand Down
109 changes: 109 additions & 0 deletions test/jest/LndClient.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import LndClient from '../../lib/lndclient/LndClient';
import { LndClientConfig } from '../../lib/lndclient/types';
import Logger from '../../lib/Logger';
import { getValidDeal } from '../utils';

const getSendPaymentSyncResponse = () => {
return {
getPaymentError: () => {},
getPaymentPreimage_asB64: () =>
'IDAKXrx4dayn0H/gCxN12jPK2/LchwPZop4zICw43jg=',
};
};

const getSendPaymentSyncErrorResponse = () => {
return {
getPaymentError: () => 'error!',
};
};

jest.mock('../../lib/Logger');
const mockedLogger = <jest.Mock<Logger>><any>Logger;
describe('LndClient', () => {
let lnd: LndClient;
let config: LndClientConfig;
let currency: string;
let logger: Logger;

beforeEach(() => {
config = {
disable: false,
certpath: '/cert/path',
macaroonpath: '/macaroon/path',
host: '127.0.0.1',
port: 4321,
cltvdelta: 144,
nomacaroons: true,
};
currency = 'BTC';
logger = new mockedLogger();
logger.error = jest.fn();
logger.info = jest.fn();
});

afterEach(async () => {
jest.clearAllMocks();
await lnd.close();
});

test('sendPayment maker success', async () => {
lnd = new LndClient(config, currency, logger);
lnd['sendPaymentSync'] = jest.fn()
.mockReturnValue(Promise.resolve(getSendPaymentSyncResponse()));
const deal = getValidDeal();
const buildSendRequestSpy = jest.spyOn(lnd as any, 'buildSendRequest');
await expect(lnd.sendPayment(deal))
.resolves.toMatchSnapshot();
expect(buildSendRequestSpy).toHaveBeenCalledWith({
amount: deal.takerAmount,
destination: deal.takerPubKey,
cltvDelta: deal.takerCltvDelta,
rHash: deal.rHash,
});
});

test('sendPayment taker success', async () => {
lnd = new LndClient(config, currency, logger);
lnd['sendPaymentSync'] = jest.fn()
.mockReturnValue(Promise.resolve(getSendPaymentSyncResponse()));
const deal = {
...getValidDeal(),
role: 0, // taker
};
const buildSendRequestSpy = jest.spyOn(lnd as any, 'buildSendRequest');
await expect(lnd.sendPayment(deal))
.resolves.toMatchSnapshot();
expect(buildSendRequestSpy).toHaveBeenCalledWith({
amount: deal.makerAmount,
destination: deal.destination,
cltvDelta: deal.makerCltvDelta,
rHash: deal.rHash,
});
});

test('sendPayment error', async () => {
lnd = new LndClient(config, currency, logger);
lnd['sendPaymentSync'] = jest.fn()
.mockReturnValue(Promise.resolve(getSendPaymentSyncErrorResponse()));
await expect(lnd.sendPayment(getValidDeal()))
.rejects.toMatchSnapshot();
});

test('sendSmallestAmount success', async () => {
lnd = new LndClient(config, currency, logger);
lnd['sendPaymentSync'] = jest.fn()
.mockReturnValue(Promise.resolve(getSendPaymentSyncResponse()));
const buildSendRequestSpy = jest.spyOn(lnd as any, 'buildSendRequest');
const rHash = '04b6ac45b770ec4abbb9713aebfa57b963a1f6c7a795d9b5757687e0688add80';
const destination = '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac';
await expect(lnd.sendSmallestAmount(rHash, destination))
.resolves.toMatchSnapshot();
expect(buildSendRequestSpy).toHaveBeenCalledWith({
destination,
rHash,
cltvDelta: lnd.cltvDelta,
amount: 1,
});
});

});
27 changes: 1 addition & 26 deletions test/jest/RaidenClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import RaidenClient from '../../lib/raidenclient/RaidenClient';
import { RaidenClientConfig, TokenPaymentResponse } from '../../lib/raidenclient/types';
import Logger from '../../lib/Logger';
import { SwapDeal } from '../../lib/swaps/types';
import { getValidDeal } from '../utils';

const getValidTokenPaymentResponse = () => {
return {
Expand All @@ -16,32 +17,6 @@ const getValidTokenPaymentResponse = () => {
};
};

const getValidDeal = () => {
return {
proposedQuantity: 10000,
pairId: 'LTC/BTC',
orderId: '53bc8a30-81f0-11e9-9259-a5617f44d209',
rHash: '04b6ac45b770ec4abbb9713aebfa57b963a1f6c7a795d9b5757687e0688add80',
takerCltvDelta: 144,
takerPubKey: '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac',
price: 0.1,
isBuy: true,
quantity: 10000,
makerAmount: 10000,
takerAmount: 1000,
makerCurrency: 'LTC',
takerCurrency: 'BTC',
destination: '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac',
peerPubKey: '021ea6d67c850a0811b01c78c8117dca044b224601791a4186bf5748f667f73517',
localId: '53bc8a30-81f0-11e9-9259-a5617f44d209',
phase: 3,
state: 0,
role: 1,
createTime: 1559120485138,
makerToTakerRoutes: [{ getTotalTimeLock: () => {} }],
};
};

jest.mock('../../lib/Logger');
describe('RaidenClient', () => {
let raiden: RaidenClient;
Expand Down
9 changes: 9 additions & 0 deletions test/jest/__snapshots__/LndClient.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`LndClient sendPayment error 1`] = `[Error: error!]`;

exports[`LndClient sendPayment maker success 1`] = `"20300a5ebc7875aca7d07fe00b1375da33cadbf2dc8703d9a29e33202c38de38"`;

exports[`LndClient sendPayment taker success 1`] = `"20300a5ebc7875aca7d07fe00b1375da33cadbf2dc8703d9a29e33202c38de38"`;

exports[`LndClient sendSmallestAmount success 1`] = `"20300a5ebc7875aca7d07fe00b1375da33cadbf2dc8703d9a29e33202c38de38"`;
27 changes: 27 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,30 @@ export const createPeerOrder = (
initialQuantity: quantity,
id: uuidv1(),
});

export const getValidDeal = () => {
return {
proposedQuantity: 10000,
pairId: 'LTC/BTC',
orderId: '53bc8a30-81f0-11e9-9259-a5617f44d209',
rHash: '04b6ac45b770ec4abbb9713aebfa57b963a1f6c7a795d9b5757687e0688add80',
takerCltvDelta: 144,
makerCltvDelta: 576,
takerPubKey: '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac',
price: 0.1,
isBuy: true,
quantity: 10000,
makerAmount: 10000,
takerAmount: 1000,
makerCurrency: 'LTC',
takerCurrency: 'BTC',
destination: '034c5266591bff232d1647f45bcf6bbc548d3d6f70b2992d28aba0afae067880ac',
peerPubKey: '021ea6d67c850a0811b01c78c8117dca044b224601791a4186bf5748f667f73517',
localId: '53bc8a30-81f0-11e9-9259-a5617f44d209',
phase: 3,
state: 0,
role: 1,
createTime: 1559120485138,
makerToTakerRoutes: [{ getTotalTimeLock: () => {} }],
};
};

0 comments on commit a8c50ca

Please sign in to comment.