Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent Config Typescript Definitions #2658

Merged
merged 5 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions typescript/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,27 +116,30 @@ export {
AgentConfig,
AgentConnection,
AgentConnectionType,
AgentMetadataExtSchema,
AgentMetadataExtension,
AgentSigner,
ChainMetadataForAgent,
ChainMetadataForAgentSchema,
CombinedAgentConfig,
AgentSignerSchema,
AgentSigner2,
AgentChainMetadata,
AgentChainMetadataSchema,
AgentConfigSchema,
AgentLogLevel,
AgentLogFormat,
AgentConfig2,
buildAgentConfig,
buildAgentConfigDeprecated,
buildAgentConfigNew,
} from './metadata/agentConfig';
export {
ChainMetadata,
ChainMetadataSchema,
RpcUrlSchema,
RpcUrl,
ExplorerFamily,
ExplorerFamilyValue,
getDomainId,
isValidChainMetadata,
} from './metadata/chainMetadataTypes';
export {
ChainMetadataWithArtifacts,
ChainMetadataWithArtifactsSchema,
HyperlaneDeploymentArtifacts,
HyperlaneDeploymentArtifactsSchema,
} from './metadata/deploymentArtifacts';
Expand Down
299 changes: 262 additions & 37 deletions typescript/sdk/src/metadata/agentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

import {
ChainMetadataWithArtifactsSchema,
ChainMetadata,
ChainMetadataSchema,
RpcUrlSchema,
ZHash,
ZNzUint,
ZUWei,
ZUint,
} from './chainMetadataTypes';
import {
HyperlaneDeploymentArtifacts,
HyperlaneDeploymentArtifactsSchema,
} from './deploymentArtifacts';

/**
* New agent config shape that extends the existing chain metadata with agent-specific fields.
*/
import { MatchingListSchema } from './matchingList';

export enum AgentConnectionType {
Http = 'http',
Expand All @@ -21,42 +27,263 @@ export enum AgentConnectionType {
HttpFallback = 'httpFallback',
}

export const AgentMetadataExtSchema = z.object({
rpcConsensusType: z
.nativeEnum(AgentConnectionType)
.default(AgentConnectionType.HttpFallback)
export enum AgentConsensusType {
Fallback = 'fallback',
Quorum = 'quorum',
}

export enum AgentLogLevel {
Off = 'off',
Error = 'error',
Warn = 'warn',
Info = 'info',
Debug = 'debug',
Trace = 'trace',
}

export enum AgentLogFormat {
Json = 'json',
Compact = 'compact',
Full = 'full',
Pretty = 'pretty',
}

export enum AgentIndexMode {
Block = 'block',
Sequence = 'sequence',
}

export const AgentSignerSchema = z.union([
mattiekat marked this conversation as resolved.
Show resolved Hide resolved
z
.object({
type: z.literal('hexKey').optional(),
key: ZHash,
})
.describe('A local hex key'),
z
.object({
type: z.literal('aws').optional(),
id: z.string().describe('The UUID identifying the AWS KMS key'),
region: z.string().describe('The AWS region'),
})
.describe(
'The consensus type to use when multiple RPCs are configured. `fallback` will use the first RPC that returns a result, `quorum` will require a majority of RPCs to return the same result. Different consumers may choose to default to different values here, i.e. validators may want to default to `quorum` while relayers may want to default to `fallback`.',
'An AWS signer. Note that AWS credentials must be inserted into the env separately.',
),
overrideRpcUrls: z
.string()
z
.object({
type: z.literal('node'),
})
.describe('Assume the local node will sign on RPC calls automatically'),
]);

export type AgentSigner2 = z.infer<typeof AgentSignerSchema>;
mattiekat marked this conversation as resolved.
Show resolved Hide resolved

export const AgentChainMetadataSchema = ChainMetadataSchema.merge(
HyperlaneDeploymentArtifactsSchema,
).extend({
customRpcUrls: z
.record(
RpcUrlSchema.extend({
priority: ZNzUint.optional().describe(
'The priority of this RPC relative to the others defined. A larger value means it will be preferred. Only effects some AgentConsensusTypes.',
),
}),
)
.refine((data) => Object.keys(data).length > 0, {
message:
'Must specify at least one RPC url if not using the default rpcUrls.',
})
.optional()
.describe(
'Used to allow for a comma-separated list of RPC URLs to be specified without a complex `path` in the agent configuration scheme. Agents should check for the existence of this field first and use that in conjunction with `rpcConsensusType` if it exists, otherwise fall back to `rpcUrls`.',
'Specify a custom RPC endpoint configuration for this chain. If this is set, then none of the `rpcUrls` will be used for this chain. The key value can be any valid string.',
),
rpcConsensusType: z
.nativeEnum(AgentConsensusType)
.describe('The consensus type to use when multiple RPCs are configured.')
.optional(),
signer: AgentSignerSchema.optional().describe(
'The signer to use for this chain',
),
index: z.object({
from: z
.number()
.default(1999)
.optional()
.describe('The starting block from which to index events.'),
chunk: z
.number()
.default(1000)
from: ZUint.optional().describe(
'The starting block from which to index events.',
),
chunk: ZNzUint.optional().describe(
'The number of blocks to index at a time.',
),
// TODO(2214): I think we can always interpret this from the ProtocolType
mode: z
.nativeEnum(AgentIndexMode)
.optional()
.describe('The number of blocks to index per chunk.'),
.describe(
'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.',
),
}),
});

export type AgentMetadataExtension = z.infer<typeof AgentMetadataExtSchema>;
export type AgentChainMetadata = z.infer<typeof AgentChainMetadataSchema>;

export const ChainMetadataForAgentSchema =
ChainMetadataWithArtifactsSchema.merge(AgentMetadataExtSchema);
export const AgentConfigSchema = z.object({
metricsPort: ZNzUint.lte(65535)
.optional()
.describe(
'The port to expose prometheus metrics on. Accessible via `GET /metrics`.',
),
chains: z
.record(AgentChainMetadataSchema)
.describe('Chain metadata for all chains that the agent will index.')
.superRefine((data, ctx) => {
for (const c in data) {
if (c != data[c].name) {
ctx.addIssue({
message: `Chain name ${c} does not match chain name in metadata ${data[c].name}`,
code: z.ZodIssueCode.custom,
});
}
}
}),
defaultSigner: AgentSignerSchema.optional().describe(
'Default signer to use for any chains that have not defined their own.',
),
defaultRpcConsensusType: z
.nativeEnum(AgentConsensusType)
.describe(
'The default consensus type to use for any chains that have not defined their own.',
)
.optional(),
log: z
.object({
format: z
.nativeEnum(AgentLogFormat)
.optional()
.describe('The format to use for tracing logs.'),
level: z
.nativeEnum(AgentLogLevel)
.optional()
.describe("The log level to use for the agent's logs."),
})
.optional(),
});

export type ChainMetadataForAgent<Ext = object> = z.infer<
typeof ChainMetadataForAgentSchema
> &
Ext;
const CommaSeperatedChainList = z.string().regex(/^[a-z0-9]+(,[a-z0-9]+)*$/);
const CommaSeperatedDomainList = z.string().regex(/^\d+(,\d+)*$/);

const GasPaymentEnforcementBaseSchema = z.object({
matchingList: MatchingListSchema.optional().describe(
'An optional matching list, any message that matches will use this policy. By default all messages will match.',
),
});
const GasPaymentEnforcementSchema = z.union([
GasPaymentEnforcementBaseSchema.extend({
type: z.literal('none').optional(),
}),
GasPaymentEnforcementBaseSchema.extend({
type: z.literal('minimum').optional(),
payment: ZUWei,
matchingList: MatchingListSchema.optional().describe(
'An optional matching list, any message that matches will use this policy. By default all messages will match.',
),
}),
GasPaymentEnforcementBaseSchema.extend({
type: z.literal('onChainFeeQuoting'),
gasFraction: z.string().regex(/^\d+ ?\/ ?[1-9]\d*$/),
matchingList: MatchingListSchema.optional().describe(
'An optional matching list, any message that matches will use this policy. By default all messages will match.',
),
}),
]);

export type GasPaymentEnforcement = z.infer<typeof GasPaymentEnforcementSchema>;

export const RelayerAgentConfigSchema = AgentConfigSchema.extend({
db: z
.string()
.nonempty()
.optional()
.describe('The path to the relayer database.'),
relayChains: CommaSeperatedChainList.describe(
'Comma seperated list of chains to relay messages between.',
),
gasPaymentEnforcement: z
.union([GasPaymentEnforcementSchema, z.string().nonempty()])
.optional()
.describe(
'The gas payment enforcement configuration as JSON. Expects an ordered array of `GasPaymentEnforcementConfig`.',
),
whitelist: z
.union([MatchingListSchema, z.string().nonempty()])
.optional()
.describe(
'If no whitelist is provided ALL messages will be considered on the whitelist.',
),
blacklist: z
.union([MatchingListSchema, z.string().nonempty()])
.optional()
.describe(
'If no blacklist is provided ALL will be considered to not be on the blacklist.',
),
transactionGasLimit: ZUWei.optional().describe(
'This is optional. If not specified, any amount of gas will be valid, otherwise this is the max allowed gas in wei to relay a transaction.',
),
skipTransactionGasLimitFor: CommaSeperatedDomainList.optional().describe(
'Comma separated List of chain names to skip applying the transaction gas limit to.',
),
allowLocalCheckpointSyncers: z
.boolean()
.optional()
.describe(
'If true, allows local storage based checkpoint syncers. Not intended for production use.',
),
});

export type RelayerConfig = z.infer<typeof RelayerAgentConfigSchema>;

export const ScraperAgentConfigSchema = AgentConfigSchema.extend({
db: z.string().nonempty().describe('Database connection string'),
chainsToScrape: CommaSeperatedChainList.describe(
'Comma separated list of chain names to scrape',
),
});

export type ScraperConfig = z.infer<typeof ScraperAgentConfigSchema>;

export const ValidatorAgentConfigSchema = AgentConfigSchema.extend({
db: z
.string()
.nonempty()
.optional()
.describe('The path to the validator database.'),
originChainName: z
.string()
.nonempty()
.describe('Name of the chain to validate messages on'),
validator: AgentSignerSchema.describe('The validator attestation signer'),
checkpointSyncer: z.discriminatedUnion('type', [
z
.object({
type: z.literal('localStorage'),
path: z
.string()
.nonempty()
.describe('Path to the local storage location'),
})
.describe('A local checkpoint syncer'),
z
.object({
type: z.literal('s3'),
bucket: z.string().nonempty(),
region: z.string().nonempty(),
})
.describe('A checkpoint syncer that uses S3'),
]),
interval: ZUint.optional().describe(
'How long to wait between checking for new checkpoints in seconds.',
),
});

export type ValidatorConfig = z.infer<typeof ValidatorAgentConfigSchema>;

export type AgentConfig2 = z.infer<typeof AgentConfigSchema>;
mattiekat marked this conversation as resolved.
Show resolved Hide resolved

/**
* Deprecated agent config shapes.
Expand Down Expand Up @@ -108,13 +335,12 @@ export function buildAgentConfigNew(
multiProvider: MultiProvider,
addresses: ChainMap<HyperlaneDeploymentArtifacts>,
startBlocks: ChainMap<number>,
): ChainMap<ChainMetadataForAgent> {
const configs: ChainMap<ChainMetadataForAgent> = {};
): ChainMap<AgentChainMetadata> {
const configs: ChainMap<AgentChainMetadata> = {};
for (const chain of [...chains].sort()) {
const metadata = multiProvider.getChainMetadata(chain);
const config: ChainMetadataForAgent = {
const metadata: ChainMetadata = multiProvider.getChainMetadata(chain);
mattiekat marked this conversation as resolved.
Show resolved Hide resolved
const config: AgentChainMetadata = {
...metadata,
rpcConsensusType: AgentConnectionType.HttpFallback,
mailbox: addresses[chain].mailbox,
interchainGasPaymaster: addresses[chain].interchainGasPaymaster,
validatorAnnounce: addresses[chain].validatorAnnounce,
Expand Down Expand Up @@ -161,9 +387,8 @@ export function buildAgentConfigDeprecated(
return agentConfig;
}

// For compat with the older agent config shape, we return a combination
// of the two schemas (ChainMap<ChainMetadataForAgent> & AgentConfig).
export type CombinedAgentConfig = ChainMap<ChainMetadataForAgent> | AgentConfig;
// TODO(2215): this eventually needs to to be replaced with just `AgentConfig2` (and that ident needs renaming)
export type CombinedAgentConfig = AgentConfig2['chains'] | AgentConfig;

export function buildAgentConfig(
chains: ChainName[],
Expand Down
Loading