Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/script/gcs_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,6 @@ fi

grpcurl -plaintext -d '{"file_id": {"fileNum": 102}, "limit": 0}' localhost:8081 com.hedera.mirror.api.proto.NetworkService/getNodes

node scripts/create-topic.js || result=$?
if [[ $result -ne 0 ]]; then
echo "JavaScript SDK test failed with exit code $result"
log_and_exit $result
fi

npm run solo-test -- consensus node stop -i node1 --deployment "${SOLO_DEPLOYMENT}"

if [ "${storageType}" == "aws_only" ] || [ "${storageType}" == "gcs_only" ]; then
Expand Down
37 changes: 35 additions & 2 deletions resources/extract-platform.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,38 @@ function log() {
printf "%s - %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "${message}" | tee -a "${LOG_FILE}"
}

# New: robust fetch with retries and exponential backoff
function fetch_with_retry() {
local url="${1}"
local out_file="${2}"
local desc="${3}"

local attempt=1
local max_attempts=5
local delay=2

while true; do
log "Attempt ${attempt}/${max_attempts}: downloading ${desc} from ${url}"
# follow redirects, fail on HTTP errors, be silent but show errors, write to output file
curl -sSfL "${url}" -o "${out_file}" > >(tee -a "${LOG_FILE}") 2>&1
local ec=${?}
if [[ ${ec} -eq 0 ]]; then
log "Successfully downloaded ${desc} to ${out_file}"
return 0
fi

if [[ ${attempt} -ge ${max_attempts} ]]; then
log "Failed to download ${desc} after ${attempt} attempts. Last exit code: ${ec}"
return ${ec}
fi

log "Download failed (exit ${ec}). Retrying in ${delay}s..."
sleep ${delay}
attempt=$((attempt + 1))
delay=$((delay * 2))
done
}

readonly tag="${1}"
if [[ -z "${tag}" ]]; then
echo "Release tag is required (e.g. v0.42.5)"
Expand All @@ -39,7 +71,8 @@ log "extract-platform.sh: begin................................"
log "Checking if ${BUILD_ZIP_FILE} exists..."
if [[ ! -f "${BUILD_ZIP_FILE}" ]]; then
log "Downloading ${BUILD_ZIP_URL}..."
curl -sSf "${BUILD_ZIP_URL}" -o "${BUILD_ZIP_FILE}" > >(tee -a "${LOG_FILE}") 2>&1
# use retry wrapper instead of direct curl to mitigate transient network/SSL issues
fetch_with_retry "${BUILD_ZIP_URL}" "${BUILD_ZIP_FILE}" "build zip"
ec="${?}"
if [[ "${ec}" -ne 0 ]]; then
log "Failed to download ${BUILD_ZIP_URL}. Error code: ${ec}"
Expand All @@ -51,7 +84,7 @@ log "Checking if ${CHECKSUM_FILE} exists..."

if [[ ! -f "${CHECKSUM_FILE}" ]]; then
log "Downloading ${CHECKSUM_URL}..."
curl -sSf "${CHECKSUM_URL}" -o "${CHECKSUM_FILE}" > >(tee -a "${LOG_FILE}") 2>&1
fetch_with_retry "${CHECKSUM_URL}" "${CHECKSUM_FILE}" "checksum"
ec="${?}"
if [[ "${ec}" -ne 0 ]]; then
log "Failed to download ${CHECKSUM_URL}. Error code: ${ec}"
Expand Down
64 changes: 63 additions & 1 deletion src/commands/node/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {patchInject} from '../../core/dependency-injection/container-helper.js';
import {ConsensusNode} from '../../core/model/consensus-node.js';
import {type K8} from '../../integration/kube/k8.js';
import {Base64} from 'js-base64';
import {SecretType} from '../../integration/kube/resources/secret/secret-type.js';
import {InjectTokens} from '../../core/dependency-injection/inject-tokens.js';
import {BaseCommand} from '../base.js';
import {HEDERA_PLATFORM_VERSION, MINIMUM_HIERO_PLATFORM_VERSION_FOR_GRPC_WEB_ENDPOINTS} from '../../../version.js';
Expand Down Expand Up @@ -757,7 +758,7 @@ export class NodeCommandTasks {
config.namespace,
Templates.renderNodeAdminKeyName((context_ as NodeUpdateContext | NodeDestroyContext).config.nodeAlias),
);
const privateKey = Base64.decode(keyFromK8.data.privateKey);
const privateKey: string = Base64.decode(keyFromK8.data.privateKey);
config.adminKey = PrivateKey.fromStringED25519(privateKey);
} catch (error) {
this.logger.debug(`Error in loading node admin key: ${error.message}, use default key`);
Expand Down Expand Up @@ -2521,6 +2522,36 @@ export class NodeCommandTasks {
const txResp = await signedTx.execute(config.nodeClient);
const nodeUpdateReceipt = await txResp.getReceipt(config.nodeClient);
self.logger.debug(`NodeUpdateReceipt: ${nodeUpdateReceipt.toString()}`);

// If admin key was updated, save the new key to k8s secret
if (config.newAdminKey) {
const context: string = helpers.extractContextFromConsensusNodes(config.nodeAlias, config.consensusNodes);
const data: {privateKey: string; publicKey: string} = {
privateKey: Base64.encode(parsedNewKey.toString()),
publicKey: Base64.encode(parsedNewKey.publicKey.toString()),
};

// Delete old secret and create new one with updated key
try {
await self.k8Factory
.getK8(context)
.secrets()
.delete(config.namespace, Templates.renderNodeAdminKeyName(config.nodeAlias));
} catch (deleteError) {
self.logger.debug(
`No existing admin key secret to delete for ${config.nodeAlias}: ${deleteError.message}`,
);
}

await self.k8Factory
.getK8(context)
.secrets()
.create(config.namespace, Templates.renderNodeAdminKeyName(config.nodeAlias), SecretType.OPAQUE, data, {
'solo.hedera.com/node-admin-key': 'true',
});

self.logger.debug(`Updated admin key secret for node ${config.nodeAlias}`);
}
} catch (error) {
throw new SoloError(`Error updating node to network: ${error.message}`, error);
}
Expand Down Expand Up @@ -3098,6 +3129,19 @@ export class NodeCommandTasks {
if (nodeDeleteReceipt.status !== Status.Success) {
throw new SoloError(`Node delete transaction failed with status: ${nodeDeleteReceipt.status}.`);
}

// Delete admin key secret from k8s after successful node deletion
try {
const context: string = helpers.extractContextFromConsensusNodes(config.nodeAlias, config.consensusNodes);
await this.k8Factory
.getK8(context)
.secrets()
.delete(config.namespace, Templates.renderNodeAdminKeyName(config.nodeAlias));
this.logger.debug(`Deleted admin key secret for node ${config.nodeAlias} from k8s`);
} catch (deleteError) {
// Log but don't fail the delete operation if secret doesn't exist or can't be deleted
this.logger.debug(`Could not delete admin key secret for ${config.nodeAlias}: ${deleteError.message}`);
}
} catch (error) {
throw new SoloError(`Error deleting node from network: ${error.message}`, error);
}
Expand Down Expand Up @@ -3130,6 +3174,24 @@ export class NodeCommandTasks {
if (nodeCreateReceipt.status !== Status.Success) {
throw new SoloError(`Node Create Transaction failed: ${nodeCreateReceipt.status}`);
}

// Save admin key to k8s secret after successful node creation
// nodeAlias was set in determineNewNodeAccountNumber step
const nodeAlias: NodeAlias = config.nodeAlias;
const context: string = helpers.extractContextFromConsensusNodes(nodeAlias, config.consensusNodes);
const data: {privateKey: string; publicKey: string} = {
privateKey: Base64.encode(context_.adminKey.toString()),
publicKey: Base64.encode(context_.adminKey.publicKey.toString()),
};

await this.k8Factory
.getK8(context)
.secrets()
.create(config.namespace, Templates.renderNodeAdminKeyName(nodeAlias), SecretType.OPAQUE, data, {
'solo.hedera.com/node-admin-key': 'true',
});

this.logger.debug(`Saved admin key for node ${nodeAlias} to k8s secret`);
} catch (error) {
throw new SoloError(`Error adding node to network: ${error.message}`, error);
}
Expand Down
Loading