feat(relayer): new status label for IGP not found (#4106)

### Description

Adds new status label to relayer opqueue for operations whose gas
payments were not found. This now creates a distinction between
operations without a payment and those with insufficient payment.

Helps better uncover cases where hook indexing misses events because
there is no receipt yet
pull/4127/head
Daniel Savu 3 months ago committed by GitHub
parent f800297259
commit bf1dad7f77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 102
      rust/agents/relayer/src/msg/gas_payment/mod.rs
  2. 13
      rust/agents/relayer/src/msg/pending_message.rs
  3. 13
      rust/hyperlane-base/src/db/rocks/hyperlane_db.rs
  4. 2
      rust/hyperlane-core/src/traits/pending_operation.rs
  5. 12
      rust/hyperlane-core/src/types/mod.rs

@ -34,6 +34,13 @@ pub trait GasPaymentPolicy: Debug + Send + Sync {
) -> Result<Option<U256>>;
}
#[derive(PartialEq, Debug)]
pub enum GasPolicyStatus {
NoPaymentFound,
PolicyNotMet,
PolicyMet(U256),
}
#[derive(Debug)]
pub struct GasPaymentEnforcer {
/// List of policies and a whitelist to decide if it should be used for a
@ -80,15 +87,19 @@ impl GasPaymentEnforcer {
&self,
message: &HyperlaneMessage,
tx_cost_estimate: &TxCostEstimate,
) -> Result<Option<U256>> {
) -> Result<GasPolicyStatus> {
let msg_id = message.id();
let gas_payment_key = GasPaymentKey {
message_id: msg_id,
destination: message.destination,
};
let current_payment = self
let current_payment_option = self
.db
.retrieve_gas_payment_by_gas_payment_key(gas_payment_key)?;
let current_payment = match current_payment_option {
Some(payment) => payment,
None => InterchainGasPayment::from_gas_payment_key(gas_payment_key),
};
let current_expenditure = self.db.retrieve_gas_expenditure_by_message_id(msg_id)?;
for (policy, whitelist) in &self.policies {
@ -122,7 +133,18 @@ impl GasPaymentEnforcer {
&current_expenditure,
tx_cost_estimate,
)
.await;
.await
.map(|result| {
if let Some(gas_limit) = result {
GasPolicyStatus::PolicyMet(gas_limit)
} else if current_payment_option.is_some() {
// There is a gas payment but it didn't meet the policy
GasPolicyStatus::PolicyNotMet
} else {
// No payment was found and it didn't meet the policy
GasPolicyStatus::NoPaymentFound
}
});
}
error!(
@ -130,7 +152,7 @@ impl GasPaymentEnforcer {
policies=?self.policies,
"No gas payment policy matched for message; consider adding a default policy to the end of the policies array which uses a wildcard whitelist."
);
Ok(None)
Ok(GasPolicyStatus::PolicyNotMet)
}
pub fn record_tx_outcome(&self, message: &HyperlaneMessage, outcome: TxOutcome) -> Result<()> {
@ -162,8 +184,11 @@ mod test {
};
use super::GasPaymentEnforcer;
use crate::settings::{
matching_list::MatchingList, GasPaymentEnforcementConf, GasPaymentEnforcementPolicy,
use crate::{
msg::gas_payment::GasPolicyStatus,
settings::{
matching_list::MatchingList, GasPaymentEnforcementConf, GasPaymentEnforcementPolicy,
},
};
#[tokio::test]
@ -195,7 +220,7 @@ mod test {
)
.await
.unwrap(),
None
GasPolicyStatus::NoPaymentFound
);
})
.await;
@ -224,7 +249,7 @@ mod test {
&TxCostEstimate::default(),
)
.await,
Ok(None)
Ok(GasPolicyStatus::PolicyNotMet)
));
})
.await;
@ -262,11 +287,13 @@ mod test {
hyperlane_db.process_gas_payment(wrong_destination_payment, &LogMeta::random());
// Ensure if the gas payment was made to the incorrect destination, it does not meet
// the requirement
assert!(enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap()
.is_none());
assert_eq!(
enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap(),
GasPolicyStatus::NoPaymentFound
);
let correct_destination_payment = InterchainGasPayment {
message_id: msg.id(),
@ -277,11 +304,13 @@ mod test {
hyperlane_db.process_gas_payment(correct_destination_payment, &LogMeta::random());
// Ensure if the gas payment was made to the correct destination, it meets the
// requirement
assert!(enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap()
.is_some());
assert_eq!(
enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap(),
GasPolicyStatus::PolicyMet(U256::zero())
);
})
.await;
}
@ -319,12 +348,13 @@ mod test {
hyperlane_db.process_gas_payment(initial_payment, &LogMeta::random());
// Ensure if only half gas payment was made, it does not meet the requirement
assert!(enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap()
.is_none());
assert_eq!(
enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap(),
GasPolicyStatus::PolicyNotMet
);
let deficit_payment = InterchainGasPayment {
message_id: msg.id(),
destination: msg.destination,
@ -333,11 +363,13 @@ mod test {
};
hyperlane_db.process_gas_payment(deficit_payment, &LogMeta::random());
// Ensure if the full gas payment was made, it meets the requirement
assert!(enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap()
.is_some());
assert_eq!(
enforcer
.message_meets_gas_payment_requirement(&msg, &TxCostEstimate::default(),)
.await
.unwrap(),
GasPolicyStatus::PolicyMet(U256::zero())
);
})
.await;
}
@ -383,14 +415,14 @@ mod test {
// The message should meet the requirement because it's on the whitelist for the first
// policy, even though it would not pass the second (default) policy.
assert!(enforcer
assert_eq!(enforcer
.message_meets_gas_payment_requirement(
&matching_message,
&TxCostEstimate::default(),
)
.await
.unwrap()
.is_some());
.unwrap(),
GasPolicyStatus::PolicyMet(U256::zero()));
// Switch the sender & recipient
let not_matching_message = HyperlaneMessage {
@ -401,14 +433,14 @@ mod test {
// The message should not meet the requirement because it's NOT on the first whitelist
// and doesn't satisfy the GasPaymentEnforcementPolicy
assert!(enforcer
assert_eq!(enforcer
.message_meets_gas_payment_requirement(
&not_matching_message,
&TxCostEstimate::default(),
)
.await
.unwrap()
.is_none());
.unwrap(),
GasPolicyStatus::NoPaymentFound);
})
.await;
}

@ -19,7 +19,7 @@ use serde::Serialize;
use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument};
use super::{
gas_payment::GasPaymentEnforcer,
gas_payment::{GasPaymentEnforcer, GasPolicyStatus},
metadata::{BaseMetadataBuilder, MessageMetadataBuilder, MetadataBuilder},
};
@ -286,8 +286,15 @@ impl PendingOperation for PendingMessage {
}
};
let Some(gas_limit) = gas_limit else {
return self.on_reprepare::<String>(None, ReprepareReason::GasPaymentRequirementNotMet);
let gas_limit = match gas_limit {
GasPolicyStatus::NoPaymentFound => {
return self.on_reprepare::<String>(None, ReprepareReason::GasPaymentNotFound)
}
GasPolicyStatus::PolicyNotMet => {
return self
.on_reprepare::<String>(None, ReprepareReason::GasPaymentRequirementNotMet)
}
GasPolicyStatus::PolicyMet(gas_limit) => gas_limit,
};
// Go ahead and attempt processing of message to destination chain.

@ -229,7 +229,11 @@ impl HyperlaneRocksDB {
/// Update the total gas payment for a message to include gas_payment
fn update_gas_payment_by_gas_payment_key(&self, event: InterchainGasPayment) -> DbResult<()> {
let gas_payment_key = event.into();
let existing_payment = self.retrieve_gas_payment_by_gas_payment_key(gas_payment_key)?;
let existing_payment =
match self.retrieve_gas_payment_by_gas_payment_key(gas_payment_key)? {
Some(payment) => payment,
None => InterchainGasPayment::from_gas_payment_key(gas_payment_key),
};
let total = existing_payment + event;
debug!(?event, new_total_gas_payment=?total, "Storing gas payment");
@ -261,11 +265,12 @@ impl HyperlaneRocksDB {
pub fn retrieve_gas_payment_by_gas_payment_key(
&self,
gas_payment_key: GasPaymentKey,
) -> DbResult<InterchainGasPayment> {
) -> DbResult<Option<InterchainGasPayment>> {
Ok(self
.retrieve_interchain_gas_payment_data_by_gas_payment_key(&gas_payment_key)?
.unwrap_or_default()
.complete(gas_payment_key.message_id, gas_payment_key.destination))
.map(|payment| {
payment.complete(gas_payment_key.message_id, gas_payment_key.destination)
}))
}
/// Retrieve the total gas payment for a message

@ -197,6 +197,8 @@ pub enum ReprepareReason {
#[strum(to_string = "Gas payment requirement not met")]
/// Gas payment requirement not met
GasPaymentRequirementNotMet,
/// Gas payment not found
GasPaymentNotFound,
#[strum(to_string = "Message delivery estimated gas exceeds max gas limit")]
/// Message delivery estimated gas exceeds max gas limit
ExceedsMaxGasLimit,

@ -140,6 +140,18 @@ pub struct InterchainGasPayment {
pub gas_amount: U256,
}
impl InterchainGasPayment {
/// Create a new InterchainGasPayment from a GasPaymentKey
pub fn from_gas_payment_key(key: GasPaymentKey) -> Self {
Self {
message_id: key.message_id,
destination: key.destination,
payment: Default::default(),
gas_amount: Default::default(),
}
}
}
/// Amount of gas spent attempting to send the message.
#[derive(Debug, Copy, Clone)]
pub struct InterchainGasExpenditure {

Loading…
Cancel
Save