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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
DATABASE_URL="postgres://postgres:password@localhost:5432/cosign"
NETWORK="testnet"
NETWORK="regtest"
2 changes: 1 addition & 1 deletion src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
71 changes: 68 additions & 3 deletions src/domain/user_transaction.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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<Transaction, String> {

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<Txid>,
}



#[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);
}
}
2 changes: 1 addition & 1 deletion src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
68 changes: 68 additions & 0 deletions src/routes/transactions/broadcast_psbt.rs
Original file line number Diff line number Diff line change
@@ -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<UserRawTransaction>,
) -> 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<Vec<TestMempoolAcceptResult>, 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<Txid, Error>{

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
}




3 changes: 3 additions & 0 deletions src/routes/transactions/mod.rs
Original file line number Diff line number Diff line change
@@ -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;


23 changes: 14 additions & 9 deletions src/routes/transactions/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -130,21 +130,26 @@ pub async fn get_all_user_key_pairs(user_id:i32, pool: &PgPool) -> Result<Vec<Ad
Ok(address_data)
}



//[TODO] check supplied txid and utxo
pub async fn check_txid_utxo(transaction_id:Txid, vout: u32) -> Result<Option<GetTxOutResult>, Error> {

let rpc_testnet_url = "http://localhost:18332";
pub fn init_rpc_client() -> Result<Client, Error> {
let rpc_testnet_url = "http://localhost:28332";

let rpc = Client::new(
rpc_testnet_url,
Auth::UserPass(
"bitcoin".to_string(),
"bitcoin".to_string(),
),
)
.unwrap();
);

rpc
}

// check supplied txid and utxo
pub async fn check_txid_utxo(transaction_id:Txid, vout: u32) -> Result<Option<GetTxOutResult>, Error> {

let rpc_con = init_rpc_client();
let rpc = rpc_con.unwrap();

let response = rpc.get_tx_out(&transaction_id, vout, Some(false));

response
Expand Down
3 changes: 2 additions & 1 deletion src/start_up.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -32,6 +32,7 @@ pub fn run(listener: TcpListener, db_pool: PgPool) -> Result<Server, std::io::Er
.route("/gen_multisig_addr", web::post().to(gen_multisig_address))
.route("/masterkeys", web::post().to(masterkeys))
.route("/collect_trx_input", web::post().to(collect_trx_input))
.route("/broadcast_psbt", web::post().to(broadcast_psbt))
.app_data(db_pool.clone())
})
.listen(listener)?
Expand Down
1 change: 1 addition & 0 deletions tests/api/collect_xpubs_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async fn collect_xpubs_returns_200_for_existing_user() {
// 3. Assert
// 3.1 Assert user is saved to DB
assert_eq!(201, user_resp.status().as_u16());
assert_eq!(200, xpub_resp.status().as_u16());

let saved_user = sqlx::query!("SELECT email FROM users",)
.fetch_one(&test_app.db_pool)
Expand Down