Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/asm-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ strata-asm-proof-impl.workspace = true
strata-asm-proof-types.workspace = true
strata-asm-proto-bridge-v1.workspace = true
strata-asm-proto-bridge-v1-txs.workspace = true
strata-asm-proto-bridge-v1-types.workspace = true
strata-asm-proto-checkpoint.workspace = true
strata-asm-proto-checkpoint-txs.workspace = true
strata-asm-proto-checkpoint-types.workspace = true
Expand Down
8 changes: 8 additions & 0 deletions bin/asm-runner/src/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use strata_asm_proof_db::{ProofDb, SledMohoStateDb, SledProofDb};
use strata_asm_proof_types::{AsmProof, L1Range, MohoProof};
use strata_asm_proto_bridge_v1::{AssignmentEntry, BridgeV1State, DepositEntry};
use strata_asm_proto_bridge_v1_txs::BRIDGE_V1_SUBPROTOCOL_ID;
use strata_asm_proto_bridge_v1_types::SafeHarbour;
use strata_asm_proto_checkpoint::CheckpointState;
use strata_asm_proto_checkpoint_txs::CHECKPOINT_SUBPROTOCOL_ID;
use strata_asm_proto_checkpoint_types::CheckpointTip;
Expand Down Expand Up @@ -138,6 +139,13 @@ impl AsmStateApiServer for AsmRpcServer {
}
}

async fn get_safe_harbour(&self, block_hash: BlockHash) -> RpcResult<Option<SafeHarbour>> {
match self.get_bridge_state(block_hash).await? {
Some(bridge_state) => Ok(Some(bridge_state.safe_harbour().clone())),
None => Ok(None),
}
}

async fn get_checkpoint_tip(&self, block_hash: BlockHash) -> RpcResult<Option<CheckpointTip>> {
match self.get_checkpoint_state(block_hash).await? {
Some(checkpoint_state) => Ok(Some(*checkpoint_state.verified_tip())),
Expand Down
8 changes: 7 additions & 1 deletion crates/params/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ strata-predicate.workspace = true

arbitrary = { workspace = true, optional = true }
bitcoin = { workspace = true, optional = true }
bitcoin-bosd = { workspace = true, features = ["serde"] }
serde.workspace = true
ssz.workspace = true
ssz_derive.workspace = true
Expand All @@ -25,4 +26,9 @@ proptest.workspace = true
serde_json.workspace = true

[features]
arbitrary = ["dep:arbitrary", "dep:bitcoin", "strata-predicate/arbitrary"]
arbitrary = [
"dep:arbitrary",
"dep:bitcoin",
"bitcoin-bosd/arbitrary",
"strata-predicate/arbitrary",
]
3 changes: 2 additions & 1 deletion crates/params/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ mod tests {
"denomination": 0,
"assignment_duration": 0,
"operator_fee": 0,
"recovery_delay": 0
"recovery_delay": 0,
"safe_harbour_address": "03671041727b982843f7e3db4669c2f542e05096fb"
Comment thread
barakshani marked this conversation as resolved.
}
}
]
Expand Down
5 changes: 5 additions & 0 deletions crates/params/src/subprotocols/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bitcoin_bosd::Descriptor;
use serde::{Deserialize, Serialize};
use strata_btc_types::BitcoinAmount;
use strata_crypto::EvenPublicKey;
Expand All @@ -17,6 +18,9 @@ pub struct BridgeV1InitConfig {
/// Number of Bitcoin blocks after Deposit Request Transaction that the depositor can reclaim
/// funds if operators fail to process the deposit.
pub recovery_delay: u16,
/// Predefined safe harbour address. Deactivated at init; the admin multisig toggles
/// activation.
pub safe_harbour_address: Descriptor,
}

#[cfg(feature = "arbitrary")]
Expand All @@ -34,6 +38,7 @@ impl<'a> arbitrary::Arbitrary<'a> for BridgeV1InitConfig {
assignment_duration: u.arbitrary()?,
operator_fee: u.arbitrary()?,
recovery_delay: u.arbitrary()?,
safe_harbour_address: u.arbitrary()?,
})
}
}
1 change: 1 addition & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ workspace = true
[dependencies]
strata-asm-proof-types.workspace = true
strata-asm-proto-bridge-v1.workspace = true
strata-asm-proto-bridge-v1-types.workspace = true
strata-asm-proto-checkpoint-types.workspace = true
strata-asm-worker.workspace = true

Expand Down
5 changes: 5 additions & 0 deletions crates/rpc/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bitcoin::BlockHash;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use strata_asm_proof_types::{AsmProof, MohoProof};
use strata_asm_proto_bridge_v1::{AssignmentEntry, DepositEntry};
use strata_asm_proto_bridge_v1_types::SafeHarbour;
use strata_asm_proto_checkpoint_types::CheckpointTip;
use strata_asm_worker::{AsmState, AsmWorkerStatus};

Expand Down Expand Up @@ -34,6 +35,10 @@ pub trait AsmStateApi {
#[method(name = "getDeposits")]
async fn get_deposits(&self, block_hash: BlockHash) -> RpcResult<Vec<DepositEntry>>;

/// Return the safe harbour address for the provided Bitcoin block hash.
#[method(name = "getSafeHarbour")]
async fn get_safe_harbour(&self, block_hash: BlockHash) -> RpcResult<Option<SafeHarbour>>;

/// Return the verified checkpoint tip for the provided Bitcoin block hash.
#[method(name = "getCheckpointTip")]
async fn get_checkpoint_tip(&self, block_hash: BlockHash) -> RpcResult<Option<CheckpointTip>>;
Expand Down
1 change: 1 addition & 0 deletions crates/subprotocols/bridge-v1/msgs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2024"
workspace = true

[dependencies]
bitcoin-bosd.workspace = true
ssz.workspace = true
ssz_derive.workspace = true

Expand Down
21 changes: 21 additions & 0 deletions crates/subprotocols/bridge-v1/msgs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use std::any::Any;

use bitcoin_bosd::Descriptor;
use ssz_derive::{Decode, Encode};
use strata_asm_common::{InterprotoMsg, SubprotocolId};
use strata_asm_proto_bridge_v1_txs::BRIDGE_V1_SUBPROTOCOL_ID;
Expand All @@ -26,6 +27,18 @@ pub enum BridgeIncomingMsg {
/// Emitted by the admin subprotocol when the operator set is updated.
/// Adds new operators by public key and removes existing operators by index.
UpdateOperatorSet(UpdateOperatorSetPayload),

/// Emitted by the admin subprotocol to update the safe harbour destination
/// descriptor.
UpdateSafeHarbourAddress(Descriptor),

/// Defcon1 signal raised by the admin subprotocol. The bridge must respond by
/// activating the safe harbour.
Defcon1(Defcon1Payload),

/// Defcon3 signal raised by the admin subprotocol. The bridge must respond by
/// activating the safe harbour.
Defcon3(Defcon3Payload),
}

/// Payload for [`BridgeIncomingMsg::UpdateOperatorSet`].
Expand All @@ -37,6 +50,14 @@ pub struct UpdateOperatorSetPayload {
pub remove_members: Vec<OperatorIdx>,
}

/// Empty marker payload for [`BridgeIncomingMsg::Defcon1`]; the signal itself carries no data.
#[derive(Clone, Debug, Eq, PartialEq, Default, Encode, Decode)]
pub struct Defcon1Payload {}

/// Empty marker payload for [`BridgeIncomingMsg::Defcon3`]; the signal itself carries no data.
#[derive(Clone, Debug, Eq, PartialEq, Default, Encode, Decode)]
pub struct Defcon3Payload {}

impl InterprotoMsg for BridgeIncomingMsg {
fn id(&self) -> SubprotocolId {
BRIDGE_V1_SUBPROTOCOL_ID
Expand Down
1 change: 1 addition & 0 deletions crates/subprotocols/bridge-v1/subprotocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ workspace = true
[dependencies]
arbitrary.workspace = true
bitcoin.workspace = true
bitcoin-bosd.workspace = true
rand_chacha.workspace = true
serde.workspace = true
ssz.workspace = true
Expand Down
24 changes: 23 additions & 1 deletion crates/subprotocols/bridge-v1/subprotocol/src/state/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use bitcoin_bosd::Descriptor;
use ssz_derive::{Decode, Encode};
use strata_asm_params::BridgeV1InitConfig;
use strata_asm_proto_bridge_v1_txs::{deposit::DepositInfo, errors::Mismatch};
use strata_asm_proto_bridge_v1_types::{OperatorIdx, WithdrawOutput, WithdrawalCommand};
use strata_asm_proto_bridge_v1_types::{
OperatorIdx, SafeHarbour, WithdrawOutput, WithdrawalCommand,
};
use strata_btc_types::BitcoinAmount;
use strata_identifiers::L1BlockCommitment;

Expand Down Expand Up @@ -38,6 +41,9 @@ pub struct BridgeV1State {
/// Number of blocks after Deposit Request Transaction that the depositor can reclaim
/// funds if operators fail to process the deposit.
recovery_delay: u16,

/// Safe harbour
safe_harbour: SafeHarbour,
}

impl BridgeV1State {
Expand All @@ -64,6 +70,7 @@ impl BridgeV1State {
denomination: config.denomination,
operator_fee: config.operator_fee,
recovery_delay: config.recovery_delay,
safe_harbour: SafeHarbour::new(config.safe_harbour_address.clone()),
}
}

Expand Down Expand Up @@ -92,6 +99,21 @@ impl BridgeV1State {
self.recovery_delay
}

/// Returns a reference to the safe harbour.
pub fn safe_harbour(&self) -> &SafeHarbour {
&self.safe_harbour
}

/// Sets the safe harbour activation flag.
pub fn set_safe_harbour_activated(&mut self, activated: bool) {
self.safe_harbour.set_activated(activated);
}

/// Sets the safe harbour activation flag.
pub fn update_safe_harbour_address(&mut self, new_address: Descriptor) {
self.safe_harbour.update_address(new_address);
}

/// Processes a deposit transaction by validating and adding it to the deposits table.
///
/// This function takes already parsed deposit transaction information, validates it against the
Expand Down
102 changes: 102 additions & 0 deletions crates/subprotocols/bridge-v1/subprotocol/src/subprotocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,109 @@ impl Subprotocol for BridgeV1Subproto {
);
state.apply_operator_set_update(add_members, remove_members);
}

BridgeIncomingMsg::UpdateSafeHarbourAddress(descriptor) => {
info!("Updating the safe harbour address from admin subprotocol");
state.update_safe_harbour_address(descriptor.clone());
}

BridgeIncomingMsg::Defcon1(_) | BridgeIncomingMsg::Defcon3(_) => {
info!(
"Activating safe harbour address on Defcon1 signal from admin subprotocol"
);
state.set_safe_harbour_activated(true);
}
}
}
}
}

#[cfg(test)]
mod tests {
use bitcoin_bosd::Descriptor;
use strata_asm_common::Subprotocol;
use strata_asm_proto_bridge_v1_msgs::{BridgeIncomingMsg, Defcon1Payload, Defcon3Payload};
use strata_identifiers::L1BlockCommitment;
use strata_test_utils_arb::ArbitraryGenerator;

use super::BridgeV1Subproto;
use crate::test_utils::create_test_state;

fn descriptor_a() -> Descriptor {
Descriptor::new_p2wpkh(&[0xAA; 20])
}

fn descriptor_b() -> Descriptor {
Descriptor::new_p2wpkh(&[0xBB; 20])
}

/// The safe harbour must start deactivated so it has no effect until the
/// admin subprotocol explicitly triggers a defcon signal.
#[test]
fn safe_harbour_starts_deactivated() {
let (state, _privkeys) = create_test_state();
assert!(!state.safe_harbour().is_activated());
assert_eq!(state.safe_harbour().active_address(), None);
}

#[test]
fn process_msgs_update_safe_harbour_address() {
let (mut state, _privkeys) = create_test_state();
let l1ref: L1BlockCommitment = ArbitraryGenerator::new().generate();

let new_address = descriptor_a();
let msgs = vec![BridgeIncomingMsg::UpdateSafeHarbourAddress(
new_address.clone(),
)];
BridgeV1Subproto::process_msgs(&mut state, &msgs, &l1ref);

assert_eq!(state.safe_harbour().address(), &new_address);
// Address updates alone must not activate the safe harbour.
assert!(!state.safe_harbour().is_activated());
}

#[test]
fn process_msgs_defcon1_activates_safe_harbour() {
let (mut state, _privkeys) = create_test_state();
let l1ref: L1BlockCommitment = ArbitraryGenerator::new().generate();

let msgs = vec![BridgeIncomingMsg::Defcon1(Defcon1Payload::default())];
BridgeV1Subproto::process_msgs(&mut state, &msgs, &l1ref);

assert!(state.safe_harbour().is_activated());
assert_eq!(
state.safe_harbour().active_address(),
Some(state.safe_harbour().address())
);
}

#[test]
fn process_msgs_defcon3_activates_safe_harbour() {
let (mut state, _privkeys) = create_test_state();
let l1ref: L1BlockCommitment = ArbitraryGenerator::new().generate();

let msgs = vec![BridgeIncomingMsg::Defcon3(Defcon3Payload::default())];
BridgeV1Subproto::process_msgs(&mut state, &msgs, &l1ref);

assert!(state.safe_harbour().is_activated());
}

/// Updating the address after activation must keep the new address active —
/// the strata administrator should be able to redirect an already-activated
/// safe harbour without re-issuing a defcon signal.
#[test]
fn process_msgs_update_after_activation_keeps_activated() {
let (mut state, _privkeys) = create_test_state();
let l1ref: L1BlockCommitment = ArbitraryGenerator::new().generate();

let msgs = vec![
BridgeIncomingMsg::Defcon1(Defcon1Payload::default()),
BridgeIncomingMsg::UpdateSafeHarbourAddress(descriptor_b()),
];
BridgeV1Subproto::process_msgs(&mut state, &msgs, &l1ref);

assert!(state.safe_harbour().is_activated());
assert_eq!(state.safe_harbour().address(), &descriptor_b());
assert_eq!(state.safe_harbour().active_address(), Some(&descriptor_b()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub(crate) fn create_test_state() -> (BridgeV1State, Vec<EvenSecretKey>) {
assignment_duration: 144, // ~24 hours
operator_fee: BitcoinAmount::from_sat(100_000),
recovery_delay: 1008,
safe_harbour_address: ArbitraryGenerator::new().generate(),
};
let bridge_state = BridgeV1State::new(&config);
(bridge_state, privkeys)
Expand Down
1 change: 1 addition & 0 deletions crates/subprotocols/bridge-v1/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ serde.workspace = true
thiserror.workspace = true

[dev-dependencies]
serde_json.workspace = true
strata-test-utils-arb.workspace = true
2 changes: 2 additions & 0 deletions crates/subprotocols/bridge-v1/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
use strata_identifiers::{AccountId, AccountSerial};

mod operator;
mod safe_harbour;
mod withdrawal;

pub use operator::{
OperatorBitmap, OperatorBitmapError, OperatorIdx, OperatorSelection, filter_eligible_operators,
};
pub use safe_harbour::SafeHarbour;
pub use withdrawal::{WithdrawOutput, WithdrawalCommand};

const BRIDGE_GATEWAY_REF: u8 = 0x10;
Expand Down
Loading
Loading