From ecc681a7216410f90f028f545d60c1271c8d0391 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Wed, 21 Jun 2023 09:16:05 -0700 Subject: [PATCH] batch stuff --- price_pusher/README.md | 24 ++++---- price_pusher/price-config.mainnet.sample.yaml | 4 ++ price_pusher/price-config.testnet.sample.yaml | 4 ++ price_pusher/src/controller.ts | 23 ++++++-- price_pusher/src/evm/command.ts | 5 ++ price_pusher/src/evm/evm.ts | 2 +- price_pusher/src/price-config.ts | 58 +++++++++++++++---- 7 files changed, 93 insertions(+), 27 deletions(-) diff --git a/price_pusher/README.md b/price_pusher/README.md index 04a2cb5780..6badd162f7 100644 --- a/price_pusher/README.md +++ b/price_pusher/README.md @@ -37,9 +37,20 @@ The parameters above are configured per price feed in a price configuration YAML time_difference: 60 # Time difference threshold (in seconds) to push a newer price feed. price_deviation: 0.5 # The price deviation (%) threshold to push a newer price feed. confidence_ratio: 1 # The confidence/price (%) threshold to push a newer price feed. + # Optional block to configure whether this feed can be early updated. If at least one feed meets the + # triggering conditions above, all other feeds who meet the early update conditions will be included in + # the submitted batch of prices. This logic takes advantage of the fact that adding a feed to a larger + # batch of updates incurs a minimal gas cost. All fields below are optional (and interpreted as infinity if omitted) + # and have the same semantics as the corresponding fields above. + early_update: + time_difference: 30 + price_deviation: 0.1 + confidence_ratio: 0.5 - ... ``` +Two sample YAML configuration files are available in the root of this repo. + You can get the list of available price feeds from [here](https://pyth.network/developers/price-feed-ids/). @@ -57,7 +68,7 @@ cd price_pusher npm run start -- evm --endpoint wss://example-rpc.com \ --pyth-contract-address 0xff1a0f4744e8582DF...... \ --price-service-endpoint https://example-pyth-price.com \ - --price-config-file "path/to/price-config-file.yaml.testnet.sample.yaml" \ + --price-config-file "path/to/price-config.testnet.sample.yaml" \ --mnemonic-file "path/to/mnemonic.txt" \ [--pushing-frequency 10] \ [--polling-frequency 5] \ @@ -66,7 +77,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \ # For Injective npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \ --pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-pyth-price.com" \ - --price-config-file "path/to/price-config-file.yaml.testnet.sample.yaml" \ + --price-config-file "path/to/price-config.testnet.sample.yaml" \ --mnemonic-file "path/to/mnemonic.txt" \ [--pushing-frequency 10] \ [--polling-frequency 5] \ @@ -94,15 +105,6 @@ npm run start -- sui [--polling-frequency 5] \ - ---endpoint https://fullnode.testnet.aptoslabs.com/v1 \ - --pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 --price-service-endpoint "https://xc-testnet.pyth.network" \ - --price-config-file "./price-config.testnet.sample.yaml" \ - --mnemonic-file "path/to/mnemonic.txt" \ - [--pushing-frequency 10] \ - [--polling-frequency 5] \ - - # Or, run the price pusher docker image instead of building from the source docker run public.ecr.aws/pyth-network/xc-price-pusher:v -- ``` diff --git a/price_pusher/price-config.mainnet.sample.yaml b/price_pusher/price-config.mainnet.sample.yaml index 374acd8de2..dd47b48a50 100644 --- a/price_pusher/price-config.mainnet.sample.yaml +++ b/price_pusher/price-config.mainnet.sample.yaml @@ -8,3 +8,7 @@ time_difference: 60 price_deviation: 1 confidence_ratio: 1 + early_update: + time_difference: 30 + price_deviation: 0.5 + confidence_ratio: 0.1 diff --git a/price_pusher/price-config.testnet.sample.yaml b/price_pusher/price-config.testnet.sample.yaml index adf90873e9..8861eb844d 100644 --- a/price_pusher/price-config.testnet.sample.yaml +++ b/price_pusher/price-config.testnet.sample.yaml @@ -8,3 +8,7 @@ time_difference: 60 price_deviation: 1 confidence_ratio: 1 + early_update: + time_difference: 30 + price_deviation: 0.5 + confidence_ratio: 0.1 diff --git a/price_pusher/src/controller.ts b/price_pusher/src/controller.ts index 3023ea9b2f..13fc733e71 100644 --- a/price_pusher/src/controller.ts +++ b/price_pusher/src/controller.ts @@ -1,7 +1,7 @@ import { UnixTimestamp } from "@pythnetwork/price-service-client"; import { DurationInSeconds, sleep } from "./utils"; -import { IPricePusher, IPriceListener } from "./interface"; -import { PriceConfig, shouldUpdate } from "./price-config"; +import { IPriceListener, IPricePusher } from "./interface"; +import { PriceConfig, shouldUpdate, UpdateCondition } from "./price-config"; export class Controller { private pushingFrequency: DurationInSeconds; @@ -28,6 +28,9 @@ export class Controller { await sleep(this.pushingFrequency * 1000); for (;;) { + // We will push all prices whose update condition is YES or EARLY as long as there is + // at least one YES. + let pushThresholdMet = false; const pricesToPush: PriceConfig[] = []; const pubTimesToPush: UnixTimestamp[] = []; @@ -39,12 +42,24 @@ export class Controller { const sourceLatestPrice = this.sourcePriceListener.getLatestPriceInfo(priceId); - if (shouldUpdate(priceConfig, sourceLatestPrice, targetLatestPrice)) { + const priceShouldUpdate = shouldUpdate( + priceConfig, + sourceLatestPrice, + targetLatestPrice + ); + if (priceShouldUpdate == UpdateCondition.YES) { + pushThresholdMet = true; + } + + if ( + priceShouldUpdate == UpdateCondition.YES || + priceShouldUpdate == UpdateCondition.EARLY + ) { pricesToPush.push(priceConfig); pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1); } } - if (pricesToPush.length !== 0) { + if (pushThresholdMet) { console.log( "Some of the above values passed the threshold. Will push the price." ); diff --git a/price_pusher/src/evm/command.ts b/price_pusher/src/evm/command.ts index c87a0e8ce3..bc8b90cdca 100644 --- a/price_pusher/src/evm/command.ts +++ b/price_pusher/src/evm/command.ts @@ -91,6 +91,11 @@ export default { mnemonic, pythContractAddress ); + console.log( + `Pushing updates from wallet address: ${pythContractFactory + .createWeb3PayerProvider() + .getAddress()}` + ); const evmListener = new EvmPriceListener(pythContractFactory, priceItems, { pollingFrequency, diff --git a/price_pusher/src/evm/evm.ts b/price_pusher/src/evm/evm.ts index 831bc89841..d1b327c50c 100644 --- a/price_pusher/src/evm/evm.ts +++ b/price_pusher/src/evm/evm.ts @@ -224,7 +224,7 @@ export class EvmPricePusher implements IPricePusher { // the update data is valid there is no possible rejection cause other than // the target chain price being already updated. console.log( - "Execution reverted. With high probablity, the target chain price " + + "Execution reverted. With high probability, the target chain price " + "has already updated, Skipping this push." ); return; diff --git a/price_pusher/src/price-config.ts b/price_pusher/src/price-config.ts index bbbd2a4e3f..b6f97ad167 100644 --- a/price_pusher/src/price-config.ts +++ b/price_pusher/src/price-config.ts @@ -15,6 +15,11 @@ const PriceConfigFileSchema: Joi.Schema = Joi.array() time_difference: Joi.number().required(), price_deviation: Joi.number().required(), confidence_ratio: Joi.number().required(), + early_update: Joi.object({ + time_difference: Joi.number().optional(), + price_deviation: Joi.number().optional(), + confidence_ratio: Joi.number().optional(), + }).optional(), }) ) .unique("id") @@ -27,6 +32,12 @@ export type PriceConfig = { timeDifference: DurationInSeconds; priceDeviation: PctNumber; confidenceRatio: PctNumber; + + // An early update happens when another price has met the conditions to be pushed, so this + // price can be included in a batch update for minimal gas cost. + earlyUpdateTimeDifference: DurationInSeconds | undefined; + earlyUpdatePriceDeviation: PctNumber | undefined; + earlyUpdateConfidenceRatio: PctNumber | undefined; }; export function readPriceConfigFile(path: string): PriceConfig[] { @@ -44,11 +55,24 @@ export function readPriceConfigFile(path: string): PriceConfig[] { timeDifference: priceConfigRaw.time_difference, priceDeviation: priceConfigRaw.price_deviation, confidenceRatio: priceConfigRaw.confidence_ratio, + + earlyUpdateTimeDifference: priceConfigRaw.early_update?.time_difference, + earlyUpdatePriceDeviation: priceConfigRaw.early_update?.price_deviation, + earlyUpdateConfidenceRatio: priceConfigRaw.early_update?.confidence_ratio, }; return priceConfig; }); } +export enum UpdateCondition { + // This price feed must be updated + YES, + // This price feed may be updated as part of a larger batch + EARLY, + // This price feed shouldn't be updated + NO, +} + /** * Checks whether on-chain price needs to be updated with the latest pyth price information. * @@ -59,12 +83,12 @@ export function shouldUpdate( priceConfig: PriceConfig, sourceLatestPrice: PriceInfo | undefined, targetLatestPrice: PriceInfo | undefined -): boolean { +): UpdateCondition { const priceId = priceConfig.id; // There is no price to update the target with. if (sourceLatestPrice === undefined) { - return false; + return UpdateCondition.YES; } // It means that price never existed there. So we should push the latest price feed. @@ -72,12 +96,12 @@ export function shouldUpdate( console.log( `${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.` ); - return true; + return UpdateCondition.YES; } // The current price is not newer than the price onchain if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) { - return false; + return UpdateCondition.NO; } const timeDifference = @@ -99,19 +123,31 @@ export function shouldUpdate( console.log("Target latest price: ", targetLatestPrice); console.log( - `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}?) OR ` + + `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` + `Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${ priceConfig.priceDeviation - }%?) OR ` + + }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` + `Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${ priceConfig.confidenceRatio - }%?)` + }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?)` ); - const result = + if ( timeDifference >= priceConfig.timeDifference || priceDeviationPct >= priceConfig.priceDeviation || - confidenceRatioPct >= priceConfig.confidenceRatio; - - return result; + confidenceRatioPct >= priceConfig.confidenceRatio + ) { + return UpdateCondition.YES; + } else if ( + (priceConfig.earlyUpdateTimeDifference !== undefined && + timeDifference >= priceConfig.earlyUpdateTimeDifference) || + (priceConfig.earlyUpdatePriceDeviation !== undefined && + priceDeviationPct >= priceConfig.earlyUpdatePriceDeviation) || + (priceConfig.earlyUpdateConfidenceRatio !== undefined && + confidenceRatioPct >= priceConfig.earlyUpdateConfidenceRatio) + ) { + return UpdateCondition.EARLY; + } else { + return UpdateCondition.NO; + } }