Skip to content
Open
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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub extern crate bitcoind;
pub mod error;
pub mod fee_estimation;
pub mod maker;
pub mod nostr_coinswap;
pub mod protocol;
pub mod security;
pub mod taker;
Expand Down
32 changes: 6 additions & 26 deletions src/maker/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ use crate::{
},
wallet::{RPCConfig, SwapCoin, WalletSwapCoin},
watch_tower::{
registry_storage::FileRegistry,
rpc_backend::BitcoinRpc,
service::WatchService,
watcher::{Role, Watcher, WatcherEvent},
zmq_backend::ZmqBackend,
service::{start_maker_watch_service, WatchService},
watcher::{Role, WatcherEvent},
},
};
use bitcoin::{
Expand All @@ -38,9 +35,9 @@ use std::{
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc, Arc, Mutex, RwLock,
Arc, Mutex, RwLock,
},
thread::{self, JoinHandle},
thread::JoinHandle,
time::{Duration, Instant},
};

Expand Down Expand Up @@ -297,25 +294,8 @@ impl Maker {
config.network_port = port;
}

// ## TODO: Encapsulate these initialization inside the watcher and
// pollute the client declaration.
let backend = ZmqBackend::new(&zmq_addr);
let rpc_backend = BitcoinRpc::new(rpc_config.clone())?;
let blockchain_info = rpc_backend.get_blockchain_info()?;
let file_registry = data_dir
.join(format!(".maker_{}_watcher", config.network_port))
.join(blockchain_info.chain.to_string());
let registry = FileRegistry::load(file_registry);
let (tx_requests, rx_requests) = mpsc::channel();
let (tx_events, rx_responses) = mpsc::channel();
let rpc_config_watcher = rpc_config.clone();

let mut watcher = Watcher::<Maker>::new(backend, registry, rx_requests, tx_events);
_ = thread::Builder::new()
.name("Watcher thread".to_string())
.spawn(move || watcher.run(rpc_config_watcher));

let watch_service = WatchService::new(tx_requests, rx_responses);
let watch_service =
start_maker_watch_service(&zmq_addr, &rpc_config, &data_dir, config.network_port)?;

let mut wallet = Wallet::load_or_init_wallet(&wallet_path, &rpc_config, password)?;

Expand Down
44 changes: 12 additions & 32 deletions src/maker/api2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ use crate::{
WalletError,
},
watch_tower::{
registry_storage::FileRegistry,
rpc_backend::BitcoinRpc,
service::WatchService,
watcher::{Role, Watcher},
zmq_backend::ZmqBackend,
service::{start_maker_watch_service, WatchService},
watcher::Role,
},
};
use bitcoin::{
Expand All @@ -42,9 +39,9 @@ use std::{
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc, Arc, Mutex, RwLock,
Arc, Mutex, RwLock,
},
thread::{self, JoinHandle},
thread::JoinHandle,
time::{Duration, Instant},
};

Expand Down Expand Up @@ -238,7 +235,7 @@ pub struct Maker {
/// Map of IP address to Connection State + last Connected instant
pub(crate) ongoing_swap_state: Mutex<HashMap<String, (ConnectionState, Instant)>>,
/// Highest Value Fidelity Proof
pub(crate) highest_fidelity_proof: RwLock<Option<crate::protocol::messages2::FidelityProof>>,
pub(crate) highest_fidelity_proof: RwLock<Option<crate::protocol::messages::FidelityProof>>,
/// Is setup complete
pub is_setup_complete: AtomicBool,
/// Path for the data directory.
Expand Down Expand Up @@ -283,25 +280,8 @@ impl Maker {
config.network_port = port;
}

// ## TODO: Encapsulate these initialization inside the watcher and
// pollute the client declaration.
let backend = ZmqBackend::new(&zmq_addr);
let rpc_backend = BitcoinRpc::new(rpc_config.clone())?;
let blockchain_info = rpc_backend.get_blockchain_info()?;
let file_registry = data_dir
.join(format!(".maker_{}_watcher", config.network_port))
.join(blockchain_info.chain.to_string());
let registry = FileRegistry::load(file_registry);
let (tx_requests, rx_requests) = mpsc::channel();
let (tx_events, rx_responses) = mpsc::channel();
let rpc_config_watcher = rpc_config.clone();

let mut watcher = Watcher::<Maker>::new(backend, registry, rx_requests, tx_events);
_ = thread::Builder::new()
.name("Watcher thread".to_string())
.spawn(move || watcher.run(rpc_config_watcher));

let watch_service = WatchService::new(tx_requests, rx_responses);
let watch_service =
start_maker_watch_service(&zmq_addr, &rpc_config, &data_dir, config.network_port)?;

let mut wallet = if wallet_path.exists() {
let wallet = Wallet::load(&wallet_path, &rpc_config, password)?;
Expand Down Expand Up @@ -372,10 +352,9 @@ impl Maker {
connection_state: &mut ConnectionState,
) -> Result<Offer, MakerError> {
let wallet = self.wallet.read()?;
let (incoming_contract_my_privkey, incoming_contract_my_pubkey) =
wallet.get_tweakable_keypair()?;
connection_state.incoming_contract.my_privkey = Some(incoming_contract_my_privkey);
connection_state.incoming_contract.my_pubkey = Some(incoming_contract_my_pubkey);
// Create a temporary incoming contract pubkey here, replace with the actual pubkey after sending AckResponse msg to taker.
let (_, incoming_contract_temporary_pubkey) = wallet.get_tweakable_keypair()?;
connection_state.incoming_contract.my_pubkey = Some(incoming_contract_temporary_pubkey);
log::info!(
"[{}] create_offer: Set my_privkey for incoming contract, is_some={}",
self.config.network_port,
Expand All @@ -397,7 +376,8 @@ impl Maker {
let max_size = max_size.to_sat();

Ok(Offer {
tweakable_point: incoming_contract_my_pubkey,
// send temporary pubkey to taker for now
tweakable_point: incoming_contract_temporary_pubkey,
base_fee: BASE_FEE,
amount_relative_fee: AMOUNT_RELATIVE_FEE_PCT,
time_relative_fee: TIME_RELATIVE_FEE_PCT,
Expand Down
51 changes: 32 additions & 19 deletions src/maker/handlers2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@

use std::sync::Arc;

use bitcoin::Amount;

use super::{
api2::{ConnectionState, Maker},
error::MakerError,
};

use crate::protocol::messages2::{
AckResponse, GetOffer, MakerToTakerMessage, PrivateKeyHandover, SendersContract, SwapDetails,
TakerToMakerMessage,
use crate::protocol::{
self,
messages2::{
GetOffer, MakerToTakerMessage, PrivateKeyHandover, SendersContract, SwapDetails,
TakerToMakerMessage,
},
};

/// The Global Handle Message function for taproot protocol. Takes in a [`Arc<Maker>`] and handles
Expand Down Expand Up @@ -82,27 +87,31 @@ fn handle_swap_details(
swap_details.no_of_tx
);

// Reject if GetOffer wasn't received first (my_privkey must be set)
// This ensures the taker has a fresh offer with a valid tweakable_point
if connection_state.incoming_contract.my_privkey.is_none() {
// Reject if GetOffer wasn't received first (temporary_pubkey must be set)
// This ensures the taker has a fresh offer.
if connection_state.incoming_contract.my_pubkey.is_none() {
log::warn!(
"[{}] Rejecting SwapDetails - GetOffer must be sent first to establish keypair",
maker.config.network_port
);
return Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Nack)));
}

// Reject if there's already an active swap in progress for this connection
// This prevents an attacker from resetting another taker's swap state
// [TODO] Remove this once we have a way to handle multiple swaps using swap_id
// if connection_state.swap_amount > Amount::ZERO {
// log::warn!(
// "[{}] Rejecting SwapDetails - swap already in progress with amount {}",
// maker.config.network_port,
// connection_state.swap_amount
// );
// return Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Nack)));
// }
let (privkey, pubkey) = maker.wallet.read()?.get_tweakable_keypair()?;
connection_state.incoming_contract.my_privkey = Some(privkey);
connection_state.incoming_contract.my_pubkey = Some(pubkey);

if connection_state.swap_amount > Amount::ZERO {
log::warn!(
"[{}] Rejecting SwapDetails - swap already in progress with amount {}",
maker.config.network_port,
connection_state.swap_amount
);
return Ok(Some(MakerToTakerMessage::AckResponse(
protocol::messages2::AckResponse {
tweakable_point: None,
},
)));
}

// Validate swap parameters using api2
maker.validate_swap_parameters(&swap_details)?;
Expand Down Expand Up @@ -132,7 +141,11 @@ fn handle_swap_details(
}

// Send acknowledgment
Ok(Some(MakerToTakerMessage::AckResponse(AckResponse::Ack)))
Ok(Some(MakerToTakerMessage::AckResponse(
protocol::messages2::AckResponse {
tweakable_point: Some(pubkey),
},
)))
}

/// Handles SendersContract message and creates our receiver contract
Expand Down
104 changes: 2 additions & 102 deletions src/maker/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,9 @@
//! The server maintains the thread pool for P2P Connection, Watchtower, Bitcoin Backend, and RPC Client Request.
//! The server listens at two ports: 6102 for P2P, and 6103 for RPC Client requests.

use crate::{
protocol::messages::FidelityProof,
utill::{COINSWAP_KIND, NOSTR_RELAYS},
};
use crate::protocol::messages::FidelityProof;
use bitcoin::{absolute::LockTime, Amount};
use bitcoind::bitcoincore_rpc::RpcApi;
use nostr::{
event::{EventBuilder, Kind},
key::{Keys, SecretKey},
message::{ClientMessage, RelayMessage},
util::JsonUtil,
};
use tungstenite::Message;

use std::{
io::ErrorKind,
Expand Down Expand Up @@ -44,6 +34,7 @@ use crate::{
handlers::handle_message,
rpc::start_rpc_server,
},
nostr_coinswap::broadcast_bond_on_nostr,
protocol::messages::TakerToMakerMessage,
utill::{read_message, send_message, HEART_BEAT_INTERVAL, MIN_FEE_RATE},
wallet::{AddressType, WalletError},
Expand Down Expand Up @@ -146,97 +137,6 @@ fn spawn_nostr_broadcast_task(
Ok(())
}

// ##TODO: Make this part of nostr module and improve error handing
// ##TODO: Try retry in case relay doesn't accept the event
fn broadcast_bond_on_nostr(fidelity: FidelityProof) -> Result<(), MakerError> {
let outpoint = fidelity.bond.outpoint;
let content = format!("{}:{}", outpoint.txid, outpoint.vout);

// ##TODO: Don't use ephemeral keys
let secret_key = SecretKey::generate();
let keys = Keys::new(secret_key);

let event = EventBuilder::new(Kind::Custom(COINSWAP_KIND), content)
.build(keys.public_key)
.sign_with_keys(&keys)
.expect("Event should be signed");

let msg = ClientMessage::Event(std::borrow::Cow::Owned(event));

log::debug!("nostr wire msg: {}", msg.as_json());

let mut success = false;

for relay in NOSTR_RELAYS {
match broadcast_to_relay(relay, &msg) {
Ok(()) => {
success = true;
}
Err(e) => {
log::warn!("failed to broadcast to {}: {:?}", relay, e);
}
}
}

if !success {
log::warn!("nostr event was not accepted by any relay");
}

Ok(())
}

fn broadcast_to_relay(relay: &str, msg: &ClientMessage) -> Result<(), MakerError> {
let (mut socket, _) = tungstenite::connect(relay).map_err(|e| {
log::warn!("failed to connect to nostr relay {}: {}", relay, e);
MakerError::General("failed to connect to nostr relay")
})?;

socket
.write(Message::Text(msg.as_json().into()))
.map_err(|e| {
log::warn!("nostr relay write failed: {}", e);
MakerError::General("failed to write to nostr relay")
})?;
socket.flush().ok();

match socket.read() {
Ok(Message::Text(text)) => {
if let Ok(relay_msg) = RelayMessage::from_json(&text) {
match relay_msg {
RelayMessage::Ok {
event_id,
status: true,
..
} => {
log::info!("nostr relay {} accepted event {}", relay, event_id);
return Ok(());
}
RelayMessage::Ok {
event_id,
status: false,
message,
} => {
log::warn!(
"nostr relay {} rejected event {}: {}",
relay,
event_id,
message
);
}
_ => {}
}
}
}
Ok(_) => {}
Err(e) => {
log::warn!("nostr relay {} read error: {}", relay, e);
}
}
log::warn!("nostr relay {} did not confirm event", relay);

Err(MakerError::General("nostr relay did not confirm event"))
}

/// Ensures the wallet has a valid fidelity bond. If no active bond exists, it creates a new one.
///
/// ### NOTE ON VALID FIDELITY BOND:
Expand Down
Loading