Skip to content

feat(argus): Delete hash chain logic #2466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions apps/argus/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use {
crate::{
chain::reader::{BlockNumber, BlockStatus, EntropyReader},
state::HashChainState,
},
anyhow::Result,
axum::{
Expand Down Expand Up @@ -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<HashChainState>,
/// The contract that the server is fulfilling requests for.
pub contract: Arc<dyn EntropyReader>,
/// The address of the provider that this server is operating for.
Expand Down
22 changes: 3 additions & 19 deletions apps/argus/src/command/register_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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::<LocalWallet>()?.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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this stuff will all get deleted later so I think it's fine to mock them out for now.


// 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 {
Expand Down
83 changes: 1 addition & 82 deletions apps/argus/src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<RpcMetrics>,
Expand All @@ -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::<CommitmentMetadata>(
&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::<usize>::new();
let mut hash_chains = Vec::<PebbleHashChain>::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,
Expand Down
76 changes: 6 additions & 70 deletions apps/argus/src/command/setup_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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))]
Expand All @@ -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::<CommitmentMetadata>(&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?;

Expand Down
30 changes: 0 additions & 30 deletions apps/argus/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ pub struct EthereumConfig {
#[serde(default)]
pub fee: u128,

/// Historical commitments made by the provider.
pub commitments: Option<Vec<Commitment>>,

/// 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<u32>,
Expand Down Expand Up @@ -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 {
Expand All @@ -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<Address>,
}

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 {
Expand Down
4 changes: 0 additions & 4 deletions apps/argus/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Loading
Loading