Jayant Krishnamurthy 2 years ago
parent
commit
e46c9ce325
4 changed files with 32 additions and 7 deletions
  1. 2 1
      pyth-rng/src/api.rs
  2. 2 3
      pyth-rng/src/api/get_randomness_proof.rs
  3. 11 3
      pyth-rng/src/main.rs
  4. 17 0
      pyth-rng/src/state.rs

+ 2 - 1
pyth-rng/src/api.rs

@@ -15,6 +15,7 @@ pub use {
 
 use crate::ethereum::PythProvider;
 use crate::PebbleHashChain;
+use crate::state::HashChainState;
 
 mod get_randomness_proof;
 
@@ -22,7 +23,7 @@ mod get_randomness_proof;
 // due to rotations. The older chains need to stick around too.
 #[derive(Clone)]
 pub struct ApiState {
-    pub state: Arc<PebbleHashChain>,
+    pub state: Arc<HashChainState>,
     pub contract: Arc<PythProvider>,
     pub provider: Address,
 }

+ 2 - 3
pyth-rng/src/api/get_randomness_proof.rs

@@ -14,7 +14,7 @@ use {
 };
 use crate::api::RestError;
 
-// FIXME docs
+// FIXME docs & RESTFUL schema
 /// Get a VAA for a price feed with a specific timestamp
 ///
 /// Given a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.
@@ -34,13 +34,12 @@ pub async fn get_random_value(
     QsQuery(params): QsQuery<GetRandomValueQueryParams>,
 ) -> Result<Json<GetRandomValueResponse>, RestError> {
     let sequence: u64 = params.sequence.try_into().map_err(|_| RestError::TestError)?;
-    let sequence_usize: usize = sequence.try_into().map_err(|_| RestError::TestError)?;
 
     let r = state.contract.get_request(state.provider, sequence).call().await.map_err(|_| RestError::TestError)?;
 
     if r.sequence_number != 0 {
         println!("Found request: {:?}", r);
-        let value = &state.state.reveal_ith(sequence_usize).map_err(|_| RestError::TestError)?;
+        let value = &state.state.reveal(sequence).map_err(|_| RestError::TestError)?;
         Ok(Json(GetRandomValueResponse { value: (*value).clone() }))
     } else {
         println!("No request for sequence number: {:?}", sequence);

+ 11 - 3
pyth-rng/src/main.rs

@@ -25,7 +25,7 @@ use ethereum::get_request;
 use ethers::core::types::Address;
 
 use crate::api::{ApiState, get_random_value};
-use crate::state::PebbleHashChain;
+use crate::state::{HashChainState, PebbleHashChain};
 use crate::ethereum::provider;
 
 pub mod api;
@@ -65,15 +65,23 @@ async fn run(opts: &RunOptions) -> Result<(), Box<dyn Error>> {
     let provider_info = contract.get_provider_info(provider_addr).call().await?;
 
     // Reconstruct the hash chain based on the metadata and check that it matches the on-chain commitment.
+    // TODO: we should instantiate the state here with multiple hash chains.
+    // This approach works fine as long as we haven't rotated the commitment (i.e., all user requests
+    // are for the most recent chain).
     let random: [u8; 32] = provider_info.commitment_metadata;
     let mut chain = PebbleHashChain::from_config(&opts.randomness, random)?;
-    if chain.reveal_ith(0)? != provider_info.original_commitment {
+    let chain_state = HashChainState {
+        offsets: vec![provider_info.original_commitment_sequence_number.try_into()?],
+        hash_chains: vec![chain],
+    };
+
+    if chain_state.reveal(provider_info.original_commitment_sequence_number)? != provider_info.original_commitment {
         println!("warning: root of chain does not match commitment!");
     } else {
         println!("Root of chain matches commitment");
     }
 
-    let mut state = ApiState { state: Arc::new(chain), contract, provider: provider_addr };
+    let mut state = ApiState { state: Arc::new(chain_state), contract, provider: provider_addr };
 
     // Initialize Axum Router. Note the type here is a `Router<State>` due to the use of the
     // `with_state` method which replaces `Body` with `State` in the type signature.

+ 17 - 0
pyth-rng/src/state.rs

@@ -53,3 +53,20 @@ impl PebbleHashChain {
         self.hash.len()
     }
 }
+
+/// `HashChainState` tracks the mapping between on-chain sequence numbers to hash chains.
+/// This struct is required to handle the case where the provider rotates their commitment,
+/// which requires tracking multiple hash chains here.
+pub struct HashChainState {
+    // The sequence number where the hash chain starts. Must be stored in sorted order.
+    pub offsets: Vec<usize>,
+    pub hash_chains: Vec<PebbleHashChain>,
+}
+
+impl HashChainState {
+    pub fn reveal(&self, sequence_number: u64) -> Result<[u8; 32]> {
+        let sequence_number: usize = sequence_number.try_into()?;
+        let chain_index = self.offsets.partition_point(|x| x < &sequence_number);
+        self.hash_chains[chain_index].reveal_ith(sequence_number - self.offsets[chain_index])
+    }
+}