Эх сурвалжийг харах

feat(fortuna): set provider fee per chain (#1546)

* set provider fee from provider config

* optional committments

* Refactor setup provider

---------

Co-authored-by: Amin Moghaddam <amin@pyth.network>
Dev Kalra 1 жил өмнө
parent
commit
6782770792

+ 1 - 1
apps/fortuna/Cargo.lock

@@ -1488,7 +1488,7 @@ dependencies = [
 
 [[package]]
 name = "fortuna"
-version = "5.3.3"
+version = "5.4.0"
 dependencies = [
  "anyhow",
  "axum",

+ 1 - 1
apps/fortuna/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name    = "fortuna"
-version = "5.3.3"
+version = "5.4.0"
 edition = "2021"
 
 [dependencies]

+ 1 - 0
apps/fortuna/provider-config.sample.yaml

@@ -5,3 +5,4 @@ chains:
       - seed: [219,125,217,197,234,88,208,120,21,181,172,143,239,102,41,233,167,212,237,106,37,255,184,165,238,121,230,155,116,158,173,48]
         chain_length: 10000
         original_commitment_sequence_number: 104
+    fee: 1500000000000000

+ 3 - 13
apps/fortuna/src/command/run.rs

@@ -149,25 +149,15 @@ pub async fn run_keeper(
 
 pub async fn run(opts: &RunOptions) -> Result<()> {
     let config = Config::load(&opts.config.config)?;
-    let provider_config = opts
-        .provider_config
-        .provider_config
-        .as_ref()
-        .map(|path| ProviderConfig::load(&path).expect("Failed to load provider config"));
+    let provider_config = ProviderConfig::load(&opts.provider_config.provider_config)?;
     let secret = opts.randomness.load_secret()?;
     let (tx_exit, rx_exit) = watch::channel(false);
 
     let mut chains: HashMap<ChainId, BlockchainState> = HashMap::new();
     for (chain_id, chain_config) in &config.chains {
         let contract = Arc::new(PythContract::from_config(&chain_config)?);
-        let provider_chain_config = provider_config
-            .as_ref()
-            .and_then(|c| c.get_chain_config(chain_id));
-        let mut provider_commitments = provider_chain_config
-            .as_ref()
-            .map(|c| c.get_sorted_commitments())
-            .unwrap_or_else(|| Vec::new());
-
+        let provider_chain_config = provider_config.get_chain_config(chain_id)?;
+        let mut provider_commitments = provider_chain_config.get_sorted_commitments();
         let provider_info = contract.get_provider_info(opts.provider).call().await?;
         let latest_metadata =
             bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)

+ 134 - 91
apps/fortuna/src/command/setup_provider.rs

@@ -1,13 +1,21 @@
 use {
     crate::{
-        api::get_register_uri,
-        chain::ethereum::SignablePythContract,
+        api::{
+            get_register_uri,
+            ChainId,
+        },
+        chain::ethereum::{
+            ProviderInfo,
+            SignablePythContract,
+        },
         command::{
             register_provider,
             register_provider::CommitmentMetadata,
         },
         config::{
             Config,
+            EthereumConfig,
+            ProviderConfig,
             RegisterProviderOptions,
             SetupProviderOptions,
         },
@@ -29,115 +37,150 @@ use {
         types::Bytes,
     },
     std::sync::Arc,
+    tracing::Instrument,
 };
 
 /// Setup provider for all the chains.
+pub async fn setup_provider(opts: &SetupProviderOptions) -> Result<()> {
+    let config = Config::load(&opts.config.config)?;
+    for (chain_id, chain_config) in &config.chains {
+        setup_chain_provider(opts, chain_id, chain_config).await?;
+    }
+    Ok(())
+}
+
+
+/// Setup provider for a single chain.
 /// 1. Register if there was no previous registration.
 /// 2. Re-register if there are no more random numbers to request on the contract.
 /// 3. Re-register if there is a mismatch in generated hash chain.
 /// 4. Update provider fee if there is a mismatch with the fee set on contract.
 /// 5. Update provider uri if there is a mismatch with the uri set on contract.
-pub async fn setup_provider(opts: &SetupProviderOptions) -> Result<()> {
-    let config = Config::load(&opts.config.config)?;
+#[tracing::instrument(name="setup_chain_provider", skip_all, fields(chain_id=chain_id))]
+async fn setup_chain_provider(
+    opts: &SetupProviderOptions,
+    chain_id: &ChainId,
+    chain_config: &EthereumConfig,
+) -> Result<()> {
+    tracing::info!("Setting up provider for chain: {0}", chain_id);
+    let provider_config = ProviderConfig::load(&opts.provider_config.provider_config)?;
     let private_key = opts.load_private_key()?;
-    let secret = opts.randomness.load_secret()?;
     let provider_address = private_key.clone().parse::<LocalWallet>()?.address();
+    let provider_fee = provider_config.get_chain_config(chain_id)?.fee;
+    // Initialize a Provider to interface with the EVM contract.
+    let contract = Arc::new(SignablePythContract::from_config(&chain_config, &private_key).await?);
 
-    for (chain_id, chain_config) in &config.chains {
-        // Initialize a Provider to interface with the EVM contract.
-        let contract =
-            Arc::new(SignablePythContract::from_config(&chain_config, &private_key).await?);
+    tracing::info!("Fetching provider info");
+    let provider_info = contract.get_provider_info(provider_address).call().await?;
+    tracing::info!("Provider info: {:?}", provider_info);
 
-        tracing::info!("{}: fetching provider info", chain_id);
-        let provider_info = contract.get_provider_info(provider_address).call().await?;
-        tracing::info!("{0}: provider info: {1:?}", chain_id, provider_info);
+    let mut register = false;
 
-        let mut register = false;
+    let uri = get_register_uri(&opts.base_uri, &chain_id)?;
 
-        let uri = get_register_uri(&opts.base_uri, &chain_id)?;
-        let uri_as_bytes: Bytes = AbiBytes::from(uri.as_str()).into();
+    // This condition satisfies for both when there is no registration and when there are no
+    // more random numbers left to request
+    if provider_info.end_sequence_number <= provider_info.sequence_number {
+        tracing::info!(
+            "endSequenceNumber <= sequenceNumber. endSequenceNumber={}, sequenceNumber={}",
+            provider_info.end_sequence_number,
+            provider_info.sequence_number
+        );
+        register = true;
+    } else {
+        let metadata =
+            bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)
+                .map_err(|e| {
+                    anyhow!(
+                        "Chain: {} - Failed to deserialize commitment metadata: {}",
+                        &chain_id,
+                        e
+                    )
+                })?;
 
-        // This condition satisfies for both when there is no registration and when there are no
-        // more random numbers left to request
-        if provider_info.end_sequence_number <= provider_info.sequence_number {
-            tracing::info!(
-                "{0}: endSequenceNumber <= sequenceNumber. endSequenceNumber={1}, sequenceNumber={2}",
-                chain_id,
-                provider_info.end_sequence_number,
-                provider_info.sequence_number
-            );
-            register = true;
-        } else {
-            let metadata =
-                bincode::deserialize::<CommitmentMetadata>(&provider_info.commitment_metadata)
-                    .map_err(|e| {
-                        anyhow!(
-                            "Chain: {} - Failed to deserialize commitment metadata: {}",
-                            &chain_id,
-                            e
-                        )
-                    })?;
-
-            let hash_chain = PebbleHashChain::from_config(
-                &secret,
-                &chain_id,
-                &provider_address,
-                &chain_config.contract_addr,
-                &metadata.seed,
-                opts.randomness.chain_length,
-            )?;
-            let chain_state = HashChainState {
-                offsets:     vec![provider_info
-                    .original_commitment_sequence_number
-                    .try_into()?],
-                hash_chains: vec![hash_chain],
-            };
+        let secret = opts.randomness.load_secret()?;
+        let hash_chain = PebbleHashChain::from_config(
+            &secret,
+            &chain_id,
+            &provider_address,
+            &chain_config.contract_addr,
+            &metadata.seed,
+            opts.randomness.chain_length,
+        )?;
+        let chain_state = HashChainState {
+            offsets:     vec![provider_info
+                .original_commitment_sequence_number
+                .try_into()?],
+            hash_chains: vec![hash_chain],
+        };
 
 
-            if chain_state.reveal(provider_info.original_commitment_sequence_number)?
-                != provider_info.original_commitment
-            {
-                tracing::info!(
-                    "{}: the root of the generated hash chain does not match the commitment",
-                    &chain_id
-                );
-                register = true;
-            }
+        if chain_state.reveal(provider_info.original_commitment_sequence_number)?
+            != provider_info.original_commitment
+        {
+            tracing::info!("The root of the generated hash chain does not match the commitment",);
+            register = true;
         }
+    }
+
+    if register {
+        tracing::info!("Registering");
+        register_provider(&RegisterProviderOptions {
+            config: opts.config.clone(),
+            chain_id: chain_id.clone(),
+            private_key: private_key.clone(),
+            randomness: opts.randomness.clone(),
+            fee: provider_fee,
+            uri,
+        })
+        .await
+        .map_err(|e| anyhow!("Chain: {} - Failed to register provider: {}", &chain_id, e))?;
+        tracing::info!("Registered");
+    } else {
+        sync_fee(&contract, &provider_info, provider_fee)
+            .in_current_span()
+            .await?;
+        sync_uri(&contract, &provider_info, uri)
+            .in_current_span()
+            .await?;
+    }
+    Ok(())
+}
 
-        if register {
-            tracing::info!("{}: registering", &chain_id);
-            register_provider(&RegisterProviderOptions {
-                config: opts.config.clone(),
-                chain_id: chain_id.clone(),
-                private_key: private_key.clone(),
-                randomness: opts.randomness.clone(),
-                fee: opts.fee,
-                uri,
-            })
-            .await
-            .map_err(|e| anyhow!("Chain: {} - Failed to register provider: {}", &chain_id, e))?;
-            tracing::info!("{}: registered", &chain_id);
-        } else {
-            if provider_info.fee_in_wei != opts.fee {
-                tracing::info!("{}: updating provider fee", chain_id);
-                if let Some(r) = contract.set_provider_fee(opts.fee).send().await?.await? {
-                    tracing::info!("{0}: updated provider fee: {1:?}", chain_id, r);
-                }
-            }
+async fn sync_uri(
+    contract: &Arc<SignablePythContract>,
+    provider_info: &ProviderInfo,
+    uri: String,
+) -> Result<()> {
+    let uri_as_bytes: Bytes = AbiBytes::from(uri.as_str()).into();
+    if &provider_info.uri != &uri_as_bytes {
+        tracing::info!("Updating provider uri to {}", uri);
+        if let Some(receipt) = contract
+            .set_provider_uri(uri_as_bytes)
+            .send()
+            .await?
+            .await?
+        {
+            tracing::info!("Updated provider uri: {:?}", receipt);
+        }
+    }
+    Ok(())
+}
 
-            if &provider_info.uri != &uri_as_bytes {
-                tracing::info!("{}: updating provider uri", chain_id);
-                if let Some(receipt) = contract
-                    .set_provider_uri(uri_as_bytes)
-                    .send()
-                    .await?
-                    .log_msg("Pending transfer hash")
-                    .await?
-                {
-                    tracing::info!("{0}: updated provider uri: {1:?}", chain_id, receipt);
-                }
-            }
+async fn sync_fee(
+    contract: &Arc<SignablePythContract>,
+    provider_info: &ProviderInfo,
+    provider_fee: u128,
+) -> Result<()> {
+    if provider_info.fee_in_wei != provider_fee {
+        tracing::info!("Updating provider fee {}", provider_fee);
+        if let Some(r) = contract
+            .set_provider_fee(provider_fee)
+            .send()
+            .await?
+            .await?
+        {
+            tracing::info!("Updated provider fee: {:?}", r);
         }
     }
     Ok(())

+ 14 - 7
apps/fortuna/src/config.rs

@@ -167,7 +167,7 @@ pub struct EthereumConfig {
 pub struct ProviderConfigOptions {
     #[arg(long = "provider-config")]
     #[arg(env = "FORTUNA_PROVIDER_CONFIG")]
-    pub provider_config: Option<String>,
+    pub provider_config: String,
 }
 
 #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -185,26 +185,33 @@ impl ProviderConfig {
 
     /// Get the provider chain config. The method returns an Option for ProviderChainConfig.
     /// We may not have past any commitments for a chain. For example, for a new chain
-    pub fn get_chain_config(&self, chain_id: &ChainId) -> Option<ProviderChainConfig> {
-        self.chains.get(chain_id).map(|x| x.clone())
+    pub fn get_chain_config(&self, chain_id: &ChainId) -> Result<ProviderChainConfig> {
+        self.chains.get(chain_id).map(|x| x.clone()).ok_or(
+            anyhow!(
+                "Could not find chain id {} in provider configuration",
+                &chain_id
+            )
+            .into(),
+        )
     }
 }
 
 #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 pub struct ProviderChainConfig {
-    commitments: Vec<Commitment>,
+    commitments: Option<Vec<Commitment>>,
+    pub fee:     u128,
 }
 
 impl ProviderChainConfig {
     /// Returns a clone of the commitments in the sorted order.
     /// `HashChainState`  requires offsets to be in order.
     pub fn get_sorted_commitments(&self) -> Vec<Commitment> {
-        let mut sorted_commitments = self.commitments.clone();
-        sorted_commitments.sort_by(|c1, c2| {
+        let mut commitments = self.commitments.clone().unwrap_or(Vec::new());
+        commitments.sort_by(|c1, c2| {
             c1.original_commitment_sequence_number
                 .cmp(&c2.original_commitment_sequence_number)
         });
-        sorted_commitments
+        commitments
     }
 }
 

+ 4 - 5
apps/fortuna/src/config/setup_provider.rs

@@ -1,6 +1,7 @@
 use {
     crate::config::{
         ConfigOptions,
+        ProviderConfigOptions,
         RandomnessOptions,
     },
     anyhow::Result,
@@ -15,6 +16,9 @@ pub struct SetupProviderOptions {
     #[command(flatten)]
     pub config: ConfigOptions,
 
+    #[command(flatten)]
+    pub provider_config: ProviderConfigOptions,
+
     /// Path to a file containing a 20-byte (40 char) hex encoded Ethereum private key.
     /// This key is required to submit transactions (such as registering with the contract).
     #[arg(long = "private-key")]
@@ -24,11 +28,6 @@ pub struct SetupProviderOptions {
     #[command(flatten)]
     pub randomness: RandomnessOptions,
 
-    /// The fee to charge (in wei) for each requested random number
-    #[arg(long = "pyth-contract-fee")]
-    #[arg(default_value = "100")]
-    pub fee: u128,
-
     /// The base URI for fortuna.
     /// e.g., https://fortuna-staging.dourolabs.app
     #[arg(long = "uri")]