cosmic-interchain-cli
Version:
A command-line utility for Cosmic Wire's interchain messaging protocol
236 lines • 11.7 kB
JavaScript
import { ECDSAStakeRegistry__factory, IDelegationManager__factory, MerkleTreeHook__factory, ValidatorAnnounce__factory, } from '@hyperlane-xyz/core';
import { ProtocolType, isObjEmpty } from '@hyperlane-xyz/utils';
import { errorRed, log, logBlue, logBlueKeyValue, logBoldBlue, logDebug, logGreen, warnYellow, } from '../logger.js';
import { indentYamlOrJson } from '../utils/files.js';
import { getLatestMerkleTreeCheckpointIndex, getLatestValidatorCheckpointIndexAndUrl, getValidatorStorageLocations, isValidatorSigningLatestCheckpoint, } from '../validator/utils.js';
import { avsAddresses } from './config.js';
import { readOperatorFromEncryptedJson } from './stakeRegistry.js';
export const checkValidatorAvsSetup = async (chain, context, operatorKeyPath, operatorAddress) => {
logBlue(`Checking AVS validator status for ${chain}, ${!operatorKeyPath ? 'this may take up to a minute to run' : ''}...`);
const { multiProvider } = context;
const topLevelErrors = [];
let operatorWallet;
if (operatorKeyPath) {
operatorWallet = await readOperatorFromEncryptedJson(operatorKeyPath);
}
const avsOperatorRecord = await getAvsOperators(chain, multiProvider, topLevelErrors, operatorAddress ?? operatorWallet?.address);
await setOperatorName(chain, avsOperatorRecord, multiProvider, topLevelErrors);
if (!isObjEmpty(avsOperatorRecord)) {
await setValidatorInfo(context, avsOperatorRecord, topLevelErrors);
}
logOutput(avsOperatorRecord, topLevelErrors);
};
const getAvsOperators = async (chain, multiProvider, topLevelErrors, operatorKey) => {
const avsOperators = {};
const ecdsaStakeRegistryAddress = getEcdsaStakeRegistryAddress(chain, topLevelErrors);
if (!ecdsaStakeRegistryAddress) {
return avsOperators;
}
const ecdsaStakeRegistry = ECDSAStakeRegistry__factory.connect(ecdsaStakeRegistryAddress, multiProvider.getProvider(chain));
if (operatorKey) {
// If operator key is provided, only fetch the operator's validator info
const signingKey = await ecdsaStakeRegistry.getLastestOperatorSigningKey(operatorKey);
avsOperators[signingKey] = {
operatorAddress: operatorKey,
chains: {},
};
return avsOperators;
}
const filter = ecdsaStakeRegistry.filters.SigningKeyUpdate(null, null);
const provider = multiProvider.getProvider(chain);
const latestBlock = await provider.getBlockNumber();
const blockLimit = 50000; // 50k blocks per query
let fromBlock = 1625972; // when ecdsaStakeRegistry was deployed
while (fromBlock < latestBlock) {
const toBlock = Math.min(fromBlock + blockLimit, latestBlock);
const logs = await ecdsaStakeRegistry.queryFilter(filter, fromBlock, toBlock);
logs.forEach((log) => {
const event = ecdsaStakeRegistry.interface.parseLog(log);
const operatorKey = event.args.operator;
const signingKey = event.args.newSigningKey;
if (avsOperators[signingKey]) {
avsOperators[signingKey].operatorAddress = operatorKey;
}
else {
avsOperators[signingKey] = {
operatorAddress: operatorKey,
chains: {},
};
}
});
fromBlock = toBlock + 1;
}
return avsOperators;
};
const getAVSMetadataURI = async (chain, operatorAddress, multiProvider) => {
const delegationManagerAddress = avsAddresses[chain]['delegationManager'];
const delegationManager = IDelegationManager__factory.connect(delegationManagerAddress, multiProvider.getProvider(chain));
const filter = delegationManager.filters.OperatorMetadataURIUpdated(operatorAddress, null);
const provider = multiProvider.getProvider(chain);
const latestBlock = await provider.getBlockNumber();
const blockLimit = 50000; // 50k blocks per query
let fromBlock = 17445563;
while (fromBlock < latestBlock) {
const toBlock = Math.min(fromBlock + blockLimit, latestBlock);
const logs = await delegationManager.queryFilter(filter, fromBlock, toBlock);
if (logs.length > 0) {
const event = delegationManager.interface.parseLog(logs[0]);
return event.args.metadataURI;
}
fromBlock = toBlock + 1;
}
return undefined;
};
const setOperatorName = async (chain, avsOperatorRecord, multiProvider, topLevelErrors = []) => {
for (const [_, validatorInfo] of Object.entries(avsOperatorRecord)) {
const metadataURI = await getAVSMetadataURI(chain, validatorInfo.operatorAddress, multiProvider);
if (metadataURI) {
const operatorName = await fetchOperatorName(metadataURI);
if (operatorName) {
validatorInfo.operatorName = operatorName;
}
else {
topLevelErrors.push(`❗️ Failed to fetch operator name from metadataURI: ${metadataURI}`);
}
}
}
};
const setValidatorInfo = async (context, avsOperatorRecord, topLevelErrors) => {
const { multiProvider, registry, chainMetadata } = context;
const failedToReadChains = [];
const validatorAddresses = Object.keys(avsOperatorRecord);
const chains = await registry.getChains();
const addresses = await registry.getAddresses();
for (const chain of chains) {
// skip if chain is not an Ethereum chain
if (chainMetadata[chain].protocol !== ProtocolType.Ethereum)
continue;
const chainAddresses = addresses[chain];
// skip if no contract addresses are found for this chain
if (chainAddresses === undefined)
continue;
if (!chainAddresses.validatorAnnounce) {
topLevelErrors.push(`❗️ ValidatorAnnounce is not deployed on ${chain}`);
}
if (!chainAddresses.merkleTreeHook) {
topLevelErrors.push(`❗️ MerkleTreeHook is not deployed on ${chain}`);
}
if (!chainAddresses.validatorAnnounce || !chainAddresses.merkleTreeHook) {
continue;
}
const validatorAnnounce = ValidatorAnnounce__factory.connect(chainAddresses.validatorAnnounce, multiProvider.getProvider(chain));
const merkleTreeHook = MerkleTreeHook__factory.connect(chainAddresses.merkleTreeHook, multiProvider.getProvider(chain));
const latestMerkleTreeCheckpointIndex = await getLatestMerkleTreeCheckpointIndex(merkleTreeHook, chain);
const validatorStorageLocations = await getValidatorStorageLocations(validatorAnnounce, validatorAddresses, chain);
if (!validatorStorageLocations) {
failedToReadChains.push(chain);
continue;
}
for (let i = 0; i < validatorAddresses.length; i++) {
const validatorAddress = validatorAddresses[i];
const storageLocation = validatorStorageLocations[i];
const warnings = [];
// Skip if no storage location is found, address is not validating on this chain or if storage location string doesn't not start with s3://
if (storageLocation.length === 0 ||
!storageLocation[0].startsWith('s3://')) {
continue;
}
const [latestValidatorCheckpointIndex, latestCheckpointUrl] = (await getLatestValidatorCheckpointIndexAndUrl(storageLocation[0])) ?? [
undefined,
undefined,
];
if (!latestMerkleTreeCheckpointIndex) {
warnings.push(`❗️ Failed to fetch latest checkpoint index of merkleTreeHook on ${chain}.`);
}
if (!latestValidatorCheckpointIndex) {
warnings.push(`❗️ Failed to fetch latest signed checkpoint index of validator on ${chain}, this is likely due to failing to read an S3 bucket`);
}
let validatorSynced = undefined;
if (latestMerkleTreeCheckpointIndex && latestValidatorCheckpointIndex) {
validatorSynced = isValidatorSigningLatestCheckpoint(latestValidatorCheckpointIndex, latestMerkleTreeCheckpointIndex);
}
const chainInfo = {
storageLocation: latestCheckpointUrl,
latestMerkleTreeCheckpointIndex,
latestValidatorCheckpointIndex,
validatorSynced,
warnings,
};
const validatorInfo = avsOperatorRecord[validatorAddress];
if (validatorInfo) {
validatorInfo.chains[chain] = chainInfo;
}
}
}
if (failedToReadChains.length > 0) {
topLevelErrors.push(`❗️ Failed to read storage locations onchain for ${failedToReadChains.join(', ')}`);
}
};
const logOutput = (avsKeysRecord, topLevelErrors) => {
if (topLevelErrors.length > 0) {
for (const error of topLevelErrors) {
errorRed(error);
}
}
for (const [validatorAddress, data] of Object.entries(avsKeysRecord)) {
log('\n\n');
if (data.operatorName)
logBlueKeyValue('Operator name', data.operatorName);
logBlueKeyValue('Operator address', data.operatorAddress);
logBlueKeyValue('Validator address', validatorAddress);
if (!isObjEmpty(data.chains)) {
logBoldBlue(indentYamlOrJson('Validating on...', 2));
for (const [chain, chainInfo] of Object.entries(data.chains)) {
logBoldBlue(indentYamlOrJson(chain, 2));
if (chainInfo.storageLocation) {
logBlueKeyValue(indentYamlOrJson('Storage location', 2), chainInfo.storageLocation);
}
if (chainInfo.latestMerkleTreeCheckpointIndex) {
logBlueKeyValue(indentYamlOrJson('Latest merkle tree checkpoint index', 2), String(chainInfo.latestMerkleTreeCheckpointIndex));
}
if (chainInfo.latestValidatorCheckpointIndex) {
logBlueKeyValue(indentYamlOrJson('Latest validator checkpoint index', 2), String(chainInfo.latestValidatorCheckpointIndex));
if (chainInfo.validatorSynced) {
logGreen(indentYamlOrJson('✅ Validator is signing latest checkpoint', 2));
}
else {
errorRed(indentYamlOrJson('❌ Validator is not signing latest checkpoint', 2));
}
}
else {
errorRed(indentYamlOrJson('❌ Failed to fetch latest signed checkpoint index', 2));
}
if (chainInfo.warnings && chainInfo.warnings.length > 0) {
warnYellow(indentYamlOrJson('The following warnings were encountered:', 2));
for (const warning of chainInfo.warnings) {
warnYellow(indentYamlOrJson(warning, 3));
}
}
}
}
else {
logBlue('Validator is not validating on any chain');
}
}
};
const getEcdsaStakeRegistryAddress = (chain, topLevelErrors) => {
try {
return avsAddresses[chain]['ecdsaStakeRegistry'];
}
catch (err) {
topLevelErrors.push(`❗️ EcdsaStakeRegistry address not found for ${chain}`);
return undefined;
}
};
const fetchOperatorName = async (metadataURI) => {
try {
const response = await fetch(metadataURI);
const data = await response.json();
return data['name'];
}
catch (err) {
logDebug(`Failed to fetch operator name from ${metadataURI}: ${err}`);
return undefined;
}
};
//# sourceMappingURL=check.js.map