From 25c82fdc8185147e7c36dbf6ae0fa40d77e7b419 Mon Sep 17 00:00:00 2001 From: Troy Kessler <43882936+troykessler@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:46:11 +0200 Subject: [PATCH] chore: implemented dry-run (#150) * chore: implemented dry-run --- common/protocol/src/index.ts | 20 +++++- .../src/methods/checks/isPoolActive.ts | 1 - common/protocol/src/methods/main/runNode.ts | 45 +++++++++++-- .../src/methods/queries/canPropose.ts | 4 ++ .../protocol/src/methods/queries/canVote.ts | 7 +++ .../src/methods/setups/setupMetrics.ts | 2 +- .../src/methods/txs/claimUploaderRole.ts | 5 ++ .../src/methods/txs/voteBundleProposal.ts | 63 +++++++++++-------- 8 files changed, 114 insertions(+), 33 deletions(-) diff --git a/common/protocol/src/index.ts b/common/protocol/src/index.ts index 11bfcb47..d77ec859 100644 --- a/common/protocol/src/index.ts +++ b/common/protocol/src/index.ts @@ -99,6 +99,8 @@ export class Validator { protected metrics!: boolean; protected metricsPort!: number; protected home!: string; + protected dryRun!: boolean; + protected dryRunBundles!: number; // tmp variables protected lastUploadedBundle: { @@ -278,6 +280,15 @@ export class Validator { "--skip-data-availability-check", "Skip data availability check and join pool instantly without waiting for the data source. WARNING: Only use this if you know what you are doing since this can lead to timeout slashes" ) + .option( + "--dry-run", + "Run the node without uploading or voting on bundles so the operator can test his setup before joining as a validator." + ) + .option( + "--dry-run-bundles ", + "Specify the number of bundles that should be tested before the node properly exits. If zero the node will run indefinitely [default = 0]", + "0" + ) .action((options) => { this.start(options); }); @@ -315,6 +326,8 @@ export class Validator { this.metrics = options.metrics; this.metricsPort = parseInt(options.metricsPort); this.home = options.home; + this.dryRun = options.dryRun; + this.dryRunBundles = parseInt(options.dryRunBundles); // name the log file after the time the node got started this.logFile = `${new Date().toISOString()}.log`; @@ -326,7 +339,7 @@ export class Validator { await this.setupSDK(); await this.syncPoolState(true); - if (await this.isStorageBalanceZero()) { + if (!this.dryRun && (await this.isStorageBalanceZero())) { process.exit(1); } @@ -339,7 +352,10 @@ export class Validator { } } - await this.setupValidator(); + if (!this.dryRun) { + await this.setupValidator(); + } + await this.setupCacheProvider(); // start the node process. Validator and cache should run at the same time. diff --git a/common/protocol/src/methods/checks/isPoolActive.ts b/common/protocol/src/methods/checks/isPoolActive.ts index 3dfa7c07..057b0137 100644 --- a/common/protocol/src/methods/checks/isPoolActive.ts +++ b/common/protocol/src/methods/checks/isPoolActive.ts @@ -17,7 +17,6 @@ export function isPoolActive(this: Validator): boolean { case PoolStatus.POOL_STATUS_ACTIVE: return true; case PoolStatus.POOL_STATUS_NO_FUNDS: - this.logger.warn("Pool is out of funds, rewards may be reduced"); return true; case PoolStatus.POOL_STATUS_DISABLED: this.logger.info( diff --git a/common/protocol/src/methods/main/runNode.ts b/common/protocol/src/methods/main/runNode.ts index d9b8560b..99335c16 100644 --- a/common/protocol/src/methods/main/runNode.ts +++ b/common/protocol/src/methods/main/runNode.ts @@ -28,10 +28,13 @@ export async function runNode(this: Validator): Promise { // get latest state of the chain to start round await this.syncPoolState(); - await this.getBalancesForMetrics(); - if (!this.isNodeValidator()) { - process.exit(1); + if (!this.dryRun) { + await this.getBalancesForMetrics(); + + if (!this.isNodeValidator()) { + process.exit(1); + } } if (this.pool.status === PoolStatus.POOL_STATUS_END_KEY_REACHED) { @@ -61,7 +64,13 @@ export async function runNode(this: Validator): Promise { } // log out the role of this node in this particular round - if (this.pool.bundle_proposal!.next_uploader === this.staker) { + if (this.dryRun) { + this.logger.info( + `Participating in bundle proposal round ${ + this.pool.data!.total_bundles + } as NON-VALIDATOR` + ); + } else if (this.pool.bundle_proposal!.next_uploader === this.staker) { this.logger.info( `Participating in bundle proposal round ${ this.pool.data!.total_bundles @@ -87,6 +96,34 @@ export async function runNode(this: Validator): Promise { } } + // exit the node properly if the provided bundle rounds have been reached + if (this.dryRun && this.dryRunBundles > 0) { + const rounds = await this.m.bundles_amount.get(); + + if (rounds.values[0].value === this.dryRunBundles) { + const valid = await this.m.bundles_voted_valid.get(); + const invalid = await this.m.bundles_voted_invalid.get(); + const abstain = await this.m.bundles_voted_abstain.get(); + + console.log(); + this.logger.info( + `Participated in ${rounds.values[0].value} bundle rounds and successfully finished dry run` + ); + + console.log(); + this.logger.info(`Voted valid: ${valid.values[0].value}`); + this.logger.info(`Voted invalid: ${invalid.values[0].value}`); + this.logger.info(`Voted abstain: ${abstain.values[0].value}`); + + console.log(); + this.logger.info( + `Note that the total sum of the votes can be greater than the rounds since a node can still vote valid/invalid after initially voting abstain` + ); + + process.exit(0); + } + } + // wait until the upload interval has passed to continue with the proposal // of a new bundle. the node waits because a new round won't start during // that time diff --git a/common/protocol/src/methods/queries/canPropose.ts b/common/protocol/src/methods/queries/canPropose.ts index cd4552ba..87dbce76 100644 --- a/common/protocol/src/methods/queries/canPropose.ts +++ b/common/protocol/src/methods/queries/canPropose.ts @@ -18,6 +18,10 @@ export async function canPropose( updatedAt: number ): Promise { try { + if (this.dryRun) { + return false; + } + const canPropose = await callWithBackoffStrategy( async () => { for (let l = 0; l < this.lcd.length; l++) { diff --git a/common/protocol/src/methods/queries/canVote.ts b/common/protocol/src/methods/queries/canVote.ts index 8a08ded6..96874b4c 100644 --- a/common/protocol/src/methods/queries/canVote.ts +++ b/common/protocol/src/methods/queries/canVote.ts @@ -46,6 +46,13 @@ export async function canVote( }; } + if (this.dryRun) { + return { + possible: true, + reason: "", + }; + } + this.logger.debug(this.rest[l]); this.logger.debug( `this.lcd.kyve.query.v1beta1.canVote({pool_id: ${this.poolId.toString()},staker: ${ diff --git a/common/protocol/src/methods/setups/setupMetrics.ts b/common/protocol/src/methods/setups/setupMetrics.ts index 36156974..01f6f675 100644 --- a/common/protocol/src/methods/setups/setupMetrics.ts +++ b/common/protocol/src/methods/setups/setupMetrics.ts @@ -279,7 +279,7 @@ export function setupMetrics(this: Validator): void { this.logger.debug(`Initializing metrics: bundles_amount`); - this.m.bundles_amount = new prom_client.Gauge({ + this.m.bundles_amount = new prom_client.Counter({ name: "bundles_amount", help: "The amount of bundles the validator participated in.", }); diff --git a/common/protocol/src/methods/txs/claimUploaderRole.ts b/common/protocol/src/methods/txs/claimUploaderRole.ts index dee1904d..1a9ff041 100644 --- a/common/protocol/src/methods/txs/claimUploaderRole.ts +++ b/common/protocol/src/methods/txs/claimUploaderRole.ts @@ -14,6 +14,11 @@ import { Validator, standardizeError } from "../.."; export async function claimUploaderRole(this: Validator): Promise { for (let c = 0; c < this.client.length; c++) { try { + // if the node runs in dry run abort + if (this.dryRun) { + return false; + } + // if next uploader is already defined abort if (this.pool.bundle_proposal!.next_uploader) { return false; diff --git a/common/protocol/src/methods/txs/voteBundleProposal.ts b/common/protocol/src/methods/txs/voteBundleProposal.ts index 4b9ab88d..b2efdef1 100644 --- a/common/protocol/src/methods/txs/voteBundleProposal.ts +++ b/common/protocol/src/methods/txs/voteBundleProposal.ts @@ -34,37 +34,50 @@ export async function voteBundleProposal( throw Error(`Invalid vote: ${vote}`); } - this.logger.debug(this.rpc[c]); - this.logger.debug( - `this.client.kyve.bundles.v1beta1.voteBundleProposal({staker: ${ - this.staker - },pool_id: ${this.poolId.toString()},storage_id: ${storageId},vote: ${vote}})` - ); + let tx: any; + let receipt = { + code: 0, + }; - // use a higher gas multiplier of 1.5 because while voting the gas can drastically increase, - // making late submitted votes fail due to not enough gas - const tx = await this.client[c].kyve.bundles.v1beta1.voteBundleProposal( - { - staker: this.staker, - pool_id: this.poolId.toString(), - storage_id: storageId, - vote, - }, - { - fee: 1.6, - } - ); + if (!this.dryRun) { + this.logger.debug(this.rpc[c]); + this.logger.debug( + `this.client.kyve.bundles.v1beta1.voteBundleProposal({staker: ${ + this.staker + },pool_id: ${this.poolId.toString()},storage_id: ${storageId},vote: ${vote}})` + ); - this.logger.debug(`VoteProposal = ${tx.txHash}`); + // use a higher gas multiplier of 1.5 because while voting the gas can drastically increase, + // making late submitted votes fail due to not enough gas + tx = await this.client[c].kyve.bundles.v1beta1.voteBundleProposal( + { + staker: this.staker, + pool_id: this.poolId.toString(), + storage_id: storageId, + vote, + }, + { + fee: 1.6, + } + ); - const receipt = await tx.execute(); + this.logger.debug(`VoteProposal = ${tx.txHash}`); - this.logger.debug( - JSON.stringify({ ...receipt, rawLog: null, data: null }) - ); + receipt = await tx.execute(); + + this.logger.debug( + JSON.stringify({ ...receipt, rawLog: null, data: null }) + ); + } if (receipt.code === 0) { - this.logger.info(`Voted ${voteMessage} on bundle "${storageId}"`); + if (this.dryRun) { + this.logger.warn( + `Node would have voted ${voteMessage} on bundle "${storageId}"` + ); + } else { + this.logger.info(`Voted ${voteMessage} on bundle "${storageId}"`); + } this.m.tx_vote_bundle_proposal_successful.inc(); this.m.fees_vote_bundle_proposal.inc(