Skip to content

Commit

Permalink
initializes service:bridge-relay command (#4314)
Browse files Browse the repository at this point in the history
* 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

* renames viewKey to incomingViewKey

viewKey and incomingViewKey are two different keys, so we should keep the terms
clear

* changes progress log to debug

* changes head arg to fromHead flag

* 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
  • Loading branch information
hughy authored Sep 22, 2023
1 parent 0f08a71 commit 1834d4b
Showing 1 changed file with 136 additions and 0 deletions.
136 changes: 136 additions & 0 deletions ironfish-cli/src/commands/service/bridge/relay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* 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',
}),
incomingViewKey: Flags.string({
char: 'k',
description: 'View key to watch transactions with',
parse: (input: string): Promise<string> => Promise.resolve(input.trim()),
required: true,
}),
confirmations: Flags.integer({
char: 'c',
description: 'Minimum number of block confirmations needed to process deposits',
required: false,
}),
fromHead: Flags.string({
char: 'f',
description: 'The block hash to start following at',
required: false,
}),
}

async start(): Promise<void> {
const { flags } = 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.incomingViewKey, confirmations, flags.fromHead)
}

async syncBlocks(
api: WebApi,
incomingViewKey: string,
confirmations: number,
head?: string,
): Promise<void> {
this.log('Connecting to node...')
const client = await this.sdk.connectRpc()

this.log('Watching with view key:', incomingViewKey)

// 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: incomingViewKey,
head,
})

const speed = new Meter()
speed.start()

const buffer = new Array<GetTransactionStreamResponse>()

for await (const content of response.contentStream()) {
if (content.type === 'connected') {
buffer.push(content)
speed.add(1)
} else if (content.type === 'disconnected') {
buffer.pop()
}

this.logger.debug(
`${content.type}: ${content.block.hash} - ${content.block.sequence}${
' - ' +
TimeUtils.renderEstimate(content.block.sequence, content.head.sequence, speed.rate5m)
}`,
)

if (buffer.length > confirmations) {
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
}
}
}
}

0 comments on commit 1834d4b

Please sign in to comment.