Skip to content

Commit 484f219

Browse files
fix: change service endpoints from byte array to base64 string for creating genesis network json file (#1854)
Signed-off-by: Jeromy Cannon <[email protected]>
1 parent b176449 commit 484f219

File tree

7 files changed

+132
-52
lines changed

7 files changed

+132
-52
lines changed

src/core/genesis-network-models/genesis-network-data-wrapper.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import {type NodeId} from '../../types/aliases.js';
44
import {type ServiceEndpoint} from '../../types/index.js';
5-
import {ipv4ToByteArray, isIPv4Address} from '../helpers.js';
5+
import {ipv4ToBase64, isIPv4Address} from '../helpers.js';
66

77
export abstract class GenesisNetworkDataWrapper {
88
public gossipEndpoint: ServiceEndpoint[] = [];
@@ -20,7 +20,7 @@ export abstract class GenesisNetworkDataWrapper {
2020
this.gossipEndpoint.push({
2121
domainName: isIpV4Address ? '' : address,
2222
port,
23-
ipAddressV4: isIpV4Address ? ipv4ToByteArray(address) : undefined,
23+
ipAddressV4: isIpV4Address ? ipv4ToBase64(address) : undefined,
2424
});
2525
}
2626
}

src/core/genesis-network-models/genesis-network-node-data-wrapper.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type ToObject,
99
} from '../../types/index.js';
1010
import {GenesisNetworkDataWrapper} from './genesis-network-data-wrapper.js';
11-
import {ipv4ToByteArray, isIPv4Address} from '../helpers.js';
11+
import {ipv4ToBase64, isIPv4Address} from '../helpers.js';
1212

1313
export class GenesisNetworkNodeDataWrapper
1414
extends GenesisNetworkDataWrapper
@@ -36,7 +36,7 @@ export class GenesisNetworkNodeDataWrapper
3636
this.serviceEndpoint.push({
3737
domainName: isIpV4Address ? '' : address,
3838
port,
39-
ipAddressV4: isIpV4Address ? ipv4ToByteArray(address) : undefined,
39+
ipAddressV4: isIpV4Address ? ipv4ToBase64(address) : undefined,
4040
});
4141
}
4242

src/core/helpers.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,30 @@ export function isIPv4Address(input: string): boolean {
546546
}
547547

548548
/**
549-
* Convert an IPv4 address to a 4 byte array removing the periods
550-
* @param ip The IPv4 address to convert
551-
* @returns A Uint8Array represented as a number array representing the IPv4 address
549+
* Convert an IPv4 address to a base64 string
550+
* @param ipv4 The IPv4 address to convert
551+
* @returns The base64 encoded string representation of the IPv4 address
552552
*/
553-
export function ipv4ToByteArray(ip: string): number[] {
554-
return [...new Uint8Array(ip.split('.').map(Number))];
553+
export function ipv4ToBase64(ipv4: string): string {
554+
// Split the IPv4 address into its octets
555+
const octets: number[] = ipv4.split('.').map(octet => {
556+
const number_: number = Number.parseInt(octet, 10);
557+
// eslint-disable-next-line unicorn/prefer-number-properties
558+
if (isNaN(number_) || number_ < 0 || number_ > 255) {
559+
throw new Error(`Invalid IPv4 address: ${ipv4}`);
560+
}
561+
return number_;
562+
});
563+
564+
if (octets.length !== 4) {
565+
throw new Error(`Invalid IPv4 address: ${ipv4}`);
566+
}
567+
568+
// Convert the octets to a Uint8Array
569+
const uint8Array: Uint8Array<ArrayBuffer> = new Uint8Array(octets);
570+
571+
// Base64 encode the byte array
572+
return btoa(String.fromCodePoint(...uint8Array));
555573
}
556574

557575
/** Get the Apple Silicon chip type */

src/types/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export type SoloListrTaskWrapper<T> = ListrTaskWrapper<T, any, any>;
8585
export type SoloListr<T> = Listr<T, any, any>;
8686

8787
export interface ServiceEndpoint {
88-
ipAddressV4?: number[];
88+
ipAddressV4?: string;
8989
port: number;
9090
domainName: string;
9191
}

test/e2e/commands/dual-cluster-full.test.ts

+78-17
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import {describe} from 'mocha';
44

5+
import * as semver from 'semver';
56
import {Flags} from '../../../src/commands/flags.js';
6-
import {getTestCacheDirectory, getTestCluster} from '../../test-utility.js';
7+
import {getTestCacheDirectory, getTestCluster, HEDERA_PLATFORM_VERSION_TAG} from '../../test-utility.js';
78
import {main} from '../../../src/index.js';
89
import {resetForTest} from '../../test-container.js';
910
import {
@@ -73,6 +74,9 @@ describe('Dual Cluster Full E2E Test', async function dualClusterFullEndToEndTes
7374
const testCacheDirectory: string = getTestCacheDirectory(testName);
7475
let testLogger: SoloWinstonLogger;
7576
const createdAccountIds: string[] = [];
77+
const enableLocalBuildPathTesting: boolean = process.env.SOLO_LOCAL_BUILD_PATH_TESTING?.toLowerCase() === 'true';
78+
const localBuildPath: string = process.env.SOLO_LOCAL_BUILD_PATH || '../hiero-consensus-node/hedera-node/data';
79+
const localBuildReleaseTag: string = process.env.SOLO_LOCAL_BUILD_RELEASE_TAG || HEDERA_PLATFORM_VERSION_TAG;
7680

7781
// TODO the kube config context causes issues if it isn't one of the selected clusters we are deploying to
7882
before(async (): Promise<void> => {
@@ -157,7 +161,7 @@ describe('Dual Cluster Full E2E Test', async function dualClusterFullEndToEndTes
157161
});
158162

159163
it(`${testName}: network deploy`, async (): Promise<void> => {
160-
await main(soloNetworkDeployArgv(deployment));
164+
await main(soloNetworkDeployArgv(deployment, enableLocalBuildPathTesting, localBuildReleaseTag));
161165
const k8Factory: K8Factory = container.resolve<K8Factory>(InjectTokens.K8Factory);
162166
for (const [index, context_] of contexts.entries()) {
163167
const k8: K8 = k8Factory.getK8(context_);
@@ -171,7 +175,7 @@ describe('Dual Cluster Full E2E Test', async function dualClusterFullEndToEndTes
171175

172176
// TODO node setup still list --node-aliases
173177
it(`${testName}: node setup`, async (): Promise<void> => {
174-
await main(soloNodeSetupArgv(deployment));
178+
await main(soloNodeSetupArgv(deployment, enableLocalBuildPathTesting, localBuildPath, localBuildReleaseTag));
175179
const k8Factory: K8Factory = container.resolve<K8Factory>(InjectTokens.K8Factory);
176180
for (const context_ of contexts) {
177181
const k8: K8 = k8Factory.getK8(context_);
@@ -181,10 +185,12 @@ describe('Dual Cluster Full E2E Test', async function dualClusterFullEndToEndTes
181185
PodReference.of(namespace, pods[0].podReference.name),
182186
ROOT_CONTAINER,
183187
);
184-
expect(
185-
await k8.containers().readByRef(rootContainer).hasFile(`${HEDERA_USER_HOME_DIR}/extract-platform.sh`),
186-
'expect extract-platform.sh to be present on the pods',
187-
).to.be.true;
188+
if (!enableLocalBuildPathTesting) {
189+
expect(
190+
await k8.containers().readByRef(rootContainer).hasFile(`${HEDERA_USER_HOME_DIR}/extract-platform.sh`),
191+
'expect extract-platform.sh to be present on the pods',
192+
).to.be.true;
193+
}
188194
expect(await k8.containers().readByRef(rootContainer).hasFile(`${HEDERA_HAPI_PATH}/data/apps/HederaNode.jar`)).to
189195
.be.true;
190196
expect(
@@ -230,8 +236,14 @@ describe('Dual Cluster Full E2E Test', async function dualClusterFullEndToEndTes
230236

231237
it(`${testName}: mirror node deploy`, async (): Promise<void> => {
232238
await main(soloMirrorNodeDeployArgv(deployment, testClusterArray[1]));
233-
await verifyMirrorNodeDeployWasSuccessful(contexts, namespace, testLogger, createdAccountIds);
234-
// TODO validate the new accounts are showing up with the mirror node rest url
239+
await verifyMirrorNodeDeployWasSuccessful(
240+
contexts,
241+
namespace,
242+
testLogger,
243+
createdAccountIds,
244+
enableLocalBuildPathTesting,
245+
localBuildReleaseTag,
246+
);
235247
}).timeout(Duration.ofMinutes(10).toMillis());
236248

237249
it(`${testName}: explorer deploy`, async (): Promise<void> => {
@@ -336,7 +348,11 @@ function soloNodeKeysArgv(deployment: DeploymentName): string[] {
336348
return argv;
337349
}
338350

339-
function soloNetworkDeployArgv(deployment: DeploymentName): string[] {
351+
function soloNetworkDeployArgv(
352+
deployment: DeploymentName,
353+
enableLocalBuildPathTesting: boolean,
354+
localBuildReleaseTag: string,
355+
): string[] {
340356
const argv: string[] = newArgv();
341357
argv.push(
342358
'network',
@@ -345,13 +361,29 @@ function soloNetworkDeployArgv(deployment: DeploymentName): string[] {
345361
deployment,
346362
optionFromFlag(Flags.loadBalancerEnabled),
347363
); // have to enable load balancer to resolve cross cluster in multi-cluster
364+
if (enableLocalBuildPathTesting) {
365+
argv.push(optionFromFlag(Flags.releaseTag), localBuildReleaseTag);
366+
}
348367
argvPushGlobalFlags(argv, true, true);
349368
return argv;
350369
}
351370

352-
function soloNodeSetupArgv(deployment: DeploymentName): string[] {
371+
function soloNodeSetupArgv(
372+
deployment: DeploymentName,
373+
enableLocalBuildPathTesting: boolean,
374+
localBuildPath: string,
375+
localBuildReleaseTag: string,
376+
): string[] {
353377
const argv: string[] = newArgv();
354378
argv.push('node', 'setup', optionFromFlag(Flags.deployment), deployment);
379+
if (enableLocalBuildPathTesting) {
380+
argv.push(
381+
optionFromFlag(Flags.localBuildPath),
382+
localBuildPath,
383+
optionFromFlag(Flags.releaseTag),
384+
localBuildReleaseTag,
385+
);
386+
}
355387
argvPushGlobalFlags(argv, true);
356388
return argv;
357389
}
@@ -421,6 +453,8 @@ async function verifyMirrorNodeDeployWasSuccessful(
421453
namespace: NamespaceName,
422454
testLogger: SoloWinstonLogger,
423455
createdAccountIds: string[],
456+
enableLocalBuildPathTesting: boolean,
457+
localBuildReleaseTag: string,
424458
): Promise<void> {
425459
const k8Factory: K8Factory = container.resolve<K8Factory>(InjectTokens.K8Factory);
426460
const k8: K8 = k8Factory.getK8(contexts[1]);
@@ -432,11 +466,13 @@ async function verifyMirrorNodeDeployWasSuccessful(
432466
'app.kubernetes.io/component=rest',
433467
]);
434468
expect(mirrorNodeRestPods).to.have.lengthOf(1);
469+
435470
let portForwarder: ExtendedNetServer;
436471
try {
437472
portForwarder = await k8.pods().readByReference(mirrorNodeRestPods[0].podReference).portForward(5551, 5551);
438473
await sleep(Duration.ofSeconds(2));
439474
const queryUrl: string = 'http://localhost:5551/api/v1/network/nodes';
475+
440476
let received: boolean = false;
441477
// wait until the transaction reached consensus and retrievable from the mirror node API
442478
while (!received) {
@@ -445,30 +481,41 @@ async function verifyMirrorNodeDeployWasSuccessful(
445481
{method: 'GET', timeout: 100, headers: {Connection: 'close'}},
446482
(response: http.IncomingMessage): void => {
447483
response.setEncoding('utf8');
484+
448485
response.on('data', (chunk): void => {
449486
// convert chunk to json object
450-
const object: {nodes: unknown[]} = JSON.parse(chunk);
487+
const object: {nodes: {service_endpoints: unknown[]}[]} = JSON.parse(chunk);
451488
expect(
452489
object.nodes?.length,
453490
"expect there to be two nodes in the mirror node's copy of the address book",
454491
).to.equal(2);
455-
// TODO need to enable this, but looks like mirror node currently is getting no service endpoints, hopefully they will be in v0.60+
456-
// expect(
457-
// obj.nodes[0].service_endpoints?.length,
458-
// 'expect there to be at least one service endpoint',
459-
// ).to.be.greaterThan(0);
492+
493+
if (
494+
(enableLocalBuildPathTesting && semver.gte(localBuildReleaseTag.slice(1), '0.62.0')) ||
495+
semver.gte(HEDERA_PLATFORM_VERSION_TAG, '0.62.0')
496+
) {
497+
expect(
498+
object.nodes[0].service_endpoints?.length,
499+
'expect there to be at least one service endpoint',
500+
).to.be.greaterThan(0);
501+
}
502+
460503
received = true;
461504
});
462505
},
463506
);
507+
464508
request.on('error', (error: Error): void => {
465509
testLogger.debug(`problem with request: ${error.message}`, error);
466510
});
511+
467512
request.end(); // make the request
468513
await sleep(Duration.ofSeconds(2));
469514
}
515+
470516
for (const accountId of createdAccountIds) {
471517
const accountQueryUrl: string = `http://localhost:5551/api/v1/accounts/${accountId}`;
518+
472519
received = false;
473520
// wait until the transaction reached consensus and retrievable from the mirror node API
474521
while (!received) {
@@ -477,27 +524,34 @@ async function verifyMirrorNodeDeployWasSuccessful(
477524
{method: 'GET', timeout: 100, headers: {Connection: 'close'}},
478525
(response: http.IncomingMessage): void => {
479526
response.setEncoding('utf8');
527+
480528
response.on('data', (chunk): void => {
481529
// convert chunk to json object
482530
const object: {account: string} = JSON.parse(chunk);
531+
483532
expect(
484533
object.account,
485534
'expect the created account to exist in the mirror nodes copy of the accounts',
486535
).to.equal(accountId);
536+
487537
received = true;
488538
});
489539
},
490540
);
541+
491542
request.on('error', (error: Error): void => {
492543
testLogger.debug(`problem with request: ${error.message}`, error);
493544
});
545+
494546
request.end(); // make the request
495547
await sleep(Duration.ofSeconds(2));
496548
}
549+
497550
await sleep(Duration.ofSeconds(1));
498551
}
499552
} finally {
500553
if (portForwarder) {
554+
// eslint-disable-next-line unicorn/no-null
501555
await k8.pods().readByReference(null).stopPortForward(portForwarder);
502556
}
503557
}
@@ -540,6 +594,7 @@ async function verifyExplorerDeployWasSuccessful(
540594
const queryUrl: string = 'http://127.0.0.1:8080/api/v1/accounts?limit=15&order=desc';
541595
const packageDownloader: PackageDownloader = container.resolve<PackageDownloader>(InjectTokens.PackageDownloader);
542596
expect(await packageDownloader.urlExists(queryUrl), 'the hedera explorer Accounts URL should exist').to.be.true;
597+
543598
let received: boolean = false;
544599
// wait until the transaction reached consensus and retrievable from the mirror node API
545600
while (!received) {
@@ -548,31 +603,37 @@ async function verifyExplorerDeployWasSuccessful(
548603
{method: 'GET', timeout: 100, headers: {Connection: 'close'}},
549604
(response: http.IncomingMessage): void => {
550605
response.setEncoding('utf8');
606+
551607
response.on('data', (chunk): void => {
552608
// convert chunk to json object
553609
const object: {accounts: {account: string}[]} = JSON.parse(chunk);
554610
expect(
555611
object.accounts?.length,
556612
"expect there to be more than one account in the hedera explorer's call to mirror node",
557613
).to.be.greaterThan(1);
614+
558615
for (const accountId of createdAccountIds) {
559616
expect(
560617
object.accounts.some((account: {account: string}): boolean => account.account === accountId),
561618
`expect ${accountId} to be in the response`,
562619
).to.be.true;
563620
}
621+
564622
received = true;
565623
});
566624
},
567625
);
626+
568627
request.on('error', (error: Error): void => {
569628
testLogger.debug(`problem with request: ${error.message}`, error);
570629
});
630+
571631
request.end(); // make the request
572632
await sleep(Duration.ofSeconds(2));
573633
}
574634
} finally {
575635
if (portForwarder) {
636+
// eslint-disable-next-line unicorn/no-null
576637
await k8.pods().readByReference(null).stopPortForward(portForwarder);
577638
}
578639
}

0 commit comments

Comments
 (0)