mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-12-23 21:45:35 +00:00
more comments
This commit is contained in:
@ -123,23 +123,7 @@ async fn submit_escrow_psbt(
|
||||
Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
}
|
||||
_ => Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
|
||||
// Err(RequestError::NotFound) => {
|
||||
// info!("Offer for escrow psbt not found");
|
||||
// Ok(StatusCode::NOT_FOUND.into_response())
|
||||
// }
|
||||
// Err(RequestError::NotConfirmed) => {
|
||||
// info!("Offer for escrow psbt not confirmed");
|
||||
// Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
// }
|
||||
// Err(RequestError::Database(e)) => {
|
||||
// error!("Database error fetching escrow psbt: {e}");
|
||||
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
// }
|
||||
}
|
||||
// check if psbt is correct, valid and signed
|
||||
// publish psbt if it is correct
|
||||
// return 200 if everything is correct
|
||||
// return 400 if something is wrong
|
||||
}
|
||||
|
||||
/// Will get polled by the traders once they submitted their PSBT part. The coorinator will return status code 200 once he received both PSBTs and they got mined,
|
||||
@ -163,6 +147,7 @@ async fn poll_escrow_confirmation(
|
||||
}
|
||||
}
|
||||
|
||||
/// gets called if the trader is happy and does not want to initiate escrow
|
||||
async fn submit_obligation_confirmation(
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<OfferTakenRequest>,
|
||||
@ -190,8 +175,8 @@ async fn submit_obligation_confirmation(
|
||||
|
||||
// or
|
||||
|
||||
// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat)
|
||||
// before timeout ends, just sets the maker unhappy and escrow onging flag in the db
|
||||
/// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat)
|
||||
/// before timeout ends, just sets the maker unhappy and escrow onging flag in the db
|
||||
async fn request_escrow(
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<TradeObligationsUnsatisfied>,
|
||||
@ -255,6 +240,7 @@ async fn poll_final_payout(
|
||||
}
|
||||
}
|
||||
|
||||
/// recieves the partial signature for the keyspend payout transaction
|
||||
async fn submit_payout_signature(
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<PayoutSignatureRequest>,
|
||||
@ -266,18 +252,6 @@ async fn submit_payout_signature(
|
||||
// this was the first signature
|
||||
Ok(false) => Ok(StatusCode::ACCEPTED.into_response()),
|
||||
|
||||
// Err(RequestError::NotConfirmed) => {
|
||||
// info!("Offer tx for final payout not confirmed");
|
||||
// Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
// }
|
||||
// Err(RequestError::NotFound) => {
|
||||
// info!("Offer for final payout not found");
|
||||
// Ok(StatusCode::NOT_FOUND.into_response())
|
||||
// }
|
||||
// Err(RequestError::Database(e)) => {
|
||||
// error!("Database error fetching final payout: {e}");
|
||||
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
// }
|
||||
e => {
|
||||
error!("Unknown error handling submit_payout_signature(): {:?}", e);
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
@ -285,6 +259,7 @@ async fn submit_payout_signature(
|
||||
}
|
||||
}
|
||||
|
||||
/// testing endpoint
|
||||
async fn test_api() -> &'static str {
|
||||
"Hello, World!"
|
||||
}
|
||||
@ -312,7 +287,7 @@ pub async fn api_server(coordinator: Arc<Coordinator>) -> Result<()> {
|
||||
let port: u16 = env::var("PORT")
|
||||
.unwrap_or_else(|_| "9999".to_string())
|
||||
.parse()?;
|
||||
info!("Listening on {}", port);
|
||||
info!("Coordinator is listening on port {}", port);
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
let tcp = TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(tcp, app).await?;
|
||||
|
||||
@ -19,6 +19,8 @@ pub struct PayoutData {
|
||||
pub aggregated_musig_pubkey_ctx_hex: String,
|
||||
}
|
||||
|
||||
/// KeyspendContext contains all data neccessary to create the
|
||||
/// signed keyspend payout transaction
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyspendContext {
|
||||
pub agg_sig: LiftedSignature,
|
||||
@ -27,7 +29,10 @@ pub struct KeyspendContext {
|
||||
pub keyspend_psbt: PartiallySignedTransaction,
|
||||
}
|
||||
|
||||
/// takes two hex encoded pub musig nonces (serialized according to musig2 crate) and
|
||||
/// aggregates them into a single MusigAggNonce
|
||||
pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result<MusigAggNonce> {
|
||||
// decode the hex strings into MusigPubNonces
|
||||
let musig_pub_nonce_maker = match MusigPubNonce::from_hex(maker_nonce) {
|
||||
Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker,
|
||||
Err(e) => {
|
||||
@ -47,12 +52,14 @@ pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result<Musi
|
||||
}
|
||||
};
|
||||
|
||||
// aggregate the two pub nonces
|
||||
let agg_nonce = musig2::AggNonce::sum([musig_pub_nonce_maker, musig_pub_nonce_taker]);
|
||||
|
||||
Ok(agg_nonce)
|
||||
}
|
||||
|
||||
impl KeyspendContext {
|
||||
/// Create a new KeyspendContext from hex encoded strings recieved from the maker and taker
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_hex_str(
|
||||
maker_sig: &str,
|
||||
@ -64,21 +71,26 @@ impl KeyspendContext {
|
||||
keyspend_psbt: &str,
|
||||
descriptor: &str,
|
||||
) -> anyhow::Result<Self> {
|
||||
// obtain the tweak scalar from the escrow output descriptor
|
||||
let tweak = get_keyspend_tweak_scalar(descriptor)?;
|
||||
// aggregate the maker and taker musig pubkeys with the tweak
|
||||
let agg_keyspend_pk: musig2::KeyAggContext =
|
||||
aggregate_musig_pubkeys_with_tweak(maker_pk, taker_pk, tweak)?;
|
||||
// aggregate the public nonces
|
||||
let agg_nonce: MusigAggNonce =
|
||||
coordinator_utils::agg_hex_musig_nonces(maker_nonce, taker_nonce)?;
|
||||
// deserialize the keyspend psbt
|
||||
let keyspend_psbt = PartiallySignedTransaction::deserialize(&hex::decode(keyspend_psbt)?)?;
|
||||
|
||||
let partial_maker_sig = PartialSignature::from_hex(maker_sig)?;
|
||||
let partial_taker_sig = PartialSignature::from_hex(taker_sig)?;
|
||||
let partial_signatures = vec![partial_maker_sig, partial_taker_sig];
|
||||
|
||||
// let msg = keyspend_psbt.
|
||||
// obtain the message to sign (taproot key spend signature hash)
|
||||
let msg = {
|
||||
let mut sig_hash_cache = SighashCache::new(keyspend_psbt.unsigned_tx.clone());
|
||||
|
||||
// it should only contain one utxo, the escrow locking UTXO
|
||||
let utxo = keyspend_psbt
|
||||
.iter_funding_utxos()
|
||||
.next()
|
||||
@ -110,6 +122,7 @@ impl KeyspendContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// get the scalar used to tweak the keyspend output key from the escrow output descriptor
|
||||
fn get_keyspend_tweak_scalar(descriptor: &str) -> Result<bdk::bitcoin::secp256k1::Scalar> {
|
||||
let tr_descriptor: Descriptor<XOnlyPublicKey> =
|
||||
bdk::descriptor::Descriptor::from_str(descriptor)?;
|
||||
@ -130,6 +143,7 @@ fn get_keyspend_tweak_scalar(descriptor: &str) -> Result<bdk::bitcoin::secp256k1
|
||||
Ok(tweak.to_scalar())
|
||||
}
|
||||
|
||||
/// Aggregate two hex encoded musig pubkeys with a tweak scalar
|
||||
pub fn aggregate_musig_pubkeys_with_tweak(
|
||||
maker_musig_pubkey: &str,
|
||||
taker_musig_pubkey: &str,
|
||||
@ -152,6 +166,8 @@ pub fn aggregate_musig_pubkeys_with_tweak(
|
||||
}
|
||||
|
||||
impl PayoutData {
|
||||
/// assembles the information retrieved from the database into a PayoutData struct which is used for
|
||||
/// constructing the keyspend payout transaction passed on to the traders for signing
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_from_strings(
|
||||
escrow_output_descriptor: &str,
|
||||
@ -189,17 +205,20 @@ impl PayoutData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_random_order_id(len: usize) -> String {
|
||||
/// generates a random order id of size `size` bytes and returns it as hex encoded string
|
||||
pub fn generate_random_order_id(size: usize) -> String {
|
||||
// Generate `len` random bytes
|
||||
let bytes: Vec<u8> = rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Standard)
|
||||
.take(len)
|
||||
.take(size)
|
||||
.collect();
|
||||
|
||||
// Convert bytes to hex string
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
/// does check both that a robhash and offerID are actually existing in the database
|
||||
/// and that the escrow locking transaction is confirmed
|
||||
pub async fn check_offer_and_confirmation(
|
||||
offer_id_hex: &str,
|
||||
robohash_hex: &str,
|
||||
|
||||
@ -87,6 +87,8 @@ pub struct MempoolHandler {
|
||||
}
|
||||
|
||||
impl MempoolHandler {
|
||||
/// creates a new mempool handler connected to a json rpc client which spawns
|
||||
/// a new tokio thread which keeps track of the mempool state
|
||||
pub async fn new(json_rpc_client: Arc<Client>) -> Self {
|
||||
let mempool = Arc::new(Mempool::new(json_rpc_client));
|
||||
let mempool_clone = Arc::clone(&mempool);
|
||||
@ -101,6 +103,7 @@ impl MempoolHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/// called to look for UTXOs in the mempool
|
||||
pub async fn lookup_mempool_inputs(
|
||||
&self,
|
||||
bonds: &Vec<MonitoringBond>,
|
||||
@ -125,6 +128,7 @@ impl MempoolHandler {
|
||||
Ok(bonds_to_punish)
|
||||
}
|
||||
|
||||
/// kills the mempool thread, needed to prevent long running when ending the program, especially in tests
|
||||
pub async fn shutdown(&self) {
|
||||
if let Some(sender) = self.shutdown_sender.lock().await.take() {
|
||||
let _ = sender.send(()); // Ignore the result, as the receiver might have been dropped
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
/// fetches confirmations of the txids in unconfirmed_txids using core rpc
|
||||
fn get_confirmations(
|
||||
unconfirmed_txids: Vec<String>,
|
||||
coordinator: Arc<Coordinator>,
|
||||
@ -58,6 +59,9 @@ fn get_confirmations(
|
||||
Ok(now_confirmed_txs)
|
||||
}
|
||||
|
||||
/// pulls txids of unconfirmed escrow transactions from the database, checks
|
||||
/// for confirmations using the bitcoin core rpc api and updates the database
|
||||
/// entries with the confirmation
|
||||
pub async fn update_transaction_confirmations(coordinator: Arc<Coordinator>) {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||
@ -78,6 +82,7 @@ pub async fn update_transaction_confirmations(coordinator: Arc<Coordinator>) {
|
||||
continue;
|
||||
}
|
||||
let coordinator_clone = Arc::clone(&coordinator);
|
||||
// spawn blocking because the core rpc call is blocking
|
||||
let newly_confirmed_txids = match tokio::task::spawn_blocking(move || {
|
||||
get_confirmations(unconfirmed_transactions, coordinator_clone)
|
||||
})
|
||||
|
||||
@ -110,7 +110,7 @@ async fn main() -> Result<()> {
|
||||
coordinator_wallet: Arc::new(init_coordinator_wallet().await?),
|
||||
});
|
||||
|
||||
// begin monitoring bonds as separate tokio taks which runs concurrently
|
||||
// begin monitoring bonds as separate tokio task which runs concurrently
|
||||
let coordinator_ref = Arc::clone(&coordinator);
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
|
||||
@ -12,6 +12,7 @@ pub struct EscrowPsbtConstructionData {
|
||||
}
|
||||
|
||||
impl EscrowPsbtConstructionData {
|
||||
/// get the sum of the input utxos
|
||||
pub fn input_sum(&self) -> Result<u64> {
|
||||
let mut input_sum = 0;
|
||||
for input in &self.escrow_input_utxos {
|
||||
@ -26,6 +27,7 @@ impl EscrowPsbtConstructionData {
|
||||
}
|
||||
}
|
||||
|
||||
/// aggregate hex encoded musig pubkeys to a KeyAggContext without tweaking
|
||||
pub fn aggregate_musig_pubkeys(
|
||||
maker_musig_pubkey: &str,
|
||||
taker_musig_pubkey: &str,
|
||||
@ -43,7 +45,7 @@ pub fn aggregate_musig_pubkeys(
|
||||
Ok(key_agg_ctx)
|
||||
}
|
||||
|
||||
/// this function builds the escrow output with all possible spending conditions
|
||||
/// this function builds the escrow output descriptor with all possible spending conditions
|
||||
pub fn build_escrow_transaction_output_descriptor(
|
||||
maker_escrow_data: &EscrowPsbtConstructionData,
|
||||
taker_escrow_data: &EscrowPsbtConstructionData,
|
||||
@ -113,8 +115,8 @@ pub fn build_escrow_transaction_output_descriptor(
|
||||
Ok(descriptor) // then spend to descriptor.address(Network::Regtest)
|
||||
}
|
||||
|
||||
// pub fn assemble_escrow_psbts(
|
||||
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
/// assemble the escrow locking transaction as psbt and return it with relevant associated data
|
||||
pub async fn create_escrow_psbt(
|
||||
&self,
|
||||
db: &Arc<CoordinatorDB>,
|
||||
@ -149,32 +151,9 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
.await?;
|
||||
|
||||
let (escrow_psbt, details) = {
|
||||
// maybe we can generate a address/taproot pk directly from the descriptor without a new wallet?
|
||||
// let temp_wallet = Wallet::new(
|
||||
// &escrow_output_descriptor,
|
||||
// None,
|
||||
// bitcoin::Network::Regtest,
|
||||
// MemoryDatabase::new(),
|
||||
// )?;
|
||||
|
||||
// get address for escrow output from the descriptor
|
||||
let escrow_address =
|
||||
escrow_output_descriptor.address(bdk::bitcoin::Network::Regtest)?;
|
||||
// dbg!("building untweaked address now");
|
||||
// let agg_untweaked: musig2::secp256k1::PublicKey = aggregate_musig_pubkeys(
|
||||
// &maker_psbt_input_data.musig_pubkey_compressed_hex,
|
||||
// &taker_psbt_input_data.musig_pubkey_compressed_hex,
|
||||
// )?
|
||||
// .aggregated_pubkey_untweaked();
|
||||
// let xonly_hex = agg_untweaked.x_only_public_key().0.to_string();
|
||||
// let bdk_xonly = bdk::bitcoin::key::XOnlyPublicKey::from_str(&xonly_hex)?;
|
||||
// let output_key = TweakedPublicKey::dangerous_assume_tweaked(bdk_xonly);
|
||||
// let escrow_address = bdk::bitcoin::Address::p2tr_tweaked(output_key, Network::Regtest);
|
||||
// escrow_output_descriptor = Descriptor::<XOnlyPublicKey>::new_tr(bdk_xonly, None)
|
||||
// .context("Error assembling escrow output descriptor")?;
|
||||
|
||||
// dummy escrow address for testing the psbt signing flow
|
||||
// let escrow_address =
|
||||
// Address::from_str(self.get_new_address().await?.as_str())?.assume_checked();
|
||||
|
||||
// using absolute fee for now, in production we should come up with a way to determine the tx weight
|
||||
// upfront and substract the fee from the change outputs (10k == ~30/sat vbyte)
|
||||
|
||||
@ -37,6 +37,7 @@ pub struct BondRequirements {
|
||||
pub min_input_sum_sat: u64,
|
||||
}
|
||||
|
||||
/// sets up the coordinator bdk wallet from the env variables
|
||||
pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabase>> {
|
||||
let wallet_xprv = ExtendedPrivKey::from_str(
|
||||
&env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?,
|
||||
@ -49,7 +50,7 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
|
||||
password: env::var("BITCOIN_RPC_PASSWORD")?,
|
||||
},
|
||||
network: Network::Regtest,
|
||||
// wallet_name: env::var("BITCOIN_RPC_WALLET_NAME")?,
|
||||
// derives wallet name from xprv/wallet
|
||||
wallet_name: bdk::wallet::wallet_name_from_descriptor(
|
||||
Bip86(wallet_xprv, KeychainKind::External),
|
||||
Some(Bip86(wallet_xprv, KeychainKind::Internal)),
|
||||
@ -63,10 +64,9 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
|
||||
rpc_config.auth.clone().into(),
|
||||
)?);
|
||||
let json_rpc_client_clone = Arc::clone(&json_rpc_client);
|
||||
// start new mempool instance
|
||||
let mempool = MempoolHandler::new(json_rpc_client_clone).await;
|
||||
let backend = RpcBlockchain::from_config(&rpc_config)?;
|
||||
// let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000);
|
||||
// let sled_db = sled::open(env::var("BDK_DB_PATH")?)?.open_tree("default_wallet")?;
|
||||
let wallet = Wallet::new(
|
||||
Bip86(wallet_xprv, KeychainKind::External),
|
||||
Some(Bip86(wallet_xprv, KeychainKind::Internal)),
|
||||
@ -88,17 +88,21 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
|
||||
}
|
||||
|
||||
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
/// shutdown function to end the mempool task
|
||||
pub async fn shutdown(&self) {
|
||||
debug!("Shutting down wallet");
|
||||
self.mempool.shutdown().await;
|
||||
}
|
||||
|
||||
/// get a new address of the coordinator wallet
|
||||
pub async fn get_new_address(&self) -> Result<String> {
|
||||
let wallet = self.wallet.lock().await;
|
||||
let address = wallet.get_address(bdk::wallet::AddressIndex::New)?;
|
||||
Ok(address.address.to_string())
|
||||
}
|
||||
|
||||
/// used to validate submitted bond transactions using the same logic as in the continoous monitoring
|
||||
/// puts the bond in a dummy MonitoringBond struct use the existing logic
|
||||
pub async fn validate_bond_tx_hex(
|
||||
&self,
|
||||
bond_tx_hex: &str,
|
||||
@ -263,6 +267,7 @@ fn test_mempool_accept_bonds(
|
||||
) -> Result<HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)>> {
|
||||
let mut invalid_bonds: HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)> = HashMap::new();
|
||||
|
||||
// split bonds into chunks of 25 to avoid hitting the maxmimum allowed size of the rpc call
|
||||
let raw_bonds: Vec<Vec<String>> = bonds
|
||||
.iter()
|
||||
.map(|bond| bond.bond_tx_hex.clone().raw_hex())
|
||||
|
||||
@ -5,6 +5,7 @@ pub struct PsbtInput {
|
||||
pub utxo: bdk::bitcoin::OutPoint,
|
||||
}
|
||||
|
||||
/// implements functions required for bond transactions on the bdk::bitcoin::Transaction struct
|
||||
pub trait BondTx {
|
||||
fn input_sum<D: Database, B: GetTx>(&self, blockchain: &B, db: &D) -> Result<u64>;
|
||||
fn bond_output_sum(&self, bond_address: &str) -> Result<u64>;
|
||||
@ -57,6 +58,7 @@ impl BondTx for Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// converts a csv string of bincode binary serialized, hex encoded bdk psbt inputs to a vector of PsbtInput
|
||||
pub fn csv_hex_to_bdk_input(inputs_csv_hex: &str) -> Result<Vec<PsbtInput>> {
|
||||
let mut inputs: Vec<PsbtInput> = Vec::new();
|
||||
for input_hex in inputs_csv_hex.split(',') {
|
||||
|
||||
Reference in New Issue
Block a user