Skip to content

Commit

Permalink
Merge pull request #99 from confio/12-relayer-loop
Browse files Browse the repository at this point in the history
Relayer Loop
  • Loading branch information
ethanfrey authored Mar 10, 2021
2 parents 90c6e05 + 06fc292 commit 5ab4938
Show file tree
Hide file tree
Showing 17 changed files with 648 additions and 200 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,14 @@ ibc-setup # should be available
This is just mean for manual testing with the local CI chains. First get some keys:

```bash
ibc-setup init --src local_wasmd --dest local_simapp
ibc-setup init --src local_wasm --dest local_simapp
ibc-setup keys list
```

Then edit [testutils.spec.ts](./src/lib/testutils.spec.ts) under `'fund relayer'` and place your keys there.
Change it from `skip` to `only` and run it:
Then edit [manual/consts.ts](./src/lib/manual/consts.ts) and place your keys in those address variables.

```bash
yarn build && yarn test:unit ./src/lib/testutils.spec.ts
yarn build && yarn test:unit ./src/lib/manual/fund-relayer.spec.ts
```

Now you should see an updated balance, and can make an ics20 channel:
Expand All @@ -72,13 +71,19 @@ ibc-setup balances
ibc-setup ics20 --dest-port custom
```

With that set up, let's start the relayer:
Now we have a channel, let's send some packets. Go back to [manual/consts.ts](./src/lib/manual/consts.ts)
place the proper channel ids from in the channels object. Make sure to place the channel that was listed
next to (custom) on the top part. Then run a task to generate packets:

```bash
ibc-relayer start
yarn build && yarn test:unit ./src/lib/manual/create-packets.spec.ts
```

TODO: how to send some transfer packets to test it? Another testutils function???
With a connection, channel, and packets, let's start the relayer:

```bash
ibc-relayer start
```

### Testing

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"test:lint": "eslint src --ext .ts",
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
"test:unit": "nyc --silent ava --serial",
"focused-test": "run-s build && yarn test:unit ./src/binary/ibc-setup/commands/connect.spec.ts",
"focused-test": "run-s build && yarn test:unit ./src/lib/link.spec.ts",
"check-cli": "run-s test diff-integration-tests check-integration-tests",
"check-integration-tests": "run-s check-integration-test:*",
"diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
Expand Down
50 changes: 45 additions & 5 deletions src/binary/ibc-relayer/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path';

import { Logger } from 'winston';

import { Link } from '../../../lib/link';
import { registryFile } from '../../constants';
import { createLogger, Level } from '../../create-logger';
import { LoggerFlags } from '../../types';
Expand All @@ -12,6 +13,7 @@ import { resolveRequiredOption } from '../../utils/options/resolve-required-opti
import { resolveHomeOption } from '../../utils/options/shared/resolve-home-option';
import { resolveKeyFileOption } from '../../utils/options/shared/resolve-key-file-option';
import { resolveMnemonicOption } from '../../utils/options/shared/resolve-mnemonic-option';
import { signingClient } from '../../utils/signing-client';

type Flags = {
interactive: boolean;
Expand All @@ -24,14 +26,23 @@ type Flags = {
destConnection?: string;
} & LoggerFlags;

// TODO: do we want to make this a flag?
type LoopOptions = {
runOnce: boolean;
// number of seconds old the client on chain A can be
maxAgeA: number;
// number of seconds old the client on chain B can be
maxAgeB: number;
};

type Options = {
home: string;
src: string;
dest: string;
mnemonic: string;
srcConnection: string;
destConnection: string;
};
} & LoopOptions;

export async function start(flags: Flags) {
const logLevel = resolveOption(
Expand Down Expand Up @@ -81,12 +92,17 @@ export async function start(flags: Flags) {
mnemonic,
srcConnection,
destConnection,
// TODO: make configurable
runOnce: true,
// once per day: 86400s
maxAgeA: 86400,
maxAgeB: 86400,
};

run(options, logger);
await run(options, logger);
}

function run(options: Options, logger: Logger) {
async function run(options: Options, logger: Logger) {
const registryFilePath = path.join(options.home, registryFile);
const { chains } = loadAndValidateRegistry(registryFilePath);
const srcChain = chains[options.src];
Expand All @@ -98,6 +114,30 @@ function run(options: Options, logger: Logger) {
throw new Error('dest chain not found in registry');
}

console.log('ibc-relayer start with options:', options);
logger.info('logger is available');
const nodeA = await signingClient(srcChain, options.mnemonic, logger);
const nodeB = await signingClient(destChain, options.mnemonic, logger);
const link = await Link.createWithExistingConnections(
nodeA,
nodeB,
options.srcConnection,
options.destConnection,
logger
);

await relayerLoop(link, options);
}

async function relayerLoop(link: Link, options: LoopOptions) {
if (!options.runOnce) {
throw new Error('Loop is not supported yet, try runOnce = true');
}

// TODO: fill this in with real data (how far back do we start querying... where do we store state?)
let nextRelay = {};
nextRelay = await link.checkAndRelayPacketsAndAcks(nextRelay);
console.log(nextRelay);

// ensure the headers are up to date (only submits if old and we didn't just update them above)
await link.updateClientIfStale('A', options.maxAgeB);
await link.updateClientIfStale('B', options.maxAgeA);
}
5 changes: 2 additions & 3 deletions src/binary/ibc-relayer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
mnemonicOption,
srcOption,
} from '../commander-options';
import { rootBoundary } from '../utils/error-boundary';

import { start } from './commands/start';

Expand All @@ -28,7 +29,6 @@ program
.addOption(mnemonicOption)
.option('--src-connection <connection>')
.option('--dest-connection <connection>')

.addOption(
new Option('--log-level <level>').choices([
'debug',
Expand All @@ -40,7 +40,6 @@ program
)
.option('-v, --verbose')
.option('-q, --quiet')

.action(start);
.action(rootBoundary(start));

program.parse(process.argv);
6 changes: 6 additions & 0 deletions src/binary/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GasPrice } from '@cosmjs/launchpad';

export type Chain = {
chain_id: string;
prefix: string;
Expand Down Expand Up @@ -25,3 +27,7 @@ export type LoggerFlags = {
verbose: boolean;
quiet: boolean;
};

export function feeDenom(chain: Chain): string {
return GasPrice.fromString(chain.gas_price).denom;
}
31 changes: 31 additions & 0 deletions src/binary/utils/error-boundary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Logger } from '../../lib/logger';

// This is meant to be used around top-level command.
// If you have access to a logger, please use errorBoundary
export function rootBoundary<T>(
command: (flags: T) => Promise<void>
): (flags: T) => Promise<void> {
return async function (flags: T) {
try {
await command(flags);
} catch (e) {
// TODO: should we format this somehow?
console.error(e);
process.exit(1);
}
};
}

// FIXME: figure this one out better once we have loggers
export async function errorBoundary(
logger: Logger,
command: () => Promise<void>
) {
try {
await command();
} catch (e) {
// TODO: should we format this somehow?
logger.error(e);
process.exit(1);
}
}
33 changes: 33 additions & 0 deletions src/binary/utils/signing-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { stringToPath } from '@cosmjs/crypto';
import { GasPrice } from '@cosmjs/launchpad';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';

import { IbcClient, IbcClientOptions } from '../../lib/ibcclient';
import { Logger } from '../../lib/logger';
import { Chain } from '../types';

export async function signingClient(
chain: Chain,
mnemonic: string,
logger?: Logger
): Promise<IbcClient> {
const hdPath = chain.hd_path ? stringToPath(chain.hd_path) : undefined;
const signer = await DirectSecp256k1HdWallet.fromMnemonic(
mnemonic,
hdPath,
chain.prefix
);
const { address } = (await signer.getAccounts())[0];
const options: IbcClientOptions = {
prefix: chain.prefix,
gasPrice: GasPrice.fromString(chain.gas_price),
logger,
};
const client = await IbcClient.connectWithSigner(
chain.rpc[0],
signer,
address,
options
);
return client;
}
2 changes: 1 addition & 1 deletion src/lib/endpoint.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import test from 'ava';

import { Link } from './link';
import { ics20, randomAddress, setup, simapp, wasmd } from './testutils.spec';
import { ics20, randomAddress, setup, simapp, wasmd } from './testutils';
import { parseAcksFromLogs } from './utils';

test.serial('submit multiple tx, query all packets', async (t) => {
Expand Down
17 changes: 6 additions & 11 deletions src/lib/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export class Endpoint {
this.connectionID = connectionID;
}

public async chainId(): Promise<string> {
return this.client.getChainId();
public chainId(): string {
return this.client.chainId;
}

public async getLatestCommit(): Promise<CommitResponse> {
return this.client.getCommit();
}

// TODO: return info for pagination, accept arg
// returns all packets (auto-paginates, so be careful about not setting a minHeight)
public async querySentPackets({
minHeight,
maxHeight,
Expand All @@ -67,8 +67,7 @@ export class Endpoint {
query = `${query} AND tx.height<=${maxHeight}`;
}

// TODO: txSearchAll or do we paginate?
const search = await this.client.tm.txSearch({ query });
const search = await this.client.tm.txSearchAll({ query });
const resultsNested = search.txs.map(({ height, result }) => {
const parsedLogs = parseRawLog(result.log);
const sender = logs.findAttribute(parsedLogs, 'message', 'sender').value;
Expand All @@ -81,7 +80,7 @@ export class Endpoint {
return ([] as PacketWithMetadata[]).concat(...resultsNested);
}

// TODO: return info for pagination, accept arg
// returns all acks (auto-paginates, so be careful about not setting a minHeight)
public async queryWrittenAcks({
minHeight,
maxHeight,
Expand All @@ -94,8 +93,7 @@ export class Endpoint {
query = `${query} AND tx.height<=${maxHeight}`;
}

// TODO: txSearchAll or do we paginate?
const search = await this.client.tm.txSearch({ query });
const search = await this.client.tm.txSearchAll({ query });
const resultsNested = search.txs.map(({ height, result }) => {
const parsedLogs = parseRawLog(result.log);
// const sender = logs.findAttribute(parsedLogs, 'message', 'sender').value;
Expand All @@ -106,9 +104,6 @@ export class Endpoint {
});
return ([] as AckWithMetadata[]).concat(...resultsNested);
}

// TODO: subscription based packets/acks?
// until then, poll every X seconds
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ibcclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
simapp,
TestLogger,
wasmd,
} from './testutils.spec';
} from './testutils';
import {
buildClientState,
buildConsensusState,
Expand Down
15 changes: 10 additions & 5 deletions src/lib/ibcclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,18 +304,23 @@ export class IbcClient {
return this.sign.getChainId();
}

public async header(height: number): Promise<RpcHeader> {
this.logger.verbose(`Get header for height ${height}`);
// TODO: expose header method on tmClient and use that
const resp = await this.tm.blockchain(height, height);
return resp.blockMetas[0].header;
}

public async latestHeader(): Promise<RpcHeader> {
this.logger.verbose('Get latest header');
// TODO: expose header method on tmClient and use that
const block = await this.tm.block();
return block.block.header;
}

public async header(height: number): Promise<RpcHeader> {
this.logger.verbose(`Get header for height ${height}`);
// TODO: expose header method on tmClient and use that
const resp = await this.tm.blockchain(height, height);
return resp.blockMetas[0].header;
public async currentHeight(): Promise<number> {
const status = await this.tm.status();
return status.syncInfo.latestBlockHeight;
}

public async waitOneBlock(): Promise<void> {
Expand Down
Loading

0 comments on commit 5ab4938

Please sign in to comment.