Skip to content

Commit b74df4f

Browse files
committed
refactor(hermes): pass run args as struct, add docstrings
In some cases arguments are passed and renamed (see `api_addr -> rpc_addr`) or are unnecesarily converted (see `api_addr.to_string()` -> `api_addr.parse()`. In the future, we are likely to add many more arguments to Hermes as well, so this commit moves them into a separate struct which is forwarded throughout the application instead. The struct's are cloned, but only happens during launch of a hermes service component so the cost doesn't matter.
1 parent 1a00598 commit b74df4f

File tree

5 files changed

+132
-127
lines changed

5 files changed

+132
-127
lines changed

hermes/src/api.rs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use {
22
self::ws::notify_updates,
3-
crate::store::Store,
3+
crate::{
4+
config::RunOptions,
5+
store::Store,
6+
},
47
anyhow::Result,
58
axum::{
69
extract::Extension,
@@ -41,23 +44,34 @@ impl State {
4144
///
4245
/// Currently this is based on Axum due to the simplicity and strong ecosystem support for the
4346
/// packages they are based on (tokio & hyper).
44-
pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: String) -> Result<()> {
47+
pub async fn run(opts: RunOptions, store: Arc<Store>, mut update_rx: Receiver<()>) -> Result<()> {
48+
log::info!("Starting RPC server on {}", opts.api_addr);
49+
4550
#[derive(OpenApi)]
4651
#[openapi(
47-
paths(
48-
rest::latest_price_feeds,
49-
rest::latest_vaas,
50-
rest::get_price_feed,
51-
rest::get_vaa,
52-
rest::get_vaa_ccip,
53-
rest::price_feed_ids,
54-
),
55-
components(
56-
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed, types::RpcPrice, types::RpcPriceIdentifier, types::PriceIdInput, rest::GetVaaResponse, rest::GetVaaCcipResponse, rest::GetVaaCcipInput)
57-
),
58-
tags(
59-
(name = "hermes", description = "Pyth Real-Time Pricing API")
60-
)
52+
paths(
53+
rest::get_price_feed,
54+
rest::get_vaa,
55+
rest::get_vaa_ccip,
56+
rest::latest_price_feeds,
57+
rest::latest_vaas,
58+
rest::price_feed_ids,
59+
),
60+
components(
61+
schemas(
62+
rest::GetVaaCcipInput,
63+
rest::GetVaaCcipResponse,
64+
rest::GetVaaResponse,
65+
types::PriceIdInput,
66+
types::RpcPrice,
67+
types::RpcPriceFeed,
68+
types::RpcPriceFeedMetadata,
69+
types::RpcPriceIdentifier,
70+
)
71+
),
72+
tags(
73+
(name = "hermes", description = "Pyth Real-Time Pricing API")
74+
)
6175
)]
6276
struct ApiDoc;
6377

@@ -103,7 +117,7 @@ pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: Strin
103117

104118
// Binds the axum's server to the configured address and port. This is a blocking call and will
105119
// not return until the server is shutdown.
106-
axum::Server::try_bind(&rpc_addr.parse()?)?
120+
axum::Server::try_bind(&opts.api_addr)?
107121
.serve(app.into_make_service())
108122
.with_graceful_shutdown(async {
109123
signal::ctrl_c()

hermes/src/config.rs

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,60 @@ use {
55
structopt::StructOpt,
66
};
77

8-
/// StructOpt definitions that provides the following arguments and commands:
9-
///
10-
/// Some of these arguments are not currently used, but are included for future use to guide the
11-
/// structure of the application.
8+
const DEFAULT_NETWORK_ID: &str = "/wormhole/mainnet/2";
9+
const DEFAULT_WORMHOLE_BOOTSTRAP_ADDRS: &str = "/dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7";
10+
const DEFAULT_WORMHOLE_LISTEN_ADDRS: &str = "/ip4/0.0.0.0/udp/30910/quic,/ip6/::/udp/30910/quic";
11+
const DEFAULT_API_ADDR: &str = "127.0.0.1:33999";
12+
13+
/// `Options` is a structup definition to provide clean command-line args for Hermes.
1214
#[derive(StructOpt, Debug)]
1315
#[structopt(name = "hermes", about = "Hermes")]
1416
pub enum Options {
15-
Run {
16-
#[structopt(long, env = "PYTHNET_WS_ENDPOINT")]
17-
pythnet_ws_endpoint: String,
18-
19-
#[structopt(long, env = "PYTHNET_HTTP_ENDPOINT")]
20-
pythnet_http_endpoint: String,
21-
22-
/// Network ID for Wormhole
23-
#[structopt(
24-
long,
25-
default_value = "/wormhole/mainnet/2",
26-
env = "WORMHOLE_NETWORK_ID"
27-
)]
28-
wh_network_id: String,
29-
30-
/// Multiaddresses for Wormhole bootstrap peers (separated by comma).
31-
#[structopt(
32-
long,
33-
use_delimiter = true,
34-
default_value = "/dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7",
35-
env = "WORMHOLE_BOOTSTRAP_ADDRS"
36-
)]
37-
wh_bootstrap_addrs: Vec<Multiaddr>,
38-
39-
/// Multiaddresses to bind Wormhole P2P to (separated by comma)
40-
#[structopt(
41-
long,
42-
use_delimiter = true,
43-
default_value = "/ip4/0.0.0.0/udp/30910/quic,/ip6/::/udp/30910/quic",
44-
env = "WORMHOLE_LISTEN_ADDRS"
45-
)]
46-
wh_listen_addrs: Vec<Multiaddr>,
47-
48-
/// The address to bind the API server to.
49-
#[structopt(long, default_value = "127.0.0.1:33999")]
50-
api_addr: SocketAddr,
51-
52-
/// Address of the Wormhole contract on the target PythNet cluster.
53-
#[structopt(long, default_value = "H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU")]
54-
wh_contract_addr: Pubkey,
55-
},
17+
/// Run the hermes service.
18+
Run(RunOptions),
19+
}
20+
21+
#[derive(Clone, Debug, StructOpt)]
22+
pub struct RunOptions {
23+
/// The address to bind the API server to.
24+
#[structopt(long)]
25+
#[structopt(default_value = DEFAULT_API_ADDR)]
26+
#[structopt(env = "API_ADDR")]
27+
pub api_addr: SocketAddr,
28+
29+
/// Address of a PythNet compatible websocket RPC endpoint.
30+
#[structopt(long)]
31+
#[structopt(env = "PYTHNET_WS_ENDPOINT")]
32+
pub pythnet_ws_endpoint: String,
33+
34+
/// Addres of a PythNet compatible HTP RPC endpoint.
35+
#[structopt(long)]
36+
#[structopt(env = "PYTHNET_HTTP_ENDPOINT")]
37+
pub pythnet_http_endpoint: String,
38+
39+
/// Multiaddresses for Wormhole bootstrap peers (separated by comma).
40+
#[structopt(long)]
41+
#[structopt(use_delimiter = true)]
42+
#[structopt(default_value = DEFAULT_WORMHOLE_BOOTSTRAP_ADDRS)]
43+
#[structopt(env = "WORMHOLE_BOOTSTRAP_ADDRS")]
44+
pub wh_bootstrap_addrs: Vec<Multiaddr>,
45+
46+
/// Address of the Wormhole contract on the target PythNet cluster.
47+
#[structopt(long)]
48+
#[structopt(default_value = "H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU")]
49+
#[structopt(env = "WORMHOLE_CONTRACT_ADDR")]
50+
pub wh_contract_addr: Pubkey,
51+
52+
/// Multiaddresses to bind Wormhole P2P to (separated by comma)
53+
#[structopt(long)]
54+
#[structopt(use_delimiter = true)]
55+
#[structopt(default_value = DEFAULT_WORMHOLE_LISTEN_ADDRS)]
56+
#[structopt(env = "WORMHOLE_LISTEN_ADDRS")]
57+
pub wh_listen_addrs: Vec<Multiaddr>,
58+
59+
/// Network ID for Wormhole
60+
#[structopt(long)]
61+
#[structopt(default_value = DEFAULT_NETWORK_ID)]
62+
#[structopt(env = "WORMHOLE_NETWORK_ID")]
63+
pub wh_network_id: String,
5664
}

hermes/src/main.rs

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,44 +22,18 @@ async fn init() -> Result<()> {
2222
// Parse the command line arguments with StructOpt, will exit automatically on `--help` or
2323
// with invalid arguments.
2424
match config::Options::from_args() {
25-
config::Options::Run {
26-
pythnet_ws_endpoint,
27-
pythnet_http_endpoint,
28-
wh_network_id,
29-
wh_bootstrap_addrs,
30-
wh_listen_addrs,
31-
wh_contract_addr,
32-
api_addr,
33-
} => {
34-
// A channel to emit state updates to api
25+
config::Options::Run(opts) => {
26+
log::info!("Starting hermes service...");
27+
28+
// The update channel is used to send store update notifications to the public API.
3529
let (update_tx, update_rx) = tokio::sync::mpsc::channel(1000);
3630

37-
log::info!("Running Hermes...");
31+
// Initialize a cache store with a 1000 element circular buffer.
3832
let store = Store::new(update_tx, 1000);
3933

40-
// Spawn the P2P layer.
41-
log::info!("Starting P2P server on {:?}", wh_listen_addrs);
42-
network::p2p::spawn(
43-
store.clone(),
44-
wh_network_id.to_string(),
45-
wh_bootstrap_addrs,
46-
wh_listen_addrs,
47-
)
48-
.await?;
49-
50-
// Spawn the Pythnet listener
51-
log::info!("Starting Pythnet listener using {}", pythnet_ws_endpoint);
52-
network::pythnet::spawn(
53-
store.clone(),
54-
pythnet_ws_endpoint,
55-
pythnet_http_endpoint,
56-
wh_contract_addr,
57-
)
58-
.await?;
59-
60-
// Run the RPC server and wait for it to shutdown gracefully.
61-
log::info!("Starting RPC server on {}", api_addr);
62-
api::run(store.clone(), update_rx, api_addr.to_string()).await?;
34+
network::p2p::spawn(opts.clone(), store.clone()).await?;
35+
network::pythnet::spawn(opts.clone(), store.clone()).await?;
36+
api::run(opts.clone(), store.clone(), update_rx).await?;
6337
}
6438
}
6539

hermes/src/network/p2p.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
//! their infrastructure.
1111
1212
use {
13-
crate::store::{
14-
types::Update,
15-
Store,
13+
crate::{
14+
config::RunOptions,
15+
store::{
16+
types::Update,
17+
Store,
18+
},
1619
},
1720
anyhow::Result,
1821
libp2p::Multiaddr,
@@ -122,13 +125,17 @@ pub fn bootstrap(
122125
}
123126

124127
// Spawn's the P2P layer as a separate thread via Go.
125-
pub async fn spawn(
126-
store: Arc<Store>,
127-
network_id: String,
128-
wh_bootstrap_addrs: Vec<Multiaddr>,
129-
wh_listen_addrs: Vec<Multiaddr>,
130-
) -> Result<()> {
131-
std::thread::spawn(|| bootstrap(network_id, wh_bootstrap_addrs, wh_listen_addrs).unwrap());
128+
pub async fn spawn(opts: RunOptions, store: Arc<Store>) -> Result<()> {
129+
log::info!("Starting P2P server on {:?}", opts.wh_listen_addrs);
130+
131+
std::thread::spawn(|| {
132+
bootstrap(
133+
opts.wh_network_id,
134+
opts.wh_bootstrap_addrs,
135+
opts.wh_listen_addrs,
136+
)
137+
.unwrap()
138+
});
132139

133140
tokio::spawn(async move {
134141
// Listen in the background for new VAA's from the p2p layer

hermes/src/network/pythnet.rs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
//! storage.
44
55
use {
6-
crate::store::{
7-
types::{
8-
AccumulatorMessages,
9-
Update,
6+
crate::{
7+
config::RunOptions,
8+
store::{
9+
types::{
10+
AccumulatorMessages,
11+
Update,
12+
},
13+
wormhole::{
14+
BridgeData,
15+
GuardianSet,
16+
GuardianSetData,
17+
},
18+
Store,
1019
},
11-
wormhole::{
12-
BridgeData,
13-
GuardianSet,
14-
GuardianSetData,
15-
},
16-
Store,
1720
},
1821
anyhow::{
1922
anyhow,
@@ -248,23 +251,22 @@ async fn fetch_existing_guardian_sets(
248251
Ok(())
249252
}
250253

254+
pub async fn spawn(opts: RunOptions, store: Arc<Store>) -> Result<()> {
255+
log::info!(
256+
"Starting Pythnet listener using {}",
257+
opts.pythnet_ws_endpoint
258+
);
251259

252-
pub async fn spawn(
253-
store: Arc<Store>,
254-
pythnet_ws_endpoint: String,
255-
pythnet_http_endpoint: String,
256-
wormhole_contract_addr: Pubkey,
257-
) -> Result<()> {
258260
fetch_existing_guardian_sets(
259261
store.clone(),
260-
pythnet_http_endpoint.clone(),
261-
wormhole_contract_addr,
262+
opts.pythnet_http_endpoint.clone(),
263+
opts.wh_contract_addr,
262264
)
263265
.await?;
264266

265267
{
266268
let store = store.clone();
267-
let pythnet_ws_endpoint = pythnet_ws_endpoint.clone();
269+
let pythnet_ws_endpoint = opts.pythnet_ws_endpoint.clone();
268270
tokio::spawn(async move {
269271
loop {
270272
let current_time = Instant::now();
@@ -285,15 +287,15 @@ pub async fn spawn(
285287

286288
{
287289
let store = store.clone();
288-
let pythnet_http_endpoint = pythnet_http_endpoint.clone();
290+
let pythnet_http_endpoint = opts.pythnet_http_endpoint.clone();
289291
tokio::spawn(async move {
290292
loop {
291293
tokio::time::sleep(Duration::from_secs(60)).await;
292294

293295
match fetch_existing_guardian_sets(
294296
store.clone(),
295297
pythnet_http_endpoint.clone(),
296-
wormhole_contract_addr,
298+
opts.wh_contract_addr,
297299
)
298300
.await
299301
{

0 commit comments

Comments
 (0)