fix: deriving ISM is very slow (#4720)

### Description
This PR adds concurrency back to the modules and use retryAsync.

### Drive-by changes

Add `maxAliasCount` param to yaml reader to prevent `ReferenceError:
Excessive alias count indicates a resource exhaustion attack`

### Backward compatibility
Yes

### Testing
Manual
pull/4659/merge
Lee 1 month ago committed by GitHub
parent 013d3211c0
commit 9de66f0233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/fresh-donkeys-smile.md
  2. 7
      typescript/cli/src/utils/files.ts
  3. 2
      typescript/sdk/src/consts/concurrency.ts
  4. 127
      typescript/sdk/src/hook/EvmHookReader.ts
  5. 97
      typescript/sdk/src/ism/EvmIsmReader.ts

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Enable concurrency for IsmModule

@ -15,6 +15,7 @@ import { objMerge } from '@hyperlane-xyz/utils';
import { log } from '../logger.js';
export const MAX_READ_LINE_OUTPUT = 250;
export const MAX_ALIAS_YAML = 100_000; // Used for yaml maxAliasCount. Ref: https://eemeli.org/yaml/#tojs-options
export type FileFormat = 'yaml' | 'json';
@ -92,7 +93,9 @@ export function mergeJson<T extends Record<string, any>>(
}
export function readYaml<T>(filepath: string): T {
return yamlParse(readFileAtPath(filepath)) as T;
return yamlParse(readFileAtPath(filepath), {
maxAliasCount: MAX_ALIAS_YAML,
}) as T;
}
export function tryReadYamlAtPath<T>(filepath: string): T | null {
@ -250,7 +253,7 @@ export function logYamlIfUnderMaxLines(
): void {
const asYamlString = yamlStringify(obj, null, margin);
const lineCounter = new LineCounter();
parse(asYamlString, { lineCounter });
parse(asYamlString, { lineCounter, maxAliasCount: MAX_ALIAS_YAML });
log(lineCounter.lineStarts.length < maxLines ? asYamlString : '');
}

@ -1 +1 @@
export const DEFAULT_CONTRACT_READ_CONCURRENCY = 1;
export const DEFAULT_CONTRACT_READ_CONCURRENCY = 20;

@ -22,6 +22,7 @@ import {
concurrentMap,
eqAddress,
getLogLevel,
retryAsync,
rootLogger,
} from '@hyperlane-xyz/utils';
@ -95,71 +96,73 @@ export class EvmHookReader extends HyperlaneReader implements HookReader {
}
async deriveHookConfig(address: Address): Promise<DerivedHookConfig> {
let onchainHookType: OnchainHookType | undefined = undefined;
let derivedHookConfig: DerivedHookConfig;
try {
const hook = IPostDispatchHook__factory.connect(address, this.provider);
this.logger.debug('Deriving HookConfig:', { address });
// Temporarily turn off SmartProvider logging
// Provider errors are expected because deriving will call methods that may not exist in the Bytecode
this.setSmartProviderLogLevel('silent');
onchainHookType = await hook.hookType();
switch (onchainHookType) {
case OnchainHookType.ROUTING:
derivedHookConfig = await this.deriveDomainRoutingConfig(address);
break;
case OnchainHookType.AGGREGATION:
derivedHookConfig = await this.deriveAggregationConfig(address);
break;
case OnchainHookType.MERKLE_TREE:
derivedHookConfig = await this.deriveMerkleTreeConfig(address);
break;
case OnchainHookType.INTERCHAIN_GAS_PAYMASTER:
derivedHookConfig = await this.deriveIgpConfig(address);
break;
case OnchainHookType.FALLBACK_ROUTING:
derivedHookConfig = await this.deriveFallbackRoutingConfig(address);
break;
case OnchainHookType.PAUSABLE:
derivedHookConfig = await this.derivePausableConfig(address);
break;
case OnchainHookType.PROTOCOL_FEE:
derivedHookConfig = await this.deriveProtocolFeeConfig(address);
break;
// ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook
// For now assume it's OP_STACK
case OnchainHookType.ID_AUTH_ISM:
derivedHookConfig = await this.deriveOpStackConfig(address);
break;
case OnchainHookType.ARB_L2_TO_L1:
derivedHookConfig = await this.deriveArbL2ToL1Config(address);
break;
default:
throw new Error(
`Unsupported HookType: ${OnchainHookType[onchainHookType]}`,
return retryAsync(async () => {
let onchainHookType: OnchainHookType | undefined = undefined;
let derivedHookConfig: DerivedHookConfig;
try {
const hook = IPostDispatchHook__factory.connect(address, this.provider);
this.logger.debug('Deriving HookConfig:', { address });
// Temporarily turn off SmartProvider logging
// Provider errors are expected because deriving will call methods that may not exist in the Bytecode
this.setSmartProviderLogLevel('silent');
onchainHookType = await hook.hookType();
switch (onchainHookType) {
case OnchainHookType.ROUTING:
derivedHookConfig = await this.deriveDomainRoutingConfig(address);
break;
case OnchainHookType.AGGREGATION:
derivedHookConfig = await this.deriveAggregationConfig(address);
break;
case OnchainHookType.MERKLE_TREE:
derivedHookConfig = await this.deriveMerkleTreeConfig(address);
break;
case OnchainHookType.INTERCHAIN_GAS_PAYMASTER:
derivedHookConfig = await this.deriveIgpConfig(address);
break;
case OnchainHookType.FALLBACK_ROUTING:
derivedHookConfig = await this.deriveFallbackRoutingConfig(address);
break;
case OnchainHookType.PAUSABLE:
derivedHookConfig = await this.derivePausableConfig(address);
break;
case OnchainHookType.PROTOCOL_FEE:
derivedHookConfig = await this.deriveProtocolFeeConfig(address);
break;
// ID_AUTH_ISM could be OPStackHook, ERC5164Hook or LayerZeroV2Hook
// For now assume it's OP_STACK
case OnchainHookType.ID_AUTH_ISM:
derivedHookConfig = await this.deriveOpStackConfig(address);
break;
case OnchainHookType.ARB_L2_TO_L1:
derivedHookConfig = await this.deriveArbL2ToL1Config(address);
break;
default:
throw new Error(
`Unsupported HookType: ${OnchainHookType[onchainHookType]}`,
);
}
} catch (e: any) {
let customMessage: string = `Failed to derive ${onchainHookType} hook (${address})`;
if (
!onchainHookType &&
e.message.includes('Invalid response from provider')
) {
customMessage = customMessage.concat(
` [The provided hook contract might be outdated and not support hookType()]`,
);
this.logger.info(`${customMessage}:\n\t${e}`);
} else {
this.logger.debug(`${customMessage}:\n\t${e}`);
}
throw new Error(`${customMessage}:\n\t${e}`);
} finally {
this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger
}
} catch (e: any) {
let customMessage: string = `Failed to derive ${onchainHookType} hook (${address})`;
if (
!onchainHookType &&
e.message.includes('Invalid response from provider')
) {
customMessage = customMessage.concat(
` [The provided hook contract might be outdated and not support hookType()]`,
);
this.logger.info(`${customMessage}:\n\t${e}`);
} else {
this.logger.debug(`${customMessage}:\n\t${e}`);
}
throw new Error(`${customMessage}:\n\t${e}`);
} finally {
this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger
}
return derivedHookConfig;
return derivedHookConfig;
});
}
async deriveMerkleTreeConfig(

@ -17,6 +17,7 @@ import {
assert,
concurrentMap,
getLogLevel,
retryAsync,
rootLogger,
} from '@hyperlane-xyz/utils';
@ -71,55 +72,57 @@ export class EvmIsmReader extends HyperlaneReader implements IsmReader {
}
async deriveIsmConfig(address: Address): Promise<DerivedIsmConfig> {
let moduleType: ModuleType | undefined = undefined;
let derivedIsmConfig: DerivedIsmConfig;
try {
const ism = IInterchainSecurityModule__factory.connect(
address,
this.provider,
);
this.logger.debug('Deriving IsmConfig:', { address });
// Temporarily turn off SmartProvider logging
// Provider errors are expected because deriving will call methods that may not exist in the Bytecode
this.setSmartProviderLogLevel('silent');
moduleType = await ism.moduleType();
switch (moduleType) {
case ModuleType.UNUSED:
throw new Error('UNUSED does not have a corresponding IsmType');
case ModuleType.ROUTING:
// IsmType is either ROUTING or FALLBACK_ROUTING, but that's determined inside deriveRoutingConfig
derivedIsmConfig = await this.deriveRoutingConfig(address);
break;
case ModuleType.AGGREGATION:
derivedIsmConfig = await this.deriveAggregationConfig(address);
break;
case ModuleType.LEGACY_MULTISIG:
throw new Error('LEGACY_MULTISIG is deprecated and not supported');
case ModuleType.MERKLE_ROOT_MULTISIG:
case ModuleType.MESSAGE_ID_MULTISIG:
derivedIsmConfig = await this.deriveMultisigConfig(address);
break;
case ModuleType.NULL:
derivedIsmConfig = await this.deriveNullConfig(address);
break;
case ModuleType.CCIP_READ:
throw new Error('CCIP_READ does not have a corresponding IsmType');
case ModuleType.ARB_L2_TO_L1:
return this.deriveArbL2ToL1Config(address);
default:
throw new Error(`Unknown ISM ModuleType: ${moduleType}`);
return retryAsync(async () => {
let moduleType: ModuleType | undefined = undefined;
let derivedIsmConfig: DerivedIsmConfig;
try {
const ism = IInterchainSecurityModule__factory.connect(
address,
this.provider,
);
this.logger.debug('Deriving IsmConfig:', { address });
// Temporarily turn off SmartProvider logging
// Provider errors are expected because deriving will call methods that may not exist in the Bytecode
this.setSmartProviderLogLevel('silent');
moduleType = await ism.moduleType();
switch (moduleType) {
case ModuleType.UNUSED:
throw new Error('UNUSED does not have a corresponding IsmType');
case ModuleType.ROUTING:
// IsmType is either ROUTING or FALLBACK_ROUTING, but that's determined inside deriveRoutingConfig
derivedIsmConfig = await this.deriveRoutingConfig(address);
break;
case ModuleType.AGGREGATION:
derivedIsmConfig = await this.deriveAggregationConfig(address);
break;
case ModuleType.LEGACY_MULTISIG:
throw new Error('LEGACY_MULTISIG is deprecated and not supported');
case ModuleType.MERKLE_ROOT_MULTISIG:
case ModuleType.MESSAGE_ID_MULTISIG:
derivedIsmConfig = await this.deriveMultisigConfig(address);
break;
case ModuleType.NULL:
derivedIsmConfig = await this.deriveNullConfig(address);
break;
case ModuleType.CCIP_READ:
throw new Error('CCIP_READ does not have a corresponding IsmType');
case ModuleType.ARB_L2_TO_L1:
return this.deriveArbL2ToL1Config(address);
default:
throw new Error(`Unknown ISM ModuleType: ${moduleType}`);
}
} catch (e: any) {
const errorMessage = `Failed to derive ISM module type ${moduleType} on ${this.chain} (${address}) :\n\t${e}`;
this.logger.debug(errorMessage);
throw new Error(errorMessage);
} finally {
this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger
}
} catch (e: any) {
const errorMessage = `Failed to derive ISM module type ${moduleType} on ${this.chain} (${address}) :\n\t${e}`;
this.logger.debug(errorMessage);
throw new Error(errorMessage);
} finally {
this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger
}
return derivedIsmConfig;
return derivedIsmConfig;
});
}
async deriveRoutingConfig(

Loading…
Cancel
Save