Skip to content

Commit

Permalink
feat(sim-ln): update docker compose setup for simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvinator07 committed Oct 29, 2024
1 parent ddc2599 commit 265dd92
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 5 deletions.
14 changes: 13 additions & 1 deletion src/lib/docker/composeFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
litdCredentials,
} from 'utils/constants';
import { getContainerName, getDefaultCommand } from 'utils/network';
import { bitcoind, clightning, eclair, litd, lnd, tapd } from './nodeTemplates';
import { bitcoind, clightning, eclair, litd, lnd, simln, tapd } from './nodeTemplates';

export interface ComposeService {
image: string;
Expand Down Expand Up @@ -51,6 +51,7 @@ class ComposeFile {
environment: {
USERID: '${USERID:-1000}',
GROUPID: '${GROUPID:-1000}',
...service.environment,
},
stop_grace_period: '30s',
...service,
Expand Down Expand Up @@ -193,6 +194,17 @@ class ComposeFile {
this.addService(svc);
}

addSimLn(networkId: number) {
const svc: ComposeService = simln(
dockerConfigs['simln'].name,
`polar-n${networkId}-simln`,
dockerConfigs['simln'].imageName,
dockerConfigs['simln'].command,
{ ...dockerConfigs['simln'].env },
);
this.addService(svc);
}

private mergeCommand(command: string, variables: Record<string, string>) {
let merged = command;
Object.keys(variables).forEach(key => {
Expand Down
160 changes: 158 additions & 2 deletions src/lib/docker/dockerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ import { migrateNetworksFile } from 'utils/migrations';
import { isLinux, isMac } from 'utils/system';
import ComposeFile from './composeFile';

type SimulationNode = {
id: string;
address: string;
macaroon?: string;
cert?: string;
ca_cert?: string;
client_cert?: string;
client_key?: string;
};

type SimulationActivity = {
source: string;
destination: string;
interval_secs: number;
amount_msat: number;
};

let dockerInst: Dockerode | undefined;
/**
* Creates a new Dockerode instance by detecting the docker socket
Expand Down Expand Up @@ -160,6 +177,10 @@ class DockerService implements DockerLibrary {
}
});

if (network.simulationActivities.length > 0) {
file.addSimLn(network.id);
}

const yml = yaml.dump(file.content);
const path = join(network.path, 'docker-compose.yml');
await write(path, yml);
Expand All @@ -176,8 +197,37 @@ class DockerService implements DockerLibrary {

info(`Starting docker containers for ${network.name}`);
info(` - path: ${network.path}`);
const result = await this.execute(compose.upAll, this.getArgs(network));
info(`Network started:\n ${result.out || result.err}`);

// we don't want to start the simln service when starting the network
// because it depends on the running lightning nodes and the simulation
// activity should be started separately based on user preference
const servicesToStart = this.getServicesToStart(
[...bitcoin, ...lightning, ...tap],
['simln'],
);

for (const service of servicesToStart) {
const result = await this.execute(compose.upOne, service, this.getArgs(network));
info(`Network started: ${service}\n ${result.out || result.err}`);
}
}

/**
* Filter out services based on exclude list and return a list of service names to start
* @param nodes Array of all nodes
* @param exclude Array of container names to exclude
*/
private getServicesToStart(
nodes:
| CommonNode[]
| {
name: 'simln';
}[],
exclude: string[],
): string[] {
return nodes
.map(node => node.name)
.filter(serviceName => !exclude.includes(serviceName));
}

/**
Expand Down Expand Up @@ -317,6 +367,112 @@ class DockerService implements DockerLibrary {
}
}

/**
* Constructs the contents of sim.json file for the simulation activity
* @param network the network to start
*/
constructSimJson(network: Network) {
const simJson: {
nodes: SimulationNode[];
activity: SimulationActivity[];
} = {
nodes: [],
activity: [],
};

network.simulationActivities.forEach(activity => {
const { source, destination } = activity;
const nodeArray = [source, destination];

for (const node of nodeArray) {
let simNode: SimulationNode;

// split the macaroon and cert path at "volumes/" to get the relative path
// to the docker volume. This is necessary because the docker volumes are
// mounted as a different path in the container.
switch (node.implementation) {
case 'LND':
const lnd = node as LndNode;
simNode = {
id: lnd.name,
macaroon: `/home/simln/.${lnd.paths.adminMacaroon.split('volumes/').pop()}`,
address: `https://host.docker.internal:${lnd.ports.grpc}`,
cert: `/home/simln/.${lnd.paths.tlsCert.split('volumes/').pop()}`,
};
break;

case 'c-lightning':
const cln = node as CLightningNode;
simNode = {
id: cln.name,
address: `https://host.docker.internal:${cln.ports.grpc}`,
ca_cert: `/home/simln/.${cln.paths?.tlsCert?.split('volumes/').pop()}`,
client_cert: `/home/simln/.${cln.paths?.tlsClientCert
?.split('volumes/')
.pop()}`,
client_key: `/home/simln/.${cln.paths?.tlsClientKey
?.split('volumes/')
.pop()}`,
};
break;

default:
throw new Error(`unsupported node type ${node.implementation}`);
}

// console.log(`simNode >> \n ${JSON.stringify(simNode)}`);
// Add the node to the nodes Set (duplicates are automatically handled)
simJson.nodes.push(simNode);
}

// Add the activity
const simActivity: SimulationActivity = {
source: activity.source.name,
destination: activity.destination.name,
interval_secs: activity.intervalSecs,
amount_msat: activity.amountMsat * 1000,
};

// console.log(`simActivity >> \n ${JSON.stringify(simActivity)}`);
// Add the activity to the activity Set (duplicates are automatically handled)
simJson.activity.push(simActivity);
});
return {
nodes: [...new Map(simJson.nodes.map(node => [node['id'], node])).values()],
activity: [
...new Map(
simJson.activity.map(activity => [
`${activity.source}-${activity.destination}`,
activity,
]),
).values(),
],
};
}

/**
* Start a simulation activity in the network using docker compose
* @param network the network containing the simulation activity
*/
async startSimulationActivity(network: Network) {
const simJson = this.constructSimJson(network);
// console.log(`simJson >> \n ${JSON.stringify(simJson)}`);
await this.ensureDirs(network, [
...network.nodes.bitcoin,
...network.nodes.lightning,
...network.nodes.tap,
]);
const simjsonPath = nodePath(network, 'simln', 'sim.json');
await write(simjsonPath, JSON.stringify(simJson));
console.log(`simjsonPath >> \n ${JSON.stringify(simjsonPath)}`);
const result = await this.execute(compose.upOne, 'simln', this.getArgs(network));
info(`Simulation activity started:\n ${result.out || result.err}`);
}

async stopSimulationActivity(network: Network) {
info(`[stopSimulationActivity] \n ${network}`);
}

/**
* Helper method to trap and format exceptions thrown and
* @param cmd the compose function to call
Expand Down
22 changes: 22 additions & 0 deletions src/lib/docker/nodeTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,25 @@ export const litd = (
`${webPort}:8443`, // web
],
});

export const simln = (
name: string,
container: string,
image: string,
command: string,
environment: Record<string, string>,
): ComposeService => ({
image,
container_name: container,
hostname: name,
command: trimInside(command),
environment,
restart: 'always',
volumes: [
`./volumes/${name}:/home/simln/.simln`,
`./volumes/${dockerConfigs.LND.volumeDirName}:/home/simln/.lnd`,
`./volumes/${dockerConfigs['c-lightning'].volumeDirName}:/home/simln/.clightning`,
],
expose: [],
ports: [],
});
36 changes: 36 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface Network {
status: Status;
path: string;
autoMineMode: AutoMineMode;
simulationActivities: SimulationActivity[];
nodes: {
bitcoin: BitcoinNode[];
lightning: LightningNode[];
Expand Down Expand Up @@ -100,6 +101,7 @@ export interface DockerConfig {
variables: string[];
dataDir?: string;
apiDir?: string;
env?: Record<string, string>;
}

export interface DockerRepoImage {
Expand Down Expand Up @@ -136,6 +138,8 @@ export interface DockerLibrary {
saveNetworks: (networks: NetworksFile) => Promise<void>;
loadNetworks: () => Promise<NetworksFile>;
renameNodeDir: (network: Network, node: AnyNode, newName: string) => Promise<void>;
startSimulationActivity: (network: Network) => Promise<void>;
stopSimulationActivity: (network: Network) => Promise<void>;
}

export interface RepoServiceInjection {
Expand Down Expand Up @@ -281,3 +285,35 @@ export enum AutoMineMode {
Auto5m = 300,
Auto10m = 600,
}

/**
* A running lightning node that is used in the simulation activity
*/
export interface SimulationActivityNode {
id: string;
label: string;
type: LightningNode['implementation'];
address: string;
macaroon: string;
tlsCert: string;
clientCert?: string;
clientKey?: string;
}

/**
* A simulation activity is a payment from one node to another at a given interval and amount
*/
export interface SimulationActivity {
networkId: number;
source: LightningNode;
destination: LightningNode;
intervalSecs: number;
amountMsat: number;
}

export interface ActivityInfo {
sourceNode: LightningNode | undefined;
targetNode: LightningNode | undefined;
amount: number;
frequency: number;
}
2 changes: 1 addition & 1 deletion src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const networksPath = join(dataPath, 'networks');
*/
export const nodePath = (
network: Network,
implementation: NodeImplementation,
implementation: NodeImplementation | 'simln',
name: string,
): string =>
join(network.path, 'volumes', dockerConfigs[implementation].volumeDirName, name);
18 changes: 17 additions & 1 deletion src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const litdCredentials = {
pass: 'polarpass',
};

export const dockerConfigs: Record<NodeImplementation, DockerConfig> = {
export const dockerConfigs: Record<NodeImplementation | 'simln', DockerConfig> = {
LND: {
name: 'LND',
imageName: 'polarlightning/lnd',
Expand Down Expand Up @@ -125,6 +125,8 @@ export const dockerConfigs: Record<NodeImplementation, DockerConfig> = {
'--bitcoind.rpcpass={{rpcPass}}',
'--bitcoind.zmqpubrawblock=tcp://{{backendName}}:28334',
'--bitcoind.zmqpubrawtx=tcp://{{backendName}}:28335',
'--accept-keysend',
'--accept-amp',
].join('\n '),
// if vars are modified, also update composeFile.ts & the i18n strings for cmps.nodes.CommandVariables
variables: ['name', 'containerName', 'backendName', 'rpcUser', 'rpcPass'],
Expand Down Expand Up @@ -316,6 +318,20 @@ export const dockerConfigs: Record<NodeImplementation, DockerConfig> = {
// if vars are modified, also update composeFile.ts & the i18n strings for cmps.nodes.CommandVariables
variables: ['name', 'containerName', 'backendName', 'rpcUser', 'rpcPass'],
},
simln: {
name: 'simln',
imageName: 'bitcoindevproject/simln:0.2.0',
logo: '',
platforms: ['mac', 'linux', 'windows'],
volumeDirName: 'simln',
env: {
SIMFILE_PATH: '/home/simln/.simln/sim.json',
DATA_DIR: '/home/simln/.simln',
LOG_LEVEL: 'info',
},
command: '',
variables: ['DEFAULT_SIMFILE_PATH', 'LOG_LEVEL', 'DATA_DIR'],
},
};

/**
Expand Down

0 comments on commit 265dd92

Please sign in to comment.