Division of main code in differentiated engines for old Pricekeeper and Wormhole client.

This commit is contained in:
Hernán Di Pietro 2021-11-10 14:46:46 -03:00
parent 88132b3a19
commit 9e7b26c89b
12 changed files with 320 additions and 137 deletions

View File

@ -1,4 +1,13 @@
/**
* Pricecaster Service.
*
* Fetcher backend component.
*
* (c) 2021 Randlabs, Inc.
*/
export interface IAppSettings extends Record<string, unknown> {
mode: string,
algo: {
token: string,
api: string,

13
backend/common/sleep.ts Normal file
View File

@ -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)
})
}

View File

@ -1,3 +1,11 @@
/**
* Pricecaster Service.
*
* Fetcher backend component.
*
* (c) 2021 Randlabs, Inc.
*/
/* eslint-disable no-unused-vars */
export enum StatusCode {
OK,

11
backend/engine/IEngine.ts Normal file
View File

@ -0,0 +1,11 @@
/**
* Pricecaster Service.
*
* Fetcher backend component.
*
* (c) 2021 Randlabs, Inc.
*/
export interface IEngine {
start(): Promise<void>
}

View File

@ -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<WorkerRoutineStatus> {
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)
}
}
}

View File

@ -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<WorkerRoutineStatus> {
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)
}
}
}

View File

@ -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)
// }
// }

View File

@ -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<WorkerRoutineStatus> {
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`)
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()
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}`)
let engine
switch (settings.mode) {
case 'pkeeper':
engine = new PriceKeeperEngine(settings)
break
}
case StatusCode.NO_TICKER:
console.log('No ticker available from fetcher data source')
case 'wormhole-client':
engine = new WormholeClientEngine(settings)
break
default:
console.log('Error. Reason: ' + wrs.reason)
}
await sleep(settings.params.publishIntervalSecs * 1000)
console.error('Invalid specified mode in settings')
exit(2)
}
engine.start()
})()

View File

@ -1,4 +1,5 @@
module.exports = {
mode: 'pkeeper',
algo: {
token: '',
api: 'https://api.testnet.algoexplorer.io',

View File

@ -1,4 +1,5 @@
module.exports = {
mode: 'pkeeper',
algo: {
token: '',
api: 'https://api.testnet.algoexplorer.io',

View File

@ -1,4 +1,5 @@
module.exports = {
mode: 'pkeeper',
algo: {
token: '',
api: 'https://api.testnet.algoexplorer.io',

View File

@ -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 () {