From 8bd27ee086bc13ad66ddb96100e6244576a91e44 Mon Sep 17 00:00:00 2001 From: Stan Drozd Date: Wed, 22 Feb 2023 14:32:09 +0100 Subject: [PATCH 1/5] attester: Add an on-chain last attestation timestamp and rate limit In consequence, attester clients are able to rate-limit attestations among _all_ active attesters - because the new last attestation timestamp is kept up to date on chain. Ultimately, this value being shared by concurrent clients, this feature limits our tx expenses while fulfilling our preferred attestation rates. --- third_party/pyth/p2w_autoattest.py | 3 +- .../client/src/attestation_cfg.rs | 19 +++++-- wormhole_attester/client/src/lib.rs | 6 ++- wormhole_attester/client/src/main.rs | 54 ++++++++++++++----- wormhole_attester/client/tests/test_attest.rs | 1 + wormhole_attester/program/src/attest.rs | 54 +++++++++++++++++-- .../program/src/attestation_state.rs | 2 + 7 files changed, 116 insertions(+), 23 deletions(-) diff --git a/third_party/pyth/p2w_autoattest.py b/third_party/pyth/p2w_autoattest.py index b18e7def87..5e49051040 100755 --- a/third_party/pyth/p2w_autoattest.py +++ b/third_party/pyth/p2w_autoattest.py @@ -116,9 +116,10 @@ default_attestation_conditions: min_interval_secs: 10 symbol_groups: - - group_name: fast_interval_only + - group_name: fast_interval_rate_limited conditions: min_interval_secs: 1 + rate_limit_interval_secs: 2 symbols: """ diff --git a/wormhole_attester/client/src/attestation_cfg.rs b/wormhole_attester/client/src/attestation_cfg.rs index 908e700d26..8b33b67b15 100644 --- a/wormhole_attester/client/src/attestation_cfg.rs +++ b/wormhole_attester/client/src/attestation_cfg.rs @@ -321,16 +321,27 @@ pub const fn default_max_batch_jobs() -> usize { 20 } -/// Spontaneous attestation triggers. Attestation is triggered if any -/// of the active conditions is met. Option<> fields can be +/// Per-group attestation resend rules. Attestation is triggered if +/// any of the active conditions is met. Option<> fields can be /// de-activated with None. All conditions are inactive by default, /// except for the non-Option ones. #[derive(Clone, Debug, Hash, Deserialize, Serialize, PartialEq, Eq)] pub struct AttestationConditions { - /// Baseline, unconditional attestation interval. Attestation is triggered if the specified interval elapsed since last attestation. + /// Lower bound on attestation rate. Attestation is triggered + /// unconditionally whenever the specified interval elapses since + /// last attestation. #[serde(default = "default_min_interval_secs")] pub min_interval_secs: u64, + /// Upper bound on attestation rate. Attesting the same batch + /// before this many seconds pass fails the tx. This limit is + /// enforced on-chain, letting concurret attesters prevent + /// redundant batch resends and tx expenses. NOTE: The client + /// logic does not include rate limit failures in monitoring error + /// counts. + #[serde(default)] + pub rate_limit_interval_secs: Option, + /// Limit concurrent attestation attempts per batch. This setting /// should act only as a failsafe cap on resource consumption and is /// best set well above the expected average number of jobs. @@ -358,6 +369,7 @@ impl AttestationConditions { max_batch_jobs: _max_batch_jobs, price_changed_bps, publish_time_min_delta_secs, + rate_limit_interval_secs: _, } = self; price_changed_bps.is_some() || publish_time_min_delta_secs.is_some() @@ -371,6 +383,7 @@ impl Default for AttestationConditions { max_batch_jobs: default_max_batch_jobs(), price_changed_bps: None, publish_time_min_delta_secs: None, + rate_limit_interval_secs: None, } } } diff --git a/wormhole_attester/client/src/lib.rs b/wormhole_attester/client/src/lib.rs index a4273c43ad..2c80772bd0 100644 --- a/wormhole_attester/client/src/lib.rs +++ b/wormhole_attester/client/src/lib.rs @@ -298,6 +298,9 @@ pub fn gen_attest_tx( wh_msg_id: u64, symbols: &[P2WSymbol], latest_blockhash: Hash, + // Desired rate limit interval. If all of the symbols are over + // the limit, the tx will fail + rate_limit_interval_secs: Option, ) -> Result { let emitter_addr = P2WEmitter::key(None, &p2w_addr); @@ -390,8 +393,9 @@ pub fn gen_attest_tx( let ix_data = ( pyth_wormhole_attester::instruction::Instruction::Attest, AttestData { - consistency_level: ConsistencyLevel::Confirmed, + consistency_level: ConsistencyLevel::Confirmed, message_account_id: wh_msg_id, + rate_limit_interval_secs, }, ); diff --git a/wormhole_attester/client/src/main.rs b/wormhole_attester/client/src/main.rs index 4d91629fe1..ae8dc01457 100644 --- a/wormhole_attester/client/src/main.rs +++ b/wormhole_attester/client/src/main.rs @@ -1,3 +1,5 @@ +use pyth_wormhole_attester::attest::RATE_LIMIT_EXCEEDED_MSG; + pub mod cli; use { @@ -585,6 +587,7 @@ async fn attestation_sched_job(args: AttestationSchedJobArgs) -> Result<(), ErrB symbols: batch.symbols.to_vec(), max_jobs_sema: sema.clone(), message_q_mtx: message_q_mtx.clone(), + rate_limit_interval_secs: batch.conditions.rate_limit_interval_secs, }); // This short-lived permit prevents scheduling excess @@ -603,16 +606,17 @@ async fn attestation_sched_job(args: AttestationSchedJobArgs) -> Result<(), ErrB /// Arguments for attestation_job(). This struct rules out same-type /// ordering errors due to the large argument count pub struct AttestationJobArgs { - pub rlmtx: Arc>, - pub batch_no: usize, - pub batch_count: usize, - pub group_name: String, - pub p2w_addr: Pubkey, - pub config: Pyth2WormholeConfig, - pub payer: Keypair, - pub symbols: Vec, - pub max_jobs_sema: Arc, - pub message_q_mtx: Arc>, + pub rlmtx: Arc>, + pub batch_no: usize, + pub batch_count: usize, + pub group_name: String, + pub p2w_addr: Pubkey, + pub config: Pyth2WormholeConfig, + pub payer: Keypair, + pub symbols: Vec, + pub max_jobs_sema: Arc, + pub rate_limit_interval_secs: Option, + pub message_q_mtx: Arc>, } /// A future for a single attempt to attest a batch on Solana. @@ -627,6 +631,7 @@ async fn attestation_job(args: AttestationJobArgs) -> Result<(), ErrBoxSend> { payer, symbols, max_jobs_sema, + rate_limit_interval_secs, message_q_mtx, } = args; let batch_no4err_msg = batch_no; @@ -662,19 +667,42 @@ async fn attestation_job(args: AttestationJobArgs) -> Result<(), ErrBoxSend> { let wh_msg_id = message_q_mtx.lock().await.get_account()?.id; - let tx_res: Result<_, ErrBoxSend> = gen_attest_tx( + let tx = gen_attest_tx( p2w_addr, &config, &payer, wh_msg_id, symbols.as_slice(), latest_blockhash, - ); + rate_limit_interval_secs, + )?; + + // Detect rate limiting error early + let simulation_res = rpc.simulate_transaction(&tx).await?; + + match simulation_res.value.logs { + Some(logs) => { + for log in logs { + if log.contains(RATE_LIMIT_EXCEEDED_MSG) { + info!( + "Batch {}/{}, group {:?} OK: rate limit reached, backing off", + batch_no, batch_count, group_name + ); + // Note: We return early if tx simulation + // suggests rate limit was reached. This + // ensures that we don't count this attempt in + // ok/err monitoring and healthcheck counters. + return Ok(()); + } + } + } + None => {} + } let tx_processing_start_time = Instant::now(); let sig = rpc - .send_and_confirm_transaction(&tx_res?) + .send_and_confirm_transaction(&tx) .map_err(|e| -> ErrBoxSend { e.into() }) .await?; let tx_data = rpc diff --git a/wormhole_attester/client/tests/test_attest.rs b/wormhole_attester/client/tests/test_attest.rs index 4c81f03f05..b398b3c870 100644 --- a/wormhole_attester/client/tests/test_attest.rs +++ b/wormhole_attester/client/tests/test_attest.rs @@ -111,6 +111,7 @@ async fn test_happy_path() -> Result<(), p2wc::ErrBoxSend> { 0, symbols.as_slice(), ctx.last_blockhash, + None, )?; // NOTE: 2022-09-05 diff --git a/wormhole_attester/program/src/attest.rs b/wormhole_attester/program/src/attest.rs index 77d046bbd7..dc668c566f 100644 --- a/wormhole_attester/program/src/attest.rs +++ b/wormhole_attester/program/src/attest.rs @@ -54,6 +54,9 @@ use { /// correct value dynamically. pub const P2W_MAX_BATCH_SIZE: u16 = 5; +/// Log message contents for easier detection in attester clients +pub const RATE_LIMIT_EXCEEDED_MSG: &str = "ATTESTER ALL SYMBOLS EXCEED RATE LIMIT"; + #[derive(FromAccounts)] pub struct Attest<'b> { // Payer also used for wormhole @@ -127,8 +130,16 @@ pub struct Attest<'b> { #[derive(BorshDeserialize, BorshSerialize)] pub struct AttestData { - pub consistency_level: ConsistencyLevel, - pub message_account_id: u64, + pub consistency_level: ConsistencyLevel, + pub message_account_id: u64, + /// Fail the transaction if the global attestation rate of all + /// symbols in this batch is more frequent than the passed + /// interval. This is checked using the attestation time stored in + /// attestation state. This enables all of the clients to only + /// contribute attestations if their desired interval is not + /// already reached. If at least one symbol has been waiting + /// longer than this interval, we attest the whole batch. + pub rate_limit_interval_secs: Option, } pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> SoliResult<()> { @@ -180,6 +191,10 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // Collect the validated symbols here for batch serialization let mut attestations = Vec::with_capacity(price_pairs.len()); + let this_attestation_time = accs.clock.unix_timestamp; + + + let mut over_rate_limit = true; for (state, price) in price_pairs.into_iter() { // Pyth must own the price if accs.config.pyth_owner != *price.owner { @@ -200,8 +215,6 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So return Err(ProgramError::InvalidAccountData.into()); } - let attestation_time = accs.clock.unix_timestamp; - let price_data_ref = price.try_borrow_data()?; // Parse the upstream Pyth struct to extract current publish @@ -214,6 +227,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // Retrieve and rotate last_attested_tradind_publish_time + // Pick the value to store for the next attestation of this // symbol. We use the prev_ value if the symbol is not // currently being traded. The oracle marks the last known @@ -237,7 +251,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // Build an attestatioin struct for this symbol using the just decided current value let attestation = PriceAttestation::from_pyth_price_struct( Identifier::new(price.key.to_bytes()), - attestation_time, + this_attestation_time, current_last_attested_trading_publish_time, price_struct, ); @@ -245,6 +259,29 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // Save the new value for the next attestation of this symbol state.0 .0.last_attested_trading_publish_time = new_last_attested_trading_publish_time; + // don't re-evaluate if at least one symbol was found to be under limit + if over_rate_limit { + // Evaluate rate limit + match data.rate_limit_interval_secs { + Some(interval) + if (this_attestation_time - state.0 .0.last_attestation_time) + >= interval as i64 => + { + over_rate_limit = false; + } + // Unset attestation + None => { + over_rate_limit = false; + } + Some(_other) => { + trace!("Price {:?}: over rate limit", price.key); + } + } + } + + // Update last attestation time + state.0 .0.last_attestation_time = this_attestation_time; + // handling of last_attested_trading_publish_time ends here if !state.0 .0.is_initialized() { @@ -272,6 +309,13 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So attestations.push(attestation); } + // Do not proceed if none of the symbols is under rate limit + if over_rate_limit { + trace!("All symbols over limit, bailing out"); + solana_program::msg!(RATE_LIMIT_EXCEEDED_MSG); + return Err(ProgramError::InvalidInstructionData.into()); + } + let batch_attestation = BatchPriceAttestation { price_attestations: attestations, }; diff --git a/wormhole_attester/program/src/attestation_state.rs b/wormhole_attester/program/src/attestation_state.rs index 9299a6cbad..33e0a38b79 100644 --- a/wormhole_attester/program/src/attestation_state.rs +++ b/wormhole_attester/program/src/attestation_state.rs @@ -25,6 +25,8 @@ use { pub struct AttestationState { /// The last trading publish_time this attester saw pub last_attested_trading_publish_time: UnixTimestamp, + /// The last time this symbol was attested + pub last_attestation_time: UnixTimestamp, } impl Owned for AttestationState { From e3641fe274b8634d30f26bd8c4a28d9adea57dfc Mon Sep 17 00:00:00 2001 From: Stan Drozd Date: Fri, 24 Feb 2023 12:32:03 +0100 Subject: [PATCH 2/5] attester: Use custom error code instead of log for rate limit --- wormhole_attester/client/src/main.rs | 51 ++++++++++++------------- wormhole_attester/program/src/attest.rs | 9 ++--- wormhole_attester/program/src/error.rs | 6 +++ wormhole_attester/program/src/lib.rs | 1 + 4 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 wormhole_attester/program/src/error.rs diff --git a/wormhole_attester/client/src/main.rs b/wormhole_attester/client/src/main.rs index ae8dc01457..604bde667f 100644 --- a/wormhole_attester/client/src/main.rs +++ b/wormhole_attester/client/src/main.rs @@ -1,4 +1,8 @@ -use pyth_wormhole_attester::attest::RATE_LIMIT_EXCEEDED_MSG; +use { + pyth_wormhole_attester::error::AttesterCustomError, + solana_program::instruction::InstructionError, + solana_sdk::transaction::TransactionError, +}; pub mod cli; @@ -677,34 +681,27 @@ async fn attestation_job(args: AttestationJobArgs) -> Result<(), ErrBoxSend> { rate_limit_interval_secs, )?; - // Detect rate limiting error early - let simulation_res = rpc.simulate_transaction(&tx).await?; - - match simulation_res.value.logs { - Some(logs) => { - for log in logs { - if log.contains(RATE_LIMIT_EXCEEDED_MSG) { - info!( - "Batch {}/{}, group {:?} OK: rate limit reached, backing off", - batch_no, batch_count, group_name - ); - // Note: We return early if tx simulation - // suggests rate limit was reached. This - // ensures that we don't count this attempt in - // ok/err monitoring and healthcheck counters. - return Ok(()); - } - } - } - None => {} - } - let tx_processing_start_time = Instant::now(); - let sig = rpc - .send_and_confirm_transaction(&tx) - .map_err(|e| -> ErrBoxSend { e.into() }) - .await?; + let sig = match rpc.send_and_confirm_transaction(&tx).await { + Ok(s) => Ok(s), + Err(e) => match e.get_transaction_error() { + Some(TransactionError::InstructionError(_idx, InstructionError::Custom(code))) + if code == AttesterCustomError::AttestRateLimitReached as u32 => + { + info!( + "Batch {}/{}, group {:?} OK: configured {} second rate limit interval reached, backing off", + batch_no, batch_count, group_name, rate_limit_interval_secs.unwrap(), + ); + // Note: We return early if rate limit tx + // error is detected. This ensures that we + // don't count this attempt in ok/err + // monitoring and healthcheck counters. + return Ok(()); + } + _other => Err(e), + }, + }?; let tx_data = rpc .get_transaction_with_config( &sig, diff --git a/wormhole_attester/program/src/attest.rs b/wormhole_attester/program/src/attest.rs index dc668c566f..ee3d13b284 100644 --- a/wormhole_attester/program/src/attest.rs +++ b/wormhole_attester/program/src/attest.rs @@ -2,6 +2,7 @@ use { crate::{ attestation_state::AttestationStatePDA, config::P2WConfigAccount, + error::AttesterCustomError, message::{ P2WMessage, P2WMessageDrvData, @@ -54,9 +55,6 @@ use { /// correct value dynamically. pub const P2W_MAX_BATCH_SIZE: u16 = 5; -/// Log message contents for easier detection in attester clients -pub const RATE_LIMIT_EXCEEDED_MSG: &str = "ATTESTER ALL SYMBOLS EXCEED RATE LIMIT"; - #[derive(FromAccounts)] pub struct Attest<'b> { // Payer also used for wormhole @@ -312,8 +310,9 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // Do not proceed if none of the symbols is under rate limit if over_rate_limit { trace!("All symbols over limit, bailing out"); - solana_program::msg!(RATE_LIMIT_EXCEEDED_MSG); - return Err(ProgramError::InvalidInstructionData.into()); + return Err( + ProgramError::Custom(AttesterCustomError::AttestRateLimitReached as u32).into(), + ); } let batch_attestation = BatchPriceAttestation { diff --git a/wormhole_attester/program/src/error.rs b/wormhole_attester/program/src/error.rs new file mode 100644 index 0000000000..6471c53f54 --- /dev/null +++ b/wormhole_attester/program/src/error.rs @@ -0,0 +1,6 @@ +/// Append-only custom error list. +#[repr(u32)] +pub enum AttesterCustomError { + /// Explicitly checked for in client code, change carefully + AttestRateLimitReached = 13, +} diff --git a/wormhole_attester/program/src/lib.rs b/wormhole_attester/program/src/lib.rs index 1d949eb273..2bcbe70cca 100644 --- a/wormhole_attester/program/src/lib.rs +++ b/wormhole_attester/program/src/lib.rs @@ -3,6 +3,7 @@ pub mod attest; pub mod attestation_state; pub mod config; +pub mod error; pub mod initialize; pub mod message; pub mod migrate; From 94d49404a99d980f47b28266c2d2c0e8af8d617d Mon Sep 17 00:00:00 2001 From: Stan Drozd Date: Fri, 24 Feb 2023 13:41:33 +0100 Subject: [PATCH 3/5] attester: Add defaults for default attestation conditions --- wormhole_attester/client/src/attestation_cfg.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wormhole_attester/client/src/attestation_cfg.rs b/wormhole_attester/client/src/attestation_cfg.rs index 8b33b67b15..1d243d3e92 100644 --- a/wormhole_attester/client/src/attestation_cfg.rs +++ b/wormhole_attester/client/src/attestation_cfg.rs @@ -66,6 +66,7 @@ pub struct AttestationConfig { /// Attestation conditions that will be used for any symbols included in the mapping /// that aren't explicitly in one of the groups below, and any groups without explicitly /// configured attestation conditions. + #[serde(default)] pub default_attestation_conditions: AttestationConditions, /// Groups of symbols to publish. @@ -383,7 +384,7 @@ impl Default for AttestationConditions { max_batch_jobs: default_max_batch_jobs(), price_changed_bps: None, publish_time_min_delta_secs: None, - rate_limit_interval_secs: None, + rate_limit_interval_secs: Some(1), } } } From 63bf171424ee48adf3d4658ae8740c4ec0702bbe Mon Sep 17 00:00:00 2001 From: Stan Drozd Date: Fri, 24 Feb 2023 14:07:58 +0100 Subject: [PATCH 4/5] attester: Use a dedicated function for rate limit default --- wormhole_attester/client/src/attestation_cfg.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wormhole_attester/client/src/attestation_cfg.rs b/wormhole_attester/client/src/attestation_cfg.rs index 1d243d3e92..f9e5f78bab 100644 --- a/wormhole_attester/client/src/attestation_cfg.rs +++ b/wormhole_attester/client/src/attestation_cfg.rs @@ -318,6 +318,10 @@ pub const fn default_min_interval_secs() -> u64 { 60 } +pub const fn default_rate_limit_interval_secs() -> Option { + Some(1) +} + pub const fn default_max_batch_jobs() -> usize { 20 } @@ -340,7 +344,7 @@ pub struct AttestationConditions { /// redundant batch resends and tx expenses. NOTE: The client /// logic does not include rate limit failures in monitoring error /// counts. - #[serde(default)] + #[serde(default = "default_rate_limit_interval_secs")] pub rate_limit_interval_secs: Option, /// Limit concurrent attestation attempts per batch. This setting @@ -384,7 +388,7 @@ impl Default for AttestationConditions { max_batch_jobs: default_max_batch_jobs(), price_changed_bps: None, publish_time_min_delta_secs: None, - rate_limit_interval_secs: Some(1), + rate_limit_interval_secs: default_rate_limit_interval_secs(), } } } From fa5ceaa7c5ec1b4b2d0b60af9688e1369a495214 Mon Sep 17 00:00:00 2001 From: Stan Drozd Date: Fri, 24 Feb 2023 14:57:30 +0100 Subject: [PATCH 5/5] attester: Option -> u32 rate limit interval This lets users pass 0 to disable the feature (0-rate limiting means no rate limiting at all), which was not possible with the Option type. --- .../client/src/attestation_cfg.rs | 8 +++--- wormhole_attester/client/src/lib.rs | 4 +-- wormhole_attester/client/src/main.rs | 4 +-- wormhole_attester/client/tests/test_attest.rs | 2 +- wormhole_attester/program/src/attest.rs | 27 +++++++------------ 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/wormhole_attester/client/src/attestation_cfg.rs b/wormhole_attester/client/src/attestation_cfg.rs index f9e5f78bab..e42f9df577 100644 --- a/wormhole_attester/client/src/attestation_cfg.rs +++ b/wormhole_attester/client/src/attestation_cfg.rs @@ -318,8 +318,8 @@ pub const fn default_min_interval_secs() -> u64 { 60 } -pub const fn default_rate_limit_interval_secs() -> Option { - Some(1) +pub const fn default_rate_limit_interval_secs() -> u32 { + 1 } pub const fn default_max_batch_jobs() -> usize { @@ -343,9 +343,9 @@ pub struct AttestationConditions { /// enforced on-chain, letting concurret attesters prevent /// redundant batch resends and tx expenses. NOTE: The client /// logic does not include rate limit failures in monitoring error - /// counts. + /// counts. 0 effectively disables this feature. #[serde(default = "default_rate_limit_interval_secs")] - pub rate_limit_interval_secs: Option, + pub rate_limit_interval_secs: u32, /// Limit concurrent attestation attempts per batch. This setting /// should act only as a failsafe cap on resource consumption and is diff --git a/wormhole_attester/client/src/lib.rs b/wormhole_attester/client/src/lib.rs index 2c80772bd0..a3e5f26891 100644 --- a/wormhole_attester/client/src/lib.rs +++ b/wormhole_attester/client/src/lib.rs @@ -299,8 +299,8 @@ pub fn gen_attest_tx( symbols: &[P2WSymbol], latest_blockhash: Hash, // Desired rate limit interval. If all of the symbols are over - // the limit, the tx will fail - rate_limit_interval_secs: Option, + // the limit, the tx will fail. 0 means off. + rate_limit_interval_secs: u32, ) -> Result { let emitter_addr = P2WEmitter::key(None, &p2w_addr); diff --git a/wormhole_attester/client/src/main.rs b/wormhole_attester/client/src/main.rs index 604bde667f..139ab34c0c 100644 --- a/wormhole_attester/client/src/main.rs +++ b/wormhole_attester/client/src/main.rs @@ -619,7 +619,7 @@ pub struct AttestationJobArgs { pub payer: Keypair, pub symbols: Vec, pub max_jobs_sema: Arc, - pub rate_limit_interval_secs: Option, + pub rate_limit_interval_secs: u32, pub message_q_mtx: Arc>, } @@ -691,7 +691,7 @@ async fn attestation_job(args: AttestationJobArgs) -> Result<(), ErrBoxSend> { { info!( "Batch {}/{}, group {:?} OK: configured {} second rate limit interval reached, backing off", - batch_no, batch_count, group_name, rate_limit_interval_secs.unwrap(), + batch_no, batch_count, group_name, rate_limit_interval_secs, ); // Note: We return early if rate limit tx // error is detected. This ensures that we diff --git a/wormhole_attester/client/tests/test_attest.rs b/wormhole_attester/client/tests/test_attest.rs index b398b3c870..3c935ae1bf 100644 --- a/wormhole_attester/client/tests/test_attest.rs +++ b/wormhole_attester/client/tests/test_attest.rs @@ -111,7 +111,7 @@ async fn test_happy_path() -> Result<(), p2wc::ErrBoxSend> { 0, symbols.as_slice(), ctx.last_blockhash, - None, + 0, )?; // NOTE: 2022-09-05 diff --git a/wormhole_attester/program/src/attest.rs b/wormhole_attester/program/src/attest.rs index ee3d13b284..4b39e8eba9 100644 --- a/wormhole_attester/program/src/attest.rs +++ b/wormhole_attester/program/src/attest.rs @@ -136,8 +136,9 @@ pub struct AttestData { /// attestation state. This enables all of the clients to only /// contribute attestations if their desired interval is not /// already reached. If at least one symbol has been waiting - /// longer than this interval, we attest the whole batch. - pub rate_limit_interval_secs: Option, + /// longer than this interval, we attest the whole batch. 0 + /// effectively disables this feature. + pub rate_limit_interval_secs: u32, } pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> SoliResult<()> { @@ -259,21 +260,13 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So // don't re-evaluate if at least one symbol was found to be under limit if over_rate_limit { - // Evaluate rate limit - match data.rate_limit_interval_secs { - Some(interval) - if (this_attestation_time - state.0 .0.last_attestation_time) - >= interval as i64 => - { - over_rate_limit = false; - } - // Unset attestation - None => { - over_rate_limit = false; - } - Some(_other) => { - trace!("Price {:?}: over rate limit", price.key); - } + // Evaluate rate limit - should be smaller than duration from last attestation + if this_attestation_time - state.0 .0.last_attestation_time + >= data.rate_limit_interval_secs as i64 + { + over_rate_limit = false; + } else { + trace!("Price {:?}: over rate limit", price.key); } }