Browse Source

Update price feeds early if part of a batch (#905)

Jayant Krishnamurthy 2 years ago
parent
commit
339b2f6ce0

+ 13 - 11
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.
   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.
   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.
   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
 You can get the list of available price feeds from
 [here](https://pyth.network/developers/price-feed-ids/).
 [here](https://pyth.network/developers/price-feed-ids/).
 
 
@@ -57,7 +68,7 @@ cd price_pusher
 npm run start -- evm --endpoint wss://example-rpc.com \
 npm run start -- evm --endpoint wss://example-rpc.com \
     --pyth-contract-address 0xff1a0f4744e8582DF...... \
     --pyth-contract-address 0xff1a0f4744e8582DF...... \
     --price-service-endpoint https://example-pyth-price.com \
     --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" \
     --mnemonic-file "path/to/mnemonic.txt" \
     [--pushing-frequency 10] \
     [--pushing-frequency 10] \
     [--polling-frequency 5] \
     [--polling-frequency 5] \
@@ -66,7 +77,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \
 # For Injective
 # For Injective
 npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
 npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
     --pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-pyth-price.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" \
     --mnemonic-file "path/to/mnemonic.txt" \
     [--pushing-frequency 10] \
     [--pushing-frequency 10] \
     [--polling-frequency 5] \
     [--polling-frequency 5] \
@@ -94,15 +105,6 @@ npm run start -- sui
   [--polling-frequency 5] \
   [--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
 # Or, run the price pusher docker image instead of building from the source
 docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-arguments>
 docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-arguments>
 ```
 ```

+ 4 - 0
price_pusher/price-config.mainnet.sample.yaml

@@ -8,3 +8,7 @@
   time_difference: 60
   time_difference: 60
   price_deviation: 1
   price_deviation: 1
   confidence_ratio: 1
   confidence_ratio: 1
+  early_update:
+    time_difference: 30
+    price_deviation: 0.5
+    confidence_ratio: 0.1

+ 4 - 0
price_pusher/price-config.testnet.sample.yaml

@@ -8,3 +8,7 @@
   time_difference: 60
   time_difference: 60
   price_deviation: 1
   price_deviation: 1
   confidence_ratio: 1
   confidence_ratio: 1
+  early_update:
+    time_difference: 30
+    price_deviation: 0.5
+    confidence_ratio: 0.1

+ 19 - 4
price_pusher/src/controller.ts

@@ -1,7 +1,7 @@
 import { UnixTimestamp } from "@pythnetwork/price-service-client";
 import { UnixTimestamp } from "@pythnetwork/price-service-client";
 import { DurationInSeconds, sleep } from "./utils";
 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 {
 export class Controller {
   private pushingFrequency: DurationInSeconds;
   private pushingFrequency: DurationInSeconds;
@@ -28,6 +28,9 @@ export class Controller {
     await sleep(this.pushingFrequency * 1000);
     await sleep(this.pushingFrequency * 1000);
 
 
     for (;;) {
     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 pricesToPush: PriceConfig[] = [];
       const pubTimesToPush: UnixTimestamp[] = [];
       const pubTimesToPush: UnixTimestamp[] = [];
 
 
@@ -39,12 +42,24 @@ export class Controller {
         const sourceLatestPrice =
         const sourceLatestPrice =
           this.sourcePriceListener.getLatestPriceInfo(priceId);
           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);
           pricesToPush.push(priceConfig);
           pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1);
           pubTimesToPush.push((targetLatestPrice?.publishTime || 0) + 1);
         }
         }
       }
       }
-      if (pricesToPush.length !== 0) {
+      if (pushThresholdMet) {
         console.log(
         console.log(
           "Some of the above values passed the threshold. Will push the price."
           "Some of the above values passed the threshold. Will push the price."
         );
         );

+ 5 - 0
price_pusher/src/evm/command.ts

@@ -91,6 +91,11 @@ export default {
       mnemonic,
       mnemonic,
       pythContractAddress
       pythContractAddress
     );
     );
+    console.log(
+      `Pushing updates from wallet address: ${pythContractFactory
+        .createWeb3PayerProvider()
+        .getAddress()}`
+    );
 
 
     const evmListener = new EvmPriceListener(pythContractFactory, priceItems, {
     const evmListener = new EvmPriceListener(pythContractFactory, priceItems, {
       pollingFrequency,
       pollingFrequency,

+ 1 - 1
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 update data is valid there is no possible rejection cause other than
           // the target chain price being already updated.
           // the target chain price being already updated.
           console.log(
           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."
               "has already updated, Skipping this push."
           );
           );
           return;
           return;

+ 47 - 11
price_pusher/src/price-config.ts

@@ -15,6 +15,11 @@ const PriceConfigFileSchema: Joi.Schema = Joi.array()
       time_difference: Joi.number().required(),
       time_difference: Joi.number().required(),
       price_deviation: Joi.number().required(),
       price_deviation: Joi.number().required(),
       confidence_ratio: 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")
   .unique("id")
@@ -27,6 +32,12 @@ export type PriceConfig = {
   timeDifference: DurationInSeconds;
   timeDifference: DurationInSeconds;
   priceDeviation: PctNumber;
   priceDeviation: PctNumber;
   confidenceRatio: 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[] {
 export function readPriceConfigFile(path: string): PriceConfig[] {
@@ -44,11 +55,24 @@ export function readPriceConfigFile(path: string): PriceConfig[] {
       timeDifference: priceConfigRaw.time_difference,
       timeDifference: priceConfigRaw.time_difference,
       priceDeviation: priceConfigRaw.price_deviation,
       priceDeviation: priceConfigRaw.price_deviation,
       confidenceRatio: priceConfigRaw.confidence_ratio,
       confidenceRatio: priceConfigRaw.confidence_ratio,
+
+      earlyUpdateTimeDifference: priceConfigRaw.early_update?.time_difference,
+      earlyUpdatePriceDeviation: priceConfigRaw.early_update?.price_deviation,
+      earlyUpdateConfidenceRatio: priceConfigRaw.early_update?.confidence_ratio,
     };
     };
     return priceConfig;
     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.
  * Checks whether on-chain price needs to be updated with the latest pyth price information.
  *
  *
@@ -59,12 +83,12 @@ export function shouldUpdate(
   priceConfig: PriceConfig,
   priceConfig: PriceConfig,
   sourceLatestPrice: PriceInfo | undefined,
   sourceLatestPrice: PriceInfo | undefined,
   targetLatestPrice: PriceInfo | undefined
   targetLatestPrice: PriceInfo | undefined
-): boolean {
+): UpdateCondition {
   const priceId = priceConfig.id;
   const priceId = priceConfig.id;
 
 
   // There is no price to update the target with.
   // There is no price to update the target with.
   if (sourceLatestPrice === undefined) {
   if (sourceLatestPrice === undefined) {
-    return false;
+    return UpdateCondition.YES;
   }
   }
 
 
   // It means that price never existed there. So we should push the latest price feed.
   // It means that price never existed there. So we should push the latest price feed.
@@ -72,12 +96,12 @@ export function shouldUpdate(
     console.log(
     console.log(
       `${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.`
       `${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
   // The current price is not newer than the price onchain
   if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
   if (sourceLatestPrice.publishTime < targetLatestPrice.publishTime) {
-    return false;
+    return UpdateCondition.NO;
   }
   }
 
 
   const timeDifference =
   const timeDifference =
@@ -99,19 +123,31 @@ export function shouldUpdate(
   console.log("Target latest price: ", targetLatestPrice);
   console.log("Target latest price: ", targetLatestPrice);
 
 
   console.log(
   console.log(
-    `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}?) OR ` +
+    `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` +
       `Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${
       `Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${
         priceConfig.priceDeviation
         priceConfig.priceDeviation
-      }%?) OR ` +
+      }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` +
       `Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${
       `Confidence ratio: ${confidenceRatioPct.toFixed(5)}% (< ${
         priceConfig.confidenceRatio
         priceConfig.confidenceRatio
-      }%?)`
+      }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?)`
   );
   );
 
 
-  const result =
+  if (
     timeDifference >= priceConfig.timeDifference ||
     timeDifference >= priceConfig.timeDifference ||
     priceDeviationPct >= priceConfig.priceDeviation ||
     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;
+  }
 }
 }