Skip to content

feat(argus): ControllerService update loop, ChainPriceService poll loop #2693

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tejasbadadare
Copy link
Contributor

@tejasbadadare tejasbadadare commented May 15, 2025

Summary

  • Create main Controller update loop. This orchestrates the other services and makes the decision on whether or not to push prices for a subscription.
  • Create ChainPriceService polling loop. This runs on an interval to track the on-chain prices.
    • Update ChainPriceState data structures to be subscription oriented, since we need to maintain the on-chain price for each feed for each subscription.

How has this been tested?

  • Current tests cover my changes
  • Added new tests
    • Added integration tests for the update loop that validate the orchestration of other services (e.g. calling PricePusherService when feeds need to be updated)
    • Added unit tests for needs_update, which tests scenarios around update criteria. Given a pyth price, chain price, and update criteria, the function returns a boolean indicating whether the subscription should be updated or not.
    • Considered adding integration tests for ChainPriceService, but since it's a light wrapper around an RPC API, figured there's not much value in writing tests for this.
  • Manually tested the code

Copy link

vercel bot commented May 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
api-reference ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
component-library ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
entropy-debugger ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
entropy-explorer ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
insights ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
proposals ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm
staking ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 15, 2025 9:21pm

Copy link
Collaborator

@ali-bahjati ali-bahjati left a comment

Choose a reason for hiding this comment

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

Looks very good! I left some comments, please address them before merging the code.

.update_prices(subscription_id, prices_map);
}
Err(e) => {
// If we failed to get prices for a subscription, we'll retry on the next poll interval.
Copy link
Collaborator

Choose a reason for hiding this comment

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

this implicitly enforces having a high poll interval

let subscription_id = item.key().clone();
let subscription_params = item.value().clone();

// TODO: do this in parallel using tokio tasks?
Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah let's do it.

todo!()
subscription_id: SubscriptionId,
) -> Result<Vec<Price>> {
let price_ids = self.get_prices_unsafe(subscription_id, vec![]).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

vec![] seems wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

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

actually it seems that it's part of the api. maybe document it in the IScheduler? or Ipulse

@@ -64,7 +115,13 @@ impl Service for ChainPriceService {
loop {
tokio::select! {
_ = interval.tick() => {
self.poll_prices(self.chain_price_state.clone()).await;
if let Err(e) = self.poll_prices().await {
Copy link
Collaborator

Choose a reason for hiding this comment

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

poll_prices doesn't seem to ever fail (maybe change its signature?)

service = self.name,
error = %e,
"Failed to perform price update"
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

no retry here?

// If there's no price currently on the chain for this feed, an update is always needed.
let chain_price = match chain_price_opt {
None => {
tracing::debug!("Update criteria met: No chain price available.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

in price pusher sometimes i found these logs helpful, might not be bad to have them at info level, or maybe expose as metrics or trace

// the chain price plus `heartbeat_seconds`.
if update_criteria.update_on_heartbeat {
if pyth_price.publish_time
>= chain_price.publish_time + (update_criteria.heartbeat_seconds as i64)
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's never use + , instead use saturated_add to make sure we never overflow (otherwise it might result in some security issues)

Comment on lines +214 to +218
let threshold_val = (chain_price.price.abs() as u64
* update_criteria.deviation_threshold_bps as u64)
/ 10000;

if price_diff > threshold_val {
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's double check this matches the onchain logic 100% (it looked the same when i looked)

Comment on lines +206 to +210
if pyth_price.price != 0 {
tracing::debug!(
"Deviation criteria met: Chain price is 0, Pyth price is non-zero."
);
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is not in the on-chain logic, however the on-chain logic accepts any update if there's no update, maybe it is this one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants