Skip to content

Commit

Permalink
Merge pull request #1065 from ExchangeUnion/raiden/direct-channel-check
Browse files Browse the repository at this point in the history
feat(raiden): check for direct channels
  • Loading branch information
sangaman authored Jul 17, 2019
2 parents 55cd849 + a87e903 commit ab78f26
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 26 deletions.
5 changes: 5 additions & 0 deletions bin/xud
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ const { argv } = require('yargs')
type: 'string',
alias: 'x',
},
'debug.raidenDirectChannelChecks': {
describe: 'Whether to require direct channels for raiden payments',
type: 'boolean',
default: undefined,
},
'http.port': {
describe: 'Port to listen for http requests',
type: 'number',
Expand Down
4 changes: 4 additions & 0 deletions lib/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Config {
public raiden: RaidenClientConfig;
public orderthresholds: OrderBookThresholds;
public webproxy: { port: number, disable: boolean };
public debug: { raidenDirectChannelChecks: boolean };
public instanceid = 0;
/** Whether to intialize a new database with default values. */
public initdb = true;
Expand Down Expand Up @@ -96,6 +97,9 @@ class Config {
disable: true,
port: 8080,
};
this.debug = {
raidenDirectChannelChecks: true,
};
// TODO: add dynamic max/min price limits
this.orderthresholds = {
minQuantity: 0, // 0 = disabled
Expand Down
2 changes: 1 addition & 1 deletion lib/lndclient/LndClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ class LndClient extends SwapClient {
return this.unaryCall<lndrpc.ListChannelsRequest, lndrpc.ListChannelsResponse>('listChannels', new lndrpc.ListChannelsRequest());
}

public getRoutes = async (amount: number, destination: string, finalCltvDelta = this.cltvDelta): Promise<lndrpc.Route[]> => {
public getRoutes = async (amount: number, destination: string, _currency: string, finalCltvDelta = this.cltvDelta): Promise<lndrpc.Route[]> => {
const request = new lndrpc.QueryRoutesRequest();
request.setAmt(amount);
request.setFinalCltvDelta(finalCltvDelta);
Expand Down
2 changes: 1 addition & 1 deletion lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class OrderBook extends EventEmitter {
return order.quantity >= minQuantity;
}
/**
* Checks that a currency advertised by a peer are known to us, have a swap client identifier,
* Checks that a currency advertised by a peer is known to us, has a swap client identifier,
* and that their token identifier matches ours.
*/
private isPeerCurrencySupported = (peer: Peer, currency: string) => {
Expand Down
8 changes: 6 additions & 2 deletions lib/p2p/Peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ class Peer extends EventEmitter {
public active = false;
/** Timer to periodically call getNodes #402 */
public discoverTimer?: NodeJS.Timer;
/** Currencies that we have verified that we can swap for this peer. */
/** Currencies that we have verified we can swap with this peer. */
public verifiedCurrencies = new Set<string>();
/**
* Currencies that we cannot swap because we are missing a swap client identifier or because our
* Currencies that we cannot swap because we are missing a swap client identifier or because the
* peer's token identifier for this currency does not match ours - for example this may happen
* because a peer is using a different raiden token contract address for a currency than we are.
*/
Expand Down Expand Up @@ -130,6 +130,10 @@ class Peer extends EventEmitter {
return this.nodeState ? this.nodeState.addresses : undefined;
}

public get raidenAddress(): string | undefined {
return this.nodeState ? this.nodeState.raidenAddress : undefined;
}

/** Returns a list of trading pairs advertised by this peer. */
public get advertisedPairs(): string[] {
if (this.nodeState) {
Expand Down
39 changes: 32 additions & 7 deletions lib/raidenclient/RaidenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ class RaidenClient extends SwapClient {
private disable: boolean;
private unitConverter: UnitConverter;
private maximumOutboundAmounts = new Map<string, number>();
private directChannelChecks: boolean;

/**
* Creates a raiden client.
*/
constructor(
{ config, logger, unitConverter }:
{ config: RaidenClientConfig, logger: Logger, unitConverter: UnitConverter },
{ config, logger, unitConverter, directChannelChecks = false }:
{ config: RaidenClientConfig, logger: Logger, unitConverter: UnitConverter, directChannelChecks: boolean },
) {
super(logger);
const { disable, host, port } = config;
Expand All @@ -66,6 +67,7 @@ class RaidenClient extends SwapClient {
this.host = host;
this.disable = disable;
this.unitConverter = unitConverter;
this.directChannelChecks = directChannelChecks;
}

/**
Expand Down Expand Up @@ -201,12 +203,35 @@ class RaidenClient extends SwapClient {
// not implemented, raiden does not use invoices
}

public getRoutes = async (_amount: number, _destination: string) => {
// stub placeholder, query routes not currently implemented in raiden
// assume a fixed lock time of 100 Raiden's blocks
return [{
public getRoutes = async (amount: number, destination: string, currency: string) => {
// a query routes call is not currently provided by raiden

/** A placeholder route value that assumes a fixed lock time of 100 Raiden's blocks. */
const placeholderRoute = {
getTotalTimeLock: () => 101,
}];
};

if (this.directChannelChecks) {
// temporary check for a direct channel in raiden
const tokenAddress = this.tokenAddresses.get(currency);
const channels = await this.getChannels(tokenAddress);
for (const channel of channels) {
if (channel.partner_address && channel.partner_address === destination) {
const balance = channel.balance;
if (balance >= amount) {
this.logger.debug(`found a direct channel for ${currency} to ${destination} with ${balance} balance`);
return [placeholderRoute];
} else {
this.logger.warn(`direct channel found for ${currency} to ${destination} with balance of ${balance} is insufficient for ${amount})`);
return []; // we have a direct channel but it doesn't have enough balance, return no routes
}
}
}
this.logger.warn(`no direct channel found for ${currency} to ${destination}`);
return []; // no direct channels, return no routes
} else {
return [placeholderRoute];
}
}

public getHeight = async () => {
Expand Down
2 changes: 2 additions & 0 deletions lib/raidenclient/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type Channel = OpenChannelPayload & {
channel_address: string;
token_network_identifier: string;
channel_identifier: number;
/** The balance of the channel denominated in the smallest units supported by the token. */
balance: number
state: string;
};
Expand All @@ -49,6 +50,7 @@ export type TokenPaymentResponse = TokenPaymentRequest & {
export type TokenPaymentRequest = {
token_address: string,
target_address: string,
/** The amount of the payment request denominated in the smallest units supported by the token. */
amount: number,
secret_hash: string,
identifier?: number,
Expand Down
10 changes: 6 additions & 4 deletions lib/swaps/SwapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ enum ClientStatus {
}

type ChannelBalance = {
/** The cumulative balance of open channels denominated in satoshis. */
balance: number,
/** The cumulative balance of pending channels denominated in satoshis. */
pendingOpenBalance: number,
};

Expand Down Expand Up @@ -107,12 +109,12 @@ abstract class SwapClient extends EventEmitter {
public abstract async sendSmallestAmount(rHash: string, destination: string, currency: string): Promise<string>;

/**
* Gets routes for the given currency, amount and peerPubKey.
* @param amount the capacity of the route
* @param destination target node for the route
* Gets routes for the given currency, amount, and swap identifier.
* @param amount the capacity the route must support denominated in the smallest units supported by its currency
* @param destination the identifier for the receiving node
* @returns routes
*/
public abstract async getRoutes(amount: number, destination: string, finalCltvDelta?: number): Promise<Route[]>;
public abstract async getRoutes(amount: number, destination: string, currency: string, finalCltvDelta?: number): Promise<Route[]>;

public abstract async addInvoice(rHash: string, amount: number, cltvExpiry: number): Promise<void>;

Expand Down
1 change: 1 addition & 0 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class SwapClientManager extends EventEmitter {
unitConverter,
config: config.raiden,
logger: loggers.raiden,
directChannelChecks: config.debug.raidenDirectChannelChecks,
});
}

Expand Down
10 changes: 5 additions & 5 deletions lib/swaps/Swaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class Swaps extends EventEmitter {

let routes;
try {
routes = await swapClient.getRoutes(makerUnits, destination);
routes = await swapClient.getRoutes(makerUnits, destination, makerCurrency);
} catch (err) {
throw SwapFailureReason.UnexpectedClientError;
}
Expand Down Expand Up @@ -484,11 +484,10 @@ class Swaps extends EventEmitter {
return false;
}

const takerPubKey = peer.getIdentifier(takerSwapClient.type, takerCurrency)!;
const takerIdentifier = peer.getIdentifier(takerSwapClient.type, takerCurrency)!;

const deal: SwapDeal = {
...requestBody,
takerPubKey,
price,
isBuy,
quantity,
Expand All @@ -498,7 +497,8 @@ class Swaps extends EventEmitter {
takerCurrency,
makerUnits,
takerUnits,
destination: takerPubKey,
takerPubKey: takerIdentifier,
destination: takerIdentifier,
peerPubKey: peer.nodePubKey!,
localId: orderToAccept.localId,
phase: SwapPhase.SwapCreated,
Expand Down Expand Up @@ -526,7 +526,7 @@ class Swaps extends EventEmitter {
}

try {
deal.makerToTakerRoutes = await takerSwapClient.getRoutes(takerUnits, takerPubKey, deal.takerCltvDelta);
deal.makerToTakerRoutes = await takerSwapClient.getRoutes(takerUnits, takerIdentifier, deal.takerCurrency, deal.takerCltvDelta);
} catch (err) {
this.failDeal(deal, SwapFailureReason.UnexpectedClientError, err.message);
await this.sendErrorToPeer({
Expand Down
12 changes: 6 additions & 6 deletions test/jest/RaidenClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('RaidenClient', () => {

describe('sendPayment', () => {
test('it removes 0x from secret', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
await raiden.init(currencyInstances as CurrencyInstance[]);
const validTokenPaymentResponse: TokenPaymentResponse = getValidTokenPaymentResponse();
raiden['tokenPayment'] = jest.fn()
Expand All @@ -129,7 +129,7 @@ describe('RaidenClient', () => {
});

test('it rejects in case of empty secret response', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
await raiden.init(currencyInstances as CurrencyInstance[]);
const invalidTokenPaymentResponse: TokenPaymentResponse = {
...getValidTokenPaymentResponse(),
Expand Down Expand Up @@ -157,7 +157,7 @@ describe('RaidenClient', () => {

test('it fails when tokenAddress for currency not found', async () => {
expect.assertions(1);
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
await raiden.init([] as CurrencyInstance[]);
try {
await raiden.openChannel({
Expand All @@ -172,7 +172,7 @@ describe('RaidenClient', () => {

test('it throws when openChannel fails', async () => {
expect.assertions(1);
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
const peerRaidenAddress = '0x10D8CCAD85C7dc123090B43aA1f98C00a303BFC5';
const currency = 'WETH';
const mockTokenAddresses = new Map<string, string>();
Expand All @@ -195,7 +195,7 @@ describe('RaidenClient', () => {

test('it opens a channel', async () => {
expect.assertions(2);
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
const peerRaidenAddress = '0x10D8CCAD85C7dc123090B43aA1f98C00a303BFC5';
const currency = 'WETH';
const mockTokenAddresses = new Map<string, string>();
Expand All @@ -219,7 +219,7 @@ describe('RaidenClient', () => {
});

test('channelBalance calculates the total balance of open channels for a currency', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
raiden = new RaidenClient({ unitConverter, config, directChannelChecks: true, logger: raidenLogger });
await raiden.init(currencyInstances as CurrencyInstance[]);
raiden.tokenAddresses.get = jest.fn().mockReturnValue(channelBalanceTokenAddress);
raiden['getChannels'] = jest.fn()
Expand Down
3 changes: 3 additions & 0 deletions test/jest/SwapClientManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ describe('Swaps.SwapClientManager', () => {
host: 'localhost',
port: 1234,
};
config.debug = {
raidenDirectChannelChecks: true,
};
db = new DB(loggers.db, config.dbpath);
unitConverter = new UnitConverter();
unitConverter.init();
Expand Down
1 change: 1 addition & 0 deletions test/jest/integration/Swaps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ describe('Swaps', () => {
expect(lndBtc.getRoutes).toHaveBeenCalledWith(
1000,
peerLndBtcPubKey,
takerCurrency,
swapRequestBody.takerCltvDelta,
);
expect(lndLtc.addInvoice).toHaveBeenCalledTimes(1);
Expand Down

0 comments on commit ab78f26

Please sign in to comment.