Skip to content

Commit

Permalink
feat(fortuna): set provider fee per chain (#1546)
Browse files Browse the repository at this point in the history
* set provider fee from provider config

* optional committments

* Refactor setup provider

---------

Co-authored-by: Amin Moghaddam <[email protected]>
  • Loading branch information
Dev Kalra and m30m authored May 23, 2024
1 parent 98c1fcc commit 6782770
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 118 deletions.
2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/fortuna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "5.3.3"
version = "5.4.0"
edition = "2021"

[dependencies]
Expand Down
1 change: 1 addition & 0 deletions apps/fortuna/provider-config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 3 additions & 13 deletions apps/fortuna/src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
225 changes: 134 additions & 91 deletions apps/fortuna/src/command/setup_provider.rs
Original file line number Diff line number Diff line change
@@ -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,
},
Expand All @@ -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(())
Expand Down
21 changes: 14 additions & 7 deletions apps/fortuna/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
}
}

Expand Down
9 changes: 4 additions & 5 deletions apps/fortuna/src/config/setup_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::config::{
ConfigOptions,
ProviderConfigOptions,
RandomnessOptions,
},
anyhow::Result,
Expand All @@ -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")]
Expand All @@ -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")]
Expand Down

0 comments on commit 6782770

Please sign in to comment.