ln-service
Version:
Interaction helper for your Lightning Network daemon
427 lines (380 loc) • 11.1 kB
JavaScript
const {promisify} = require('util');
const asyncAuto = require('async/auto');
const asyncEach = require('async/each');
const asyncRetry = require('async/retry');
const tinysecp = require('tiny-secp256k1');
const {addPeer} = require('./../../');
const chainSendTransaction = require('./chain_send_transaction');
const connectChainNode = require('./connect_chain_node');
const {createChainAddress} = require('./../../');
const generateBlocks = require('./generate_blocks');
const {getWalletInfo} = require('./../../');
const mineTransaction = require('./mine_transaction');
const rpc = require('./rpc');
const spawnLnd = require('./spawn_lnd');
const waitForTermination = require('./wait_for_termination');
const defaultFee = 1e3;
const defaultVout = 0;
const format = 'np2wpkh';
const maturityBlockCount = 429;
const retryMs = 20;
const retryTimes = 1000;
const seed = 'abandon tank dose ripple foil subway close flock laptop cabbage primary silent plastic unhappy west weird panda plastic brave prefer diesel glad jazz isolate';
const tokens = 50e8;
/** Create a cluster of lnds
{
[is_circular_enabled]: <Allow Circular Payments Bool>
[is_keysend_enabled]: <Nodes Accept Keysend Payments Bool>
[is_remote_skipped]: <Is Remote Node Creation Skipped Bool>
[nodes]: [{
chain_rpc_cert: <RPC Cert Path String>
chain_rpc_pass: <Chain RPC Password String>
chain_rpc_port: <RPC Port Number>
chain_rpc_user: <Chain RPC Username String>
kill: <Kill Function>
listen_ip: <Listen Ip String>
}]
}
@returns via cbk
{
control: {
kill: <Kill Function>
lnd: <Authenticated LND gRPC Object>
}
generate: <Generate Function> ({node, count}) -> (err) -> ()
kill: <Kill Nodes Promise Function>
[remote]: {
kill: <Kill Function>
lnd: <Authenticated LND gRPC Object>
}
target: {
kill: <Kill Function>
lnd: <Authenticated LND gRPC Object>
}
[remote_node_public_key]: <Remote Node Public Key Hex String>
target_node_public_key: <Target Node Public Key Hex String>
}
*/
module.exports = (args, cbk) => {
return asyncAuto({
// Import ECPair library
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
// Create control lnd
control: cbk => {
return spawnLnd({
seed,
circular: args.is_circular_enabled,
keysend: args.is_keysend_enabled,
},
cbk);
},
// Create target lnd
target: cbk => {
return spawnLnd({
circular: args.is_circular_enabled,
keysend: args.is_keysend_enabled,
},
cbk);
},
// Create remote lnd
remote: cbk => {
if (!!args.is_remote_skipped) {
return cbk();
}
return spawnLnd({
circular: args.is_circular_enabled,
keysend: args.is_keysend_enabled,
},
cbk);
},
// Get the remote node info
remoteNode: ['remote', ({remote}, cbk) => {
if (!!args.is_remote_skipped) {
return cbk();
}
return getWalletInfo({lnd: remote.lnd}, cbk);
}],
// Get the chain rpc cert
controlCert: ['control', ({control}, cbk) => {
return cbk(null, control.chain_rpc_cert_file);
}],
// Get the target rpc cert
targetCert: ['target', ({target}, cbk) => {
return cbk(null, target.chain_rpc_cert_file);
}],
// Get the target node info
targetNode: ['target', ({target}, cbk) => {
return getWalletInfo({lnd: target.lnd}, cbk);
}],
// Make a chain address for control
controlChainAddress: ['control', ({control}, cbk) => {
return createChainAddress({format, lnd: control.lnd}, cbk);
}],
// Make a chain address for target
targetChainAddress: ['target', ({target}, cbk) => {
return createChainAddress({format, lnd: target.lnd}, cbk);
}],
// Connect target and remote chains together
connectRemoteAndTargetChain: [
'control',
'controlCert',
'remote',
({control, controlCert, remote}, cbk) =>
{
if (!!args.is_remote_skipped) {
return cbk();
}
return connectChainNode({
cert: controlCert,
connect: `127.0.0.1:${remote.chain_listen_port}`,
host: control.listen_ip,
pass: control.chain_rpc_pass,
port: control.chain_rpc_port,
user: control.chain_rpc_user,
},
cbk);
}],
connectExtra: ['target', ({target}, cbk) => {
return asyncEach(args.nodes || [], (node, cbk) => {
return connectChainNode({
cert: node.chain_rpc_cert_file,
connect: `127.0.0.1:${target.chain_listen_port}`,
host: node.listen_ip,
pass: node.chain_rpc_pass,
port: node.chain_rpc_port,
user: node.chain_rpc_user,
},
cbk);
},
cbk);
}],
// Connect control and target chains together
connectTargetChain: [
'control',
'controlCert',
'target',
({control, controlCert, target}, cbk) =>
{
return connectChainNode({
cert: controlCert,
connect: `127.0.0.1:${target.chain_listen_port}`,
host: control.listen_ip,
pass: control.chain_rpc_pass,
port: control.chain_rpc_port,
user: control.chain_rpc_user,
},
cbk);
}],
// Generate blocks to maturity
generateBlocks: [
'connectRemoteAndTargetChain',
'connectTargetChain',
'control',
'controlCert',
({control, controlCert}, cbk) =>
{
return generateBlocks({
cert: controlCert,
count: maturityBlockCount,
host: control.listen_ip,
pass: control.chain_rpc_pass,
port: control.chain_rpc_port,
user: control.chain_rpc_user,
},
cbk);
}],
// Add target peer
addTargetPeer: [
'control',
'generateBlocks',
'target',
'targetNode',
({control, target, targetNode}, cbk) =>
{
return asyncRetry({interval: retryMs, times: retryTimes}, cbk => {
return addPeer({
lnd: control.lnd,
public_key: targetNode.public_key,
socket: target.socket,
},
cbk);
},
cbk);
}],
// Chain funding transactions
funding: [
'control',
'controlChainAddress',
'ecp',
'generateBlocks',
'targetChainAddress',
({
control,
controlChainAddress,
ecp,
generateBlocks,
targetChainAddress,
},
cbk) =>
{
const {blocks} = generateBlocks;
const [controlBlock, targetBlock] = blocks;
const [controlCoinbase] = controlBlock.transaction_ids;
const [targetCoinbase] = targetBlock.transaction_ids;
const controlChainSend = chainSendTransaction({
ecp,
tokens,
destination: controlChainAddress.address,
fee: defaultFee,
private_key: control.mining_key,
spend_transaction_id: controlCoinbase,
spend_vout: defaultVout,
});
const targetChainSend = chainSendTransaction({
ecp,
tokens,
destination: targetChainAddress.address,
fee: defaultFee,
private_key: control.mining_key,
spend_transaction_id: targetCoinbase,
spend_vout: defaultVout,
});
return cbk(null, {
control: controlChainSend.transaction,
target: targetChainSend.transaction
});
}],
// Generate some funds for control
generateControlFunds: [
'control',
'controlCert',
'funding',
({control, controlCert, funding}, cbk) =>
{
return mineTransaction({
transaction: funding.control,
cert: controlCert,
host: control.listen_ip,
pass: control.chain_rpc_pass,
port: control.chain_rpc_port,
user: control.chain_rpc_user,
},
cbk);
}],
// Generate some funds for target
generateTargetFunds: [
'connectRemoteAndTargetChain',
'control',
'controlCert',
'funding',
'generateControlFunds',
({control, controlCert, funding}, cbk) =>
{
return mineTransaction({
transaction: funding.target,
cert: controlCert,
host: control.listen_ip,
pass: control.chain_rpc_pass,
port: control.chain_rpc_port,
user: control.chain_rpc_user,
},
cbk);
}],
// Add remote peer
addRemotePeer: [
'connectRemoteAndTargetChain',
'generateTargetFunds',
'remote',
'remoteNode',
'target',
({remote, remoteNode, target}, cbk) =>
{
if (!!args.is_remote_skipped) {
return cbk();
}
return asyncRetry({interval: retryMs, times: retryTimes}, cbk => {
return addPeer({
lnd: target.lnd,
public_key: remoteNode.public_key,
socket: remote.socket,
},
cbk);
},
cbk);
}],
// Delay so that all the nodes can sync to the chain
syncToChain: [
'addRemotePeer',
'control',
'remote',
'target',
({control, remote, target}, cbk) =>
{
return asyncEach([control, remote, target], (node, cbk) => {
if (!node) {
return cbk();
}
return asyncRetry({interval: retryMs, times: retryTimes}, cbk => {
return getWalletInfo({lnd: node.lnd}, (err, res) => {
if (!!err) {
return cbk(err);
}
if (!res.is_synced_to_chain) {
return cbk([503, 'NodeWaitingToSyncToChain']);
}
return cbk();
});
},
cbk);
},
cbk);
}],
},
(err, res) => {
if (!!err && !!res.control) {
res.control.kill();
}
if (!!err && !!res.remote && !!res.remote.kill) {
res.remote.kill();
}
if (!!err && !!res.target) {
res.target.kill();
}
if (!!err) {
return cbk(err);
}
const {control} = res;
const {remote} = res;
const {remoteNode} = res;
const {target} = res;
const kill = promisify((args, cbk) => {
const nodes = [control, remote, target].concat(args.nodes)
.filter(n => !!n);
nodes.forEach(({kill}) => kill());
return asyncEach(nodes, ({lnd}, cbk) => {
return waitForTermination({lnd}, cbk);
},
cbk);
});
const generate = async (args, cbk) => {
return generateBlocks({
cert: (args.node || control).chain_rpc_cert_file,
count: args.count || 1,
host: (args.node || control).listen_ip,
pass: (args.node || control).chain_rpc_pass,
port: (args.node || control).chain_rpc_port,
user: (args.node || control).chain_rpc_user,
},
cbk);
};
return cbk(null, {
control,
kill,
remote,
generate,
target,
remote_node_public_key: !remoteNode ? null : remoteNode.public_key,
target_node_public_key: res.targetNode.public_key,
});
});
};