-
Notifications
You must be signed in to change notification settings - Fork 5
[2/n][guardian-integration] in-process e2e harness #466
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
Changes from all commits
d9d08bf
fc429c2
44fa42c
265b39e
4d8d06a
4c8fd40
c64923a
dffff49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // Copyright (c) Mysten Labs, Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! In-process `hashi-guardian` for integration tests. Two stages: | ||
| //! [`GuardianHarness::start`] serves gRPC; [`GuardianHarness::finalize`] | ||
| //! runs provisioner-init once hashi DKG output is on chain. | ||
|
|
||
| use anyhow::Context; | ||
| use anyhow::Result; | ||
| use bitcoin::Network; | ||
| use hashi_guardian::Enclave; | ||
| use hashi_guardian::OperatorInitTestArgs; | ||
| use hashi_guardian::create_operator_initialized_enclave; | ||
| use hashi_guardian::rpc::GuardianGrpc; | ||
| use hashi_types::committee::Committee as HashiCommittee; | ||
| use hashi_types::guardian::BitcoinPubkey; | ||
| use hashi_types::guardian::LimiterState; | ||
| use hashi_types::guardian::WithdrawalConfig; | ||
| use hashi_types::proto::guardian_service_server::GuardianServiceServer; | ||
| use std::net::SocketAddr; | ||
| use std::sync::Arc; | ||
| use tokio::net::TcpListener; | ||
| use tokio::sync::oneshot; | ||
| use tokio::task::JoinHandle; | ||
| use tonic::transport::Server; | ||
|
|
||
| /// In-process guardian reachable over gRPC on a local TCP socket. | ||
| pub struct GuardianHarness { | ||
| enclave: Arc<Enclave>, | ||
| endpoint: String, | ||
| shutdown_tx: Option<oneshot::Sender<()>>, | ||
| server_handle: Option<JoinHandle<()>>, | ||
| } | ||
|
|
||
| impl GuardianHarness { | ||
| /// Start an operator-init'd guardian. Withdrawal RPCs stay gated until | ||
| /// [`Self::finalize`] completes provisioner-init. | ||
| pub async fn start(network: Network) -> Result<Self> { | ||
|
Comment on lines
+36
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At which stage is the guardian pubkey available? Here or after finalize? we do need to commit to it, and likely a url of some such, onchain and it would probably be beneficial to be able to have the key available at contract init time.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a conversation we had today, the committing onchain will be done post this series |
||
| let enclave = create_operator_initialized_enclave( | ||
| OperatorInitTestArgs::default().with_network(network), | ||
| ) | ||
| .await; | ||
|
|
||
| let listener = TcpListener::bind("127.0.0.1:0") | ||
| .await | ||
| .context("bind guardian harness listener")?; | ||
| let addr: SocketAddr = listener.local_addr()?; | ||
| let endpoint = format!("http://{addr}"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note, do we want to use traditional TLS termination or require mTLS from the validators to the guardian? |
||
|
|
||
| let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); | ||
| let svc = GuardianGrpc { | ||
| enclave: enclave.clone(), | ||
| setup_mode: false, | ||
| }; | ||
| let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener); | ||
| let server_handle = tokio::spawn(async move { | ||
| let result = Server::builder() | ||
| .add_service(GuardianServiceServer::new(svc)) | ||
| .serve_with_incoming_shutdown(incoming, async move { | ||
| let _ = shutdown_rx.await; | ||
| }) | ||
| .await; | ||
| if let Err(e) = result { | ||
| tracing::warn!("guardian harness server exited: {e}"); | ||
| } | ||
| }); | ||
|
|
||
| Ok(Self { | ||
| enclave, | ||
| endpoint, | ||
| shutdown_tx: Some(shutdown_tx), | ||
| server_handle: Some(server_handle), | ||
| }) | ||
| } | ||
|
|
||
| /// Complete provisioner-init using the committee and master pubkey from hashi DKG. | ||
| pub async fn finalize( | ||
| &self, | ||
| committee: HashiCommittee, | ||
| master_pubkey: BitcoinPubkey, | ||
| withdrawal_config: WithdrawalConfig, | ||
| limiter_state: LimiterState, | ||
| ) -> Result<()> { | ||
| hashi_guardian::test_utils::finalize_enclave( | ||
| &self.enclave, | ||
| committee, | ||
| master_pubkey, | ||
| withdrawal_config, | ||
| limiter_state, | ||
| ) | ||
| .map_err(|e| anyhow::anyhow!("finalize guardian enclave: {e:?}"))?; | ||
|
|
||
| anyhow::ensure!( | ||
| self.enclave.is_fully_initialized(), | ||
| "guardian did not reach fully-initialized state" | ||
| ); | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn endpoint(&self) -> &str { | ||
| &self.endpoint | ||
| } | ||
|
|
||
| pub fn enclave(&self) -> &Arc<Enclave> { | ||
| &self.enclave | ||
| } | ||
| } | ||
|
|
||
| impl Drop for GuardianHarness { | ||
| fn drop(&mut self) { | ||
| if let Some(tx) = self.shutdown_tx.take() { | ||
| let _ = tx.send(()); | ||
| } | ||
| if let Some(handle) = self.server_handle.take() { | ||
| handle.abort(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn default_test_withdrawal_config(committee: &HashiCommittee) -> WithdrawalConfig { | ||
| let total_weight = committee.total_weight(); | ||
| let committee_threshold = total_weight.div_ceil(3) * 2; | ||
| WithdrawalConfig { | ||
| committee_threshold, | ||
| refill_rate_sats_per_sec: 0, | ||
| max_bucket_capacity_sats: 100_000_000, | ||
| } | ||
| } | ||
|
|
||
| pub fn full_bucket(config: &WithdrawalConfig) -> LimiterState { | ||
| LimiterState { | ||
| num_tokens_available: config.max_bucket_capacity_sats, | ||
| last_updated_at: 0, | ||
| next_seq: 0, | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this makes sense while we are getting the integration hooked up. We may want to evaluate if we want to have all tests run with guardian by default eventually.