-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(agent): extract oracle component/service
- Loading branch information
Showing
11 changed files
with
922 additions
and
969 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
pub mod keypairs; | ||
pub mod notifier; | ||
pub mod oracle; | ||
|
||
pub use { | ||
keypairs::keypairs, | ||
notifier::notifier, | ||
oracle::oracle, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
//! Oracle | ||
//! | ||
//! The Oracle service is respoinsible for reacting to all remote/on-chain events. | ||
use { | ||
crate::agent::{ | ||
solana::{ | ||
key_store::KeyStore, | ||
network::{ | ||
Config, | ||
Network, | ||
}, | ||
}, | ||
state::oracle::{ | ||
Oracle, | ||
PricePublishingMetadata, | ||
}, | ||
}, | ||
anyhow::Result, | ||
solana_account_decoder::UiAccountEncoding, | ||
solana_client::{ | ||
nonblocking::{ | ||
pubsub_client::PubsubClient, | ||
rpc_client::RpcClient, | ||
}, | ||
rpc_config::{ | ||
RpcAccountInfoConfig, | ||
RpcProgramAccountsConfig, | ||
}, | ||
}, | ||
solana_sdk::{ | ||
account::Account, | ||
commitment_config::CommitmentConfig, | ||
pubkey::Pubkey, | ||
}, | ||
std::{ | ||
collections::HashMap, | ||
sync::Arc, | ||
time::{ | ||
Duration, | ||
Instant, | ||
}, | ||
}, | ||
tokio::sync::watch::Sender, | ||
tokio_stream::StreamExt, | ||
}; | ||
|
||
pub async fn oracle<S>( | ||
config: Config, | ||
network: Network, | ||
state: Arc<S>, | ||
publisher_permissions_tx: Sender<HashMap<Pubkey, HashMap<Pubkey, PricePublishingMetadata>>>, | ||
) where | ||
S: Oracle, | ||
S: Send + Sync + 'static, | ||
{ | ||
let Ok(key_store) = KeyStore::new(config.key_store.clone()) else { | ||
tracing::warn!("Key store not available, Oracle won't start."); | ||
return; | ||
}; | ||
|
||
tokio::spawn(poller( | ||
config.clone(), | ||
network, | ||
state.clone(), | ||
key_store.mapping_key, | ||
config.oracle.max_lookup_batch_size, | ||
publisher_permissions_tx.clone(), | ||
)); | ||
|
||
if config.oracle.subscriber_enabled { | ||
tokio::spawn(async move { | ||
loop { | ||
let current_time = Instant::now(); | ||
if let Err(ref err) = subscriber( | ||
config.clone(), | ||
network, | ||
state.clone(), | ||
key_store.program_key, | ||
) | ||
.await | ||
{ | ||
tracing::error!(err = ?err, "Subscriber exited unexpectedly."); | ||
if current_time.elapsed() < Duration::from_secs(30) { | ||
tracing::warn!("Subscriber restarting too quickly. Sleeping for 1 second."); | ||
tokio::time::sleep(Duration::from_secs(1)).await; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/// When an account RPC Subscription update is receiveed. | ||
/// | ||
/// We check if the account is one we're aware of and tracking, and if so, spawn | ||
/// a small background task that handles that update. We only do this for price | ||
/// accounts, all other accounts are handled below in the poller. | ||
async fn subscriber<S>( | ||
config: Config, | ||
network: Network, | ||
state: Arc<S>, | ||
program_key: Pubkey, | ||
) -> Result<()> | ||
where | ||
S: Oracle, | ||
S: Send + Sync + 'static, | ||
{ | ||
// Setup PubsubClient to listen for account changes on the Oracle program. | ||
let client = PubsubClient::new(config.wss_url.as_str()).await?; | ||
|
||
let (mut notifier, _unsub) = { | ||
let program_key = program_key; | ||
let commitment = config.oracle.commitment; | ||
let config = RpcProgramAccountsConfig { | ||
account_config: RpcAccountInfoConfig { | ||
commitment: Some(CommitmentConfig { commitment }), | ||
encoding: Some(UiAccountEncoding::Base64Zstd), | ||
..Default::default() | ||
}, | ||
filters: None, | ||
with_context: Some(true), | ||
}; | ||
client.program_subscribe(&program_key, Some(config)).await | ||
}?; | ||
|
||
while let Some(update) = notifier.next().await { | ||
match update.value.account.decode::<Account>() { | ||
Some(account) => { | ||
let pubkey: Pubkey = update.value.pubkey.as_str().try_into()?; | ||
let state = state.clone(); | ||
tokio::spawn(async move { | ||
if let Err(err) = | ||
Oracle::handle_price_account_update(&*state, network, &pubkey, &account) | ||
.await | ||
{ | ||
tracing::error!(err = ?err, "Failed to handle account update."); | ||
} | ||
}); | ||
} | ||
|
||
None => { | ||
tracing::error!( | ||
update = ?update, | ||
"Failed to decode account from update.", | ||
); | ||
} | ||
} | ||
} | ||
|
||
tracing::debug!("Subscriber closed connection."); | ||
return Ok(()); | ||
} | ||
|
||
/// On poll lookup all Pyth Mapping/Product/Price accounts and sync. | ||
async fn poller<S>( | ||
config: Config, | ||
network: Network, | ||
state: Arc<S>, | ||
mapping_key: Pubkey, | ||
max_lookup_batch_size: usize, | ||
publisher_permissions_tx: Sender<HashMap<Pubkey, HashMap<Pubkey, PricePublishingMetadata>>>, | ||
) where | ||
S: Oracle, | ||
S: Send + Sync + 'static, | ||
{ | ||
// Setup an RpcClient for manual polling. | ||
let mut tick = tokio::time::interval(config.oracle.poll_interval_duration); | ||
let client = Arc::new(RpcClient::new_with_timeout_and_commitment( | ||
config.rpc_url, | ||
config.rpc_timeout, | ||
CommitmentConfig { | ||
commitment: config.oracle.commitment, | ||
}, | ||
)); | ||
|
||
loop { | ||
tick.tick().await; | ||
tracing::debug!("Polling for updates."); | ||
if let Err(err) = async { | ||
Oracle::poll_updates( | ||
&*state, | ||
mapping_key, | ||
&client, | ||
max_lookup_batch_size, | ||
publisher_permissions_tx.clone(), | ||
) | ||
.await?; | ||
Oracle::sync_global_store(&*state, network).await | ||
} | ||
.await | ||
{ | ||
tracing::error!(err = ?err, "Failed to handle poll updates."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.