From 0531aa5467e8953e4d09f845676bf768bf9bf75b Mon Sep 17 00:00:00 2001 From: Kamil Koczurek Date: Tue, 31 Oct 2023 10:49:57 +0100 Subject: [PATCH] payment: Detect tx overspending (#2867) A provider will now detect if a transaction will be used for multiple SendPayment instances exceeding the actual tx amount. --- core/payment/src/dao/payment.rs | 26 ++++++++++++++++++++++++++ core/payment/src/error.rs | 4 ++++ core/payment/src/processor.rs | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/core/payment/src/dao/payment.rs b/core/payment/src/dao/payment.rs index 834530c157..958ade449f 100644 --- a/core/payment/src/dao/payment.rs +++ b/core/payment/src/dao/payment.rs @@ -188,6 +188,32 @@ impl<'c> PaymentDao<'c> { .await } + pub async fn get_for_confirmation(&self, details: Vec) -> DbResult> { + readonly_transaction(self.pool, move |conn| { + let mut result = Vec::new(); + + let payments: Vec = dsl::pay_payment + .filter(dsl::details.eq(&details)) + .load(conn)?; + + for payment in payments { + let activity_payments = activity_pay_dsl::pay_activity_payment + .filter(activity_pay_dsl::payment_id.eq(&payment.id)) + .filter(activity_pay_dsl::owner_id.eq(&payment.owner_id)) + .load(conn)?; + let agreement_payments = agreement_pay_dsl::pay_agreement_payment + .filter(agreement_pay_dsl::payment_id.eq(&payment.id)) + .filter(agreement_pay_dsl::owner_id.eq(&payment.owner_id)) + .load(conn)?; + + result.push(payment.into_api_model(activity_payments, agreement_payments)) + } + + Ok(result) + }) + .await + } + pub async fn get_for_node_id( &self, node_id: NodeId, diff --git a/core/payment/src/error.rs b/core/payment/src/error.rs index 1d2984959f..0b28a511cf 100644 --- a/core/payment/src/error.rs +++ b/core/payment/src/error.rs @@ -355,6 +355,10 @@ pub mod processor { "Transaction balance too low (probably tx hash re-used)".to_owned(), )) } + + pub fn overspending(tx_amount: &BigDecimal, total_amount: &BigDecimal) -> Result<(), Self> { + Err(Self::Validation(format!("Transaction for {tx_amount} used for multiple payments amounting to {total_amount}"))) + } } #[derive(thiserror::Error, Debug)] diff --git a/core/payment/src/processor.rs b/core/payment/src/processor.rs index 2c895136fc..db6e6407a2 100644 --- a/core/payment/src/processor.rs +++ b/core/payment/src/processor.rs @@ -546,7 +546,7 @@ impl PaymentProcessor { }; let details: PaymentDetails = driver_endpoint(&driver) .send(driver::VerifyPayment::new( - confirmation, + confirmation.clone(), platform.clone(), payment.clone(), )) @@ -623,9 +623,37 @@ impl PaymentProcessor { } } - // Insert payment into database (this operation creates and updates all related entities) + // Verify totals for all agreements and activities with the same confirmation let payment_dao: PaymentDao = self.db_executor.as_dao(); + let shared_payments = payment_dao + .get_for_confirmation(confirmation.confirmation) + .await?; + let all_payment_sum = shared_payments + .iter() + .map(|payment| { + let agreement_total = payment + .agreement_payments + .iter() + .map(|ap| &ap.amount) + .sum::(); + + let activity_total = payment + .activity_payments + .iter() + .map(|ap| &ap.amount) + .sum::(); + + agreement_total + activity_total + }) + .sum::(); + + if &all_payment_sum + agreement_sum + activity_sum > details.amount { + return VerifyPaymentError::overspending(&details.amount, &all_payment_sum); + } + + // Insert payment into database (this operation creates and updates all related entities) payment_dao.insert_received(payment, payee_id).await?; + Ok(()) }