-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add metadata health tests and improve export types (#9)
- Loading branch information
Showing
8 changed files
with
276 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hyperlane-xyz/registry': patch | ||
--- | ||
|
||
Improve types for chain metadata exports |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
name: cron | ||
|
||
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows | ||
on: | ||
schedule: | ||
- cron: '45 14 * * *' | ||
workflow_dispatch: | ||
|
||
env: | ||
LOG_LEVEL: DEBUG | ||
LOG_FORMAT: PRETTY | ||
|
||
jobs: | ||
install: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/cache@v3 | ||
with: | ||
path: | | ||
**/node_modules | ||
.yarn/cache | ||
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} | ||
- name: yarn-install | ||
run: yarn install | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
needs: [install] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/cache@v3 | ||
with: | ||
path: | | ||
**/node_modules | ||
.yarn/cache | ||
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} | ||
- name: build | ||
run: yarn run build | ||
|
||
metadata-health: | ||
runs-on: ubuntu-latest | ||
needs: [build] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/cache@v3 | ||
with: | ||
path: | | ||
**/node_modules | ||
.yarn/cache | ||
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} | ||
|
||
- name: Metadata Health Check | ||
run: yarn test:health | ||
|
||
- name: Post to discord webhook if metadata check fails | ||
if: failure() | ||
run: | | ||
curl -X POST -H 'Content-type: application/json' --data '{"content":"SDK metadata check failed, see ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' ${{ secrets.DISCORD_WEBHOOK_URL }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { ChainMetadata } from '@hyperlane-xyz/sdk'; | ||
import { | ||
getExplorerAddressUrl, | ||
getExplorerBaseUrl, | ||
getExplorerTxUrl, | ||
} from '@hyperlane-xyz/sdk/dist/metadata/blockExplorer.js'; | ||
import { Address, ProtocolType, sleep } from '@hyperlane-xyz/utils'; | ||
import { expect } from 'chai'; | ||
import { chainMetadata } from '../../dist/index.js'; | ||
|
||
const HEALTH_CHECK_TIMEOUT = 10_000; // 10s | ||
const HEALTH_CHECK_DELAY = 3_000; // 3s | ||
|
||
const PROTOCOL_TO_ADDRESS: Record<ProtocolType, Address> = { | ||
[ProtocolType.Ethereum]: '0x0000000000000000000000000000000000000000', | ||
[ProtocolType.Sealevel]: '11111111111111111111111111111111', | ||
[ProtocolType.Cosmos]: 'cosmos100000000000000000000000000000000000000', | ||
[ProtocolType.Fuel]: '', | ||
}; | ||
|
||
const PROTOCOL_TO_TX_HASH: Record<ProtocolType, Address> = { | ||
[ProtocolType.Ethereum]: '0x0000000000000000000000000000000000000000000000000000000000000000', | ||
[ProtocolType.Sealevel]: | ||
'1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111', | ||
[ProtocolType.Cosmos]: '0000000000000000000000000000000000000000000000000000000000000000', | ||
[ProtocolType.Fuel]: '', | ||
}; | ||
|
||
export async function isBlockExplorerHealthy( | ||
chainMetadata: ChainMetadata, | ||
address?: Address, | ||
txHash?: string, | ||
): Promise<boolean> { | ||
const baseUrl = getExplorerBaseUrl(chainMetadata); | ||
if (!baseUrl) return false; | ||
console.debug(`Got base url: ${baseUrl}`); | ||
|
||
console.debug(`Checking explorer home for ${chainMetadata.name}`); | ||
const homeReq = await fetch(baseUrl); | ||
if (!homeReq.ok) return false; | ||
console.debug(`Explorer home okay for ${chainMetadata.name}`); | ||
|
||
if (address) { | ||
console.debug(`Checking explorer address page for ${chainMetadata.name}`); | ||
const addressUrl = getExplorerAddressUrl(chainMetadata, address); | ||
if (!addressUrl) return false; | ||
console.debug(`Got address url: ${addressUrl}`); | ||
const addressReq = await fetch(addressUrl); | ||
if (!addressReq.ok && addressReq.status !== 404) return false; | ||
console.debug(`Explorer address page okay for ${chainMetadata.name}`); | ||
} | ||
|
||
if (txHash) { | ||
console.debug(`Checking explorer tx page for ${chainMetadata.name}`); | ||
const txUrl = getExplorerTxUrl(chainMetadata, txHash); | ||
if (!txUrl) return false; | ||
console.debug(`Got tx url: ${txUrl}`); | ||
const txReq = await fetch(txUrl); | ||
if (!txReq.ok && txReq.status !== 404) return false; | ||
console.debug(`Explorer tx page okay for ${chainMetadata.name}`); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
describe('Chain block explorer health', async () => { | ||
for (const [chain, metadata] of Object.entries(chainMetadata)) { | ||
if (!metadata.blockExplorers?.length) continue; | ||
it(`${chain} default explorer is healthy`, async () => { | ||
const isHealthy = await isBlockExplorerHealthy( | ||
metadata, | ||
PROTOCOL_TO_ADDRESS[metadata.protocol], | ||
PROTOCOL_TO_TX_HASH[metadata.protocol], | ||
); | ||
if (!isHealthy) await sleep(HEALTH_CHECK_DELAY); | ||
expect(isHealthy).to.be.true; | ||
}) | ||
.timeout(HEALTH_CHECK_TIMEOUT + HEALTH_CHECK_DELAY * 2) | ||
.retries(3); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
ChainMetadata, | ||
CosmJsProvider, | ||
CosmJsWasmProvider, | ||
EthersV5Provider, | ||
ProviderType, | ||
RpcUrl, | ||
SolanaWeb3Provider, | ||
protocolToDefaultProviderBuilder, | ||
} from '@hyperlane-xyz/sdk'; | ||
import { sleep } from '@hyperlane-xyz/utils'; | ||
import { expect } from 'chai'; | ||
import { chainAddresses, chainMetadata } from '../../dist/index.js'; | ||
|
||
import { Mailbox__factory } from '@hyperlane-xyz/core'; | ||
|
||
const HEALTH_CHECK_TIMEOUT = 10_000; // 10s | ||
const HEALTH_CHECK_DELAY = 3_000; // 3s | ||
|
||
async function isRpcHealthy(rpc: RpcUrl, metadata: ChainMetadata): Promise<boolean> { | ||
const builder = protocolToDefaultProviderBuilder[metadata.protocol]; | ||
const provider = builder([rpc], metadata.chainId); | ||
if (provider.type === ProviderType.EthersV5) | ||
return isEthersV5ProviderHealthy(provider.provider, metadata); | ||
else if (provider.type === ProviderType.SolanaWeb3) | ||
return isSolanaWeb3ProviderHealthy(provider.provider, metadata); | ||
else if (provider.type === ProviderType.CosmJsWasm || provider.type === ProviderType.CosmJs) | ||
return isCosmJsProviderHealthy(provider.provider, metadata); | ||
else throw new Error(`Unsupported provider type ${provider.type}, new health check required`); | ||
} | ||
|
||
async function isEthersV5ProviderHealthy( | ||
provider: EthersV5Provider['provider'], | ||
metadata: ChainMetadata, | ||
): Promise<boolean> { | ||
const chainName = metadata.name; | ||
const blockNumber = await provider.getBlockNumber(); | ||
if (!blockNumber || blockNumber < 0) return false; | ||
console.debug(`Block number is okay for ${chainName}`); | ||
|
||
if (chainAddresses[chainName]) { | ||
const mailboxAddr = chainAddresses[chainName].mailbox; | ||
const mailbox = Mailbox__factory.createInterface(); | ||
const topics = mailbox.encodeFilterTopics(mailbox.events['DispatchId(bytes32)'], []); | ||
console.debug(`Checking mailbox logs for ${chainName}`); | ||
const mailboxLogs = await provider.getLogs({ | ||
address: mailboxAddr, | ||
topics, | ||
fromBlock: blockNumber - 99, | ||
toBlock: blockNumber, | ||
}); | ||
if (!mailboxLogs) return false; | ||
console.debug(`Mailbox logs okay for ${chainName}`); | ||
} | ||
return true; | ||
} | ||
|
||
async function isSolanaWeb3ProviderHealthy( | ||
provider: SolanaWeb3Provider['provider'], | ||
metadata: ChainMetadata, | ||
): Promise<boolean> { | ||
const blockNumber = await provider.getBlockHeight(); | ||
if (!blockNumber || blockNumber < 0) return false; | ||
console.debug(`Block number is okay for ${metadata.name}`); | ||
return true; | ||
} | ||
|
||
async function isCosmJsProviderHealthy( | ||
provider: CosmJsProvider['provider'] | CosmJsWasmProvider['provider'], | ||
metadata: ChainMetadata, | ||
): Promise<boolean> { | ||
const readyProvider = await provider; | ||
const blockNumber = await readyProvider.getHeight(); | ||
if (!blockNumber || blockNumber < 0) return false; | ||
console.debug(`Block number is okay for ${metadata.name}`); | ||
return true; | ||
} | ||
|
||
describe('Chain RPC health', async () => { | ||
for (const [chain, metadata] of Object.entries(chainMetadata)) { | ||
metadata.rpcUrls.map((rpc, i) => { | ||
it(`${chain} RPC number ${i} is healthy`, async () => { | ||
const isHealthy = await isRpcHealthy(rpc, metadata); | ||
if (!isHealthy) await sleep(HEALTH_CHECK_DELAY); | ||
expect(isHealthy).to.be.true; | ||
}) | ||
.timeout(HEALTH_CHECK_TIMEOUT + HEALTH_CHECK_DELAY * 2) | ||
.retries(3); | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters