From 9e7b26c89bd1e8717cd5c039e8550d0d4af21677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A1n=20Di=20Pietro?= Date: Wed, 10 Nov 2021 14:46:46 -0300 Subject: [PATCH] Division of main code in differentiated engines for old Pricekeeper and Wormhole client. --- backend/common/settings.ts | 9 ++ backend/common/sleep.ts | 13 +++ backend/common/statusCodes.ts | 8 ++ backend/engine/IEngine.ts | 11 ++ backend/engine/PriceKeeperEngine.ts | 101 ++++++++++++++++++ backend/engine/WormholeEngine.ts | 102 ++++++++++++++++++ backend/fetcher/WormholePythPriceFetcher.ts | 112 ++++++++++---------- backend/main.ts | 96 +++-------------- settings/settings-btc.js | 1 + settings/settings-doge.js | 1 + settings/settings-eth.js | 1 + test/wormhole-sc-test.js | 2 +- 12 files changed, 320 insertions(+), 137 deletions(-) create mode 100644 backend/common/sleep.ts create mode 100644 backend/engine/IEngine.ts create mode 100644 backend/engine/PriceKeeperEngine.ts create mode 100644 backend/engine/WormholeEngine.ts diff --git a/backend/common/settings.ts b/backend/common/settings.ts index 8bfcf1b6..99f013b0 100644 --- a/backend/common/settings.ts +++ b/backend/common/settings.ts @@ -1,4 +1,13 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + export interface IAppSettings extends Record { + mode: string, algo: { token: string, api: string, diff --git a/backend/common/sleep.ts b/backend/common/sleep.ts new file mode 100644 index 00000000..b0c57edd --- /dev/null +++ b/backend/common/sleep.ts @@ -0,0 +1,13 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + +export function sleep (ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} diff --git a/backend/common/statusCodes.ts b/backend/common/statusCodes.ts index ad94f49c..e6aefbd6 100644 --- a/backend/common/statusCodes.ts +++ b/backend/common/statusCodes.ts @@ -1,3 +1,11 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + /* eslint-disable no-unused-vars */ export enum StatusCode { OK, diff --git a/backend/engine/IEngine.ts b/backend/engine/IEngine.ts new file mode 100644 index 00000000..909dc8d9 --- /dev/null +++ b/backend/engine/IEngine.ts @@ -0,0 +1,11 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + +export interface IEngine { + start(): Promise + } diff --git a/backend/engine/PriceKeeperEngine.ts b/backend/engine/PriceKeeperEngine.ts new file mode 100644 index 00000000..95bfca59 --- /dev/null +++ b/backend/engine/PriceKeeperEngine.ts @@ -0,0 +1,101 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + +import { IEngine } from './IEngine' +import { PythPriceFetcher } from '../fetcher/PythPriceFetcher' +import { StdAlgoPublisher } from '../publisher/stdAlgoPublisher' +import { StrategyLastPrice } from '../strategy/strategyLastPrice' +import { IAppSettings } from '../common/settings' +import { IPriceFetcher } from '../fetcher/IPriceFetcher' +import { IPublisher, PublishInfo } from '../publisher/IPublisher' +import { PriceTicker } from '../common/priceTicker' +import { StatusCode } from '../common/statusCodes' +import { sleep } from '../common/sleep' +const algosdk = require('algosdk') +const charm = require('charm')() + +type WorkerRoutineStatus = { + status: StatusCode, + reason?: string, + tick?: PriceTicker, + pub?: PublishInfo +} + +async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise { + const tick = fetcher.queryTicker() + if (tick === undefined) { + return { status: StatusCode.NO_TICKER } + } + const pub = await publisher.publish(tick) + return { status: pub.status, reason: pub.reason, tick, pub } +} + +export class PriceKeeperEngine implements IEngine { + private settings: IAppSettings + + constructor (settings: IAppSettings) { + this.settings = settings + } + + async start () { + charm.write('Setting up for') + .foreground('yellow') + .write(` ${this.settings.params.symbol} `) + .foreground('white') + .write('for PriceKeeper App') + .foreground('yellow') + .write(` ${this.settings.params.priceKeeperAppId} `) + .foreground('white') + .write(`interval ${this.settings.params.publishIntervalSecs} secs\n`) + + const publisher = new StdAlgoPublisher(this.settings.params.symbol, + this.settings.params.priceKeeperAppId, + this.settings.params.validator, + (algosdk.mnemonicToSecretKey(this.settings.params.mnemo)).sk, + this.settings.algo.token, + this.settings.algo.api, + this.settings.algo.port + ) + + const fetcher = new PythPriceFetcher(this.settings.params.symbol, + new StrategyLastPrice(this.settings.params.bufferSize), this.settings.pyth?.solanaClusterName!) + await fetcher.start() + + console.log('Waiting for fetcher to boot...') + while (!fetcher.hasData()) { + await sleep(250) + } + console.log('Waiting for publisher to boot...') + await publisher.start() + console.log('Starting worker.') + + let active = true + charm.removeAllListeners('^C') + charm.on('^C', () => { + console.log('CTRL+C: Aborted by user.') + active = false + }) + let pubCount = 0 + // eslint-disable-next-line no-unmodified-loop-condition + while (active) { + const wrs = await workerRoutine(fetcher, publisher) + switch (wrs.status) { + case StatusCode.OK: { + console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`) + break + } + case StatusCode.NO_TICKER: + console.log('No ticker available from fetcher data source') + break + default: + console.log('Error. Reason: ' + wrs.reason) + } + await sleep(this.settings.params.publishIntervalSecs * 1000) + } + } +} diff --git a/backend/engine/WormholeEngine.ts b/backend/engine/WormholeEngine.ts new file mode 100644 index 00000000..69b639ce --- /dev/null +++ b/backend/engine/WormholeEngine.ts @@ -0,0 +1,102 @@ +/** + * Pricecaster Service. + * + * Fetcher backend component. + * + * (c) 2021 Randlabs, Inc. + */ + +import { IEngine } from './IEngine' +import { PythPriceFetcher } from '../fetcher/PythPriceFetcher' +import { StdAlgoPublisher } from '../publisher/stdAlgoPublisher' +import { StrategyLastPrice } from '../strategy/strategyLastPrice' +import { IAppSettings } from '../common/settings' +import { IPriceFetcher } from '../fetcher/IPriceFetcher' +import { IPublisher, PublishInfo } from '../publisher/IPublisher' +import { PriceTicker } from '../common/priceTicker' +import { StatusCode } from '../common/statusCodes' +import { sleep } from '../common/sleep' + +const algosdk = require('algosdk') +const charm = require('charm')() + +type WorkerRoutineStatus = { + status: StatusCode, + reason?: string, + tick?: PriceTicker, + pub?: PublishInfo +} + +async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise { + const tick = fetcher.queryTicker() + if (tick === undefined) { + return { status: StatusCode.NO_TICKER } + } + const pub = await publisher.publish(tick) + return { status: pub.status, reason: pub.reason, tick, pub } +} + +export class WormholeClientEngine implements IEngine { + private settings: IAppSettings + + constructor (settings: IAppSettings) { + this.settings = settings + } + + async start () { + charm.write('Setting up for') + .foreground('yellow') + .write(` ${this.settings.params.symbol} `) + .foreground('white') + .write('for PriceKeeper App') + .foreground('yellow') + .write(` ${this.settings.params.priceKeeperAppId} `) + .foreground('white') + .write(`interval ${this.settings.params.publishIntervalSecs} secs\n`) + + const publisher = new StdAlgoPublisher(this.settings.params.symbol, + this.settings.params.priceKeeperAppId, + this.settings.params.validator, + (algosdk.mnemonicToSecretKey(this.settings.params.mnemo)).sk, + this.settings.algo.token, + this.settings.algo.api, + this.settings.algo.port + ) + + const fetcher = new PythPriceFetcher(this.settings.params.symbol, + new StrategyLastPrice(this.settings.params.bufferSize), this.settings.pyth?.solanaClusterName!) + await fetcher.start() + + console.log('Waiting for fetcher to boot...') + while (!fetcher.hasData()) { + await sleep(250) + } + console.log('Waiting for publisher to boot...') + await publisher.start() + console.log('Starting worker.') + + let active = true + charm.removeAllListeners('^C') + charm.on('^C', () => { + console.log('CTRL+C: Aborted by user.') + active = false + }) + let pubCount = 0 + // eslint-disable-next-line no-unmodified-loop-condition + while (active) { + const wrs = await workerRoutine(fetcher, publisher) + switch (wrs.status) { + case StatusCode.OK: { + console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`) + break + } + case StatusCode.NO_TICKER: + console.log('No ticker available from fetcher data source') + break + default: + console.log('Error. Reason: ' + wrs.reason) + } + await sleep(this.settings.params.publishIntervalSecs * 1000) + } + } +} diff --git a/backend/fetcher/WormholePythPriceFetcher.ts b/backend/fetcher/WormholePythPriceFetcher.ts index 70056191..dc1c9fb8 100644 --- a/backend/fetcher/WormholePythPriceFetcher.ts +++ b/backend/fetcher/WormholePythPriceFetcher.ts @@ -1,66 +1,66 @@ -/** - * Pricecaster Service. - * - * Fetcher backend component. - * - * (c) 2021 Randlabs, Inc. - */ +// /** +// * Pricecaster Service. +// * +// * Fetcher backend component. +// * +// * (c) 2021 Randlabs, Inc. +// */ -import { IPriceFetcher } from './IPriceFetcher' -import { IStrategy } from '../strategy/strategy' -import { getPythProgramKeyForCluster, PriceData, Product, PythConnection } from '@pythnetwork/client' -import { Cluster, clusterApiUrl, Connection } from '@solana/web3.js' -import { PriceTicker } from '../common/priceTicker' -import { getEmitterAddressEth, getSignedVAA } from '@certusone/wormhole-sdk' +// import { IPriceFetcher } from './IPriceFetcher' +// import { IStrategy } from '../strategy/strategy' +// import { getPythProgramKeyForCluster, PriceData, Product, PythConnection } from '@pythnetwork/client' +// import { Cluster, clusterApiUrl, Connection } from '@solana/web3.js' +// import { PriceTicker } from '../common/priceTicker' +// import { getEmitterAddressEth, getSignedVAA } from '@certusone/wormhole-sdk' -export class WormholePythPriceFetcher implements IPriceFetcher { - private strategy: IStrategy - private symbol: string - private pythConnection: PythConnection +// export class WormholePythPriceFetcher implements IPriceFetcher { +// private strategy: IStrategy +// private symbol: string +// private pythConnection: PythConnection - constructor (symbol: string, strategy: IStrategy, solanaClusterName: string) { - const SOLANA_CLUSTER_NAME: Cluster = solanaClusterName as Cluster - const connection = new Connection(clusterApiUrl(SOLANA_CLUSTER_NAME)) - const pythPublicKey = getPythProgramKeyForCluster(SOLANA_CLUSTER_NAME) - this.pythConnection = new PythConnection(connection, pythPublicKey) - this.strategy = strategy - this.symbol = symbol - } +// constructor (symbol: string, strategy: IStrategy, solanaClusterName: string) { +// const SOLANA_CLUSTER_NAME: Cluster = solanaClusterName as Cluster +// const connection = new Connection(clusterApiUrl(SOLANA_CLUSTER_NAME)) +// const pythPublicKey = getPythProgramKeyForCluster(SOLANA_CLUSTER_NAME) +// this.pythConnection = new PythConnection(connection, pythPublicKey) +// this.strategy = strategy +// this.symbol = symbol +// } - async start () { - await this.pythConnection.start() - this.pythConnection.onPriceChange((product: Product, price: PriceData) => { - if (product.symbol === this.symbol) { - this.onPriceChange(price) - } - }) - } +// async start () { +// await this.pythConnection.start() +// this.pythConnection.onPriceChange((product: Product, price: PriceData) => { +// if (product.symbol === this.symbol) { +// this.onPriceChange(price) +// } +// }) +// } - stop (): void { - this.pythConnection.stop() - } +// stop (): void { +// this.pythConnection.stop() +// } - setStrategy (s: IStrategy) { - this.strategy = s - } +// setStrategy (s: IStrategy) { +// this.strategy = s +// } - hasData (): boolean { - return this.strategy.bufferCount() > 0 - } +// hasData (): boolean { +// return this.strategy.bufferCount() > 0 +// } - queryTicker (): PriceTicker | undefined { - getEmitterAddressEth() +// queryTicker (): PriceTicker | undefined { +// getEmitterAddressEth() - await getSignedVAA("https://wormhole-v2-testnet-api.certus.one", ) - //return this.strategy.getPrice() - } +// await getSignedVAA("https://wormhole-v2-testnet-api.certus.one", ) +// //return this.strategy.getPrice() +// } - private onPriceChange (price: PriceData) { - GrpcWebImpl - PublicRPCServiceClientImpl - getSignedVAA() - const pt: PriceTicker = new PriceTicker(price.priceComponent, - price.confidenceComponent, price.exponent, price.publishSlot) - this.strategy.put(pt) - } -} +// private onPriceChange (price: PriceData) { +// GrpcWebImpl +// PublicRPCServiceClientImpl +// getSignedVAA() +// const pt: PriceTicker = new PriceTicker(price.priceComponent, +// price.confidenceComponent, price.exponent, price.publishSlot) +// this.strategy.put(pt) +// } +// } diff --git a/backend/main.ts b/backend/main.ts index 3190f80d..312d34c0 100644 --- a/backend/main.ts +++ b/backend/main.ts @@ -8,45 +8,17 @@ */ import * as Config from '@randlabs/js-config-reader' -import { PythPriceFetcher } from './fetcher/PythPriceFetcher' -import { StdAlgoPublisher } from './publisher/stdAlgoPublisher' -import { StrategyLastPrice } from './strategy/strategyLastPrice' -import { IPriceFetcher } from './fetcher/IPriceFetcher' -import { IPublisher, PublishInfo } from './publisher/IPublisher' -import { PriceTicker } from './common/priceTicker' -import { StatusCode } from './common/statusCodes' import { IAppSettings } from './common/settings' import { exit } from 'process' -const algosdk = require('algosdk') -const charm = require('charm')() - -function sleep (ms: number) { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) -} - -type WorkerRoutineStatus = { - status: StatusCode, - reason?: string, - tick?: PriceTicker, - pub?: PublishInfo -} - -async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise { - const tick = fetcher.queryTicker() - if (tick === undefined) { - return { status: StatusCode.NO_TICKER } - } - const pub = await publisher.publish(tick) - return { status: pub.status, reason: pub.reason, tick, pub } -} +import { PriceKeeperEngine } from './engine/PriceKeeperEngine' +import { WormholeClientEngine } from './engine/WormholeEngine' +const charm = require('charm')(); (async () => { charm.pipe(process.stdout) charm.reset() charm.foreground('cyan').display('bright') - console.log('Pricecaster Service Fetcher -- (c) 2021 Randlabs.io\n') + console.log('Pricecaster Service Backend -- (c) 2021 Randlabs, Inc.\n') charm.foreground('white') let settings: IAppSettings @@ -58,56 +30,20 @@ async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Pr exit(1) } - charm.write('Setting up for') - .foreground('yellow') - .write(` ${settings.params.symbol} `) - .foreground('white') - .write('for PriceKeeper App') - .foreground('yellow') - .write(` ${settings.params.priceKeeperAppId} `) - .foreground('white') - .write(`interval ${settings.params.publishIntervalSecs} secs\n`) + let engine + switch (settings.mode) { + case 'pkeeper': + engine = new PriceKeeperEngine(settings) + break - const publisher = new StdAlgoPublisher(settings.params.symbol, - settings.params.priceKeeperAppId, - settings.params.validator, - (algosdk.mnemonicToSecretKey(settings.params.mnemo)).sk, - settings.algo.token, - settings.algo.api, - settings.algo.port - ) - const fetcher = new PythPriceFetcher(settings.params.symbol, new StrategyLastPrice(settings.params.bufferSize), settings.pyth?.solanaClusterName!) - await fetcher.start() + case 'wormhole-client': + engine = new WormholeClientEngine(settings) + break - console.log('Waiting for fetcher to boot...') - while (!fetcher.hasData()) { - await sleep(250) + default: + console.error('Invalid specified mode in settings') + exit(2) } - console.log('Waiting for publisher to boot...') - await publisher.start() - console.log('Starting worker.') - let active = true - charm.removeAllListeners('^C') - charm.on('^C', () => { - console.log('CTRL+C: Aborted by user.') - active = false - }) - let pubCount = 0 - // eslint-disable-next-line no-unmodified-loop-condition - while (active) { - const wrs = await workerRoutine(fetcher, publisher) - switch (wrs.status) { - case StatusCode.OK: { - console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`) - break - } - case StatusCode.NO_TICKER: - console.log('No ticker available from fetcher data source') - break - default: - console.log('Error. Reason: ' + wrs.reason) - } - await sleep(settings.params.publishIntervalSecs * 1000) - } + engine.start() })() diff --git a/settings/settings-btc.js b/settings/settings-btc.js index 470f9a37..222d5b6d 100644 --- a/settings/settings-btc.js +++ b/settings/settings-btc.js @@ -1,4 +1,5 @@ module.exports = { + mode: 'pkeeper', algo: { token: '', api: 'https://api.testnet.algoexplorer.io', diff --git a/settings/settings-doge.js b/settings/settings-doge.js index 7aea64a4..2d167d0d 100644 --- a/settings/settings-doge.js +++ b/settings/settings-doge.js @@ -1,4 +1,5 @@ module.exports = { + mode: 'pkeeper', algo: { token: '', api: 'https://api.testnet.algoexplorer.io', diff --git a/settings/settings-eth.js b/settings/settings-eth.js index dc427fdd..b3f444f8 100644 --- a/settings/settings-eth.js +++ b/settings/settings-eth.js @@ -1,4 +1,5 @@ module.exports = { + mode: 'pkeeper', algo: { token: '', api: 'https://api.testnet.algoexplorer.io', diff --git a/test/wormhole-sc-test.js b/test/wormhole-sc-test.js index 1593df17..7c5e0041 100644 --- a/test/wormhole-sc-test.js +++ b/test/wormhole-sc-test.js @@ -114,7 +114,7 @@ describe('VAA Processor Smart-contract Tests', function () { const txid = await pclib.setVAAVerifyProgramHash(OWNER_ADDR, compiledProgram.hash, signCallback) await pclib.waitForTransactionResponse(txid) const vphstate = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vphash') - expect(algosdk.encodeAddress(Buffer.from(vphstate, 'base64'))).to.equal(compiledProgram.hash) + expect(vphstate).to.equal(compiledProgram.hash) }) it('Must disallow setting stateless logic hash from non-owner', async function () {