From 2f578ca505d5eaf663bddd38403c40ef62cad968 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Thu, 21 Sep 2023 11:28:38 -0700 Subject: [PATCH 1/5] initializes service:bridge-relay command adds a service command to act as a relay; decrypting deposit transactions on Iron Fish and calling the bridge contract on Ethereum relies on remote node to decrypt transactions with a given view key via chain/getTransactionStream waits until block confirmed before processing deposit transactions --- .../src/commands/service/bridge-relay.ts | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 ironfish-cli/src/commands/service/bridge-relay.ts diff --git a/ironfish-cli/src/commands/service/bridge-relay.ts b/ironfish-cli/src/commands/service/bridge-relay.ts new file mode 100644 index 0000000000..466f5517c6 --- /dev/null +++ b/ironfish-cli/src/commands/service/bridge-relay.ts @@ -0,0 +1,147 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { Assert, GetTransactionStreamResponse, Meter, TimeUtils, WebApi } from '@ironfish/sdk' +import { Flags } from '@oclif/core' +import { IronfishCommand } from '../../command' +import { RemoteFlags } from '../../flags' + +export default class BridgeRelay extends IronfishCommand { + static hidden = true + + static description = ` + Relay Iron Fish deposits to the Sepolia bridge contract + ` + + static flags = { + ...RemoteFlags, + endpoint: Flags.string({ + char: 'e', + description: 'API host to sync to', + parse: (input: string) => Promise.resolve(input.trim()), + env: 'IRONFISH_API_HOST', + }), + token: Flags.string({ + char: 't', + description: 'API token to authenticate with', + parse: (input: string) => Promise.resolve(input.trim()), + env: 'IRONFISH_API_TOKEN', + }), + viewKey: Flags.string({ + char: 'k', + description: 'View key to watch transactions with', + parse: (input: string): Promise => Promise.resolve(input.trim()), + required: true, + }), + confirmations: Flags.integer({ + char: 'c', + description: 'Minimum number of block confirmations needed to process deposits', + required: false, + }), + } + + static args = [ + { + name: 'head', + required: false, + description: 'The block hash to start following at', + }, + ] + + async start(): Promise { + const { flags, args } = await this.parse(BridgeRelay) + + if (!flags.endpoint) { + this.log( + `No api host set. You must set IRONFISH_API_HOST env variable or pass --endpoint flag.`, + ) + this.exit(1) + } + + if (!flags.token) { + this.log( + `No api token set. You must set IRONFISH_API_TOKEN env variable or pass --token flag.`, + ) + this.exit(1) + } + + const api = new WebApi({ host: flags.endpoint, token: flags.token }) + + const confirmations = flags.confirmations ?? this.sdk.config.get('confirmations') + + await this.syncBlocks(api, flags.viewKey, confirmations, args.head) + } + + async syncBlocks( + api: WebApi, + viewKey: string, + confirmations: number, + head: string | null, + ): Promise { + this.log('Connecting to node...') + const client = await this.sdk.connectRpc() + + this.log('Watching with view key:', viewKey) + + // TODO: track chain state of relay in API + if (!head) { + const chainInfo = await client.chain.getChainInfo() + head = chainInfo.content.genesisBlockIdentifier.hash + } + this.log(`Starting from head ${head}`) + + const response = client.chain.getTransactionStream({ + incomingViewKey: viewKey, + head, + }) + + const speed = new Meter() + speed.start() + + const buffer = new Array() + + for await (const content of response.contentStream()) { + if (content.type === 'connected') { + buffer.push(content) + speed.add(1) + } else if (content.type === 'disconnected') { + buffer.pop() + } + + const committing = buffer.length > confirmations + + this.log( + `${content.type}: ${content.block.hash} - ${content.block.sequence}${ + committing + ? ' - ' + + TimeUtils.renderEstimate( + content.block.sequence, + content.head.sequence, + speed.rate5m, + ) + : '' + }`, + ) + + if (committing) { + const response = buffer.shift() + Assert.isNotUndefined(response) + this.commit(api, response) + } + } + } + + commit(api: WebApi, response: GetTransactionStreamResponse): void { + Assert.isNotUndefined(response) + + const transactions = response.transactions + + for (const transaction of transactions) { + for (const note of transaction.notes) { + this.log(`Processing deposit ${note.memo}, from transaction ${transaction.hash}`) + // TODO: get Eth deposit address from API + // TODO: call Eth bridge contract to mint + } + } + } +} From a8e6c26da158be8b3fba40de8f808ab03fc915a7 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Thu, 21 Sep 2023 13:33:04 -0700 Subject: [PATCH 2/5] renames viewKey to incomingViewKey viewKey and incomingViewKey are two different keys, so we should keep the terms clear --- ironfish-cli/src/commands/service/bridge-relay.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ironfish-cli/src/commands/service/bridge-relay.ts b/ironfish-cli/src/commands/service/bridge-relay.ts index 466f5517c6..752dfa35d9 100644 --- a/ironfish-cli/src/commands/service/bridge-relay.ts +++ b/ironfish-cli/src/commands/service/bridge-relay.ts @@ -27,7 +27,7 @@ export default class BridgeRelay extends IronfishCommand { parse: (input: string) => Promise.resolve(input.trim()), env: 'IRONFISH_API_TOKEN', }), - viewKey: Flags.string({ + incomingViewKey: Flags.string({ char: 'k', description: 'View key to watch transactions with', parse: (input: string): Promise => Promise.resolve(input.trim()), @@ -69,19 +69,19 @@ export default class BridgeRelay extends IronfishCommand { const confirmations = flags.confirmations ?? this.sdk.config.get('confirmations') - await this.syncBlocks(api, flags.viewKey, confirmations, args.head) + await this.syncBlocks(api, flags.incomingViewKey, confirmations, args.head) } async syncBlocks( api: WebApi, - viewKey: string, + incomingViewKey: string, confirmations: number, head: string | null, ): Promise { this.log('Connecting to node...') const client = await this.sdk.connectRpc() - this.log('Watching with view key:', viewKey) + this.log('Watching with view key:', incomingViewKey) // TODO: track chain state of relay in API if (!head) { @@ -91,7 +91,7 @@ export default class BridgeRelay extends IronfishCommand { this.log(`Starting from head ${head}`) const response = client.chain.getTransactionStream({ - incomingViewKey: viewKey, + incomingViewKey: incomingViewKey, head, }) From 1feabba07af296cf18c59a60700594a88f5a7364 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Thu, 21 Sep 2023 13:37:02 -0700 Subject: [PATCH 3/5] changes progress log to debug --- .../src/commands/service/bridge-relay.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/ironfish-cli/src/commands/service/bridge-relay.ts b/ironfish-cli/src/commands/service/bridge-relay.ts index 752dfa35d9..e5dd1f9f98 100644 --- a/ironfish-cli/src/commands/service/bridge-relay.ts +++ b/ironfish-cli/src/commands/service/bridge-relay.ts @@ -108,22 +108,14 @@ export default class BridgeRelay extends IronfishCommand { buffer.pop() } - const committing = buffer.length > confirmations - - this.log( + this.logger.debug( `${content.type}: ${content.block.hash} - ${content.block.sequence}${ - committing - ? ' - ' + - TimeUtils.renderEstimate( - content.block.sequence, - content.head.sequence, - speed.rate5m, - ) - : '' + ' - ' + + TimeUtils.renderEstimate(content.block.sequence, content.head.sequence, speed.rate5m) }`, ) - if (committing) { + if (buffer.length > confirmations) { const response = buffer.shift() Assert.isNotUndefined(response) this.commit(api, response) From 26ac84e86a206f9f3e853823558e8083d57c565c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Thu, 21 Sep 2023 13:45:29 -0700 Subject: [PATCH 4/5] changes head arg to fromHead flag --- .../src/commands/service/bridge-relay.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/ironfish-cli/src/commands/service/bridge-relay.ts b/ironfish-cli/src/commands/service/bridge-relay.ts index e5dd1f9f98..71a6ccf4fb 100644 --- a/ironfish-cli/src/commands/service/bridge-relay.ts +++ b/ironfish-cli/src/commands/service/bridge-relay.ts @@ -38,18 +38,15 @@ export default class BridgeRelay extends IronfishCommand { description: 'Minimum number of block confirmations needed to process deposits', required: false, }), - } - - static args = [ - { - name: 'head', - required: false, + fromHead: Flags.string({ + char: 'f', description: 'The block hash to start following at', - }, - ] + required: false, + }), + } async start(): Promise { - const { flags, args } = await this.parse(BridgeRelay) + const { flags } = await this.parse(BridgeRelay) if (!flags.endpoint) { this.log( @@ -69,14 +66,14 @@ export default class BridgeRelay extends IronfishCommand { const confirmations = flags.confirmations ?? this.sdk.config.get('confirmations') - await this.syncBlocks(api, flags.incomingViewKey, confirmations, args.head) + await this.syncBlocks(api, flags.incomingViewKey, confirmations, flags.fromHead) } async syncBlocks( api: WebApi, incomingViewKey: string, confirmations: number, - head: string | null, + head?: string, ): Promise { this.log('Connecting to node...') const client = await this.sdk.connectRpc() From f18078068eefe3c3614cb2ae683874ef9320aae7 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Fri, 22 Sep 2023 12:23:37 -0700 Subject: [PATCH 5/5] creates bridge sub-namespace in service commands changes 'service:bridge-relay' to 'service:bridge:relay' we expect to implement at least one more bridge service command --- .../src/commands/service/{bridge-relay.ts => bridge/relay.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename ironfish-cli/src/commands/service/{bridge-relay.ts => bridge/relay.ts} (97%) diff --git a/ironfish-cli/src/commands/service/bridge-relay.ts b/ironfish-cli/src/commands/service/bridge/relay.ts similarity index 97% rename from ironfish-cli/src/commands/service/bridge-relay.ts rename to ironfish-cli/src/commands/service/bridge/relay.ts index 71a6ccf4fb..b24c3d89cf 100644 --- a/ironfish-cli/src/commands/service/bridge-relay.ts +++ b/ironfish-cli/src/commands/service/bridge/relay.ts @@ -3,8 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Assert, GetTransactionStreamResponse, Meter, TimeUtils, WebApi } from '@ironfish/sdk' import { Flags } from '@oclif/core' -import { IronfishCommand } from '../../command' -import { RemoteFlags } from '../../flags' +import { IronfishCommand } from '../../../command' +import { RemoteFlags } from '../../../flags' export default class BridgeRelay extends IronfishCommand { static hidden = true