Skip to content

Commit

Permalink
Implement methods to adding/removing chains to a LocalRegistry (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmrossy authored Apr 20, 2024
1 parent dc688ea commit d9989d5
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/three-rice-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/registry': patch
---

Implement methods to adding/removing chains to a LocalRegistry
5 changes: 3 additions & 2 deletions scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ChainMetadataSchemaObject } from '@hyperlane-xyz/sdk';
import { pick } from '@hyperlane-xyz/utils';
import fs from 'fs';
Expand All @@ -24,8 +25,8 @@ if (fs.existsSync('./tmp')) fs.rmSync(`./tmp`, { recursive: true });
// Start with the contents of src, which we will add to in this script
fs.cpSync(`./src`, `./tmp`, { recursive: true });

let chainMetadata = {};
let chainAddresses = {};
const chainMetadata = {};
const chainAddresses = {};

console.log('Parsing and copying chain data');
for (const file of fs.readdirSync('./chains')) {
Expand Down
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CHAIN_SCHEMA_REF = '# yaml-language-server: $schema=../schema.json';
13 changes: 12 additions & 1 deletion src/registry/BaseRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Logger } from 'pino';

import type { ChainMap, ChainMetadata, ChainName } from '@hyperlane-xyz/sdk';
import { ChainAddresses, MaybePromise } from '../types.js';
import type { ChainAddresses, MaybePromise } from '../types.js';
import type { IRegistry, RegistryContent, RegistryType } from './IRegistry.js';

export const CHAIN_FILE_REGEX = /chains\/([a-z]+)\/([a-z]+)\.yaml/;
Expand Down Expand Up @@ -36,4 +36,15 @@ export abstract class BaseRegistry implements IRegistry {
abstract getChainMetadata(chainName: ChainName): MaybePromise<ChainMetadata | null>;
abstract getAddresses(): MaybePromise<ChainMap<ChainAddresses>>;
abstract getChainAddresses(chainName: ChainName): MaybePromise<ChainAddresses | null>;
abstract addChain(chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
abstract updateChain(chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
abstract removeChain(chains: ChainName): MaybePromise<void>;
}
18 changes: 18 additions & 0 deletions src/registry/GithubRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ export class GithubRegistry extends BaseRegistry implements IRegistry {
return ChainAddressesSchema.parse(yamlParse(data));
}

async addChain(_chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): Promise<void> {
throw new Error('TODO: Implement');
}
async updateChain(_chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): Promise<void> {
throw new Error('TODO: Implement');
}
async removeChain(_chains: ChainName): Promise<void> {
throw new Error('TODO: Implement');
}

protected getRawContentUrl(path: string): string {
return `https://raw.githubusercontent.com/${this.repoOwner}/${this.repoName}/${this.branch}/${path}`;
}
Expand Down
16 changes: 14 additions & 2 deletions src/registry/IRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ChainMap, ChainMetadata, ChainName } from '@hyperlane-xyz/sdk';
import { ChainAddresses, MaybePromise } from '../types.js';
import type { ChainAddresses, MaybePromise } from '../types.js';

export interface ChainFiles {
metadata?: string;
Expand All @@ -26,5 +26,17 @@ export interface IRegistry {
getChainMetadata(chainName: ChainName): MaybePromise<ChainMetadata | null>;
getAddresses(): MaybePromise<ChainMap<ChainAddresses>>;
getChainAddresses(chainName: ChainName): MaybePromise<ChainAddresses | null>;
// TODO: Define write-related methods
addChain(chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
updateChain(chains: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): MaybePromise<void>;
removeChain(chains: ChainName): MaybePromise<void>;

// TODO define deployment artifact related methods
}
99 changes: 98 additions & 1 deletion src/registry/LocalRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import path from 'path';
import type { Logger } from 'pino';
import { parse as yamlParse } from 'yaml';

import { type ChainMap, type ChainMetadata, type ChainName } from '@hyperlane-xyz/sdk';
import type { ChainMap, ChainMetadata, ChainName } from '@hyperlane-xyz/sdk';

import { CHAIN_SCHEMA_REF } from '../consts.js';
import { ChainAddresses, ChainAddressesSchema } from '../types.js';
import { toYamlString } from '../utils.js';
import { BaseRegistry, CHAIN_FILE_REGEX } from './BaseRegistry.js';
import {
RegistryType,
Expand Down Expand Up @@ -89,6 +91,40 @@ export class LocalRegistry extends BaseRegistry implements IRegistry {
return addresses[chainName];
}

addChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): void {
const currentChains = this.listRegistryContent().chains;
if (currentChains[chain.chainName])
throw new Error(`Chain ${chain.chainName} already exists in registry`);

this.createOrUpdateChain(chain);
}

updateChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): void {
const currentChains = this.listRegistryContent();
if (!currentChains.chains[chain.chainName]) {
this.logger.debug(`Chain ${chain.chainName} not found in registry, adding it now`);
}
this.createOrUpdateChain(chain);
}

removeChain(chainName: ChainName): void {
const currentChains = this.listRegistryContent().chains;
if (!currentChains[chainName]) throw new Error(`Chain ${chainName} does not exist in registry`);

this.removeFiles(Object.values(currentChains[chainName]));
if (this.listContentCache?.chains[chainName]) delete this.listContentCache.chains[chainName];
if (this.metadataCache?.[chainName]) delete this.metadataCache[chainName];
if (this.addressCache?.[chainName]) delete this.addressCache[chainName];
}

protected listFiles(dirPath: string): string[] {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });

Expand All @@ -99,4 +135,65 @@ export class LocalRegistry extends BaseRegistry implements IRegistry {

return filePaths.flat();
}

protected createOrUpdateChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): void {
if (!chain.metadata && !chain.addresses)
throw new Error(`Chain ${chain.chainName} must have metadata or addresses, preferably both`);

const currentChains = this.listRegistryContent();
if (!currentChains.chains[chain.chainName]) {
this.logger.debug(`Chain ${chain.chainName} not found in registry, adding it now`);
}

if (chain.metadata) {
this.createChainFile(
chain.chainName,
'metadata',
chain.metadata,
this.getMetadata(),
CHAIN_SCHEMA_REF,
);
}
if (chain.addresses) {
this.createChainFile(chain.chainName, 'addresses', chain.addresses, this.getAddresses());
}
}

protected createChainFile(
chainName: ChainName,
fileName: keyof ChainFiles,
data: any,
cache: ChainMap<any>,
prefix?: string,
) {
const filePath = path.join(this.uri, this.getChainsPath(), chainName, `${fileName}.yaml`);
const currentChains = this.listRegistryContent().chains;
currentChains[chainName] ||= {};
currentChains[chainName][fileName] = filePath;
cache[chainName] = data;
this.createFile({ filePath, data: toYamlString(data, prefix) });
}

protected createFile(file: { filePath: string; data: string }): void {
const dirPath = path.dirname(file.filePath);
if (!fs.existsSync(dirPath))
fs.mkdirSync(dirPath, {
recursive: true,
});
fs.writeFileSync(file.filePath, file.data);
}

protected removeFiles(filePaths: string[]): void {
for (const filePath of filePaths) {
fs.unlinkSync(filePath);
}
const parentDir = path.dirname(filePaths[0]);
if (fs.readdirSync(parentDir).length === 0) {
fs.rmdirSync(parentDir);
}
}
}
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { stringify } from 'yaml';

export function toYamlString(data: any, prefix?: string): string {
const yamlString = stringify(data);
return prefix ? `${prefix}\n${yamlString}` : yamlString;
}
26 changes: 26 additions & 0 deletions test/unit/registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { expect } from 'chai';

import type { ChainMetadata } from '@hyperlane-xyz/sdk';
import { GithubRegistry } from '../../src/registry/GithubRegistry.js';
import { LocalRegistry } from '../../src/registry/LocalRegistry.js';
import { ChainAddresses } from '../../src/types.js';

const MOCK_CHAIN_NAME = 'mockchain';

describe('Registry utilities', () => {
const githubRegistry = new GithubRegistry();
Expand Down Expand Up @@ -48,5 +52,27 @@ describe('Registry utilities', () => {
expect(Object.keys(addresses).length).to.be.greaterThan(0);
// Note the short timeout to ensure result is coming from cache
}).timeout(250);

// TODO remove this once GitHubRegistry methods are implemented
if (registry.type === 'github') continue;

it(`Adds a new chain for ${registry.type} registry`, async () => {
const mockMetadata: ChainMetadata = {
...(await registry.getChainMetadata('ethereum')),
name: MOCK_CHAIN_NAME,
};
const mockAddresses: ChainAddresses = await registry.getChainAddresses('ethereum');
await registry.addChain({
chainName: MOCK_CHAIN_NAME,
metadata: mockMetadata,
addresses: mockAddresses,
});
expect((await registry.getChains()).includes(MOCK_CHAIN_NAME)).to.be.true;
}).timeout(5_000);

it(`Removes a chain for ${registry.type} registry`, async () => {
await registry.removeChain('mockchain');
expect((await registry.getChains()).includes(MOCK_CHAIN_NAME)).to.be.false;
}).timeout(5_000);
}
});

0 comments on commit d9989d5

Please sign in to comment.