Skip to content

Commit

Permalink
feat: improve logging of debug information (#90)
Browse files Browse the repository at this point in the history
* feat: create zip archive will all debug information if voting invalid
  • Loading branch information
troykessler authored Nov 7, 2023
1 parent ca53fe1 commit ba3c1d6
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 225 deletions.
3 changes: 3 additions & 0 deletions common/protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
"bignumber.js": "^9.0.1",
"clone": "^2.1.2",
"commander": "^9.4.1",
"diff": "^5.1.0",
"fs-extra": "^10.0.1",
"jsonfile": "^6.1.0",
"jszip": "^3.10.1",
"level": "^8.0.0",
"prando": "^6.0.1",
"prom-client": "^14.1.0",
Expand All @@ -46,6 +48,7 @@
},
"devDependencies": {
"@types/clone": "^2.1.1",
"@types/diff": "^5.0.7",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^28.1.7",
"@types/node": "^18.11.9",
Expand Down
6 changes: 6 additions & 0 deletions common/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
skipUploaderRole,
submitBundleProposal,
syncPoolState,
parseProposedBundle,
validateBundleProposal,
isNodeValidator,
isPoolActive,
Expand Down Expand Up @@ -74,6 +75,7 @@ export class Validator {
public name!: string;

// logger attributes
public logFile!: string;
public logger!: Logger;

// metrics attributes
Expand Down Expand Up @@ -138,6 +140,7 @@ export class Validator {
// validate
protected saveBundleDownload = saveBundleDownload;
protected saveLoadValidationBundle = saveLoadValidationBundle;
protected parseProposedBundle = parseProposedBundle;
protected validateBundleProposal = validateBundleProposal;

// upload
Expand Down Expand Up @@ -295,6 +298,9 @@ export class Validator {
this.metricsPort = parseInt(options.metricsPort);
this.home = options.home;

// name the log file after the time the node got started
this.logFile = `${new Date().toISOString()}.log`;

// perform setups
this.setupLogger();
this.setupMetrics();
Expand Down
116 changes: 96 additions & 20 deletions common/protocol/src/methods/helpers/archiveDebugBundle.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,109 @@
import { appendFileSync, existsSync, mkdirSync } from "fs";
import { existsSync, mkdirSync, createWriteStream, readFileSync } from "fs";
import path from "path";
import { DataItem, Validator } from "../..";
import { DataItem, standardizeError, Validator } from "../..";
import JSZip from "jszip";
import * as Diff from "diff";
import { VoteType } from "@kyvejs/types/client/kyve/bundles/v1beta1/tx";

/**
* archiveDebugBundle is used for storing a bundle for debug
* purposes if the validator voted with invalid
* purposes if the validator voted with abstain or invalid
*
* @method archiveDebugBundle
* @param {Validator} this
* @param {DataItem[]} bundle local validation bundle which should get archived for debug purposes
* @param {number} vote type of the vote
* @param {DataItem[]} proposedBundle the proposed bundle uploaded to the storage provider
* @param {DataItem[]} validationBundle the local bundle from the node
* @param {object} metadata additional info about the bundle
* @return {void}
*/
export function archiveDebugBundle(this: Validator, bundle: DataItem[]): void {
// if "debug" folder under target path does not exist create it
if (!existsSync(path.join(this.home, `debug`))) {
mkdirSync(path.join(this.home, `debug`), { recursive: true });
}
export function archiveDebugBundle(
this: Validator,
vote: number,
proposedBundle: DataItem[],
validationBundle: DataItem[],
metadata: object
): void {
try {
// if "debug" folder under target path does not exist create it
if (!existsSync(path.join(this.home, `debug`))) {
mkdirSync(path.join(this.home, `debug`), { recursive: true });
}

const zip = new JSZip();

// save metadata which includes vote reasons and args
const metadata_str = JSON.stringify(metadata || {}, null, 2);
zip.file("metadata.json", metadata_str);

// save current pool state including the raw bundle proposal
const pool_str = JSON.stringify(this.pool || {}, null, 2);
zip.file("pool.json", pool_str);

// save the proposed bundle from the uploader
const proposed_bundle_str = JSON.stringify(proposedBundle || [], null, 2);
zip.file("proposed_bundle.json", proposed_bundle_str);

const storageId = this.pool?.bundle_proposal?.storage_id ?? "";
// save the locally created bundle from this node
const validation_bundle_str = JSON.stringify(
validationBundle || [],
null,
2
);
zip.file("validation_bundle.json", validation_bundle_str);

// save current pool state to specified path target
appendFileSync(
path.join(this.home, `debug`, `pool_state_${storageId}.json`),
JSON.stringify(this.pool || {})
);
// save the diff of the proposed and local bundle
const diff_str = Diff.createTwoFilesPatch(
"proposed_bundle.json",
"validation_bundle.json",
proposed_bundle_str,
validation_bundle_str
);
zip.file("diff.txt", diff_str);

// save local bundle to specified path target
appendFileSync(
path.join(this.home, `debug`, `validation_bundle_${storageId}.json`),
JSON.stringify(bundle || {})
);
// save the logfile of the current session
const debug_str = readFileSync(path.join(this.home, "logs", this.logFile));
zip.file("debug.log", debug_str);

// get human readable vote
let voteType = "";

switch (vote) {
case VoteType.VOTE_TYPE_VALID:
voteType = "valid";
break;
case VoteType.VOTE_TYPE_INVALID:
voteType = "invalid";
break;
case VoteType.VOTE_TYPE_ABSTAIN:
voteType = "abstain";
break;
case VoteType.VOTE_TYPE_UNSPECIFIED:
voteType = "unspecified";
break;
default:
voteType = "unrecognized";
}

const storageId = this.pool?.bundle_proposal?.storage_id ?? "";
const zipPath = path.join(
this.home,
`debug`,
`${voteType}_${Math.floor(Date.now() / 1000)}_${storageId.slice(
0,
6
)}.zip`
);

// save zip file
zip
.generateNodeStream({ type: "nodebuffer", streamFiles: true })
.pipe(createWriteStream(zipPath))
.on("finish", () => {
this.logger.info("Successfully saved debug information");
});
} catch (err) {
this.logger.error("Failed to save debug information");
this.logger.error(standardizeError(err));
}
}
1 change: 1 addition & 0 deletions common/protocol/src/methods/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export * from "./queries/syncPoolState";
// validate
export * from "./validate/saveBundleDownload";
export * from "./validate/saveLoadValidationBundle";
export * from "./validate/parseProposedBundle";
export * from "./validate/validateBundleProposal";

// upload
Expand Down
5 changes: 1 addition & 4 deletions common/protocol/src/methods/setups/setupLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export function setupLogger(this: Validator): void {
mkdirSync(path.join(this.home, "logs"), { recursive: true });
}

// name the log file after the time the node got started
const logFile = `${new Date().toISOString()}.log`;

const logToTransport = (log: ILogObject) => {
const message = log.argumentsArray[0];

Expand Down Expand Up @@ -53,7 +50,7 @@ export function setupLogger(this: Validator): void {
}

// save logs to specified path target
appendFileSync(path.join(this.home, `logs`, logFile), format + "\n");
appendFileSync(path.join(this.home, `logs`, this.logFile), format + "\n");
};

// hide verbose logging information
Expand Down
9 changes: 5 additions & 4 deletions common/protocol/src/methods/txs/voteBundleProposal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Validator, standardizeError, VOTE } from "../..";
import { Validator, standardizeError } from "../..";
import { VoteType } from "@kyvejs/types/client/kyve/bundles/v1beta1/tx";

/**
* voteBundleProposal submits a vote on the current bundle proposal.
Expand All @@ -23,11 +24,11 @@ export async function voteBundleProposal(
let voteMessage = "";

// determine vote type for verbose logging
if (vote === VOTE.VALID) {
if (vote === VoteType.VOTE_TYPE_VALID) {
voteMessage = "valid";
} else if (vote === VOTE.INVALID) {
} else if (vote === VoteType.VOTE_TYPE_INVALID) {
voteMessage = "invalid";
} else if (vote === VOTE.ABSTAIN) {
} else if (vote === VoteType.VOTE_TYPE_ABSTAIN) {
voteMessage = "abstain";
} else {
throw Error(`Invalid vote: ${vote}`);
Expand Down
33 changes: 33 additions & 0 deletions common/protocol/src/methods/validate/parseProposedBundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Validator } from "../..";
import { DataItem } from "../../types";
import { bytesToBundle } from "../../utils";

/**
* parseProposedBundle takes the raw data from the storage
* provider and tries to decompress and parse it back to
* the original JSON format
*
* @method parseProposedBundle
* @param {Validator} this
* @param {number} updatedAt
* @return {Promise<DataItem[] | null>}
*/
export async function parseProposedBundle(
this: Validator,
storageProviderResult: Buffer
): Promise<DataItem[]> {
// get current compression defined on pool
this.logger.debug(`this.compressionFactory()`);
const compression = this.compressionFactory();

// decompress the bundle with the specified compression type
this.logger.debug(`this.compression.decompress($STORAGE_PROVIDER_RESULT)`);
const decompressed = await compression.decompress(storageProviderResult);

this.logger.info(
`Successfully decompressed bundle with Compression:${compression.name}`
);

// parse raw decompressed bundle back to json format
return bytesToBundle(decompressed);
}
5 changes: 3 additions & 2 deletions common/protocol/src/methods/validate/saveBundleDownload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import BigNumber from "bignumber.js";

import { VoteType } from "@kyvejs/types/client/kyve/bundles/v1beta1/tx";
import { Validator } from "../..";
import { callWithBackoffStrategy, standardizeError, VOTE } from "../../utils";
import { callWithBackoffStrategy, standardizeError } from "../../utils";

/**
* saveBundleDownload downloads a bundle from the storage provider.
Expand Down Expand Up @@ -103,7 +104,7 @@ export async function saveBundleDownload(
if (!this.pool.bundle_proposal?.voters_abstain.includes(this.staker)) {
await this.voteBundleProposal(
this.pool.bundle_proposal!.storage_id,
VOTE.ABSTAIN
VoteType.VOTE_TYPE_ABSTAIN
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import BigNumber from "bignumber.js";

import { VoteType } from "@kyvejs/types/client/kyve/bundles/v1beta1/tx";
import { Validator } from "../..";
import { DataItem } from "../../types";
import { callWithBackoffStrategy, standardizeJSON, VOTE } from "../../utils";
import { callWithBackoffStrategy, standardizeJSON } from "../../utils";

/**
* saveLoadValidationBundle loads the bundle from the local
Expand Down Expand Up @@ -99,7 +100,7 @@ export async function saveLoadValidationBundle(
if (!this.pool.bundle_proposal?.voters_abstain.includes(this.staker)) {
await this.voteBundleProposal(
this.pool.bundle_proposal!.storage_id,
VOTE.ABSTAIN
VoteType.VOTE_TYPE_ABSTAIN
);
}
}
Expand Down
Loading

0 comments on commit ba3c1d6

Please sign in to comment.