Skip to content

Commit

Permalink
fix(raiden): outbound capacity check
Browse files Browse the repository at this point in the history
  • Loading branch information
Karl Ranna committed Jul 10, 2019
1 parent df694c1 commit eadc411
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 27 deletions.
14 changes: 14 additions & 0 deletions lib/lndclient/LndClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class LndClient extends SwapClient {
private chainIdentifier?: string;
private channelSubscription?: ClientReadableStream<lndrpc.ChannelEventUpdate>;
private invoiceSubscriptions = new Map<string, ClientReadableStream<lndrpc.Invoice>>();
private maximumOutboundAmount = 0;

/**
* Creates an lnd client.
Expand Down Expand Up @@ -110,6 +111,19 @@ class LndClient extends SwapClient {
return this.chainIdentifier;
}

public maximumOutboundCapacity = () => {
return this.maximumOutboundAmount;
}

protected updateCapacity = async () => {
try {
this.maximumOutboundAmount = (await this.channelBalance()).balance;
} catch (e) {
// TODO: Mark client as disconnected
this.logger.error(`failed to fetch channelbalance: ${e}`);
}
}

private unaryCall = <T, U>(methodName: string, params: T): Promise<U> => {
return new Promise((resolve, reject) => {
if (this.isDisabled()) {
Expand Down
7 changes: 4 additions & 3 deletions lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,14 @@ class OrderBook extends EventEmitter {

if (!this.nobalancechecks) {
// check if sufficient outbound channel capacity exists
const { outboundCurrency, outboundUnits } = Swaps.calculateInboundOutboundAmounts(order.quantity, order.price, order.isBuy, order.pairId);
const { outboundCurrency, outboundAmount } = Swaps.calculateInboundOutboundAmounts(order.quantity, order.price, order.isBuy, order.pairId);
const swapClient = this.swaps.swapClientManager.get(outboundCurrency);
if (!swapClient) {
throw swapsErrors.SWAP_CLIENT_NOT_FOUND(outboundCurrency);
}
if (outboundUnits > swapClient.maximumOutboundCapacity) {
throw errors.INSUFFICIENT_OUTBOUND_BALANCE(outboundCurrency, outboundUnits, swapClient.maximumOutboundCapacity);
const maximumOutboundAmount = swapClient.maximumOutboundCapacity(outboundCurrency);
if (outboundAmount > maximumOutboundAmount) {
throw errors.INSUFFICIENT_OUTBOUND_BALANCE(outboundCurrency, outboundAmount, maximumOutboundAmount);
}
}

Expand Down
40 changes: 39 additions & 1 deletion lib/raidenclient/RaidenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TokenPaymentResponse,
} from './types';
import { UnitConverter } from '../utils/UnitConverter';
import { CurrencyInstance } from '../db/types';

type RaidenErrorResponse = { errors: string };

Expand Down Expand Up @@ -49,6 +50,7 @@ class RaidenClient extends SwapClient {
private host: string;
private disable: boolean;
private unitConverter: UnitConverter;
private maximumOutboundAmounts = new Map<string, number>();

/**
* Creates a raiden client.
Expand All @@ -69,14 +71,50 @@ class RaidenClient extends SwapClient {
/**
* Checks for connectivity and gets our Raiden account address
*/
public init = async () => {
public init = async (currencyInstances: CurrencyInstance[]) => {
if (this.disable) {
await this.setStatus(ClientStatus.Disabled);
return;
}
this.setTokenAddresses(currencyInstances);
await this.verifyConnection();
}

/**
* Associate raiden with currencies that have a token address
*/
private setTokenAddresses = (currencyInstances: CurrencyInstance[]) => {
currencyInstances.forEach((currency) => {
if (currency.tokenAddress) {
this.tokenAddresses.set(currency.id, currency.tokenAddress);
}
});
}

public maximumOutboundCapacity = (currency: string): number => {
return this.maximumOutboundAmounts.get(currency) || 0;
}

protected updateCapacity = async () => {
try {
const channelBalancePromises = [];
for (const [currency] of this.tokenAddresses) {
const channelBalancePromise = this.channelBalance(currency)
.then((balance) => {
return { ...balance, currency };
});
channelBalancePromises.push(channelBalancePromise);
}
const channelBalances = await Promise.all(channelBalancePromises);
channelBalances.forEach(({ currency, balance }) => {
this.maximumOutboundAmounts.set(currency, balance);
});
} catch (e) {
// TODO: Mark client as disconnected
this.logger.error(`failed to fetch channelbalances: ${e}`);
}
}

protected verifyConnection = async () => {
this.logger.info(`trying to verify connection to raiden with uri: ${this.host}:${this.port}`);
try {
Expand Down
12 changes: 2 additions & 10 deletions lib/swaps/SwapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ interface SwapClient {
abstract class SwapClient extends EventEmitter {
public abstract readonly cltvDelta: number;
public abstract readonly type: SwapClientType;
public maximumOutboundCapacity = 0;
protected status: ClientStatus = ClientStatus.NotInitialized;
protected reconnectionTimer?: NodeJS.Timer;
/** Time in milliseconds between attempts to recheck connectivity to the client. */
Expand All @@ -53,6 +52,8 @@ abstract class SwapClient extends EventEmitter {
* currencies supported by this client are included in the balance.
*/
public abstract channelBalance(currency?: string): Promise<ChannelBalance>;
public abstract maximumOutboundCapacity(currency?: string): number;
protected abstract updateCapacity(): Promise<void>;

protected setStatus = async (status: ClientStatus): Promise<void> => {
this.logger.info(`${this.constructor.name} status: ${ClientStatus[status]}`);
Expand Down Expand Up @@ -86,15 +87,6 @@ abstract class SwapClient extends EventEmitter {
}
}

private updateCapacity = async () => {
try {
this.maximumOutboundCapacity = (await this.channelBalance()).balance;
} catch (e) {
// TODO: Mark client as disconnected
this.logger.error(`failed to fetch channelbalance from client: ${e}`);
}
}

/**
* Verifies that the swap client can be reached and is in an operational state
* and sets the [[ClientStatus]] accordingly.
Expand Down
4 changes: 2 additions & 2 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ class SwapClientManager extends EventEmitter {
}
}
// setup Raiden
initPromises.push(this.raidenClient.init());
const currencyInstances = await models.Currency.findAll();
initPromises.push(this.raidenClient.init(currencyInstances));

// bind event listeners before all swap clients have initialized
this.bind();
Expand All @@ -95,7 +96,6 @@ class SwapClientManager extends EventEmitter {
const currencyInstances = await models.Currency.findAll();
currencyInstances.forEach((currency) => {
if (currency.tokenAddress) {
this.raidenClient.tokenAddresses.set(currency.id, currency.tokenAddress);
this.swapClients.set(currency.id, this.raidenClient);
}
});
Expand Down
2 changes: 1 addition & 1 deletion test/jest/Orderbook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe('OrderBook', () => {
};
};
swaps.swapClientManager.get = jest.fn().mockReturnValue({
maximumOutboundCapacity: 1,
maximumOutboundCapacity: () => 1,
});
await expect(orderbook.placeLimitOrder(order))
.rejects.toMatchSnapshot();
Expand Down
20 changes: 12 additions & 8 deletions test/jest/RaidenClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RaidenClientConfig, TokenPaymentResponse } from '../../lib/raidenclient
import Logger from '../../lib/Logger';
import { SwapDeal } from '../../lib/swaps/types';
import { UnitConverter } from '../../lib/utils/UnitConverter';
import { CurrencyInstance } from '../../lib/db/types';

const getValidTokenPaymentResponse = () => {
return {
Expand Down Expand Up @@ -84,6 +85,11 @@ const getValidDeal = () => {
};
};

const wethTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const currencyInstances = [
{ id: 'WETH', tokenAddress: wethTokenAddress },
];

jest.mock('../../lib/Logger');
describe('RaidenClient', () => {
let raiden: RaidenClient;
Expand Down Expand Up @@ -112,7 +118,7 @@ describe('RaidenClient', () => {
describe('sendPayment', () => {
test('it removes 0x from secret', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
await raiden.init();
await raiden.init(currencyInstances as CurrencyInstance[]);
const validTokenPaymentResponse: TokenPaymentResponse = getValidTokenPaymentResponse();
raiden['tokenPayment'] = jest.fn()
.mockReturnValue(Promise.resolve(validTokenPaymentResponse));
Expand All @@ -124,7 +130,7 @@ describe('RaidenClient', () => {

test('it rejects in case of empty secret response', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
await raiden.init();
await raiden.init(currencyInstances as CurrencyInstance[]);
const invalidTokenPaymentResponse: TokenPaymentResponse = {
...getValidTokenPaymentResponse(),
secret: '',
Expand Down Expand Up @@ -152,7 +158,7 @@ describe('RaidenClient', () => {
test('it fails when tokenAddress for currency not found', async () => {
expect.assertions(1);
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
await raiden.init();
await raiden.init([] as CurrencyInstance[]);
try {
await raiden.openChannel({
units,
Expand All @@ -170,13 +176,12 @@ describe('RaidenClient', () => {
const peerRaidenAddress = '0x10D8CCAD85C7dc123090B43aA1f98C00a303BFC5';
const currency = 'WETH';
const mockTokenAddresses = new Map<string, string>();
const wethTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
mockTokenAddresses.set('WETH', wethTokenAddress);
raiden.tokenAddresses = mockTokenAddresses;
raiden['openChannelRequest'] = jest.fn().mockImplementation(() => {
throw new Error('openChannelRequest error');
});
await raiden.init();
await raiden.init(currencyInstances as CurrencyInstance[]);
try {
await raiden.openChannel({
units,
Expand All @@ -194,11 +199,10 @@ describe('RaidenClient', () => {
const peerRaidenAddress = '0x10D8CCAD85C7dc123090B43aA1f98C00a303BFC5';
const currency = 'WETH';
const mockTokenAddresses = new Map<string, string>();
const wethTokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
mockTokenAddresses.set('WETH', wethTokenAddress);
raiden.tokenAddresses = mockTokenAddresses;
raiden['openChannelRequest'] = jest.fn().mockReturnValue(Promise.resolve());
await raiden.init();
await raiden.init(currencyInstances as CurrencyInstance[]);
await raiden.openChannel({
units,
currency,
Expand All @@ -216,7 +220,7 @@ describe('RaidenClient', () => {

test('channelBalance calculates the total balance of open channels for a currency', async () => {
raiden = new RaidenClient({ unitConverter, config, logger: raidenLogger });
await raiden.init();
await raiden.init(currencyInstances as CurrencyInstance[]);
raiden.tokenAddresses.get = jest.fn().mockReturnValue(channelBalanceTokenAddress);
raiden['getChannels'] = jest.fn()
.mockReturnValue(Promise.resolve(getChannelsResponse));
Expand Down
2 changes: 0 additions & 2 deletions test/jest/SwapClientManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ describe('Swaps.SwapClientManager', () => {
expect(swapClientManager.get('BTC')).not.toBeUndefined();
expect(swapClientManager.get('LTC')).not.toBeUndefined();
expect(swapClientManager.get('WETH')).not.toBeUndefined();
expect(swapClientManager.raidenClient.tokenAddresses.size).toEqual(1);
expect(swapClientManager.raidenClient.tokenAddresses.get('WETH')).not.toBeUndefined();
swapClientManager.remove('WETH');
expect(swapClientManager['swapClients'].size).toEqual(2);
const lndClients = swapClientManager.getLndClientsMap();
Expand Down

0 comments on commit eadc411

Please sign in to comment.