Skip to content

Commit

Permalink
feat: script to check versions of default validators (#4774)
Browse files Browse the repository at this point in the history
### Description

feat: script to check versions of default validators

also prints age of the commit for mismatched validators

### Drive-by changes

update s3 validator to return `metadata_latest.json`

### Related issues

na

### Backward compatibility

ye

### Testing

manual

---------

Signed-off-by: pbio <[email protected]>
  • Loading branch information
paulbalaji authored Nov 28, 2024
1 parent 2f77bb0 commit ef33b5b
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 0 deletions.
213 changes: 213 additions & 0 deletions typescript/infra/scripts/check/check-validator-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { execSync } from 'child_process';

import { ValidatorAnnounce__factory } from '@hyperlane-xyz/core';
import {
ChainName,
S3Validator,
defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';

import { isEthereumProtocolChain } from '../../src/utils/utils.js';
import { getArgs, withChains } from '../agent-utils.js';
import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js';

// prettier-ignore
const acceptableValidatorVersions: Record<string, string> = {
// Aug 27 deploy
'72d498fa984750b9137c1211fef6c80a3e594ce7': 'aug-27-batch',
// Sep 9 deploy
'd71dd4e5ed7eb69cc4041813ef444e37d881cdda': 'sep-9-batch',
// Oct 27 deploy
'45399a314cec85723bbb5d2360531c96a3aa261e': 'oct-27-batch',
// Nov 7 deploy
'75d62ae7bbdeb77730c6d343c4fc1df97a08abe4': 'nov-7-batch',
// Nov 21 deploy
'e70431a85965d8d21681e6f4856ed3ac9bd2ba27': 'nov-21-batch',
// Nov 21 bsquared deploy
'd834d8147628584acd78a81e344bff76472d707e': 'nov-21-bsquared',
// Rolled out only to AW infra before 1.0.0, just 1 commit behind 1.0.0
'a64af8be9a76120d0cfc727bb70660fa07e70cce': 'pre-1.0.0',
// 1.0.0
'ffbe1dd82e2452dbc111b6fb469a34fb870da8f1': '1.0.0',
// Tessellated's Own Build
'79453fcd972a1e62ba8ee604f0a4999c7b938582': 'tesselated-special-build',
};

type ValidatorInfo = {
chain: ChainName;
validator: Address;
alias: string;
version: string;
age?: string;
};

function sortValidatorInfo(a: ValidatorInfo, b: ValidatorInfo) {
// First sort by alias
if (a.alias && !b.alias) return -1;
if (!a.alias && b.alias) return 1;
if (a.alias && b.alias) {
const aliasCompare = a.alias.localeCompare(b.alias);
if (aliasCompare !== 0) return aliasCompare;
}
// Then sort by validator address
return a.chain.localeCompare(b.chain);
}

function getCommitDate(sha: string): string | undefined {
try {
// Try to fetch the commit first if we don't have it
try {
execSync(`git fetch origin ${sha}`, { stdio: 'ignore' });
} catch {
// Ignore fetch errors - commit might be local or unreachable
}

// Use %cd for date and customize format with --date=format
const date = execSync(
`git show -s --date=format:'%Y-%m-%d %H:%M UTC' --format=%cd ${sha}`,
{ encoding: 'utf-8' },
).trim();
return date;
} catch {
return undefined;
}
}

function getCommitAge(sha: string): string | undefined {
const commitDateString = getCommitDate(sha);
if (!commitDateString) {
return undefined;
}

const commitDate = new Date(commitDateString);
if (isNaN(commitDate.getTime())) {
return undefined;
}

const msToNow = Date.now() - commitDate.getTime();
const days = Math.floor(msToNow / (1000 * 60 * 60 * 24));
const hours = Math.floor(
(msToNow % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
);

const dayText = days === 1 ? 'day' : 'days';
const hourText = hours === 1 ? 'hour' : 'hours';
return days > 0
? `${days} ${dayText} ${hours} ${hourText} old`
: `${hours} ${hourText} old`;
}

async function main() {
const { environment, chains, showUpdated } = await withChains(getArgs())
.describe(
'show-updated',
'If enabled, prints a table with all updated validators',
)
.boolean('show-updated')
.default('show-updated', false).argv;

const config = getEnvironmentConfig(environment);
const { core } = await getHyperlaneCore(environment);

const targetNetworks = (
chains && chains.length > 0 ? chains : config.supportedChainNames
).filter(isEthereumProtocolChain);

const mismatchedValidators: ValidatorInfo[] = [];
const upgradedValidators: ValidatorInfo[] = [];

// Manually add validator announce for OG Lumia chain deployment
const lumiaValidatorAnnounce = ValidatorAnnounce__factory.connect(
'0x989B7307d266151BE763935C856493D968b2affF',
core.multiProvider.getProvider('lumia'),
);

await Promise.all(
targetNetworks.map(async (chain) => {
const validatorAnnounce =
chain === 'lumia'
? lumiaValidatorAnnounce
: core.getContracts(chain).validatorAnnounce;
const expectedValidators = defaultMultisigConfigs[chain].validators || [];
const storageLocations =
await validatorAnnounce.getAnnouncedStorageLocations(
expectedValidators.map((v) => v.address),
);

// For each validator on this chain
for (let i = 0; i < expectedValidators.length; i++) {
const { address: validator, alias } = expectedValidators[i];
const location = storageLocations[i][0];

// Get metadata from each storage location
try {
const validatorInstance = await S3Validator.fromStorageLocation(
location,
);

const metadata = await validatorInstance.getMetadata();
const gitSha = metadata?.git_sha;

if (Object.keys(acceptableValidatorVersions).includes(gitSha)) {
upgradedValidators.push({
chain,
validator,
alias,
version: acceptableValidatorVersions[gitSha],
});
} else {
mismatchedValidators.push({
chain,
validator,
alias,
version: gitSha ? gitSha.slice(0, 7) : 'missing',
age: getCommitAge(gitSha),
});
}
} catch (error) {
console.warn(
`Error getting metadata for validator ${validator} on chain ${chain}: ${error}`,
);
mismatchedValidators.push({
chain,
validator,
alias,
version: ` ??? `,
});
}
}
}),
);

const showUpdatedValidators = () => {
if (showUpdated) {
console.log(
`\n✅ ${upgradedValidators.length} Validators with expected git SHA:`,
);
console.table(upgradedValidators.sort(sortValidatorInfo));
}
};

if (mismatchedValidators.length > 0) {
console.log(
'Expecting validators to have one of the following git SHA:\n' +
Object.entries(acceptableValidatorVersions)
.map(([key, value]) => ` • ${key} (${value})`)
.join('\n'),
);
console.log(
`\n⚠️ ${mismatchedValidators.length} Validators with mismatched git SHA:`,
);
console.table(mismatchedValidators.sort(sortValidatorInfo));

showUpdatedValidators();
process.exit(1);
}

showUpdatedValidators();
console.log('\n✅ All validators running expected git SHA!');
process.exit(0);
}

main().catch(console.error);
11 changes: 11 additions & 0 deletions typescript/sdk/src/aws/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
S3Announcement,
S3CheckpointWithId,
ValidatorConfig,
ValidatorMetadata,
isS3CheckpointWithId,
} from '@hyperlane-xyz/utils';

Expand All @@ -13,6 +14,7 @@ const checkpointWithMessageIdKey = (checkpointIndex: number) =>
`checkpoint_${checkpointIndex}_with_id.json`;
const LATEST_KEY = 'checkpoint_latest_index.json';
const ANNOUNCEMENT_KEY = 'announcement.json';
const METADATA_KEY = 'metadata_latest.json';
const LOCATION_PREFIX = 's3://';

/**
Expand Down Expand Up @@ -76,6 +78,15 @@ export class S3Validator extends BaseValidator {
return resp.data;
}

async getMetadata(): Promise<ValidatorMetadata> {
const resp = await this.s3Bucket.getS3Obj<ValidatorMetadata>(METADATA_KEY);
if (!resp) {
throw new Error(`No metadata found for ${this.config.localDomain}`);
}

return resp.data;
}

async getCheckpoint(index: number): Promise<S3CheckpointWithId | void> {
const key = checkpointWithMessageIdKey(index);
const s3Object = await this.s3Bucket.getS3Obj<S3CheckpointWithId>(key);
Expand Down
1 change: 1 addition & 0 deletions typescript/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export {
S3CheckpointWithId,
SignatureLike,
TokenCaip19Id,
ValidatorMetadata,
WithAddress,
} from './types.js';
export { isHttpsUrl, isRelativeUrl, isUrl } from './url.js';
Expand Down
4 changes: 4 additions & 0 deletions typescript/utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,7 @@ export type ParsedLegacyMultisigIsmMetadata = {
export type Annotated<T> = T & {
annotation?: string;
};

export type ValidatorMetadata = {
git_sha: string;
};

0 comments on commit ef33b5b

Please sign in to comment.