Skip to content
This repository was archived by the owner on Feb 23, 2021. It is now read-only.

Enable macaroons #552

Merged
merged 5 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions assets/script/setup_local_cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ After initiating the wallet and setting your password close the app again.
```
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir=data/btcd/data --logdir=data/btcd/logs

lnd --rpclisten=localhost:10009 --listen=localhost:10019 --restlisten=localhost:8009 --lnddir=data/lnd --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --no-macaroons
lnd --rpclisten=localhost:10009 --listen=localhost:10019 --restlisten=localhost:8009 --lnddir=data/lnd --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek

lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd unlock
lncli --rpcserver=localhost:10009 --lnddir=data/lnd unlock

lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002 --lnddir=data/lnd2 --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --no-macaroons --noencryptwallet
lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002 --lnddir=data/lnd2 --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --noencryptwallet
```

## Fund wallets addresses

```
lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd newaddress np2wkh
lncli --rpcserver=localhost:10009 --lnddir=data/lnd newaddress np2wkh

lncli --rpcserver=localhost:10002 --no-macaroons --lnddir=data/lnd2 newaddress np2wkh
lncli --rpcserver=localhost:10002 --lnddir=data/lnd2 newaddress np2wkh

btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir=data/btcd/data --logdir=data/btcd/logs --miningaddr=NEW_ADDRESS

Expand All @@ -33,19 +33,19 @@ btcctl --simnet --rpcuser=kek --rpcpass=kek generate 400
## Open channel and send payment

```
lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd getinfo
lncli --rpcserver=localhost:10009 --lnddir=data/lnd getinfo

lncli --rpcserver=localhost:10002 --no-macaroons --lnddir=data/lnd2 getinfo
lncli --rpcserver=localhost:10002 --lnddir=data/lnd2 getinfo

lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd connect PUB_KEY@localhost:10012
lncli --rpcserver=localhost:10009 --lnddir=data/lnd connect PUB_KEY@localhost:10012

lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd openchannel --node_key=PUB_KEY --local_amt=16000000
lncli --rpcserver=localhost:10009 --lnddir=data/lnd openchannel --node_key=PUB_KEY --local_amt=16000000

btcctl --simnet --rpcuser=kek --rpcpass=kek generate 6

lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd listchannels
lncli --rpcserver=localhost:10009 --lnddir=data/lnd listchannels

lncli --rpcserver=localhost:10002 --no-macaroons --lnddir=data/lnd2 addinvoice --amt=10000
lncli --rpcserver=localhost:10002 --lnddir=data/lnd2 addinvoice --amt=10000

lncli --rpcserver=localhost:10009 --no-macaroons --lnddir=data/lnd sendpayment --pay_req=ENCODED_INVOICE
lncli --rpcserver=localhost:10009 --lnddir=data/lnd sendpayment --pay_req=ENCODED_INVOICE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice update to the docs 👍

```
5 changes: 2 additions & 3 deletions public/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { startLndProcess, startBtcdProcess } = require('./lnd-child-process');
const grcpClient = require('./grpc-client');
const {
PREFIX_NAME,
MACAROONS_ENABLED,
NETWORK,
LND_PORT,
LND_PEER_PORT,
LND_INIT_DELAY,
Expand Down Expand Up @@ -123,7 +123,7 @@ function createWindow() {
ipcMain,
lndSettingsDir,
lndPort: LND_PORT,
macaroonsEnabled: MACAROONS_ENABLED,
network: isDev ? 'simnet' : NETWORK,
});
}

Expand All @@ -138,7 +138,6 @@ const startLnd = async () => {
lndProcess = await startLndProcess({
isDev,
lndSettingsDir,
macaroonsEnabled: MACAROONS_ENABLED,
lndPort: LND_PORT,
lndPeerPort: LND_PEER_PORT,
logger: Logger,
Expand Down
46 changes: 20 additions & 26 deletions public/grpc-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,27 @@ async function getCredentials(lndSettingsDir) {
return grpc.credentials.createSsl(lndCert);
}

function getMetadata(lndSettingsDir) {
const metadata = new grpc.Metadata();
const macaroonPath = path.join(lndSettingsDir, 'admin.macaroon');
const macaroonHex = fs.readFileSync(macaroonPath).toString('hex');
metadata.add('macaroon', macaroonHex);
return metadata;
function getMacaroonCreds(lndSettingsDir, network) {
return grpc.credentials.createFromMetadataGenerator((args, callback) => {
const metadata = new grpc.Metadata();
const macaroonPath = path.join(
lndSettingsDir,
`data/chain/bitcoin/${network}/admin.macaroon`
);
const macaroonHex = fs.readFileSync(macaroonPath).toString('hex');
metadata.add('macaroon', macaroonHex);
callback(null, metadata);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work with return if it's asynchronous? Don't we need to wait for the callback to set the metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to combine the ssl credentials with the macaroon metadata, we need to turn the metadata into a CallCredentials object.

createFromMetadataGenerator requires an metadata generator (the asynchronous function) and returns a CallCredentials object that can be composed with the ssl ChannelCredentials object: googleapis/google-cloud-node#1346 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Thanks for the explanation.

}

module.exports.init = async function({
ipcMain,
lndPort,
lndSettingsDir,
macaroonsEnabled,
network,
}) {
let credentials;
let protoPath;
let metadata;
let lnrpc;
let unlocker;
let lnd;
Expand All @@ -57,9 +61,6 @@ module.exports.init = async function({
credentials = await getCredentials(lndSettingsDir);
protoPath = path.join(__dirname, '..', 'assets', 'rpc.proto');
lnrpc = grpc.load(protoPath).lnrpc;
if (macaroonsEnabled) {
metadata = getMetadata(lndSettingsDir);
}
unlocker = new lnrpc.WalletUnlocker(`localhost:${lndPort}`, credentials);
grpc.waitForClientReady(unlocker, Infinity, err => {
event.sender.send('unlockReady', { err });
Expand All @@ -72,6 +73,11 @@ module.exports.init = async function({
});

ipcMain.on('lndInit', event => {
const macaroonCreds = getMacaroonCreds(lndSettingsDir, network);
credentials = grpc.credentials.combineChannelCredentials(
credentials,
macaroonCreds
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the locker doesn't need macaroons? Only the lnrpc.Lightning service?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the wallet hasn't been initialized, the macaroons don't exist yet. The wallet creation process involves creating the macaroons :)

lnd = new lnrpc.Lightning(`localhost:${lndPort}`, credentials);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we don't need to pass the metadata in each api call because we inject the credentials here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thought it simplified the code a bit to not have to remember to pass in metadata each time.

Copy link
Contributor Author

@valentinewallace valentinewallace Aug 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we stayed with passing in metadata each time, we'd also have to get the macaroons to action/grpc and pass them into the Duplex here: https://github.com/lightninglabs/lightning-app/blob/master/src/action/grpc.js#L96 or else stream writes fail due to missing macaroons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok great 👍

grpc.waitForClientReady(lnd, Infinity, err => {
event.sender.send('lndReady', { err });
Expand All @@ -88,33 +94,21 @@ module.exports.init = async function({
const handleResponse = (err, response) => {
event.sender.send(`unlockResponse_${method}`, { err, response });
};
if (metadata) {
unlocker[method](body, metadata, { deadline }, handleResponse);
} else {
unlocker[method](body, { deadline }, handleResponse);
}
unlocker[method](body, { deadline }, handleResponse);
});

ipcMain.on('lndRequest', (event, { method, body }) => {
const deadline = new Date(new Date().getTime() + GRPC_TIMEOUT);
const handleResponse = (err, response) => {
event.sender.send(`lndResponse_${method}`, { err, response });
};
if (metadata) {
lnd[method](body, metadata, { deadline }, handleResponse);
} else {
lnd[method](body, { deadline }, handleResponse);
}
lnd[method](body, { deadline }, handleResponse);
});

const streams = {};
ipcMain.on('lndStreamRequest', (event, { method, body }) => {
let stream;
if (metadata) {
stream = lnd[method](metadata, body);
} else {
stream = lnd[method](body);
}
stream = lnd[method](body);
const send = res => event.sender.send(`lndStreamEvent_${method}`, res);
stream.on('data', data => send({ event: 'data', data }));
stream.on('end', () => send({ event: 'end' }));
Expand Down
2 changes: 0 additions & 2 deletions public/lnd-child-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ function startBlockingProcess(name, args, logger) {

module.exports.startLndProcess = async function({
isDev,
macaroonsEnabled,
lndSettingsDir,
lndPort,
lndPeerPort,
Expand All @@ -61,7 +60,6 @@ module.exports.startLndProcess = async function({
'--bitcoin.active',
'--debuglevel=info',
`--lnddir=${lndSettingsDir}`,
macaroonsEnabled ? '' : '--no-macaroons',
lndPort ? `--rpclisten=localhost:${lndPort}` : '',
lndPeerPort ? `--listen=localhost:${lndPeerPort}` : '',
lndRestPort ? `--restlisten=localhost:${lndRestPort}` : '',
Expand Down
2 changes: 1 addition & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports.RATE_DELAY = 15 * 60 * 1000;

module.exports.LND_PORT = 10009;
module.exports.LND_PEER_PORT = 10019;
module.exports.MACAROONS_ENABLED = false;
module.exports.NETWORK = 'testnet';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we're in dev mode? Then the app will start with testnet instead of simnet?

module.exports.BTCD_MINING_ADDRESS = 'rfu4i1Mo2NF7TQsN9bMVLFSojSzcyQCEH5';

const prefixName = 'lightning';
Expand Down
8 changes: 3 additions & 5 deletions test/integration/action/action-integration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const grcpClient = require('../../../public/grpc-client');
/* eslint-disable no-unused-vars */

const isDev = true;
const NETWORK = 'simnet';
const BTCD_PORT = 18555;
const BTCD_SETTINGS_DIR = 'test/data/btcd';
const LND_SETTINGS_DIR_1 = 'test/data/lnd_1';
Expand All @@ -37,7 +38,6 @@ const LND_REST_PORT_1 = 8001;
const LND_REST_PORT_2 = 8002;
const HOST_1 = `localhost:${LND_PEER_PORT_1}`;
const HOST_2 = `localhost:${LND_PEER_PORT_2}`;
const MACAROONS_ENABLED = false;
const NAP_TIME = process.env.NAP_TIME || 5000;
const walletPassword = 'bitconeeeeeect';

Expand Down Expand Up @@ -111,7 +111,6 @@ describe('Action Integration Tests', function() {
await poll(() => isPortOpen(BTCD_PORT));
const lndProcess1Promise = startLndProcess({
isDev,
macaroonsEnabled: MACAROONS_ENABLED,
lndSettingsDir: LND_SETTINGS_DIR_1,
lndPort: LND_PORT_1,
lndPeerPort: LND_PEER_PORT_1,
Expand All @@ -120,7 +119,6 @@ describe('Action Integration Tests', function() {
});
const lndProcess2Promise = startLndProcess({
isDev,
macaroonsEnabled: MACAROONS_ENABLED,
lndSettingsDir: LND_SETTINGS_DIR_2,
lndPort: LND_PORT_2,
lndPeerPort: LND_PEER_PORT_2,
Expand All @@ -135,13 +133,13 @@ describe('Action Integration Tests', function() {
ipcMain: ipcMainStub1,
lndPort: LND_PORT_1,
lndSettingsDir: LND_SETTINGS_DIR_1,
macaroonsEnabled: MACAROONS_ENABLED,
network: NETWORK,
});
await grcpClient.init({
ipcMain: ipcMainStub2,
lndPort: LND_PORT_2,
lndSettingsDir: LND_SETTINGS_DIR_2,
macaroonsEnabled: MACAROONS_ENABLED,
network: NETWORK,
});

db1 = sinon.createStubInstance(AppStorage);
Expand Down