diff --git a/.env b/.env index 1b2c74b..601f837 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ DATABASE_URL="postgres://postgres:password@localhost:5432/cosign" -NETWORK="testnet" \ No newline at end of file +NETWORK="regtest" \ No newline at end of file diff --git a/src/domain/mod.rs b/src/domain/mod.rs index ac2da26..0358163 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -18,6 +18,6 @@ pub use xpub::Xpub; pub use x_pub::{Xpubs, UserId}; pub use generated_address::{ GenerateAddressData, GenerateAddressResponse}; pub use new_address_data::{AddressData, NewAddressData, DerivationIndex}; -pub use user_transaction::{UserTransactionId, TransactionInputResponse}; +pub use user_transaction::{UserTransactionId, TransactionInputResponse, RawTransaction, UserRawTransaction, BroadCastTrxResponse}; pub use transaction_payload::{TransactionAmount, TransactionPayload, NewTransactionPayload}; pub use address::UserAddress; \ No newline at end of file diff --git a/src/domain/user_transaction.rs b/src/domain/user_transaction.rs index a57e1f6..1391e17 100644 --- a/src/domain/user_transaction.rs +++ b/src/domain/user_transaction.rs @@ -1,9 +1,10 @@ use std::str::FromStr; - -use bitcoin::{hashes::{hex::{FromHex}, sha256d::Hash}}; +use bitcoin::{hashes::{hex::FromHex}}; use serde::{Deserialize, Serialize}; -use bitcoincore_rpc::bitcoin::Txid; +use bitcoincore_rpc::{RpcApi, bitcoin::{Txid, Transaction, consensus}}; use crate::domain::NewTransactionPayload; +use crate::routes::transactions::init_rpc_client; + #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -43,4 +44,68 @@ impl UserTransactionId { } } +} + + +#[derive(Serialize, Deserialize, Debug)] +pub struct RawTransaction (String); + +impl RawTransaction { + + pub fn extract_txid_from_raw_hex(raw_tx: String)->Txid { + + let tx_bytes = Vec::from_hex(&raw_tx).unwrap(); + let tx: Transaction = consensus::encode::deserialize(&tx_bytes).unwrap(); + let txid = tx.txid(); + txid + } + + pub fn convert(raw_tx: String) -> Result { + + let rpc_con = init_rpc_client(); + let rpc = rpc_con.unwrap(); + + let tx_id = RawTransaction::extract_txid_from_raw_hex(raw_tx); + + let raw_tx = rpc.get_raw_transaction(&tx_id, None); + + match raw_tx { + Ok(raw) => Ok(raw), + Err(error) => { + Err(format!("Error converting supplied raw tx: {}", error)) + } + } + } + +} + + + +#[derive(serde::Deserialize)] +pub struct UserRawTransaction { + pub raw_tx: String, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct BroadCastTrxResponse { + pub msg: String, + pub status: u16, + pub data: Option, +} + + + +#[cfg(test)] +mod tests { + use super::*; + + + #[actix_rt::test] + async fn test_extract_txid_from_raw_hex() { + let raw_tx = "02000000000101faecaca08b7a598cf484ef0d8f6731e33860a5dfb46a12a130e78ac4730111150000000000ffffffff01d8ce052a01000000160014d03bb275e7ea501cf926b458702e1cf0d54e46210247304402205ea676c864c2f3e811f47d767f225b640d7dc47bf4cd4ed8ff1b434ee909216402205f43ce7f44c332a5dd8812edff3aede826877eb17e40c15ed2baea5fc0bd9d2a0121022a369ed941445f85da7703c2e8dbcedc88e767015aa6049e4de238f322868f9b00000000".to_string(); + + let tx_hash = RawTransaction::extract_txid_from_raw_hex(raw_tx); + + assert_eq!(tx_hash.len(), 32); + } } \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 37abd74..57958b5 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -6,4 +6,4 @@ pub mod users; pub use addresses::gen_multisig_address; pub use services::masterkeys; pub use users::{create::create_user, xpub::collect_xpub}; -pub use transactions::collect_trx_input; +pub use transactions::{collect_trx_input, broadcast_psbt}; diff --git a/src/routes/transactions/broadcast_psbt.rs b/src/routes/transactions/broadcast_psbt.rs new file mode 100644 index 0000000..67d07cf --- /dev/null +++ b/src/routes/transactions/broadcast_psbt.rs @@ -0,0 +1,68 @@ +use crate::domain::{ + RawTransaction, UserRawTransaction, BroadCastTrxResponse +}; +use actix_web::{http::StatusCode, web, HttpResponse}; +use bitcoincore_rpc::bitcoin::{Txid, Transaction}; +use bitcoincore_rpc::json::TestMempoolAcceptResult; +use bitcoincore_rpc::{RpcApi}; +use bitcoincore_rpc::RawTx; +use bitcoincore_rpc::Error; +use crate::routes::transactions::init_rpc_client; + + +//endpoint to collect a transaction inputs +pub async fn broadcast_psbt( + req: web::Json, +) -> HttpResponse { + + //broadcast the transaction + match broadcast_rawtrx(req.raw_tx.clone()) { + Ok(txid) => { + let succ = BroadCastTrxResponse { + msg: "Transaction broadcasted successfully".to_string(), + status: StatusCode::OK.as_u16(), + data: Some(txid) + }; + + return HttpResponse::Ok().json(succ); + }, + Err(error) => { + let resp = BroadCastTrxResponse { + msg: error.to_string(), + status: StatusCode::BAD_REQUEST.as_u16(), + data: None, + }; + return HttpResponse::BadRequest().json(resp); + } + }; + +} + + +// test mempool acceptance +pub fn test_mempool_acceptance(raw_txid:&Transaction) -> Result, Error> +{ + let rpc_con = init_rpc_client(); + let rpc = rpc_con.unwrap(); + + let trx_array = [raw_txid.raw_hex();1]; + let response = rpc.test_mempool_accept(&trx_array); + + response +} + +pub fn broadcast_rawtrx(raw_txid: String)->Result{ + + let rpc_con = init_rpc_client(); + let rpc = rpc_con.unwrap(); + + let raw_hex = RawTx::raw_hex(raw_txid.as_str()).raw_hex(); + + let broadcast_response = rpc.send_raw_transaction(&*raw_hex); + + broadcast_response +} + + + + diff --git a/src/routes/transactions/mod.rs b/src/routes/transactions/mod.rs index 015b8df..815be29 100644 --- a/src/routes/transactions/mod.rs +++ b/src/routes/transactions/mod.rs @@ -1,5 +1,8 @@ pub mod transaction; +pub mod broadcast_psbt; pub use transaction::collect_trx_input; +pub use transaction::init_rpc_client; +pub use broadcast_psbt::broadcast_psbt; diff --git a/src/routes/transactions/transaction.rs b/src/routes/transactions/transaction.rs index e39a415..b874955 100644 --- a/src/routes/transactions/transaction.rs +++ b/src/routes/transactions/transaction.rs @@ -71,7 +71,7 @@ pub async fn collect_trx_input( return HttpResponse::ExpectationFailed().json(tx_resp); } }; - if (resp.value.as_sat().le(&new_payload.amount)) { + if resp.value.as_sat().le(&new_payload.amount) { let resp = TransactionInputResponse { msg: format!("Not enough sats in given UTXOs to complete this transaction. Total sats available: {:?}", resp.value.as_sat()).to_string(), status: StatusCode::EXPECTATION_FAILED.as_u16(), @@ -130,12 +130,8 @@ pub async fn get_all_user_key_pairs(user_id:i32, pool: &PgPool) -> Result Result, Error> { - - let rpc_testnet_url = "http://localhost:18332"; +pub fn init_rpc_client() -> Result { + let rpc_testnet_url = "http://localhost:28332"; let rpc = Client::new( rpc_testnet_url, @@ -143,8 +139,17 @@ pub async fn check_txid_utxo(transaction_id:Txid, vout: u32) -> Result Result, Error> { + + let rpc_con = init_rpc_client(); + let rpc = rpc_con.unwrap(); + let response = rpc.get_tx_out(&transaction_id, vout, Some(false)); response diff --git a/src/start_up.rs b/src/start_up.rs index 67cc149..42f0126 100644 --- a/src/start_up.rs +++ b/src/start_up.rs @@ -1,4 +1,4 @@ -use crate::routes::{collect_xpub, create_user, gen_multisig_address, masterkeys, collect_trx_input}; +use crate::routes::{collect_xpub, create_user, gen_multisig_address, masterkeys, collect_trx_input, broadcast_psbt}; use actix_web::dev::Server; use actix_web::{web, App, HttpResponse, HttpServer, http::StatusCode}; use sqlx::PgPool; @@ -32,6 +32,7 @@ pub fn run(listener: TcpListener, db_pool: PgPool) -> Result