more comments

This commit is contained in:
fbock
2024-08-27 15:18:42 +02:00
parent 4f68b303eb
commit 2687a7084c
8 changed files with 53 additions and 64 deletions

View File

@ -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?;

View File

@ -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,

View File

@ -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

View File

@ -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)
})

View File

@ -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 {

View File

@ -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)

View File

@ -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())

View File

@ -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(',') {