besu-network-manager
Version:
TypeScript library for managing Hyperledger Besu networks with Clique consensus using Docker
272 lines • 10.4 kB
JavaScript
import Docker from "dockerode";
import * as path from "path";
export class DockerManager {
docker;
constructor() {
this.docker = new Docker();
}
async createDockerNetwork(config) {
const networkName = `besu-${config.networkId}`;
try {
const network = await this.docker.createNetwork({
Name: networkName,
Driver: "bridge",
IPAM: {
Config: [
{
Subnet: config.subnet,
Gateway: config.gateway,
},
],
},
Labels: {
"besu.network.id": config.networkId,
},
});
console.info(`Docker network created: ${networkName} (${network.id})`);
return network.id;
}
catch (error) {
console.error(`Failed to create Docker network: ${error}`);
throw error;
}
}
async createBesuContainer(networkConfig, nodeConfig, nodePath) {
const containerName = `besu-${networkConfig.networkId}-${nodeConfig.id}`;
const genesisPath = path.join(nodePath, "..", "genesis.json");
const keyPath = path.join(nodePath, "key");
const besuArgs = [
`--data-path=/opt/besu/data`,
`--genesis-file=/opt/besu/genesis.json`,
`--node-private-key-file=/opt/besu/key`,
`--network-id=${networkConfig.chainId}`,
`--sync-mode=FULL`,
`--sync-min-peers=1`,
`--rpc-http-enabled`,
`--rpc-http-host=0.0.0.0`,
`--rpc-http-port=${nodeConfig.rpcPort}`,
`--rpc-http-cors-origins=*`,
`--rpc-http-api=ETH,NET,CLIQUE,ADMIN`,
`--host-allowlist=*`,
`--p2p-host=0.0.0.0`,
`--p2p-port=${nodeConfig.p2pPort}`,
`--discovery-enabled=true`,
];
if (nodeConfig.bootnodes && nodeConfig.bootnodes.length > 0) {
besuArgs.push(`--bootnodes=${nodeConfig.bootnodes.join(",")}`);
}
if (nodeConfig.type === "miner") {
besuArgs.push(`--miner-enabled`);
besuArgs.push(`--miner-coinbase=${nodeConfig.address}`);
}
const containerConfig = {
name: containerName,
Image: "hyperledger/besu:latest",
Cmd: besuArgs,
ExposedPorts: {
[`${nodeConfig.rpcPort}/tcp`]: {},
[`${nodeConfig.p2pPort}/tcp`]: {},
[`${nodeConfig.p2pPort}/udp`]: {},
},
HostConfig: {
PortBindings: {
[`${nodeConfig.rpcPort}/tcp`]: [
{ HostPort: String(nodeConfig.rpcPort) },
],
[`${nodeConfig.p2pPort}/tcp`]: [
{ HostPort: String(nodeConfig.p2pPort) },
],
[`${nodeConfig.p2pPort}/udp`]: [
{ HostPort: String(nodeConfig.p2pPort) },
],
},
Binds: [
`${genesisPath}:/opt/besu/genesis.json:ro`,
`${keyPath}:/opt/besu/key:ro`,
],
},
NetworkingConfig: {
EndpointsConfig: {
[`besu-${networkConfig.networkId}`]: {
IPAMConfig: {
IPv4Address: nodeConfig.ip,
},
},
},
},
Labels: {
"besu.network.id": networkConfig.networkId,
"besu.node.id": nodeConfig.id,
},
};
try {
const container = await this.docker.createContainer(containerConfig);
await container.start();
const containerInfo = {
id: nodeConfig.id,
containerId: container.id,
containerName,
status: "running",
};
console.info(`Container created and started: ${containerName}`);
return containerInfo;
}
catch (error) {
console.error(`Failed to create container: ${error}`);
throw error;
}
}
async removeContainer(containerId) {
try {
const container = this.docker.getContainer(containerId);
await container.stop();
await container.remove();
console.info(`Container removed: ${containerId}`);
}
catch (error) {
if (error.statusCode !== 404) {
console.error(`Failed to remove container ${containerId}: ${error}`);
throw error;
}
}
}
async removeDockerNetwork(networkId) {
const networkName = `besu-${networkId}`;
try {
const networks = await this.docker.listNetworks({
filters: { name: [networkName] },
});
if (networks.length === 0) {
console.warn(`No Docker network found with name: ${networkName}`);
return;
}
for (const networkInfo of networks) {
if (networkInfo && networkInfo.Id) {
const network = this.docker.getNetwork(networkInfo.Id);
await network.remove();
console.info(`Docker network removed: ${networkName} (${networkInfo.Id})`);
}
}
if (networks.length > 1) {
console.warn(`Removed ${networks.length} networks with name ${networkName} (duplicates detected)`);
}
}
catch (error) {
console.error(`Failed to remove Docker network ${networkName}: ${error}`);
throw error;
}
}
async removeNodeFromNetwork(networkId, nodeId) {
const containerName = `besu-${networkId}-${nodeId}`;
try {
const containers = await this.docker.listContainers({
all: true,
filters: {
name: [containerName],
label: [`besu.network.id=${networkId}`, `besu.node.id=${nodeId}`],
},
});
if (containers.length === 0) {
console.warn(`No container found for node ${nodeId} in network ${networkId}`);
return;
}
if (containers.length > 1) {
console.warn(`Found ${containers.length} containers for node ${nodeId} in network ${networkId}. Removing all of them.`);
}
for (const containerInfo of containers) {
if (!containerInfo || !containerInfo.Id) {
continue;
}
const container = this.docker.getContainer(containerInfo.Id);
try {
await container.stop();
}
catch (error) {
if (error.statusCode !== 304) {
console.warn(`Could not stop container ${containerInfo.Id}: ${error.message}`);
}
}
await container.remove();
console.info(`Container removed: ${containerInfo.Id} (${containerInfo.Names?.[0] || 'unknown'})`);
}
console.info(`Node ${nodeId} removed from network ${networkId} (${containers.length} container(s) processed)`);
}
catch (error) {
console.error(`Failed to remove node ${nodeId} from network ${networkId}: ${error}`);
throw error;
}
}
async findNetworkContainers(networkId) {
try {
const containers = await this.docker.listContainers({
all: true,
filters: {
label: [`besu.network.id=${networkId}`],
},
});
return containers;
}
catch (error) {
console.error(`Failed to find containers for network ${networkId}: ${error}`);
throw error;
}
}
async findNodeContainer(networkId, nodeId) {
try {
const containers = await this.docker.listContainers({
all: true,
filters: {
label: [`besu.network.id=${networkId}`, `besu.node.id=${nodeId}`],
},
});
if (containers.length === 0) {
return null;
}
if (containers.length > 1) {
console.warn(`Found ${containers.length} containers for node ${nodeId} in network ${networkId}. Returning the first one.`);
}
return containers[0] || null;
}
catch (error) {
console.error(`Failed to find container for node ${nodeId} in network ${networkId}: ${error}`);
throw error;
}
}
async isDockerAvailable() {
try {
await this.docker.ping();
return true;
}
catch {
return false;
}
}
async addNodeToNetwork(networkConfig, nodeConfig, nodePath) {
const networkName = `besu-${networkConfig.networkId}`;
const networks = await this.docker.listNetworks({
filters: { name: [networkName] },
});
if (networks.length === 0) {
throw new Error(`Docker network ${networkName} does not exist. Create the network first.`);
}
const existingContainer = await this.findNodeContainer(networkConfig.networkId, nodeConfig.id);
if (existingContainer) {
throw new Error(`Node ${nodeConfig.id} already exists in network ${networkConfig.networkId}`);
}
return await this.createBesuContainer(networkConfig, nodeConfig, nodePath);
}
async networkExists(networkId) {
try {
const networkName = `besu-${networkId}`;
const networks = await this.docker.listNetworks({
filters: { name: [networkName] },
});
return networks.length > 0;
}
catch (error) {
console.error(`Failed to check if network ${networkId} exists: ${error}`);
return false;
}
}
}
//# sourceMappingURL=DockerManager.js.map