Skip to content

Commit

Permalink
chore: implemented dry-run (#150)
Browse files Browse the repository at this point in the history
* chore: implemented dry-run
  • Loading branch information
troykessler authored Aug 29, 2024
1 parent 9406df3 commit 25c82fd
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 33 deletions.
20 changes: 18 additions & 2 deletions common/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 <number>",
"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);
});
Expand Down Expand Up @@ -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`;
Expand All @@ -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);
}

Expand All @@ -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.
Expand Down
1 change: 0 additions & 1 deletion common/protocol/src/methods/checks/isPoolActive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
45 changes: 41 additions & 4 deletions common/protocol/src/methods/main/runNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ export async function runNode(this: Validator): Promise<void> {

// 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) {
Expand Down Expand Up @@ -61,7 +64,13 @@ export async function runNode(this: Validator): Promise<void> {
}

// 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
Expand All @@ -87,6 +96,34 @@ export async function runNode(this: Validator): Promise<void> {
}
}

// 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
Expand Down
4 changes: 4 additions & 0 deletions common/protocol/src/methods/queries/canPropose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export async function canPropose(
updatedAt: number
): Promise<boolean> {
try {
if (this.dryRun) {
return false;
}

const canPropose = await callWithBackoffStrategy(
async () => {
for (let l = 0; l < this.lcd.length; l++) {
Expand Down
7 changes: 7 additions & 0 deletions common/protocol/src/methods/queries/canVote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: ${
Expand Down
2 changes: 1 addition & 1 deletion common/protocol/src/methods/setups/setupMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
});
Expand Down
5 changes: 5 additions & 0 deletions common/protocol/src/methods/txs/claimUploaderRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { Validator, standardizeError } from "../..";
export async function claimUploaderRole(this: Validator): Promise<boolean> {
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;
Expand Down
63 changes: 38 additions & 25 deletions common/protocol/src/methods/txs/voteBundleProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 25c82fd

Please sign in to comment.