diff --git a/apps/argus/src/api.rs b/apps/argus/src/api.rs index e02793db75..9aacd2f10c 100644 --- a/apps/argus/src/api.rs +++ b/apps/argus/src/api.rs @@ -1,7 +1,6 @@ use { crate::{ chain::reader::{BlockNumber, BlockStatus, EntropyReader}, - state::HashChainState, }, anyhow::Result, axum::{ @@ -78,8 +77,6 @@ impl ApiState { pub struct BlockchainState { /// The chain id for this blockchain, useful for logging pub id: ChainId, - /// The hash chain(s) required to serve random numbers for this blockchain - pub state: Arc, /// The contract that the server is fulfilling requests for. pub contract: Arc, /// The address of the provider that this server is operating for. diff --git a/apps/argus/src/command/register_provider.rs b/apps/argus/src/command/register_provider.rs index 1833003434..6e72a963ff 100644 --- a/apps/argus/src/command/register_provider.rs +++ b/apps/argus/src/command/register_provider.rs @@ -3,12 +3,10 @@ use { api::{get_register_uri, ChainId}, chain::ethereum::SignablePythContract, config::{Config, EthereumConfig, ProviderConfig, RegisterProviderOptions}, - state::PebbleHashChain, }, anyhow::{anyhow, Result}, ethers::{ abi::Bytes, - signers::{LocalWallet, Signer}, types::U256, }, std::sync::Arc, @@ -45,27 +43,13 @@ pub async fn register_provider_from_config( Arc::new(SignablePythContract::from_config(chain_config, &private_key_string).await?); // Create a new random hash chain. let random = rand::random::<[u8; 32]>(); - let secret = provider_config - .secret - .load()? - .ok_or(anyhow!("Please specify a provider secret in the config"))?; - let commitment_length = provider_config.chain_length; - tracing::info!("Generating hash chain"); - let chain = PebbleHashChain::from_config( - &secret, - chain_id, - &private_key_string.parse::()?.address(), - &chain_config.contract_addr, - &random, - commitment_length, - provider_config.chain_sample_interval, - )?; - tracing::info!("Done generating hash chain"); + // FIXME: delete this + let commitment_length = 1000; // Arguments to the contract to register our new provider. let fee_in_wei = chain_config.fee; - let commitment = chain.reveal_ith(0)?; + let commitment = [0; 32]; // Store the random seed and chain length in the metadata field so that we can regenerate the hash // chain at-will. (This is secure because you can't generate the chain unless you also have the secret) let commitment_metadata = CommitmentMetadata { diff --git a/apps/argus/src/command/run.rs b/apps/argus/src/command/run.rs index 69f0f1e05b..02540718d3 100644 --- a/apps/argus/src/command/run.rs +++ b/apps/argus/src/command/run.rs @@ -2,10 +2,8 @@ use { crate::{ api::{self, BlockchainState, ChainId}, chain::ethereum::InstrumentedPythContract, - command::register_provider::CommitmentMetadata, - config::{Commitment, Config, EthereumConfig, RunOptions}, + config::{Config, EthereumConfig, RunOptions}, keeper::{self, keeper_metrics::KeeperMetrics}, - state::{HashChainState, PebbleHashChain}, }, fortuna::eth_utils::traced_client::{RpcMetrics, TracedClient}, anyhow::{anyhow, Error, Result}, @@ -107,22 +105,16 @@ pub async fn run_keeper( pub async fn run(opts: &RunOptions) -> Result<()> { let config = Config::load(&opts.config.config)?; - let secret = config.provider.secret.load()?.ok_or(anyhow!( - "Please specify a provider secret in the config file." - ))?; let (tx_exit, rx_exit) = watch::channel(false); let metrics_registry = Arc::new(RwLock::new(Registry::default())); let rpc_metrics = Arc::new(RpcMetrics::new(metrics_registry.clone()).await); let mut tasks = Vec::new(); for (chain_id, chain_config) in config.chains.clone() { - let secret_copy = secret.clone(); let rpc_metrics = rpc_metrics.clone(); tasks.push(spawn(async move { let state = setup_chain_state( &config.provider.address, - &secret_copy, - config.provider.chain_sample_interval, &chain_id, &chain_config, rpc_metrics, @@ -189,8 +181,6 @@ pub async fn run(opts: &RunOptions) -> Result<()> { async fn setup_chain_state( provider: &Address, - secret: &str, - chain_sample_interval: u64, chain_id: &ChainId, chain_config: &EthereumConfig, rpc_metrics: Arc, @@ -200,80 +190,9 @@ async fn setup_chain_state( chain_id.clone(), rpc_metrics, )?); - let mut provider_commitments = chain_config.commitments.clone().unwrap_or_default(); - provider_commitments.sort_by(|c1, c2| { - c1.original_commitment_sequence_number - .cmp(&c2.original_commitment_sequence_number) - }); - - let provider_info = contract.get_provider_info(*provider).call().await?; - let latest_metadata = bincode::deserialize::( - &provider_info.commitment_metadata, - ) - .map_err(|e| { - anyhow!( - "Chain: {} - Failed to deserialize commitment metadata: {}", - &chain_id, - e - ) - })?; - - let last_prior_commitment = provider_commitments.last(); - if last_prior_commitment.is_some() - && last_prior_commitment - .unwrap() - .original_commitment_sequence_number - >= provider_info.original_commitment_sequence_number - { - return Err(anyhow!("The current hash chain for chain id {} has configured commitments for sequence numbers greater than the current on-chain sequence number. Are the commitments configured correctly?", &chain_id)); - } - - provider_commitments.push(Commitment { - seed: latest_metadata.seed, - chain_length: latest_metadata.chain_length, - original_commitment_sequence_number: provider_info.original_commitment_sequence_number, - }); - - // TODO: we may want to load the hash chain in a lazy/fault-tolerant way. If there are many blockchains, - // then it's more likely that some RPC fails. We should tolerate these faults and generate the hash chain - // later when a user request comes in for that chain. - - let mut offsets = Vec::::new(); - let mut hash_chains = Vec::::new(); - - for commitment in &provider_commitments { - let offset = commitment.original_commitment_sequence_number.try_into()?; - offsets.push(offset); - - let pebble_hash_chain = PebbleHashChain::from_config( - secret, - chain_id, - provider, - &chain_config.contract_addr, - &commitment.seed, - commitment.chain_length, - chain_sample_interval, - ) - .map_err(|e| anyhow!("Failed to create hash chain: {}", e))?; - hash_chains.push(pebble_hash_chain); - } - - let chain_state = HashChainState { - offsets, - hash_chains, - }; - - if chain_state.reveal(provider_info.original_commitment_sequence_number)? - != provider_info.original_commitment - { - return Err(anyhow!("The root of the generated hash chain for chain id {} does not match the commitment. Are the secret and chain length configured correctly?", &chain_id)); - } else { - tracing::info!("Root of chain id {} matches commitment", &chain_id); - } let state = BlockchainState { id: chain_id.clone(), - state: Arc::new(chain_state), contract, provider_address: *provider, reveal_delay_blocks: chain_config.reveal_delay_blocks, diff --git a/apps/argus/src/command/setup_provider.rs b/apps/argus/src/command/setup_provider.rs index 3c587b0d14..7cebbd9db9 100644 --- a/apps/argus/src/command/setup_provider.rs +++ b/apps/argus/src/command/setup_provider.rs @@ -2,9 +2,8 @@ use { crate::{ api::{get_register_uri, ChainId}, chain::ethereum::{ProviderInfo, SignablePythContract}, - command::register_provider::{register_provider_from_config, CommitmentMetadata}, + command::register_provider::register_provider_from_config, config::{Config, EthereumConfig, SetupProviderOptions}, - state::{HashChainState, PebbleHashChain}, }, anyhow::{anyhow, Result}, ethers::{ @@ -56,8 +55,6 @@ pub async fn setup_provider(opts: &SetupProviderOptions) -> Result<()> { /// 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. #[tracing::instrument(name = "setup_chain_provider", skip_all, fields(chain_id = chain_id))] @@ -79,72 +76,11 @@ async fn setup_chain_provider( let provider_info = contract.get_provider_info(provider_address).call().await?; tracing::info!("Provider info: {:?}", provider_info); - let mut register = false; - - // 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::(&provider_info.commitment_metadata) - .map_err(|e| { - anyhow!( - "Chain: {} - Failed to deserialize commitment metadata: {}", - &chain_id, - e - ) - })?; - - let secret = provider_config.secret.load()?.ok_or(anyhow!( - "Please specify a provider secret in the config file." - ))?; - if metadata.chain_length != provider_config.chain_length { - tracing::info!( - "Chain length mismatch. metadata.chain_length={}, provider_config.chain_length={}", - metadata.chain_length, - provider_config.chain_length - ); - register = true; - } else { - let hash_chain = PebbleHashChain::from_config( - &secret, - chain_id, - &provider_address, - &chain_config.contract_addr, - &metadata.seed, - provider_config.chain_length, - provider_config.chain_sample_interval, - )?; - 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", - ); - register = true; - } - } - } - if register { - tracing::info!("Registering"); - register_provider_from_config(provider_config, chain_id, chain_config) - .await - .map_err(|e| anyhow!("Chain: {} - Failed to register provider: {}", &chain_id, e))?; - tracing::info!("Registered"); - } + tracing::info!("Registering"); + register_provider_from_config(provider_config, chain_id, chain_config) + .await + .map_err(|e| anyhow!("Chain: {} - Failed to register provider: {}", &chain_id, e))?; + tracing::info!("Registered"); let provider_info = contract.get_provider_info(provider_address).call().await?; diff --git a/apps/argus/src/config.rs b/apps/argus/src/config.rs index 23f667178f..cd1769bccb 100644 --- a/apps/argus/src/config.rs +++ b/apps/argus/src/config.rs @@ -172,9 +172,6 @@ pub struct EthereumConfig { #[serde(default)] pub fee: u128, - /// Historical commitments made by the provider. - pub commitments: Option>, - /// Maximum number of hashes to record in a request. /// This should be set according to the maximum gas limit the provider supports for callbacks. pub max_num_hashes: Option, @@ -272,17 +269,6 @@ impl EscalationPolicyConfig { } } -/// A commitment that the provider used to generate random numbers at some point in the past. -/// These historical commitments need to be stored in the configuration to support transition points where -/// the commitment changes. In theory, this information is stored on the blockchain, but unfortunately it -/// is hard to retrieve from there. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct Commitment { - pub seed: [u8; 32], - pub chain_length: u64, - pub original_commitment_sequence_number: u64, -} - /// Configuration values that are common to a single provider (and shared across chains). #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct ProviderConfig { @@ -298,27 +284,11 @@ pub struct ProviderConfig { /// the private key (e.g., running the server). pub private_key: SecretString, - /// The provider's secret which is a 64-char hex string. - /// The secret is used for generating new hash chains - pub secret: SecretString, - - /// The length of the hash chain to generate. - pub chain_length: u64, - - /// How frequently the hash chain is sampled -- increase this value to tradeoff more - /// compute per request for less RAM use. - #[serde(default = "default_chain_sample_interval")] - pub chain_sample_interval: u64, - /// The address of the fee manager for the provider. Set this value to the keeper wallet address to /// enable keeper balance top-ups. pub fee_manager: Option
, } -fn default_chain_sample_interval() -> u64 { - 1 -} - /// Configuration values for the keeper service that are shared across chains. #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct KeeperConfig { diff --git a/apps/argus/src/keeper.rs b/apps/argus/src/keeper.rs index f6e4af6add..d9ee55513f 100644 --- a/apps/argus/src/keeper.rs +++ b/apps/argus/src/keeper.rs @@ -7,7 +7,6 @@ use { get_latest_safe_block, process_backlog, process_new_blocks, watch_blocks_wrapper, BlockRange, }, - keeper::commitment::update_commitments_loop, keeper::fee::adjust_fee_wrapper, keeper::fee::withdraw_fees_wrapper, keeper::track::track_accrued_pyth_fees, @@ -27,7 +26,6 @@ use { }; pub(crate) mod block; -pub(crate) mod commitment; pub(crate) mod fee; pub(crate) mod keeper_metrics; pub(crate) mod process_event; @@ -166,8 +164,6 @@ pub async fn run_keeper_threads( .in_current_span(), ); - spawn(update_commitments_loop(contract.clone(), chain_state.clone()).in_current_span()); - // Spawn a thread to track the provider info and the balance of the keeper spawn( async move { diff --git a/apps/argus/src/keeper/commitment.rs b/apps/argus/src/keeper/commitment.rs deleted file mode 100644 index 8b2db8f2c9..0000000000 --- a/apps/argus/src/keeper/commitment.rs +++ /dev/null @@ -1,64 +0,0 @@ -use { - crate::{ - api::BlockchainState, chain::ethereum::InstrumentedSignablePythContract, - keeper::block::get_latest_safe_block, - }, - fortuna::eth_utils::utils::send_and_confirm, - anyhow::{anyhow, Result}, - std::sync::Arc, - tokio::time::{self, Duration}, - tracing::{self, Instrument}, -}; - -/// Check whether we need to manually update the commitments to reduce numHashes for future -/// requests and reduce the gas cost of the reveal. -const UPDATE_COMMITMENTS_INTERVAL: Duration = Duration::from_secs(30); -const UPDATE_COMMITMENTS_THRESHOLD_FACTOR: f64 = 0.95; - -#[tracing::instrument(name = "update_commitments", skip_all)] -pub async fn update_commitments_loop( - contract: Arc, - chain_state: BlockchainState, -) { - loop { - if let Err(e) = update_commitments_if_necessary(contract.clone(), &chain_state) - .in_current_span() - .await - { - tracing::error!("Update commitments. error: {:?}", e); - } - time::sleep(UPDATE_COMMITMENTS_INTERVAL).await; - } -} - -pub async fn update_commitments_if_necessary( - contract: Arc, - chain_state: &BlockchainState, -) -> Result<()> { - //TODO: we can reuse the result from the last call from the watch_blocks thread to reduce RPCs - let latest_safe_block = get_latest_safe_block(chain_state).in_current_span().await; - let provider_address = chain_state.provider_address; - let provider_info = contract - .get_provider_info(provider_address) - .block(latest_safe_block) // To ensure we are not revealing sooner than we should - .call() - .await - .map_err(|e| anyhow!("Error while getting provider info. error: {:?}", e))?; - if provider_info.max_num_hashes == 0 { - return Ok(()); - } - let threshold = - ((provider_info.max_num_hashes as f64) * UPDATE_COMMITMENTS_THRESHOLD_FACTOR) as u64; - if provider_info.sequence_number - provider_info.current_commitment_sequence_number > threshold - { - let seq_number = provider_info.sequence_number - 1; - let provider_revelation = chain_state - .state - .reveal(seq_number) - .map_err(|e| anyhow!("Error revealing: {:?}", e))?; - let contract_call = - contract.advance_provider_commitment(provider_address, seq_number, provider_revelation); - send_and_confirm(contract_call).await?; - } - Ok(()) -} diff --git a/apps/argus/src/keeper/process_event.rs b/apps/argus/src/keeper/process_event.rs index 586723b55b..39b7716a18 100644 --- a/apps/argus/src/keeper/process_event.rs +++ b/apps/argus/src/keeper/process_event.rs @@ -5,7 +5,7 @@ use { chain::{ethereum::InstrumentedSignablePythContract, reader::RequestedWithCallbackEvent}, }, fortuna::eth_utils::utils::{submit_tx_with_backoff, EscalationPolicy}, - anyhow::{anyhow, Result}, + anyhow::Result, ethers::types::U256, std::sync::Arc, tracing, @@ -36,10 +36,7 @@ pub async fn process_event_with_backoff( metrics.requests.get_or_create(&account_label).inc(); tracing::info!("Started processing event"); - let provider_revelation = chain_state - .state - .reveal(event.sequence_number) - .map_err(|e| anyhow!("Error revealing: {:?}", e))?; + let provider_revelation = [0; 32]; let contract_call = contract.reveal_with_callback( event.provider_address, diff --git a/apps/argus/src/lib.rs b/apps/argus/src/lib.rs index 159e735544..784dac00c0 100644 --- a/apps/argus/src/lib.rs +++ b/apps/argus/src/lib.rs @@ -3,4 +3,3 @@ pub mod chain; pub mod command; pub mod config; pub mod keeper; -pub mod state; diff --git a/apps/argus/src/state.rs b/apps/argus/src/state.rs deleted file mode 100644 index 5460852744..0000000000 --- a/apps/argus/src/state.rs +++ /dev/null @@ -1,172 +0,0 @@ -use { - crate::api::ChainId, - anyhow::{ensure, Result}, - ethers::types::Address, - sha3::{Digest, Keccak256}, -}; - -/// A hash chain of a specific length. The hash chain has the property that -/// hash(chain.reveal_ith(i)) == chain.reveal_ith(i - 1) -/// -/// The implementation subsamples the elements of the chain such that it uses less memory -/// to keep the chain around. -#[derive(Clone)] -pub struct PebbleHashChain { - hash: Vec<[u8; 32]>, - sample_interval: usize, - length: usize, -} - -impl PebbleHashChain { - // Given a secret, we hash it with Keccak256 len times to get the final hash, this is an S/KEY - // like protocol in which revealing the hashes in reverse proves knowledge. - pub fn new(secret: [u8; 32], length: usize, sample_interval: usize) -> Self { - assert!(sample_interval > 0, "Sample interval must be positive"); - let mut hash = Vec::<[u8; 32]>::with_capacity(length); - let mut current: [u8; 32] = Keccak256::digest(secret).into(); - - hash.push(current); - for i in 1..length { - current = Keccak256::digest(current).into(); - if i % sample_interval == 0 { - hash.push(current); - } - } - - hash.reverse(); - - Self { - hash, - sample_interval, - length, - } - } - - pub fn from_config( - secret: &str, - chain_id: &ChainId, - provider_address: &Address, - contract_address: &Address, - random: &[u8; 32], - chain_length: u64, - sample_interval: u64, - ) -> Result { - let mut input: Vec = vec![]; - input.extend_from_slice(&hex::decode(secret.trim())?); - input.extend_from_slice(chain_id.as_bytes()); - input.extend_from_slice(provider_address.as_bytes()); - input.extend_from_slice(contract_address.as_bytes()); - input.extend_from_slice(random); - - let secret: [u8; 32] = Keccak256::digest(input).into(); - Ok(Self::new( - secret, - chain_length.try_into()?, - sample_interval.try_into()?, - )) - } - - pub fn reveal_ith(&self, i: usize) -> Result<[u8; 32]> { - ensure!(i < self.len(), "index not in range"); - - // Note that subsample_interval may not perfectly divide length, in which case the uneven segment is - // actually at the *front* of the list. Thus, it's easier to compute indexes from the end of the list. - let index_from_end_of_subsampled_list = ((self.len() - 1) - i) / self.sample_interval; - let mut i_index = self.len() - 1 - index_from_end_of_subsampled_list * self.sample_interval; - let mut val = self.hash[self.hash.len() - 1 - index_from_end_of_subsampled_list]; - - while i_index > i { - val = Keccak256::digest(val).into(); - i_index -= 1; - } - - Ok(val) - } - - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.length - } -} - -/// `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, - pub hash_chains: Vec, -} - -impl HashChainState { - pub fn from_chain_at_offset(offset: usize, chain: PebbleHashChain) -> HashChainState { - HashChainState { - offsets: vec![offset], - hash_chains: vec![chain], - } - } - - 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) - .checked_sub(1) - .ok_or(anyhow::anyhow!( - "Hash chain for the requested sequence number is not available." - ))?; - self.hash_chains[chain_index].reveal_ith(sequence_number - self.offsets[chain_index]) - } -} - -#[cfg(test)] -mod test { - use { - crate::state::PebbleHashChain, - sha3::{Digest, Keccak256}, - }; - - fn run_hash_chain_test(secret: [u8; 32], length: usize, sample_interval: usize) { - // Calculate the hash chain the naive way as a comparison point to the subsampled implementation. - let mut basic_chain = Vec::<[u8; 32]>::with_capacity(length); - let mut current: [u8; 32] = Keccak256::digest(secret).into(); - basic_chain.push(current); - for _ in 1..length { - current = Keccak256::digest(current).into(); - basic_chain.push(current); - } - - basic_chain.reverse(); - - let chain = PebbleHashChain::new(secret, length, sample_interval); - - let mut last_val = chain.reveal_ith(0).unwrap(); - - #[allow(clippy::needless_range_loop)] - for i in 1..length { - let cur_val = chain.reveal_ith(i).unwrap(); - println!("{}", i); - assert_eq!(basic_chain[i], cur_val); - - let expected_last_val: [u8; 32] = Keccak256::digest(cur_val).into(); - assert_eq!(expected_last_val, last_val); - last_val = cur_val; - } - } - - #[test] - fn test_hash_chain() { - run_hash_chain_test([0u8; 32], 10, 1); - run_hash_chain_test([0u8; 32], 10, 2); - run_hash_chain_test([0u8; 32], 10, 3); - run_hash_chain_test([1u8; 32], 10, 1); - run_hash_chain_test([1u8; 32], 10, 2); - run_hash_chain_test([1u8; 32], 10, 3); - run_hash_chain_test([0u8; 32], 100, 1); - run_hash_chain_test([0u8; 32], 100, 2); - run_hash_chain_test([0u8; 32], 100, 3); - run_hash_chain_test([0u8; 32], 100, 7); - run_hash_chain_test([0u8; 32], 100, 50); - run_hash_chain_test([0u8; 32], 100, 55); - } -}