mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-24 19:53:19 +00:00
making coordinator wallet generic, working on tests for bond verification
This commit is contained in:
@ -19,7 +19,7 @@ rand = "0.8.5"
|
|||||||
reqwest = { version = "0.12.4", features = ["blocking", "json"] }
|
reqwest = { version = "0.12.4", features = ["blocking", "json"] }
|
||||||
serde = "1.0.203"
|
serde = "1.0.203"
|
||||||
sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"] }
|
sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"] }
|
||||||
tokio = { version = "1.38.0", features = ["full"] }
|
tokio = { version = "1.38.0", features = ["full", "test-util"] }
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
|
@ -23,7 +23,7 @@ use tokio::net::TcpListener;
|
|||||||
/// Handler function to process the received data
|
/// Handler function to process the received data
|
||||||
async fn receive_order(
|
async fn receive_order(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(order): Json<OrderRequest>,
|
Json(order): Json<OrderRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
debug!("{:#?}", &order);
|
debug!("{:#?}", &order);
|
||||||
@ -46,7 +46,7 @@ async fn receive_order(
|
|||||||
/// receives the maker bond, verifies it and moves to offer to the active table (orderbook)
|
/// receives the maker bond, verifies it and moves to offer to the active table (orderbook)
|
||||||
async fn submit_maker_bond(
|
async fn submit_maker_bond(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<BondSubmissionRequest>,
|
Json(payload): Json<BondSubmissionRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
debug!("\n\nReceived maker bond: {:?}", payload);
|
debug!("\n\nReceived maker bond: {:?}", payload);
|
||||||
@ -112,7 +112,7 @@ async fn fetch_available_offers(
|
|||||||
/// and moves the offer to the taken table. Will return the trade contract psbt for the taker to sign.
|
/// and moves the offer to the taken table. Will return the trade contract psbt for the taker to sign.
|
||||||
async fn submit_taker_bond(
|
async fn submit_taker_bond(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<OfferPsbtRequest>,
|
Json(payload): Json<OfferPsbtRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
let bond_requirements = database
|
let bond_requirements = database
|
||||||
@ -175,7 +175,7 @@ async fn request_offer_status_maker(
|
|||||||
/// Once the coordinator has received both partitial signed PSBTs he can assemble them together to a transaction and publish it to the bitcoin network.
|
/// Once the coordinator has received both partitial signed PSBTs he can assemble them together to a transaction and publish it to the bitcoin network.
|
||||||
async fn submit_escrow_psbt(
|
async fn submit_escrow_psbt(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<PsbtSubmissionRequest>,
|
Json(payload): Json<PsbtSubmissionRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
panic!("implement")
|
panic!("implement")
|
||||||
@ -188,7 +188,7 @@ async fn submit_escrow_psbt(
|
|||||||
/// We have to see what makes more sense later, but maybe this would be more elegant. TBD.
|
/// We have to see what makes more sense later, but maybe this would be more elegant. TBD.
|
||||||
async fn poll_escrow_confirmation(
|
async fn poll_escrow_confirmation(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<OfferTakenRequest>,
|
Json(payload): Json<OfferTakenRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
panic!("implement")
|
panic!("implement")
|
||||||
@ -196,7 +196,7 @@ async fn poll_escrow_confirmation(
|
|||||||
|
|
||||||
async fn submit_obligation_confirmation(
|
async fn submit_obligation_confirmation(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<OfferTakenRequest>,
|
Json(payload): Json<OfferTakenRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
panic!("implement")
|
panic!("implement")
|
||||||
@ -208,7 +208,7 @@ async fn submit_obligation_confirmation(
|
|||||||
/// endpoint can return 201 and the escrow mediation logic will get executed (tbd).
|
/// endpoint can return 201 and the escrow mediation logic will get executed (tbd).
|
||||||
async fn poll_final_payout(
|
async fn poll_final_payout(
|
||||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||||
Extension(wallet): Extension<Arc<CoordinatorWallet>>,
|
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||||
Json(payload): Json<OfferTakenRequest>,
|
Json(payload): Json<OfferTakenRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
panic!("implement")
|
panic!("implement")
|
||||||
|
@ -4,6 +4,7 @@ mod database;
|
|||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use bdk::sled;
|
||||||
use communication::{api::*, api_server};
|
use communication::{api::*, api_server};
|
||||||
use coordinator::monitoring::monitor_bonds;
|
use coordinator::monitoring::monitor_bonds;
|
||||||
use coordinator::monitoring::*;
|
use coordinator::monitoring::*;
|
||||||
@ -17,7 +18,7 @@ use wallet::*;
|
|||||||
|
|
||||||
pub struct Coordinator {
|
pub struct Coordinator {
|
||||||
pub coordinator_db: Arc<CoordinatorDB>,
|
pub coordinator_db: Arc<CoordinatorDB>,
|
||||||
pub coordinator_wallet: Arc<CoordinatorWallet>,
|
pub coordinator_wallet: Arc<CoordinatorWallet<sled::Tree>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate .env with values before starting
|
// populate .env with values before starting
|
||||||
@ -32,7 +33,7 @@ async fn main() -> Result<()> {
|
|||||||
// Initialize the database pool
|
// Initialize the database pool
|
||||||
let coordinator = Arc::new(Coordinator {
|
let coordinator = Arc::new(Coordinator {
|
||||||
coordinator_db: Arc::new(CoordinatorDB::init().await?),
|
coordinator_db: Arc::new(CoordinatorDB::init().await?),
|
||||||
coordinator_wallet: Arc::new(CoordinatorWallet::init()?),
|
coordinator_wallet: Arc::new(init_coordinator_wallet()?),
|
||||||
});
|
});
|
||||||
|
|
||||||
// begin monitoring bonds
|
// begin monitoring bonds
|
||||||
|
@ -18,8 +18,8 @@ use utils::*;
|
|||||||
// use verify_tx::*;
|
// use verify_tx::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CoordinatorWallet {
|
pub struct CoordinatorWallet<D: bdk::database::BatchDatabase> {
|
||||||
pub wallet: Arc<Mutex<Wallet<Tree>>>,
|
pub wallet: Arc<Mutex<Wallet<D>>>,
|
||||||
pub backend: Arc<ElectrumBlockchain>,
|
pub backend: Arc<ElectrumBlockchain>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,34 +30,34 @@ pub struct BondRequirements {
|
|||||||
pub min_input_sum_sat: u64,
|
pub min_input_sum_sat: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinatorWallet {
|
pub fn init_coordinator_wallet() -> Result<CoordinatorWallet<sled::Tree>> {
|
||||||
pub fn init() -> Result<Self> {
|
let wallet_xprv = ExtendedPrivKey::from_str(
|
||||||
let wallet_xprv = ExtendedPrivKey::from_str(
|
&env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?,
|
||||||
&env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?,
|
)?;
|
||||||
)?;
|
let backend = ElectrumBlockchain::from(Client::new(
|
||||||
let backend = ElectrumBlockchain::from(Client::new(
|
&env::var("ELECTRUM_BACKEND")
|
||||||
&env::var("ELECTRUM_BACKEND")
|
.context("Parsing ELECTRUM_BACKEND from .env failed, is it set?")?,
|
||||||
.context("Parsing ELECTRUM_BACKEND from .env failed, is it set?")?,
|
)?);
|
||||||
)?);
|
// let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000);
|
||||||
// let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000);
|
let sled_db = sled::open(env::var("BDK_DB_PATH")?)?.open_tree("default_wallet")?;
|
||||||
let sled_db = sled::open(env::var("BDK_DB_PATH")?)?.open_tree("default_wallet")?;
|
let wallet = Wallet::new(
|
||||||
let wallet = Wallet::new(
|
Bip86(wallet_xprv, KeychainKind::External),
|
||||||
Bip86(wallet_xprv, KeychainKind::External),
|
Some(Bip86(wallet_xprv, KeychainKind::Internal)),
|
||||||
Some(Bip86(wallet_xprv, KeychainKind::Internal)),
|
bitcoin::Network::Testnet,
|
||||||
bitcoin::Network::Testnet,
|
sled_db,
|
||||||
sled_db,
|
)?;
|
||||||
)?;
|
|
||||||
|
|
||||||
wallet
|
wallet
|
||||||
.sync(&backend, SyncOptions::default())
|
.sync(&backend, SyncOptions::default())
|
||||||
.context("Connection to electrum server failed.")?; // we could also use Esplora to make this async
|
.context("Connection to electrum server failed.")?; // we could also use Esplora to make this async
|
||||||
dbg!(wallet.get_balance()?);
|
dbg!(wallet.get_balance()?);
|
||||||
Ok(CoordinatorWallet {
|
Ok(CoordinatorWallet {
|
||||||
wallet: Arc::new(Mutex::new(wallet)),
|
wallet: Arc::new(Mutex::new(wallet)),
|
||||||
backend: Arc::new(backend),
|
backend: Arc::new(backend),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||||
pub async fn get_new_address(&self) -> Result<String> {
|
pub async fn get_new_address(&self) -> Result<String> {
|
||||||
let wallet = self.wallet.lock().await;
|
let wallet = self.wallet.lock().await;
|
||||||
let address = wallet.get_address(bdk::wallet::AddressIndex::New)?;
|
let address = wallet.get_address(bdk::wallet::AddressIndex::New)?;
|
||||||
@ -65,6 +65,7 @@ impl CoordinatorWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate bond (check amounts, valid inputs, correct addresses, valid signature, feerate)
|
// validate bond (check amounts, valid inputs, correct addresses, valid signature, feerate)
|
||||||
|
// also check if inputs are confirmed already
|
||||||
pub async fn validate_bond_tx_hex(
|
pub async fn validate_bond_tx_hex(
|
||||||
&self,
|
&self,
|
||||||
bond: &String,
|
bond: &String,
|
||||||
@ -129,7 +130,7 @@ impl CoordinatorWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CoordinatorWallet {
|
impl fmt::Debug for CoordinatorWallet<Tree> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("CoordinatorWallet")
|
f.debug_struct("CoordinatorWallet")
|
||||||
.field("wallet", &self.wallet)
|
.field("wallet", &self.wallet)
|
||||||
@ -139,3 +140,129 @@ impl fmt::Debug for CoordinatorWallet {
|
|||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bdk::bitcoin::{Address, Network};
|
||||||
|
use bdk::database::MemoryDatabase;
|
||||||
|
use bdk::{blockchain::ElectrumBlockchain, Wallet};
|
||||||
|
// use tokio::test;
|
||||||
|
// use bitcoincore_rpc_json::GetRawTransactionResult;
|
||||||
|
|
||||||
|
async fn new_wallet(wallet_xprv: &str) -> CoordinatorWallet<MemoryDatabase> {
|
||||||
|
let backend = ElectrumBlockchain::from(Client::new("ssl://mempool.space:40002").unwrap());
|
||||||
|
|
||||||
|
let wallet_xprv = ExtendedPrivKey::from_str(wallet_xprv).unwrap();
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
Bip86(wallet_xprv, KeychainKind::External),
|
||||||
|
Some(Bip86(wallet_xprv, KeychainKind::Internal)),
|
||||||
|
Network::Testnet,
|
||||||
|
MemoryDatabase::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
wallet.sync(&backend, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
CoordinatorWallet::<MemoryDatabase> {
|
||||||
|
wallet: Arc::new(Mutex::new(wallet)),
|
||||||
|
backend: Arc::new(backend),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_valid_bond_tx() {
|
||||||
|
let test_wallet = new_wallet("xprv9s21ZrQH143K2XqaJ5boFeHgrJTsMgfzrgrsFXdk3UBYtLLhUkCj2QKPmqYpC92zd6bv46Nh8QxXmjH2MwJWVLQzfC6Bv1Tbeoz28nXjeM2").await;
|
||||||
|
let bond = "020000000001010127a9d96655011fca55dc2667f30b98655e46da98d0f84df676b53d7fb380140000000000010000000250c3000000000000225120a12e5d145a4a3ab43f6cc1188435e74f253eace72bd986f1aaf780fd0c653236aa900000000000002251207dd0d1650cdc22537709e35620f3b5cc3249b305bda1209ba4e5e01bc3ad2d8c014010e19c8b915624bd4aa0ba4d094d26ca031a6f2d8f23fe51372c7ea50e05f3caf81c7e139f6fed3e9ffd20c03d79f78542acb3d8aed664898f1c4b2909c2188c00000000";
|
||||||
|
let requirements = BondRequirements {
|
||||||
|
min_input_sum_sat: 100000,
|
||||||
|
locking_amount_sat: 50000,
|
||||||
|
bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx"
|
||||||
|
.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_invalid_bond_tx_low_input_sum() {
|
||||||
|
let test_wallet = TestWallet::new().await;
|
||||||
|
let bond = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c5080101ffffffff0200f2052a010000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
let requirements = BondRequirements {
|
||||||
|
min_input_sum_sat: 1000000, // Set higher than the actual input sum
|
||||||
|
locking_amount_sat: 50000,
|
||||||
|
bond_address: Address::from_str("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx").unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
test_wallet
|
||||||
|
.backend
|
||||||
|
.expect_get_tx()
|
||||||
|
.returning(|_| Ok(Some(Transaction::default())));
|
||||||
|
|
||||||
|
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Bond input sum too small"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_invalid_bond_tx_low_output_sum() {
|
||||||
|
let test_wallet = TestWallet::new().await;
|
||||||
|
let bond = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c5080101ffffffff0200f2052a010000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
let requirements = BondRequirements {
|
||||||
|
min_input_sum_sat: 100000,
|
||||||
|
locking_amount_sat: 1000000, // Set higher than the actual output sum
|
||||||
|
bond_address: Address::from_str("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx").unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
test_wallet
|
||||||
|
.backend
|
||||||
|
.expect_get_tx()
|
||||||
|
.returning(|_| Ok(Some(Transaction::default())));
|
||||||
|
|
||||||
|
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Bond output sum too small"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_invalid_bond_tx_low_fee_rate() {
|
||||||
|
let test_wallet = TestWallet::new().await;
|
||||||
|
let bond = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502c5080101ffffffff0200f2052a010000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
let requirements = BondRequirements {
|
||||||
|
min_input_sum_sat: 100000,
|
||||||
|
locking_amount_sat: 50000,
|
||||||
|
bond_address: Address::from_str("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx").unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
test_wallet
|
||||||
|
.backend
|
||||||
|
.expect_get_tx()
|
||||||
|
.returning(|_| Ok(Some(Transaction::default())));
|
||||||
|
|
||||||
|
// Modify the transaction to have a very low fee
|
||||||
|
let mut tx: Transaction = deserialize(&hex::decode(bond).unwrap()).unwrap();
|
||||||
|
tx.output[0].value = tx
|
||||||
|
.input_sum(
|
||||||
|
&*test_wallet.backend,
|
||||||
|
&*test_wallet.wallet.lock().await.database(),
|
||||||
|
)
|
||||||
|
.unwrap() - 1;
|
||||||
|
|
||||||
|
let low_fee_bond = hex::encode(serialize(&tx));
|
||||||
|
|
||||||
|
let result = test_wallet
|
||||||
|
.validate_bond_tx_hex(&low_fee_bond, &requirements)
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Bond fee rate too low"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user