Skip to content

Commit 42de78f

Browse files
committed
Add upgrade e2e test and fix PackageUpgradedEvent parsing
Adds an end-to-end test that exercises the full governance-gated upgrade lifecycle and verifies cascading effects across the stack. The test found a real bug: PackageUpgradedEvent was missing from HashiEvent::try_parse(), so validators never learned about package upgrades via the event stream. Fixed by adding the missing match arm. Test flow: - Deposit before upgrade, verify balance survives (state continuity) - Upgrade via propose/vote/execute+publish+finalize - Verify all node watchers pick up the new package version - Deposit after upgrade through full validator confirmation path - Call v2-only canary module (new code reachable) - Disable v1, verify entry points rejected
1 parent a6edbfd commit 42de78f

6 files changed

Lines changed: 848 additions & 190 deletions

File tree

crates/e2e-tests/src/e2e_flow.rs

Lines changed: 5 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ mod tests {
1010

1111
use futures::StreamExt;
1212
use hashi::sui_tx_executor::SuiTxExecutor;
13-
use hashi_types::move_types::DepositConfirmedEvent;
1413
use hashi_types::move_types::WithdrawalConfirmedEvent;
1514
use hashi_types::move_types::WithdrawalPickedForProcessingEvent;
1615
use std::sync::Arc;
@@ -20,27 +19,20 @@ mod tests {
2019
use sui_rpc::field::FieldMask;
2120
use sui_rpc::field::FieldMaskUtil;
2221
use sui_rpc::proto::sui::rpc::v2::Checkpoint;
23-
use sui_rpc::proto::sui::rpc::v2::GetBalanceRequest;
2422
use sui_rpc::proto::sui::rpc::v2::SubscribeCheckpointsRequest;
2523
use sui_sdk_types::Address;
26-
use sui_sdk_types::StructTag;
2724
use sui_sdk_types::bcs::FromBcs;
2825
use tracing::debug;
2926
use tracing::info;
3027

3128
use crate::TestNetworks;
3229
use crate::TestNetworksBuilder;
3330

34-
fn init_test_logging() {
35-
tracing_subscriber::fmt()
36-
.with_test_writer()
37-
.with_env_filter(
38-
tracing_subscriber::EnvFilter::from_default_env()
39-
.add_directive(tracing::Level::INFO.into()),
40-
)
41-
.try_init()
42-
.ok();
43-
}
31+
use crate::test_helpers::create_deposit_and_wait;
32+
use crate::test_helpers::get_hbtc_balance;
33+
use crate::test_helpers::init_test_logging;
34+
use crate::test_helpers::lookup_vout;
35+
use crate::test_helpers::txid_to_address;
4436

4537
async fn setup_test_networks() -> Result<TestNetworks> {
4638
info!("Setting up test networks...");
@@ -60,84 +52,6 @@ mod tests {
6052
Ok(networks)
6153
}
6254

63-
fn txid_to_address(txid: &Txid) -> Address {
64-
hashi_types::bitcoin_txid::BitcoinTxid::from(*txid).into()
65-
}
66-
67-
async fn wait_for_deposit_confirmation(
68-
sui_client: &mut sui_rpc::Client,
69-
request_id: Address,
70-
timeout: Duration,
71-
) -> Result<()> {
72-
info!(
73-
"Waiting for deposit confirmation for request_id: {}",
74-
request_id
75-
);
76-
77-
let start = std::time::Instant::now();
78-
let subscription_read_mask = FieldMask::from_paths([Checkpoint::path_builder()
79-
.transactions()
80-
.events()
81-
.events()
82-
.contents()
83-
.finish()]);
84-
let mut subscription = sui_client
85-
.subscription_client()
86-
.subscribe_checkpoints(
87-
SubscribeCheckpointsRequest::default().with_read_mask(subscription_read_mask),
88-
)
89-
.await?
90-
.into_inner();
91-
92-
while let Some(item) = subscription.next().await {
93-
if start.elapsed() > timeout {
94-
return Err(anyhow!(
95-
"Timeout waiting for deposit confirmation after {:?}",
96-
timeout
97-
));
98-
}
99-
100-
let checkpoint = match item {
101-
Ok(checkpoint) => checkpoint,
102-
Err(e) => {
103-
debug!("Error in checkpoint stream: {}", e);
104-
continue;
105-
}
106-
};
107-
108-
debug!(
109-
"Received checkpoint {}, checking for DepositConfirmedEvent...",
110-
checkpoint.cursor()
111-
);
112-
113-
for txn in checkpoint.checkpoint().transactions() {
114-
for event in txn.events().events() {
115-
let event_type = event.contents().name();
116-
117-
if event_type.contains("DepositConfirmedEvent") {
118-
match DepositConfirmedEvent::from_bcs(event.contents().value()) {
119-
Ok(event_data) => {
120-
if event_data.request_id == request_id {
121-
info!(
122-
"Deposit confirmed! Found DepositConfirmedEvent for request_id: {}",
123-
request_id
124-
);
125-
return Ok(());
126-
}
127-
}
128-
Err(e) => {
129-
debug!("Failed to parse DepositConfirmedEvent: {}", e);
130-
}
131-
}
132-
}
133-
}
134-
}
135-
tokio::time::sleep(Duration::from_millis(100)).await;
136-
}
137-
138-
Err(anyhow!("Checkpoint subscription ended unexpectedly"))
139-
}
140-
14155
async fn wait_for_withdrawal_confirmation(
14256
sui_client: &mut sui_rpc::Client,
14357
timeout: Duration,
@@ -207,105 +121,6 @@ mod tests {
207121
Err(anyhow!("Checkpoint subscription ended unexpectedly"))
208122
}
209123

210-
async fn get_hbtc_balance(
211-
sui_client: &mut sui_rpc::Client,
212-
package_id: Address,
213-
owner: Address,
214-
) -> Result<u64> {
215-
let btc_type = format!("{}::btc::BTC", package_id);
216-
let btc_struct_tag: StructTag = btc_type.parse()?;
217-
let request = GetBalanceRequest::default()
218-
.with_owner(owner.to_string())
219-
.with_coin_type(btc_struct_tag.to_string());
220-
221-
let response = sui_client
222-
.state_client()
223-
.get_balance(request)
224-
.await?
225-
.into_inner();
226-
227-
let balance = response.balance().balance_opt().unwrap_or(0);
228-
debug!("hBTC balance for {}: {} sats", owner, balance);
229-
Ok(balance)
230-
}
231-
232-
fn lookup_vout(
233-
networks: &TestNetworks,
234-
txid: Txid,
235-
address: bitcoin::Address,
236-
amount: u64,
237-
) -> Result<usize> {
238-
let tx = networks
239-
.bitcoin_node
240-
.rpc_client()
241-
.get_raw_transaction(txid)
242-
.and_then(|r| r.transaction().map_err(Into::into))?;
243-
let vout = tx
244-
.output
245-
.iter()
246-
.position(|output| {
247-
output.value == Amount::from_sat(amount)
248-
&& output.script_pubkey == address.script_pubkey()
249-
})
250-
.ok_or_else(|| {
251-
anyhow!(
252-
"Could not find output with amount {} and deposit address",
253-
amount
254-
)
255-
})?;
256-
debug!("Found deposit in tx output {}", vout);
257-
Ok(vout)
258-
}
259-
260-
async fn create_deposit_and_wait(
261-
networks: &mut TestNetworks,
262-
amount_sats: u64,
263-
) -> Result<Address> {
264-
let user_key = networks.sui_network.user_keys.first().unwrap();
265-
let hbtc_recipient = user_key.public_key().derive_address();
266-
let hashi = networks.hashi_network.nodes()[0].hashi().clone();
267-
// Use the on-chain MPC key rather than the local key-ready channel.
268-
// The on-chain key is set during end_reconfig and is guaranteed
269-
// available once HashiNetworkBuilder::build() returns.
270-
let deposit_address =
271-
hashi.get_deposit_address(&hashi.get_onchain_mpc_pubkey()?, Some(&hbtc_recipient))?;
272-
273-
info!("Sending Bitcoin to deposit address...");
274-
let txid = networks
275-
.bitcoin_node
276-
.send_to_address(&deposit_address, Amount::from_sat(amount_sats))?;
277-
info!("Transaction sent: {}", txid);
278-
279-
info!("Mining blocks for confirmation...");
280-
let blocks_to_mine = 10;
281-
networks.bitcoin_node.generate_blocks(blocks_to_mine)?;
282-
info!("{blocks_to_mine} blocks mined");
283-
284-
info!("Creating deposit request on Sui...");
285-
let vout = lookup_vout(networks, txid, deposit_address, amount_sats)?;
286-
let mut executor = SuiTxExecutor::from_config(&hashi.config, hashi.onchain_state())?
287-
.with_signer(user_key.clone());
288-
let request_id = executor
289-
.execute_create_deposit_request(
290-
txid_to_address(&txid),
291-
vout as u32,
292-
amount_sats,
293-
Some(hbtc_recipient),
294-
)
295-
.await?;
296-
info!("Deposit request created: {}", request_id);
297-
298-
wait_for_deposit_confirmation(
299-
&mut networks.sui_network.client,
300-
request_id,
301-
Duration::from_secs(300),
302-
)
303-
.await?;
304-
info!("Deposit confirmed on Sui");
305-
306-
Ok(hbtc_recipient)
307-
}
308-
309124
/// Mines one block per second on Bitcoin regtest until stopped.
310125
/// Stops automatically when dropped.
311126
struct BackgroundMiner {

crates/e2e-tests/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ pub mod e2e_flow;
2323
pub mod hashi_network;
2424
mod publish;
2525
pub mod sui_network;
26+
#[cfg(test)]
27+
mod test_helpers;
28+
pub mod upgrade_flow;
29+
pub mod upgrade_tests;
2630

2731
pub use bitcoin_node::BitcoinNodeBuilder;
2832
pub use bitcoin_node::BitcoinNodeHandle;
@@ -69,6 +73,10 @@ impl TestNetworks {
6973
&self.bitcoin_node
7074
}
7175

76+
pub fn dir(&self) -> &Path {
77+
self.dir.path()
78+
}
79+
7280
pub async fn restart(&mut self) -> Result<()> {
7381
self.hashi_network.restart().await
7482
}

0 commit comments

Comments
 (0)