瀏覽代碼

[solana push oracle] Idempotent updates (#1452)

* idempotent updates

* clippy
Jayant Krishnamurthy 1 年之前
父節點
當前提交
a60733559c

+ 1 - 0
target_chains/solana/Cargo.lock

@@ -3019,6 +3019,7 @@ name = "pyth-push-oracle"
 version = "0.1.0"
 dependencies = [
  "anchor-lang",
+ "byteorder",
  "common-test-utils",
  "program-simulator",
  "pyth-solana-receiver",

+ 1 - 0
target_chains/solana/programs/pyth-push-oracle/Cargo.toml

@@ -19,6 +19,7 @@ test-bpf = []
 anchor-lang = { workspace = true }
 pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk" }
 solana-program = { workspace = true }
+byteorder = "1.4.3"
 pyth-solana-receiver-sdk = { path = "../../pyth_solana_receiver_sdk"}
 pyth-solana-receiver = { path = "../pyth-solana-receiver", features = ["cpi"]}
 

+ 42 - 15
target_chains/solana/programs/pyth-push-oracle/src/lib.rs

@@ -9,7 +9,13 @@ use {
         price_update::PriceUpdateV2,
         PYTH_PUSH_ORACLE_ID,
     },
-    pythnet_sdk::messages::FeedId,
+    pythnet_sdk::{
+        messages::{
+            FeedId,
+            Message,
+        },
+        wire::from_slice,
+    },
 };
 
 pub mod sdk;
@@ -22,6 +28,10 @@ pub enum PushOracleError {
     UpdatesNotMonotonic,
     #[msg("Trying to update price feed with the wrong feed id")]
     PriceFeedMessageMismatch,
+    #[msg("The message in the update must be a PriceFeedMessage")]
+    UnsupportedMessageType,
+    #[msg("Could not deserialize the message in the update")]
+    DeserializeMessageFailed,
 }
 #[program]
 pub mod pyth_push_oracle {
@@ -53,7 +63,7 @@ pub mod pyth_push_oracle {
         let signer_seeds = &[&seeds[..]];
         let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
 
-
+        // Get the timestamp of the price currently stored in the price feed account.
         let current_timestamp = {
             if ctx.accounts.price_feed_account.data_is_empty() {
                 0
@@ -64,20 +74,37 @@ pub mod pyth_push_oracle {
                 price_feed_account.price_message.publish_time
             }
         };
-        pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
-        {
-            let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
-            let price_feed_account =
-                PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
 
-            require!(
-                price_feed_account.price_message.publish_time > current_timestamp,
-                PushOracleError::UpdatesNotMonotonic
-            );
-            require!(
-                price_feed_account.price_message.feed_id == feed_id,
-                PushOracleError::PriceFeedMessageMismatch
-            );
+        // Get the timestamp of the price in the arguments (that we are trying to put in the account).
+        // It is a little annoying that we have to redundantly deserialize the message here, but
+        // it is required to make txs pushing stale prices succeed w/o updating the on-chain price.
+        //
+        // Note that we don't do any validity checks on the proof etc. here. If the caller passes an
+        // invalid message with a newer timestamp, the validity checks will be performed by pyth_solana_receiver.
+        let message =
+            from_slice::<byteorder::BE, Message>(params.merkle_price_update.message.as_ref())
+                .map_err(|_| PushOracleError::DeserializeMessageFailed)?;
+        let next_timestamp = match message {
+            Message::PriceFeedMessage(price_feed_message) => price_feed_message.publish_time,
+            Message::TwapMessage(_) => {
+                return err!(PushOracleError::UnsupportedMessageType);
+            }
+        };
+
+        // Only update the price feed if the message contains a newer price. Pushing a stale price
+        // suceeds without changing the on-chain state.
+        if next_timestamp > current_timestamp {
+            pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
+            {
+                let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
+                let price_feed_account =
+                    PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
+
+                require!(
+                    price_feed_account.price_message.feed_id == feed_id,
+                    PushOracleError::PriceFeedMessageMismatch
+                );
+            }
         }
         Ok(())
     }

+ 16 - 20
target_chains/solana/programs/pyth-push-oracle/tests/test_update_price_feed.rs

@@ -161,26 +161,22 @@ async fn test_update_price_feed() {
         program_simulator.get_clock().await.unwrap().slot
     );
 
-    // post another update, outdated
-    assert_eq!(
-        program_simulator
-            .process_ix_with_default_compute_limit(
-                UpdatePriceFeed::populate(
-                    poster.pubkey(),
-                    encoded_vaa_addresses[0],
-                    DEFAULT_SHARD,
-                    feed_id,
-                    DEFAULT_TREASURY_ID,
-                    merkle_price_updates[1].clone(),
-                ),
-                &vec![&poster],
-                None,
-            )
-            .await
-            .unwrap_err()
-            .unwrap(),
-        into_transaction_error(PushOracleError::UpdatesNotMonotonic)
-    );
+    // post a stale update. The tx succeeds w/o updating on-chain account state.
+    program_simulator
+        .process_ix_with_default_compute_limit(
+            UpdatePriceFeed::populate(
+                poster.pubkey(),
+                encoded_vaa_addresses[0],
+                DEFAULT_SHARD,
+                feed_id,
+                DEFAULT_TREASURY_ID,
+                merkle_price_updates[0].clone(),
+            ),
+            &vec![&poster],
+            None,
+        )
+        .await
+        .unwrap();
 
     assert_treasury_balance(
         &mut program_simulator,