Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/ExchangeUnion/xud into fe…
Browse files Browse the repository at this point in the history
…ature/buy-sell-all-grpc

� Conflicts:
�	test/simulation/xudrpc/xudrpc.pb.go
  • Loading branch information
rsercano committed Oct 14, 2020
2 parents 8008920 + 8b18dd7 commit 1b02e78
Show file tree
Hide file tree
Showing 24 changed files with 685 additions and 315 deletions.
1 change: 1 addition & 0 deletions docs/api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 22 additions & 16 deletions lib/cli/commands/getbalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,46 @@ import { satsToCoinsStr } from '../utils';
const HEADERS = [
colors.blue('Currency'),
colors.blue('Total Balance'),
colors.blue('Channel Balance (Tradable)'),
colors.blue('Wallet Balance (Not Tradable)'),
colors.blue('Channel Balance (Tradable)'),
colors.blue('In Orders'),
];

const formatBalances = (balances: GetBalanceResponse.AsObject) => {
const formatted: any[] = [];
balances.balancesMap.forEach((balance) => {
balances.balancesMap.forEach((balanceElement) => {
const currency = balanceElement[0];
const balance = balanceElement[1];
const element = [];
element.push(
balance[0],
`${satsToCoinsStr(balance[1].totalBalance)}`,
formatBalance(balance[1].channelBalance, balance[1].pendingChannelBalance, balance[1].inactiveChannelBalance),
formatBalance(balance[1].walletBalance, balance[1].unconfirmedWalletBalance),
currency,
satsToCoinsStr(balance.totalBalance),
formatBalance(balance.channelBalance, balance.pendingChannelBalance, balance.inactiveChannelBalance),
formatBalance(balance.walletBalance, balance.unconfirmedWalletBalance),
satsToCoinsStr(balance.reservedBalance),
);
formatted.push(element);
});
return formatted;
};

const formatBalance = (confirmedBalance: number, unconfirmedBalance: number, inactiveBalance = 0) => {
const confirmedBalanceStr = satsToCoinsStr(confirmedBalance);
const unconfirmedBalanceStr = unconfirmedBalance > 0 ? `${satsToCoinsStr(unconfirmedBalance)} pending` : undefined;
const formatBalance = (availableBalance: number, pendingBalance: number, inactiveBalance = 0) => {
const availableBalanceStr = satsToCoinsStr(availableBalance);
const unconfirmedBalanceStr = pendingBalance > 0 ? `${satsToCoinsStr(pendingBalance)} pending` : undefined;
const inactiveBalanceStr = inactiveBalance > 0 ? `${satsToCoinsStr(inactiveBalance)} inactive` : undefined;
if (unconfirmedBalanceStr || inactiveBalanceStr) {
let str = `${confirmedBalanceStr} (`;
let str = availableBalanceStr;
let paranthetical = '';
if (unconfirmedBalanceStr) {
str += inactiveBalanceStr ? `${inactiveBalanceStr} | ${unconfirmedBalanceStr}` : unconfirmedBalanceStr;
} else {
str += inactiveBalanceStr;
paranthetical += paranthetical ? ` | ${unconfirmedBalanceStr}` : unconfirmedBalanceStr;
}
if (inactiveBalanceStr) {
paranthetical += paranthetical ? ` | ${inactiveBalanceStr}` : inactiveBalanceStr;
}
str += ')';
str += ` (${paranthetical})`;
return str;
}
return confirmedBalanceStr;
return availableBalanceStr;
};

const createTable = () => {
Expand All @@ -51,7 +57,7 @@ const createTable = () => {
return table;
};

const displayBalances = (balances: GetBalanceResponse.AsObject) => {
export const displayBalances = (balances: GetBalanceResponse.AsObject) => {
const table = createTable();
const formatted = formatBalances(balances);
formatted.forEach(balance => table.push(balance));
Expand Down
3 changes: 3 additions & 0 deletions lib/grpc/GrpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ class GrpcService {
balance.setInactiveChannelBalance(balanceObj.inactiveChannelBalance);
balance.setWalletBalance(balanceObj.walletBalance);
balance.setUnconfirmedWalletBalance(balanceObj.unconfirmedWalletBalance);
if (balanceObj.reservedBalance) {
balance.setReservedBalance(balanceObj.reservedBalance);
}
balancesMap.set(currency, balance);
});
callback(null, response);
Expand Down
9 changes: 7 additions & 2 deletions lib/lndclient/LndClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { LightningClient, WalletUnlockerClient } from '../proto/lndrpc_grpc_pb';
import * as lndrpc from '../proto/lndrpc_pb';
import swapErrors from '../swaps/errors';
import SwapClient, { ChannelBalance, ClientStatus, PaymentState, SwapClientInfo, TradingLimits, WithdrawArguments } from '../swaps/SwapClient';
import { SwapDeal, CloseChannelParams, OpenChannelParams } from '../swaps/types';
import { CloseChannelParams, OpenChannelParams, SwapDeal } from '../swaps/types';
import { deriveChild } from '../utils/seedutil';
import { base64ToHex, hexToUint8Array } from '../utils/utils';
import errors from './errors';
import { Chain, ChannelCount, ClientMethods, LndClientConfig, LndInfo } from './types';
Expand Down Expand Up @@ -918,7 +919,11 @@ class LndClient extends SwapClient {
public initWallet = async (walletPassword: string, seedMnemonic: string[], restore = false, backup?: Uint8Array):
Promise<lndrpc.InitWalletResponse.AsObject> => {
const request = new lndrpc.InitWalletRequest();
request.setCipherSeedMnemonicList(seedMnemonic);

// from the master seed/mnemonic we derive a child mnemonic for this specific client
const childMnemonic = await deriveChild(seedMnemonic, this.label);
request.setCipherSeedMnemonicList(childMnemonic);

request.setWalletPassword(Uint8Array.from(Buffer.from(walletPassword, 'utf8')));
if (restore) {
request.setRecoveryWindow(2500);
Expand Down
3 changes: 2 additions & 1 deletion lib/nodekey/NodeKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ class NodeKey {
}

/**
* Derives a child seed from the private key for the swap client
* Derives a child mnemonic seed from the private key for the swap client.
* @param swapClient the swap client to create the seed for
* @returns a BIP39 mnemonic
*/
public childSeed = (swapClient: SwapClientType) => {
const privKeyHex = this.privKey.toString('hex');
Expand Down
5 changes: 5 additions & 0 deletions lib/proto/xudrpc.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/proto/xudrpc_pb.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion lib/proto/xudrpc_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import OrderBook from '../orderbook/OrderBook';
import { Currency, isOwnOrder, Order, OrderPortion, OwnLimitOrder, OwnMarketOrder, OwnOrder, PeerOrder, PlaceOrderEvent } from '../orderbook/types';
import Pool from '../p2p/Pool';
import swapsErrors from '../swaps/errors';
import { TradingLimits } from '../swaps/SwapClient';
import { ChannelBalance, TradingLimits } from '../swaps/SwapClient';
import SwapClientManager from '../swaps/SwapClientManager';
import Swaps from '../swaps/Swaps';
import { ResolveRequest, SwapAccepted, SwapDeal, SwapFailure, SwapSuccess } from '../swaps/types';
Expand Down Expand Up @@ -113,7 +113,7 @@ class Service {
/** Gets the total balance for one or all currencies. */
public getBalance = async (args: { currency: string }) => {
const { currency } = args;
const channelBalances = new Map<string, { balance: number, pendingOpenBalance: number, inactiveBalance: number }>();
const channelBalances = new Map<string, ChannelBalance>();
const walletBalances = new Map<string, { confirmedBalance: number, unconfirmedBalance: number }>();

if (currency) {
Expand All @@ -125,6 +125,7 @@ class Service {
await swapClient.channelBalance(currency),
await swapClient.walletBalance(currency),
]);
channelBalance.reservedBalance = this.swapClientManager.getOutboundReservedAmount(currency);
channelBalances.set(currency, channelBalance);
walletBalances.set(currency, walletBalance);
} else {
Expand All @@ -135,6 +136,7 @@ class Service {
this.swapClientManager.swapClients.forEach((swapClient, currency) => {
if (swapClient.isConnected()) {
balancePromises.push(swapClient.channelBalance(currency).then((channelBalance) => {
channelBalance.reservedBalance = this.swapClientManager.getOutboundReservedAmount(currency);
channelBalances.set(currency, channelBalance);
}).catch(this.logger.error));
balancePromises.push(swapClient.walletBalance(currency).then((walletBalance) => {
Expand All @@ -147,7 +149,7 @@ class Service {
const balances = new Map<string, {
channelBalance: number, pendingChannelBalance: number, inactiveChannelBalance: number,
walletBalance: number, unconfirmedWalletBalance: number,
totalBalance: number,
totalBalance: number, reservedBalance?: number,
}>();
channelBalances.forEach((channelBalance, currency) => {
const walletBalance = walletBalances.get(currency);
Expand All @@ -162,6 +164,7 @@ class Service {
channelBalance: channelBalance.balance,
pendingChannelBalance: channelBalance.pendingOpenBalance,
inactiveChannelBalance: channelBalance.inactiveBalance,
reservedBalance: channelBalance.reservedBalance,
walletBalance: walletBalance.confirmedBalance,
unconfirmedWalletBalance: walletBalance.unconfirmedBalance,
});
Expand Down
2 changes: 2 additions & 0 deletions lib/swaps/SwapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type ChannelBalance = {
pendingOpenBalance: number,
/** The cumulative balance of inactive channels denominated in satoshis. */
inactiveBalance: number,
/** The balance that is reserved for open orders denominated in satoshis. */
reservedBalance?: number,
};

type WalletBalance = {
Expand Down
12 changes: 10 additions & 2 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ class SwapClientManager extends EventEmitter {
}
}

public getOutboundReservedAmount = (currency: string) => {
return this.outboundReservedAmounts.get(currency);
}

public getInboundReservedAmount = (currency: string) => {
return this.inboundReservedAmounts.get(currency);
}

public addOutboundReservedAmount = (currency: string, amount: number) => {
const outboundReservedAmount = this.outboundReservedAmounts.get(currency);
const newOutboundReservedAmount = (outboundReservedAmount ?? 0) + amount;
Expand All @@ -180,13 +188,13 @@ class SwapClientManager extends EventEmitter {
public subtractOutboundReservedAmount = (currency: string, amount: number) => {
const outboundReservedAmount = this.outboundReservedAmounts.get(currency);
assert(outboundReservedAmount && outboundReservedAmount >= amount);
this.outboundReservedAmounts.set(currency, (outboundReservedAmount ?? 0) - amount);
this.outboundReservedAmounts.set(currency, outboundReservedAmount - amount);
}

public subtractInboundReservedAmount = (currency: string, amount: number) => {
const inboundReservedAmount = this.inboundReservedAmounts.get(currency);
assert(inboundReservedAmount && inboundReservedAmount >= amount);
this.inboundReservedAmounts.set(currency, (inboundReservedAmount ?? 0) - amount);
this.inboundReservedAmounts.set(currency, inboundReservedAmount - amount);
}

/**
Expand Down
14 changes: 13 additions & 1 deletion lib/utils/seedutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ async function decipher(mnemonic: string[]) {
return Buffer.from(decipheredSeed, 'hex');
}

async function deriveChild(mnemonic: string[], clientType: string) {
const { stdout, stderr } = await exec(`${seedutilPath} derivechild -client ${clientType} ${mnemonic.join(' ')}`);

if (stderr) {
throw new Error(stderr);
}

const childMnenomic = stdout.trim().split(' ');
assert.equal(childMnenomic.length, 24, 'seedutil did not derive child mnemonic of exactly 24 words');
return childMnenomic;
}

async function generate() {
const { stdout, stderr } = await exec(`${seedutilPath} generate`);

Expand All @@ -65,4 +77,4 @@ async function generate() {
return mnemonic;
}

export { keystore, encipher, decipher, generate };
export { keystore, encipher, decipher, deriveChild, generate };
2 changes: 2 additions & 0 deletions proto/xudrpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ message Balance {
uint64 wallet_balance = 5 [json_name = "wallet_balance"];
// Unconfirmed wallet balance in satoshis.
uint64 unconfirmed_wallet_balance = 6 [json_name = "unconfirmed_wallet_balance"];
// The balance that's reserved for open orders.
uint64 reserved_balance = 7 [json_name = "reserved_balance"];
}

message BanRequest {
Expand Down
30 changes: 30 additions & 0 deletions seedutil/SeedUtil.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,36 @@ describe('SeedUtil generate', () => {
});
});

describe('SeedUtil derivechild', () => {
test('it errors without a client type', async () => {
const cmd = `./seedutil/seedutil derivechild ${VALID_SEED.seedWords.slice(0, 24).join(' ')}`;
await expect(executeCommand(cmd))
.rejects.toThrow('client is required');
});

test('it errors with 23 words', async () => {
const cmd = `./seedutil/seedutil derivechild -client BTC ${VALID_SEED.seedWords.slice(0, 23).join(' ')}`;
await expect(executeCommand(cmd))
.rejects.toThrow(ERRORS.INVALID_ARGS_LENGTH);
});

test('it succeeds with 24 words, no aezeed password', async () => {
const cmd = `./seedutil/seedutil derivechild -client BTC ${VALID_SEED_NO_PASS.seedWords.join(' ')}`;
const result = await executeCommand(cmd);
// the mnemonic will change each time due to the salt, but the deciphered seed should stay the same
const decipherCmd = `./seedutil/seedutil decipher ${result}`;
await expect(executeCommand(decipherCmd)).resolves.toMatchSnapshot();
});

test('it succeeds with 24 words, valid aezeed password', async () => {
const cmd = `./seedutil/seedutil derivechild -aezeedpass=${VALID_SEED.seedPassword} -client BTC ${VALID_SEED.seedWords.join(' ')}`;
const result = await executeCommand(cmd);
// the mnemonic will change each time due to the salt, but the deciphered seed should stay the same
const decipherCmd = `./seedutil/seedutil decipher -aezeedpass=${VALID_SEED.seedPassword} ${result}`;
await expect(executeCommand(decipherCmd)).resolves.toMatchSnapshot();
});
});

describe('SeedUtil keystore', () => {
beforeEach(async () => {
await deleteDir(DEFAULT_KEYSTORE_PATH);
Expand Down
10 changes: 10 additions & 0 deletions seedutil/__snapshots__/SeedUtil.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ exports[`SeedUtil decipher it succeeds with 24 words, valid aezeed password 1`]
"
`;

exports[`SeedUtil derivechild it succeeds with 24 words, no aezeed password 1`] = `
"000f4b90d9f9720bfac78aaea09a5193b34811
"
`;

exports[`SeedUtil derivechild it succeeds with 24 words, valid aezeed password 1`] = `
"000ecdef333ecf9054ccb4fb843a3dbbf4ac6a
"
`;

exports[`SeedUtil encipher it succeeds with 24 words, no aezeed password 1`] = `
"00738860374692022c462027a35aaaef3c3289aa0a057e2600000000002cad2e2b
"
Expand Down
Loading

0 comments on commit 1b02e78

Please sign in to comment.