Skip to content

Commit 23be68b

Browse files
authored
feat(spv): broadcast transaction (#180)
1 parent 03fe3c2 commit 23be68b

File tree

5 files changed

+85
-12
lines changed

5 files changed

+85
-12
lines changed

dash-spv-ffi/src/broadcast.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_broadcast_transaction(
2626
}
2727
};
2828

29-
let _tx = match dashcore::consensus::deserialize::<dashcore::Transaction>(&tx_bytes) {
29+
let tx = match dashcore::consensus::deserialize::<dashcore::Transaction>(&tx_bytes) {
3030
Ok(t) => t,
3131
Err(e) => {
3232
set_last_error(&format!("Invalid transaction: {}", e));
@@ -38,15 +38,26 @@ pub unsafe extern "C" fn dash_spv_ffi_client_broadcast_transaction(
3838
let inner = client.inner.clone();
3939

4040
let result: Result<(), dash_spv::SpvError> = client.runtime.block_on(async {
41-
let guard = inner.lock().unwrap();
42-
if let Some(ref _spv_client) = *guard {
43-
// TODO: broadcast_transaction not yet implemented in dash-spv
44-
Err(dash_spv::SpvError::Config("Not implemented".to_string()))
45-
} else {
46-
Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound(
47-
"Client not initialized".to_string(),
48-
)))
49-
}
41+
// Take the client out to avoid holding the lock across await
42+
let spv_client = {
43+
let mut guard = inner.lock().unwrap();
44+
match guard.take() {
45+
Some(client) => client,
46+
None => {
47+
return Err(dash_spv::SpvError::Storage(dash_spv::StorageError::NotFound(
48+
"Client not initialized".to_string(),
49+
)))
50+
}
51+
}
52+
};
53+
54+
// Broadcast the transaction over P2P
55+
let res = spv_client.broadcast_transaction(&tx).await;
56+
57+
// Put the client back
58+
let mut guard = inner.lock().unwrap();
59+
*guard = Some(spv_client);
60+
res
5061
});
5162

5263
match result {

dash-spv-ffi/src/client.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,7 +1476,7 @@ pub unsafe extern "C" fn dash_spv_ffi_client_record_send(
14761476
}
14771477
};
14781478

1479-
let _txid = match Txid::from_str(txid_str) {
1479+
let txid = match Txid::from_str(txid_str) {
14801480
Ok(t) => t,
14811481
Err(e) => {
14821482
set_last_error(&format!("Invalid txid: {}", e));
@@ -1499,9 +1499,10 @@ pub unsafe extern "C" fn dash_spv_ffi_client_record_send(
14991499
}
15001500
}
15011501
};
1502+
let res = spv_client.record_send(txid).await;
15021503
let mut guard = inner.lock().unwrap();
15031504
*guard = Some(spv_client);
1504-
Ok(())
1505+
res
15051506
});
15061507

15071508
match result {

dash-spv/src/client/mempool.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ impl<
138138
mempool_state.transactions.len()
139139
}
140140

141+
/// Record that we attempted to send a transaction (for UX/heuristics).
142+
pub async fn record_send(&self, txid: dashcore::Txid) -> Result<()> {
143+
let mut mempool_state = self.mempool_state.write().await;
144+
mempool_state.record_send(txid);
145+
Ok(())
146+
}
147+
141148
/// Update mempool filter with wallet's monitored addresses.
142149
#[allow(dead_code)]
143150
pub(super) async fn update_mempool_filter(&mut self) {

dash-spv/src/client/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! - `progress.rs` - Sync progress calculation and reporting
1111
//! - `mempool.rs` - Mempool tracking and coordination
1212
//! - `queries.rs` - Peer, masternode, and balance queries
13+
//! - `transactions.rs` - Transaction operations (e.g., broadcast)
1314
//! - `chainlock.rs` - ChainLock and InstantLock processing
1415
//! - `sync_coordinator.rs` - Sync orchestration and network monitoring (the largest module)
1516
//!
@@ -48,6 +49,7 @@ mod mempool;
4849
mod progress;
4950
mod queries;
5051
mod sync_coordinator;
52+
mod transactions;
5153

5254
// Re-export public types from extracted modules
5355
pub use block_processor::{BlockProcessingTask, BlockProcessor};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! Transaction-related client APIs (e.g., broadcasting)
2+
3+
use crate::error::{Result, SpvError};
4+
use crate::network::NetworkManager;
5+
use crate::storage::StorageManager;
6+
use dashcore::network::message::NetworkMessage;
7+
use key_wallet_manager::wallet_interface::WalletInterface;
8+
9+
use super::DashSpvClient;
10+
11+
impl<
12+
W: WalletInterface + Send + Sync + 'static,
13+
N: NetworkManager + Send + Sync + 'static,
14+
S: StorageManager + Send + Sync + 'static,
15+
> DashSpvClient<W, N, S>
16+
{
17+
/// Broadcast a transaction to all connected peers.
18+
pub async fn broadcast_transaction(&self, tx: &dashcore::Transaction) -> Result<()> {
19+
let network = self
20+
.network
21+
.as_any()
22+
.downcast_ref::<crate::network::multi_peer::MultiPeerNetworkManager>()
23+
.ok_or_else(|| {
24+
SpvError::Config("Network manager does not support broadcasting".to_string())
25+
})?;
26+
27+
if network.peer_count() == 0 {
28+
return Err(SpvError::Network(crate::error::NetworkError::NotConnected));
29+
}
30+
31+
let message = NetworkMessage::Tx(tx.clone());
32+
let results = network.broadcast(message).await;
33+
34+
let mut success = false;
35+
let mut errors = Vec::new();
36+
for res in results {
37+
match res {
38+
Ok(_) => success = true,
39+
Err(err) => errors.push(err.to_string()),
40+
}
41+
}
42+
43+
if success {
44+
Ok(())
45+
} else {
46+
Err(SpvError::Network(crate::error::NetworkError::ProtocolError(format!(
47+
"Broadcast failed: {}",
48+
errors.join(", ")
49+
))))
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)