Skip to content

Commit 2c0413d

Browse files
committed
feat(hermes): move to wormhole gRPC from p2p
1 parent 1a64d58 commit 2c0413d

File tree

15 files changed

+614
-912
lines changed

15 files changed

+614
-912
lines changed

hermes/Cargo.lock

Lines changed: 190 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hermes/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ description = "Hermes is an agent that provides Verified Prices from the Pythnet
55
edition = "2021"
66

77
[dependencies]
8-
async-trait = { version = "0.1.73" }
98
anyhow = { version = "1.0.69" }
9+
async-trait = { version = "0.1.73" }
1010
axum = { version = "0.6.20", features = ["json", "ws", "macros"] }
1111
axum-macros = { version = "0.3.8" }
1212
base64 = { version = "0.21.0" }
1313
borsh = { version = "0.10.3" }
1414
byteorder = { version = "1.4.3" }
1515
chrono = { version = "0.4.28" }
16+
clap = { version = "4.4.4", features = ["derive", "env", "cargo"] }
1617
dashmap = { version = "5.4.0" }
1718
derive_more = { version = "0.99.17" }
1819
env_logger = { version = "0.10.0" }
@@ -27,6 +28,7 @@ log = { version = "0.4.17" }
2728
mock_instant = { version = "0.3.1", features = ["sync"] }
2829
nonzero_ext = { version = "0.3.0" }
2930
prometheus-client = { version = "0.21.1" }
31+
prost = { version = "0.12.1" }
3032
pyth-sdk = { version = "0.8.0" }
3133
pythnet-sdk = { path = "../pythnet/pythnet_sdk/", version = "2.0.0", features = ["strum"] }
3234
rand = { version = "0.8.5" }
@@ -37,9 +39,9 @@ serde_json = { version = "1.0.93" }
3739
serde_qs = { version = "0.12.0", features = ["axum"] }
3840
serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }
3941
sha3 = { version = "0.10.4" }
40-
clap = { version = "4.4.4", features = ["derive", "env", "cargo"] }
4142
strum = { version = "0.24.1", features = ["derive"] }
4243
tokio = { version = "1.26.0", features = ["full"] }
44+
tonic = { version = "0.10.1", features = ["tls"] }
4345
tower-http = { version = "0.4.0", features = ["cors"] }
4446
tracing = { version = "0.1.37", features = ["log"] }
4547
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
@@ -67,6 +69,12 @@ solana-client = { version = "=1.13.3" }
6769
solana-sdk = { version = "=1.13.3" }
6870
solana-account-decoder = { version = "=1.13.3" }
6971

72+
73+
[build-dependencies]
74+
prost-build = { version = "0.12.1" }
75+
tonic-build = { version = "0.10.1" }
76+
77+
7078
# Wormhole uses patching to resolve some of its own dependencies. We need to
7179
# make sure that we use the same patch instead of simply pointing the original
7280
# dependency at git otherwise those relative imports will fail.

hermes/buf.gen.yaml

Lines changed: 0 additions & 11 deletions
This file was deleted.

hermes/build.rs

Lines changed: 39 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,49 @@
11
use std::{
22
env,
33
path::PathBuf,
4-
process::{
5-
Command,
6-
Stdio,
7-
},
4+
process::Command,
85
};
96

107
fn main() {
118
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
12-
let out_var = env::var("OUT_DIR").unwrap();
139

14-
// Download the Wormhole repository at a certain tag, which we need to access the protobuf definitions
15-
// for Wormhole P2P message types.
16-
//
17-
// TODO: This is ugly. Instead of this we should have our own tool
18-
// build process that can generate protobuf definitions for this and other user cases. For now
19-
// this is easy and works and matches upstream Wormhole's `Makefile`.
20-
21-
const WORMHOLE_VERSION: &str = "2.18.1";
22-
23-
let wh_curl = Command::new("curl")
24-
.args([
25-
"-s",
26-
"-L",
27-
format!("https://github.com/wormhole-foundation/wormhole/archive/refs/tags/v{WORMHOLE_VERSION}.tar.gz").as_str(),
28-
])
29-
.stdout(Stdio::piped())
30-
.spawn()
31-
.expect("failed to download wormhole archive");
32-
33-
let _ = Command::new("tar")
34-
.args(["xvz"])
35-
.stdin(Stdio::from(wh_curl.stdout.unwrap()))
36-
.output()
37-
.expect("failed to extract wormhole archive");
38-
39-
// Move the tools directory to the root of the repo because that's where the build script
40-
// expects it to be, paths get hardcoded into the binaries.
41-
let _ = Command::new("mv")
42-
.args([
43-
format!("wormhole-{WORMHOLE_VERSION}/tools").as_str(),
44-
"tools",
45-
])
10+
// Print OUT_DIR for debugging build issues.
11+
println!("OUT_DIR={}", out_dir.display());
12+
13+
// We'll use git to pull in protobuf dependencies. This trick lets us use the Rust OUT_DIR
14+
// directory as a mini-repo with wormhole and googleapis as remotes, so we can copy out the
15+
// TREEISH paths we want.
16+
let protobuf_setup = r#"
17+
git init .
18+
git clean -df
19+
git remote add wormhole https://github.com/wormhole-foundation/wormhole.git
20+
git remote add googleapis https://github.com/googleapis/googleapis.git
21+
git fetch --depth=1 --porcelain wormhole main
22+
git fetch --depth=1 --porcelain googleapis master
23+
git read-tree --prefix=proto/ -u wormhole/main:proto
24+
git read-tree --prefix=proto/google/api/ -u googleapis/master:google/api
25+
"#;
26+
27+
// Run each command to prepare the OUT_DIR with the protobuf definitions. We need to make sure
28+
// to change the working directory to OUT_DIR, otherwise git will complain.
29+
let _ = Command::new("sh")
30+
.args(["-c", protobuf_setup])
31+
.current_dir(&out_dir)
4632
.output()
47-
.expect("failed to move wormhole tools directory");
48-
49-
// Move the protobuf definitions to the src/network directory, we don't have to do this
50-
// but it is more intuitive when debugging.
51-
let _ = Command::new("mv")
52-
.args([
53-
format!("wormhole-{WORMHOLE_VERSION}/proto/gossip/v1/gossip.proto").as_str(),
54-
"src/network/p2p.proto",
55-
])
56-
.output()
57-
.expect("failed to move wormhole protobuf definitions");
58-
59-
// Build the protobuf compiler.
60-
let _ = Command::new("./build.sh")
61-
.current_dir("tools")
62-
.output()
63-
.expect("failed to run protobuf compiler build script");
64-
65-
// Make the protobuf compiler executable.
66-
let _ = Command::new("chmod")
67-
.args(["+x", "tools/bin/*"])
68-
.output()
69-
.expect("failed to make protofuf compiler executable");
70-
71-
// Generate the protobuf definitions. See buf.gen.yaml to see how we rename the module for our
72-
// particular use case.
73-
let _ = Command::new("./tools/bin/buf")
74-
.args(["generate", "--path", "src"])
75-
.output()
76-
.expect("failed to generate protobuf definitions");
77-
78-
let rust_target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
79-
80-
// Build the Go library.
81-
let mut cmd = Command::new("go");
82-
cmd.arg("build")
83-
.arg("-buildmode=c-archive")
84-
.arg("-o")
85-
.arg(out_dir.join("libpythnet.a"))
86-
.arg("src/network/p2p.go")
87-
.arg("src/network/p2p.pb.go");
88-
89-
// Cross-compile the Go binary based on the Rust target architecture
90-
match &*rust_target_arch {
91-
"x86_64" => {
92-
// CGO_ENABLED required for building amd64 on mac os
93-
cmd.env("GOARCH", "amd64").env("CGO_ENABLED", "1");
94-
}
95-
"aarch64" => {
96-
cmd.env("GOARCH", "arm64");
97-
}
98-
// Add other target architectures as needed.
99-
_ => {}
100-
}
101-
102-
103-
// Tell Rust to link our Go library at compile time.
104-
println!("cargo:rustc-link-search=native={out_var}");
105-
println!("cargo:rustc-link-lib=static=pythnet");
106-
println!("cargo:rustc-link-lib=resolv");
107-
108-
let go_build_output = cmd.output().expect("Failed to execute Go build command");
109-
if !go_build_output.status.success() {
110-
let error_message = String::from_utf8_lossy(&go_build_output.stderr);
111-
panic!("Go build failed:\n{}", error_message);
112-
}
33+
.expect("failed to setup protobuf definitions");
34+
35+
// We build the resulting protobuf definitions using Rust's prost_build crate, which generates
36+
// Rust code from the protobuf definitions.
37+
tonic_build::configure()
38+
.build_server(false)
39+
.compile(
40+
&[
41+
out_dir.join("proto/spy/v1/spy.proto"),
42+
out_dir.join("proto/gossip/v1/gossip.proto"),
43+
out_dir.join("proto/node/v1/node.proto"),
44+
out_dir.join("proto/publicrpc/v1/publicrpc.proto"),
45+
],
46+
&[out_dir.join("proto")],
47+
)
48+
.expect("failed to compile protobuf definitions");
11349
}

hermes/src/aggregate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use {
1919
WormholeMerkleState,
2020
},
2121
crate::{
22+
network::wormhole::VaaBytes,
2223
state::{
2324
benchmarks::Benchmarks,
2425
cache::{
@@ -28,7 +29,6 @@ use {
2829
},
2930
State,
3031
},
31-
wormhole::VaaBytes,
3232
},
3333
anyhow::{
3434
anyhow,

hermes/src/aggregate/wormhole_merkle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use {
55
Slot,
66
},
77
crate::{
8+
network::wormhole::VaaBytes,
89
state::cache::{
910
AggregateCache,
1011
MessageState,
1112
},
12-
wormhole::VaaBytes,
1313
},
1414
anyhow::{
1515
anyhow,

hermes/src/config/wormhole.rs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,27 @@
11
use {
22
clap::Args,
3-
libp2p::Multiaddr,
43
solana_sdk::pubkey::Pubkey,
54
};
65

7-
const DEFAULT_LISTEN_ADDRS: &str = "/ip4/0.0.0.0/udp/30910/quic,/ip6/::/udp/30910/quic";
86
const DEFAULT_CONTRACT_ADDR: &str = "H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU";
9-
const DEFAULT_NETWORK_ID: &str = "/wormhole/mainnet/2";
10-
const DEFAULT_BOOTSTRAP_ADDRS: &str = concat![
11-
"/dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7,",
12-
"/dns4/wormhole-v2-mainnet-bootstrap.xlabs.xyz/udp/8999/quic/p2p/12D3KooWNQ9tVrcb64tw6bNs2CaNrUGPM7yRrKvBBheQ5yCyPHKC",
13-
];
7+
const DEFAULT_WORMHOLE_RPC_ADDR: &str = "grpc://127.0.0.1:7073";
148

159
#[derive(Args, Clone, Debug)]
1610
#[command(next_help_heading = "Wormhole Options")]
1711
#[group(id = "Wormhole")]
1812
pub struct Options {
19-
/// Multiaddresses for Wormhole bootstrap peers (separated by comma).
20-
///
21-
/// Bootstraps can be found from the official Wormhole repository, note that these addresses
22-
/// are only used to bootstrap peer discovery and are not necessarily used for data transfer.
23-
/// Adding more peers will speed up P2P peer discovery.
24-
#[arg(long = "wormhole-bootstrap-addrs")]
25-
#[arg(value_delimiter = ',')]
26-
#[arg(default_value = DEFAULT_BOOTSTRAP_ADDRS)]
27-
#[arg(env = "WORMHOLE_BOOTSTRAP_ADDRS")]
28-
pub bootstrap_addrs: Vec<Multiaddr>,
29-
3013
/// Address of the Wormhole contract on the target PythNet cluster.
3114
#[arg(long = "wormhole-contract-addr")]
3215
#[arg(default_value = DEFAULT_CONTRACT_ADDR)]
3316
#[arg(env = "WORMHOLE_CONTRACT_ADDR")]
3417
pub contract_addr: Pubkey,
3518

36-
/// Multiaddresses to bind for Wormhole P2P (separated by comma)
37-
#[arg(long = "wormhole-listen-addrs")]
38-
#[arg(value_delimiter = ',')]
39-
#[arg(default_value = DEFAULT_LISTEN_ADDRS)]
40-
#[arg(env = "WORMHOLE_LISTEN_ADDRS")]
41-
pub listen_addrs: Vec<Multiaddr>,
42-
43-
/// Network ID for Wormhole
44-
#[arg(long = "wormhole-network-id")]
45-
#[arg(default_value = DEFAULT_NETWORK_ID)]
46-
#[arg(env = "WORMHOLE_NETWORK_ID")]
47-
pub network_id: String,
19+
/// gRPC endpoint for a Wormhole node.
20+
///
21+
/// This can either be a standard Wormhole node gRPC endpoint or a beacon endpoint if
22+
/// load-balancing is desired.
23+
#[arg(long = "wormhole-spy-rpc-addr")]
24+
#[arg(default_value = DEFAULT_WORMHOLE_RPC_ADDR)]
25+
#[arg(env = "WORMHOLE_RPC_ADDR")]
26+
pub spy_rpc_addr: String,
4827
}

hermes/src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
#![feature(btree_cursors)]
33

44
use {
5-
crate::state::State,
65
anyhow::Result,
76
clap::{
87
CommandFactory,
98
Parser,
109
},
1110
futures::future::join_all,
11+
state::State,
1212
std::{
1313
io::IsTerminal,
1414
sync::atomic::AtomicBool,
@@ -23,7 +23,6 @@ mod doc_examples;
2323
mod network;
2424
mod serde;
2525
mod state;
26-
mod wormhole;
2726

2827
// A static exit flag to indicate to running threads that we're shutting down. This is used to
2928
// gracefully shutdown the application.
@@ -61,7 +60,7 @@ async fn init() -> Result<()> {
6160
// Spawn all worker tasks, and wait for all to complete (which will happen if a shutdown
6261
// signal has been observed).
6362
let tasks = join_all([
64-
Box::pin(spawn(network::p2p::spawn(opts.clone(), store.clone()))),
63+
Box::pin(spawn(network::wormhole::spawn(opts.clone(), store.clone()))),
6564
Box::pin(spawn(network::pythnet::spawn(opts.clone(), store.clone()))),
6665
Box::pin(spawn(api::run(opts.clone(), store.clone(), update_rx))),
6766
])

hermes/src/network.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
pub mod p2p;
21
pub mod pythnet;
2+
pub mod wormhole;

0 commit comments

Comments
 (0)