Skip to content

Commit 376b528

Browse files
authored
test(test-tooling): support for custom docker network for besu and ethereum (#3884)
Signed-off-by: Carlos Amaro <[email protected]>
1 parent 1ba7bd9 commit 376b528

File tree

3 files changed

+149
-41
lines changed

3 files changed

+149
-41
lines changed

packages/cactus-test-geth-ledger/src/main/typescript/geth-test-ledger.ts

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { EventEmitter } from "events";
2-
import Docker, { Container } from "dockerode";
3-
import { v4 as internalIpV4 } from "internal-ip";
2+
import Docker, { Container, ContainerInfo } from "dockerode";
43
import Web3, { ContractAbi, TransactionReceipt } from "web3";
54
import type { Web3Account } from "web3-eth-accounts";
65
import { RuntimeError } from "run-time-error-cjs";
@@ -21,6 +20,7 @@ export interface IGethTestLedgerOptions {
2120
readonly envVars?: string[];
2221
// For test development, attach to ledger that is already running, don't spin up new one
2322
readonly useRunningLedger?: boolean;
23+
readonly networkName?: string;
2424
}
2525

2626
/**
@@ -33,6 +33,7 @@ export const GETH_TEST_LEDGER_DEFAULT_OPTIONS = Object.freeze({
3333
emitContainerLogs: false,
3434
envVars: [],
3535
useRunningLedger: false,
36+
networkName: "cactus-ethereum-test-network",
3637
});
3738

3839
export const WHALE_ACCOUNT_PRIVATE_KEY =
@@ -53,6 +54,8 @@ export class GethTestLedger {
5354
private _containerId: string | undefined;
5455
private _web3: Web3 | undefined;
5556

57+
private readonly networkName: string;
58+
5659
public get fullContainerImageName(): string {
5760
return [this.containerImageName, this.containerImageVersion].join(":");
5861
}
@@ -103,6 +106,9 @@ export class GethTestLedger {
103106
this.envVars =
104107
this.options.envVars || GETH_TEST_LEDGER_DEFAULT_OPTIONS.envVars;
105108

109+
this.networkName =
110+
options.networkName || GETH_TEST_LEDGER_DEFAULT_OPTIONS.networkName;
111+
106112
this.log.info(
107113
`Created ${this.className} OK. Image FQN: ${this.fullContainerImageName}`,
108114
);
@@ -164,6 +170,18 @@ export class GethTestLedger {
164170
);
165171
}
166172

173+
if (this.networkName) {
174+
const docker = new Docker();
175+
const networks = await docker.listNetworks();
176+
const networkExists = networks.some((n) => n.Name === this.networkName);
177+
if (!networkExists) {
178+
await docker.createNetwork({
179+
Name: this.networkName,
180+
Driver: "bridge",
181+
});
182+
}
183+
}
184+
167185
return new Promise<Container>((resolve, reject) => {
168186
const docker = new Docker();
169187
const eventEmitter: EventEmitter = docker.run(
@@ -178,6 +196,7 @@ export class GethTestLedger {
178196
Env: this.envVars,
179197
HostConfig: {
180198
PublishAllPorts: true,
199+
NetworkMode: this.networkName,
181200
},
182201
},
183202
{},
@@ -373,24 +392,67 @@ export class GethTestLedger {
373392
return await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
374393
}
375394

376-
public async getRpcApiHttpHost(
377-
host?: string,
378-
port?: number,
379-
): Promise<string> {
380-
const thePort = port || (await this.getHostPortHttp());
381-
const lanIpV4OrUndefined = await internalIpV4();
382-
const lanAddress = host || lanIpV4OrUndefined || "127.0.0.1"; // best effort...
383-
return `http://${lanAddress}:${thePort}`;
395+
public async getRpcApiHttpHost(asLocalhost: boolean = true): Promise<string> {
396+
if (asLocalhost) {
397+
const thePort = await this.getHostPortHttp();
398+
const ipAddress = "127.0.0.1";
399+
return `http://${ipAddress}:${thePort}`;
400+
} else {
401+
const hostIp: string = await this.getContainerIpAddress();
402+
return `http://${hostIp}:${8545}`;
403+
}
384404
}
385405

386406
public async getRpcApiWebSocketHost(
387-
host?: string,
388-
port?: number,
407+
asLocalhost: boolean = true,
389408
): Promise<string> {
390-
const thePort = port || (await this.getHostPortWs());
391-
const lanIpV4OrUndefined = await internalIpV4();
392-
const lanAddress = host || lanIpV4OrUndefined || "127.0.0.1"; // best effort...
393-
return `ws://${lanAddress}:${thePort}`;
409+
if (asLocalhost) {
410+
const thePort = await this.getHostPortWs();
411+
const ipAddress = "127.0.0.1";
412+
return `ws://${ipAddress}:${thePort}`;
413+
} else {
414+
const hostIp: string = await this.getContainerIpAddress();
415+
return `ws://${hostIp}:${8546}`;
416+
}
417+
}
418+
419+
public async getContainerIpAddress(
420+
network: string = this.networkName || "bridge",
421+
): Promise<string> {
422+
const fnTag = `${this.className}#getContainerIpAddress()`;
423+
const aContainerInfo = await this.getContainerInfo();
424+
425+
if (!aContainerInfo) {
426+
throw new Error(`${fnTag} cannot find image: ${this.containerImageName}`);
427+
}
428+
const { NetworkSettings } = aContainerInfo;
429+
const networkNames: string[] = Object.keys(NetworkSettings.Networks);
430+
if (networkNames.length < 1) {
431+
throw new Error(`${fnTag} container not connected to any networks`);
432+
}
433+
434+
return NetworkSettings.Networks[network].IPAddress;
435+
}
436+
437+
protected async getContainerInfo(): Promise<ContainerInfo> {
438+
const docker = new Docker();
439+
const image = this.getContainerImageName();
440+
const containerInfos = await docker.listContainers({});
441+
442+
let aContainerInfo;
443+
if (this._containerId !== undefined) {
444+
aContainerInfo = containerInfos.find((ci) => ci.Id === this._containerId);
445+
}
446+
447+
if (aContainerInfo) {
448+
return aContainerInfo;
449+
} else {
450+
throw new Error(`BesuTestLedger#getContainerInfo() no image "${image}"`);
451+
}
452+
}
453+
454+
public getContainerImageName(): string {
455+
return `${this.containerImageName}:${this.containerImageVersion}`;
394456
}
395457

396458
private async getHostPort(port: number): Promise<number> {
@@ -410,4 +472,12 @@ export class GethTestLedger {
410472
public async getHostPortWs(): Promise<number> {
411473
return this.getHostPort(8546);
412474
}
475+
476+
public getNetworkName(): string {
477+
const fnTag = `${this.className}#getNetworkName()`;
478+
if (this.networkName) {
479+
return this.networkName;
480+
}
481+
throw new Error(`${fnTag} network name not set`);
482+
}
413483
}

packages/cactus-test-tooling/src/main/typescript/besu/besu-test-ledger.ts

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface IBesuTestLedgerConstructorOptions {
2424
envVars?: string[];
2525
logLevel?: LogLevelDesc;
2626
emitContainerLogs?: boolean;
27+
networkName?: string;
2728
}
2829

2930
export const BESU_TEST_LEDGER_DEFAULT_OPTIONS = Object.freeze({
@@ -32,6 +33,7 @@ export const BESU_TEST_LEDGER_DEFAULT_OPTIONS = Object.freeze({
3233
rpcApiHttpPort: 8545,
3334
rpcApiWsPort: 8546,
3435
envVars: ["BESU_NETWORK=dev"],
36+
networkName: "cactus-besu-test-network",
3537
});
3638

3739
export const BESU_TEST_LEDGER_OPTIONS_JOI_SCHEMA: Joi.Schema =
@@ -48,6 +50,7 @@ export const BESU_TEST_LEDGER_OPTIONS_JOI_SCHEMA: Joi.Schema =
4850
});
4951

5052
export class BesuTestLedger implements ITestLedger {
53+
public static readonly CLASS_NAME = "BesuTestLedger";
5154
public readonly containerImageVersion: string;
5255
public readonly containerImageName: string;
5356
public readonly rpcApiHttpPort: number;
@@ -59,9 +62,13 @@ export class BesuTestLedger implements ITestLedger {
5962
private container: Container | undefined;
6063
private containerId: string | undefined;
6164

65+
private readonly networkName: string;
66+
6267
constructor(public readonly options: IBesuTestLedgerConstructorOptions = {}) {
6368
if (!options) {
64-
throw new TypeError(`BesuTestLedger#ctor options was falsy.`);
69+
throw new TypeError(
70+
`${BesuTestLedger.CLASS_NAME}#constructor options was falsy.`,
71+
);
6572
}
6673
this.containerImageVersion =
6774
options.containerImageVersion ||
@@ -74,6 +81,8 @@ export class BesuTestLedger implements ITestLedger {
7481
this.rpcApiWsPort =
7582
options.rpcApiWsPort || BESU_TEST_LEDGER_DEFAULT_OPTIONS.rpcApiWsPort;
7683
this.envVars = options.envVars || BESU_TEST_LEDGER_DEFAULT_OPTIONS.envVars;
84+
this.networkName =
85+
options.networkName || BESU_TEST_LEDGER_DEFAULT_OPTIONS.networkName;
7786

7887
this.emitContainerLogs = Bools.isBooleanStrict(options.emitContainerLogs)
7988
? (options.emitContainerLogs as boolean)
@@ -98,18 +107,28 @@ export class BesuTestLedger implements ITestLedger {
98107
return `${this.containerImageName}:${this.containerImageVersion}`;
99108
}
100109

101-
public async getRpcApiHttpHost(): Promise<string> {
102-
const ipAddress = "127.0.0.1";
103-
const hostPort: number = await this.getRpcApiPublicPort();
104-
return `http://${ipAddress}:${hostPort}`;
110+
public async getRpcApiHttpHost(asLocalhost: boolean = true): Promise<string> {
111+
if (asLocalhost) {
112+
const ipAddress = "127.0.0.1";
113+
const hostPort: number = await this.getRpcApiPublicPort();
114+
return `http://${ipAddress}:${hostPort}`;
115+
} else {
116+
const hostIp: string = await this.getContainerIpAddress();
117+
return `http://${hostIp}:${this.rpcApiHttpPort}`;
118+
}
105119
}
106120

107-
public async getRpcApiWsHost(): Promise<string> {
108-
const { rpcApiWsPort } = this;
109-
const ipAddress = "127.0.0.1";
110-
const containerInfo = await this.getContainerInfo();
111-
const port = await Containers.getPublicPort(rpcApiWsPort, containerInfo);
112-
return `ws://${ipAddress}:${port}`;
121+
public async getRpcApiWsHost(asLocalhost: boolean = true): Promise<string> {
122+
if (asLocalhost) {
123+
const { rpcApiWsPort } = this;
124+
const ipAddress = "127.0.0.1";
125+
const containerInfo = await this.getContainerInfo();
126+
const port = await Containers.getPublicPort(rpcApiWsPort, containerInfo);
127+
return `ws://${ipAddress}:${port}`;
128+
} else {
129+
const hostIp: string = await this.getContainerIpAddress();
130+
return `ws://${hostIp}:${this.rpcApiWsPort}`;
131+
}
113132
}
114133

115134
public async getFileContents(filePath: string): Promise<string> {
@@ -255,6 +274,17 @@ export class BesuTestLedger implements ITestLedger {
255274
this.log.debug(`Pulled ${imageFqn} OK. Starting container...`);
256275
}
257276

277+
if (this.networkName) {
278+
const networks = await docker.listNetworks();
279+
const networkExists = networks.some((n) => n.Name === this.networkName);
280+
if (!networkExists) {
281+
await docker.createNetwork({
282+
Name: this.networkName,
283+
Driver: "bridge",
284+
});
285+
}
286+
}
287+
258288
return new Promise<Container>((resolve, reject) => {
259289
const eventEmitter: EventEmitter = docker.run(
260290
imageFqn,
@@ -283,6 +313,7 @@ export class BesuTestLedger implements ITestLedger {
283313
},
284314
HostConfig: {
285315
PublishAllPorts: true,
316+
NetworkMode: this.networkName,
286317
},
287318
Env: this.envVars,
288319
},
@@ -403,23 +434,22 @@ export class BesuTestLedger implements ITestLedger {
403434
}
404435
}
405436

406-
public async getContainerIpAddress(): Promise<string> {
437+
public async getContainerIpAddress(
438+
network: string = this.networkName || "bridge",
439+
): Promise<string> {
407440
const fnTag = "BesuTestLedger#getContainerIpAddress()";
408441
const aContainerInfo = await this.getContainerInfo();
409442

410-
if (aContainerInfo) {
411-
const { NetworkSettings } = aContainerInfo;
412-
const networkNames: string[] = Object.keys(NetworkSettings.Networks);
413-
if (networkNames.length < 1) {
414-
throw new Error(`${fnTag} container not connected to any networks`);
415-
} else {
416-
// return IP address of container on the first network that we found
417-
// it connected to. Make this configurable?
418-
return NetworkSettings.Networks[networkNames[0]].IPAddress;
419-
}
420-
} else {
443+
if (!aContainerInfo) {
421444
throw new Error(`${fnTag} cannot find image: ${this.containerImageName}`);
422445
}
446+
const { NetworkSettings } = aContainerInfo;
447+
const networkNames: string[] = Object.keys(NetworkSettings.Networks);
448+
if (networkNames.length < 1) {
449+
throw new Error(`${fnTag} container not connected to any networks`);
450+
}
451+
452+
return NetworkSettings.Networks[network].IPAddress;
423453
}
424454

425455
private pullContainerImage(containerNameAndTag: string): Promise<unknown[]> {
@@ -458,4 +488,12 @@ export class BesuTestLedger implements ITestLedger {
458488
);
459489
}
460490
}
491+
492+
public getNetworkName(): string {
493+
const fnTag = `${BesuTestLedger.CLASS_NAME}#getNetworkName()`;
494+
if (this.networkName) {
495+
return this.networkName;
496+
}
497+
throw new Error(`${fnTag} network name not set`);
498+
}
461499
}

packages/cactus-test-tooling/src/main/typescript/fabric/fabric-test-ledger-v1.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const DEFAULT_OPTS = Object.freeze({
9999
envVars: new Map([["FABRIC_VERSION", "1.4.8"]]),
100100
stateDatabase: STATE_DATABASE.COUCH_DB,
101101
orgList: ["org1", "org2"],
102-
networkName: "cactusfabrictestnetwork",
102+
networkName: "cactus-fabric-test-network",
103103
});
104104
export const FABRIC_TEST_LEDGER_DEFAULT_OPTIONS = DEFAULT_OPTS;
105105

@@ -136,7 +136,7 @@ export class FabricTestLedgerV1 implements ITestLedger {
136136

137137
private readonly log: Logger;
138138

139-
private readonly networkName: string | undefined;
139+
private readonly networkName: string;
140140
private container: Container | undefined;
141141
private containerId: string | undefined;
142142
private readonly useRunningLedger: boolean;

0 commit comments

Comments
 (0)